May 30, 2022

Zynq - Bitstream

The bitstream is the data that gets shoveled into our FPGA to make it do what we want it to do. For me thus far, the bitstream is a totally opaque and mysterious object. This appears to be the intention. The end user works in Verilog or VHDL along with battling with Vivado and ends up with a bitstream. This is then carefully preserved along with knowledge about what it does and how to interface with it, and everything is roses and butterflies.

Most development scenarios load the bitstream using JTAG. But in a production scenario, it needs to go into flash memory or in the case of the Zynq, the PS can load the bitstream into the PL.

I would like to compress the bitstream and then write some little tool to convert it to C code. It could then be compiled into some "bare metal" software which would decompress it and load it into the PL.

Bitstream files are big. I have one that does some almost trivial processing and is little more than an AXI gpio along with a connection or two, however the size of the bitstream file is just over 2 megabytes. How much of this is actually signficant content? Let's find out:

[tom@trona impl_1]$ ls -l *.bit
-rw-r--r-- 1 tom tom 2083852 May 20 16:19 design_1_wrapper.bit
[tom@trona impl_1]$ cat design_1_wrapper.bit | tr -d '\0' | wc -c
20392
[tom@trona impl_1]$ cat design_1_wrapper.bit | tr -c -d '\0' | wc -c
2063460
Apparently only about 20K of the 2M is real content, the rest is a bunch of zeros. Clearly this situation will change if less trivial content is developed for the PL, but for now it looks entirely reasonable to do what I have proposed.

What I want to use is called the PCAP path and is part of the DevC (device configuration) hardware and registers. This is hopefully described in the TRM somewhere. The following sections are possible starting points:

Figure 6.2 PL configuration paths
6.1.9 - device configuration interface.
Table 4-7 p. 116 addr 0xf800_0000 ...
Table 6-26 p. 223
21.4 - PL configuration
32.4 = programming considerations
The following discussing doing this using functions provided in the Zynq BSP: Based on this, I do the following search in one of my existing Vivado projects:
cd /u1/home/tom/ebaz_blink_3/ebaz_blink_3.sdk/sumatra_bsp
 rgrep XDcfg_Transfer
./ps7_cortexa9_0/libsrc/devcfg_v3_5/src/xdevcfg.c:u32 XDcfg_Transfer(XDcfg *InstancePtr,
./ps7_cortexa9_0/include/xdevcfg.h:u32 XDcfg_Transfer(XDcfg *InstancePtr,

bitgen, promgen, bootgen

Xilinux provides a number of command line tools, some came with ISE, and bootgen comes with Vivado. Bootgen even has source code on Github. Bootgen even has a user guide UG1283.

Note also the above discussed two things, .bit and .bin format for bitstreams. And they discuss a Xilinx tool called "bootgen". I did around on my system, and there it is:

/u1/Xilinx/Vivado/2022.1/bin/bootgen

I run this and it is a command line tool (the front end is a bash script). It has one option that seems related to what I want to do (extract .bin from .bit), namely:

process_bitstream - Outputs bitstream in bin/mcs format
One can type "./bootgen -help process_bitstream" and this suggests that it expects a .bif file as input, not a .bit file.

And just for the record, ISE has some other related command line tools:

/u1/Xilinx/14.7/ISE_DS/ISE/bin/lin64/promgen
/u1/Xilinx/14.7/ISE_DS/ISE/bin/lin64/bitgen
To learn about promgem, see: UG628, Command Line Tools User Guide.

On linux, just use "cat"

I'll just note that under linux, they say you can do this to program the PL:
mknod /dev/xdevcfg c 259 0 > /dev/null
cat system.bit.bin > /dev/xdevcfg
They call the "bin" file a raw binary bitstream.

bit or bin?

You can extract the raw binary via the following:
promgen -b -w -p bin -data_width 32 -u 0 bitfile.bit -o bitfile.bin
Then you get it into memory somehow and then do this:
	// Transfer Bitfile using DEVCFG/PCAP
	int Status = XDcfg_TransferBitfile(XDcfg_0, Address, (BITFILE_LEN >> 2));
	if (Status != XST_SUCCESS) {
		xil_printf("ERROR : FPGA configuration failed!\n\r\n\r");
		exit(EXIT_FAILURE);
	}

int XDcfg_TransferBitfile(XDcfg *Instance, u32 StartAddress, u32 WordLength)
{
	int Status;
	volatile u32 IntrStsReg = 0;

	// Clear DMA and PCAP Done Interrupts
	XDcfg_IntrClear(Instance, (XDCFG_IXR_DMA_DONE_MASK | XDCFG_IXR_D_P_DONE_MASK));

	// Transfer bitstream from DDR into fabric in non secure mode
	Status = XDcfg_Transfer(Instance, (u32 *) StartAddress, WordLength,
	    (u32 *) XDCFG_DMA_INVALID_ADDRESS, 0, XDCFG_NON_SECURE_PCAP_WRITE);

	if (Status != XST_SUCCESS)
		return Status;

	// Poll DMA Done Interrupt
	while ((IntrStsReg & XDCFG_IXR_DMA_DONE_MASK) != XDCFG_IXR_DMA_DONE_MASK)
		IntrStsReg = XDcfg_IntrGetStatus(Instance);

	// Poll PCAP Done Interrupt
	while ((IntrStsReg & XDCFG_IXR_D_P_DONE_MASK) != XDCFG_IXR_D_P_DONE_MASK)
		IntrStsReg = XDcfg_IntrGetStatus(Instance);

	return XST_SUCCESS;
}
For one of my projects, I find the routine "XDcfg_Transfer ()" in the directory:
/u1/home/tom/ebaz_blink_3/ebaz_blink_3.sdk/sumatra_bsp/ps7_cortexa9_0/libsrc/devcfg_v3_5/src
/u1/home/tom/ebaz_blink_3/ebaz_blink_3.sdk/sumatra_bsp/ps7_cortexa9_0/include/xdevcfg_hw.h
We find the following in the include file:
#define XDCFG_CTRL_OFFSET               0x00 /**< Control Register */
#define XDCFG_LOCK_OFFSET               0x04 /**< Lock Register */
#define XDCFG_CFG_OFFSET                0x08 /**< Configuration Register */
#define XDCFG_INT_STS_OFFSET            0x0C /**< Interrupt Status Register */
#define XDCFG_INT_MASK_OFFSET           0x10 /**< Interrupt Mask Register */
#define XDCFG_STATUS_OFFSET             0x14 /**< Status Register */
#define XDCFG_DMA_SRC_ADDR_OFFSET       0x18 /**< DMA Source Address Register */
#define XDCFG_DMA_DEST_ADDR_OFFSET      0x1C /**< DMA Destination Address Reg */
#define XDCFG_DMA_SRC_LEN_OFFSET        0x20 /**< DMA Source Transfer Length */
#define XDCFG_DMA_DEST_LEN_OFFSET       0x24 /**< DMA Destination Transfer */
#define XDCFG_ROM_SHADOW_OFFSET         0x28 /**< DMA ROM Shadow Register */
#define XDCFG_MULTIBOOT_ADDR_OFFSET     0x2C /**< Multi BootAddress Pointer */
#define XDCFG_SW_ID_OFFSET              0x30 /**< Software ID Register */
#define XDCFG_UNLOCK_OFFSET             0x34 /**< Unlock Register */
#define XDCFG_MCTRL_OFFSET              0x80 /**< Miscellaneous Control Reg */
The base address is set as the third argument in the following:
int XDcfg_CfgInitialize(XDcfg *InstancePtr, XDcfg_Config *ConfigPtr, u32 EffectiveAddress)
I am unable to find the source code that has a call to this routine.
The file xparameters.h shows two sets of definitions we can admire:
/* Definitions for peripheral PS7_DEV_CFG_0 */
#define XPAR_PS7_DEV_CFG_0_DEVICE_ID 0U
#define XPAR_PS7_DEV_CFG_0_BASEADDR 0xF8007000U
#define XPAR_PS7_DEV_CFG_0_HIGHADDR 0xF80070FFU

/* Canonical definitions for peripheral PS7_DEV_CFG_0 */
#define XPAR_XDCFG_0_DEVICE_ID XPAR_PS7_DEV_CFG_0_DEVICE_ID
#define XPAR_XDCFG_0_BASEADDR 0xF8007000U
#define XPAR_XDCFG_0_HIGHADDR 0xF80070FFU
Appendix B.16 (page 1144) has information about the above registers. This section is "Device Configuration Interface (devcfg)". Table 4-7 (page 116) gives the 0xf8007000 base address, so we have both documentation and example code.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org