May 22, 2022

EBAZ4205 - PS, PL, and AXI

I just had a light bulb come on, so I thought I would take same time to step back and look at some things from a high perspective.

First some background. The Zynq chip has a dual core ARM along with a good sized FPGA. They call the ARM side the "PS" (processing system) and the FPGA side the "PL" (programmable logic). There is no telling what AXI stands for. Apparently "Advanced eXtensible Interface" and it has an AXI3 and AXI4 specification from ARM and has uses entirely outside of this context.

I have been working with the Ebaz 4205 board and Zynq chip for over a year. I am very comfortable with the ARM side (the PS), and I am learning about FPGA things (the PL). It is quite possible to ignore the ARM side (the PS) and just use the Zynq as an FPGA and deal only with the PL.

But I have been struggling trying to understand the connection between the PL and the PS. That is where the action is (so to speak), and that is what makes the Zynq so exciting. How do you play the game?

The interconnect between the two is called the AXI. On first glance this sounds like yet another ARM hardware bus that is deeply buried and that I don't get to touch directly as an ARM developer. That is usually the case, but is not entirely true here. In any case, I don't care how the interconnect is implemented, I just want to know how to use it.

I have been working with the Xilinx Vivado software, and based on some tutorials I am learning from I have been adding "AXI GPIO" blocks to my FPGA designs, then using library calls from the Xilinx SDK in my C code to send data to them. It all works out just fine.

The big discovery came when I dug a bit into the C code. All of the source code for the SDK is there to be looked at, including the code for the XGpio library I have been using. I dig around and discover that is gets some things from a file called "xparameters.h". This gets generated for each and every project and for my particular current project, it has lines like this:

/* Definitions for driver GPIO */
#define XPAR_XGPIO_NUM_INSTANCES 1

/* Definitions for peripheral AXI_GPIO_0 */
#define XPAR_AXI_GPIO_0_BASEADDR 0x41200000
#define XPAR_AXI_GPIO_0_HIGHADDR 0x4120FFFF
#define XPAR_AXI_GPIO_0_DEVICE_ID 0
So in this project, we have one of these AXI GPIO things and we know where to find it. And given the base address, the driver knows the register structure it expects to find at those addresses and all is well and good.

Now if we look at the Zynq technical reference manual, we do not find an instance of an "axi gpio" device or some number of them. We find nothing of the kind. I experimented with Vivado and it seemed more than happy to generate as many "axi gpio" things as I wanted (I stopped at 17). In other words there are not some finite number of preexising axi_gpio devices in the hardware like I expected. What we do find in the TRM is a giant address range (two of them in fact) that are dedicated to the AXI. Look at section 4.1 of the TRM.
We find:

0x40000000 to 0x7fffffff -- PL general purpose port 0 M_AXI_GP0
0x80000000 to 0xBfffffff -- PL general purpose port 1 M_AXI_GP1

How about that? Here is how I understand what is going on. These two zones are like a "blank chalkboard" for FPGA blocks to respond to and put to use. The "axi gpio" block I have been using is just "soft" FPGA logic that sets itself up. Any number of other things are available (but will need corresponding drivers that understand them to run on the ARM (PS) side).

It looks like it is entirely possible to write your own custom blocks that work with the AXI scheme and use them alongside (or instead of) existing blocks like the "AXI gpio".

Knowing the base address for such blocks is crucial. Apparently Vivado keeps track of this for axi gpio blocks (and any other block) it adds to your design diagram. It then writes these addresses to some file to pass them to the SDK for code generation. Ultimately they end up in the xparameters.h file.

This is both nicer and very different from what I expected. Note that an FPGA design kept as a bitstream will need to be kept carefully paired with one of two things:

Writing your own Custom blocks

So, now we understand that the AXI is just a "bus" that starts out with no devices on it whatsoever. We add devices to it by selecting and adding them from a collection provided for us by Xilinx, or we can design our own devices and add them in the same way. I did a search on "vivado zynq how to write my own axi ip" to find the above, and there were many more.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org