Software apps and online services
To start off 2022, I decided to cruise through my infinite list of backlogged project ideas and decided on breaking out these cool little vintage QDSP-6061 5-digit bubble display from HP and create a driver for them in Verilog.
I broke out my Arty Z7 board from Digilent and created a project in Vivado/Vitis 2021.2 to get it working, which you can read through in my last project post where I displayed scrolling text across the 5-digits of the QDSP-6061 saying "Hello 2022".
The Verilog driver I ended up creating was very simplistic in that it starting running as soon as the FPGA came out of reset on power up and just ran forever. So I wanted to add a couple of features to 1) control when the driver starts running from a C application on the ARM-core of the Zynq and 2) be able to send the driver strings of data to scroll across the display from the C application vs having it hard-coded to "Hello 2022". Both of these features necessitate a communication channel between the C application running on the ARM-core of the and the Verilog driver running in the programmable logic of the Zynq SoC FPGA.
The easiest and most direct way to do this is to add the AMBA® AXI4 (Advanced eXtensible Interface 4) protocol interface to the Verilog driver for the QDSP-6061 bubble display. Now the AXI interface is a pretty straightforward set of handshaking signals to replicate on the RTL side in Verilog or VHDL with simplistic state machines, but writing the corresponding C drivers from the application side is not trivial.
However, Vivado/Vitis have this covered with a handy feature in that it can auto-generate the RTL and C code with the desired AXI parameters. Which I'll demonstrate how to use in this project.
It is important to note that there are three main implementations of the AXI4 interface:
- Full AXI4
Each type suits different use cases better. In this case of the QDSP-6061 bubble display driver, all that is needed is to write to some values to a couple of registers which isn't a ton of data being transferred and there aren't any strict timing requirements. Thus, AXI4-Lite is the version of the AXI interface I chose to implement for this.Create New AXI4 Peripheral
Starting in the Vivado project created in my previous post for the Arty Z7, you'll see that I had just added the QDSP-6061 driver Verilog module to the block design directly with the Add Module... feature available in the right-click menu when editing the block design.
The goal now is to replace it with the new AXI4-Lite custom IP version using the package IP feature built into Vivado. So the first step is to create a new AXI4 peripheral IP block for the QDSP-6061 bubble display driver.
To create a new AXI4 peripheral IP in Vivado, select Tools > Create and Package New IP...
A window will pop up in the current Vivado Project that is open with a brief summary of options available, including the option to create a new AXI4 peripheral. Click Next to continue on.
On the second page, select the option Create a new AXI4 peripheral then give the peripheral the desired name, version, and description info on the following page.
Note: don't use dashes or other illegal characters in the name field.As whatever is passed in the Name field is used as the Verilog module name for the IP.
The next page allows for you to set the number of master and slave AXI interfaces the custom peripheral needs. This includes the type of AXI each interface is. In this case, only one slave AXI4-Lite interface is needed since we're just needing the C application to be able to write to a register in the RTL.
Furthermore on this page, there are settings to configure such as the width of the data bus of the AXI interface and the number of registers available to write to/read from. The default data width is 32 bits which is what I stuck with. And the minimum number of registers is 4, which is plenty for the C application to pass data to the QDSP-6061 driver.
The final summary page offers a new options for the next step. In this case, the Verilog for the bubble display driver still needs to be added, so the option Edit IP is what we select.
After clicking Finish with the Edit IP option selected, a second Vivado project will open:
For an AXI4-Lite IP project, there are two Verilog files generated:
- <peripheral_name>.v (the top level file)
The top level file, is where the logic for the peripheral's actual functionality is directly coded or instantiated. The second with S00_AXI appended to the name is where the logic of the AXI handshaking signaling is implemented. This is also where the custom logic is added for the peripheral to pull and send data to the AXI bus, which is then passed back up to the top level file for to the peripheral's code functionality RTL to use.
The auto-generated code is well commented and clearly marks when to add your custom RTL (including where to add ports and signal declarations) with "Add User Logic Here" comment sections.AXI4-Lite Custom IP Verilog
I recommend starting with adding the logic for the the custom IP's core functionality first, then focus on how it will interact with the AXI interface. So I started with the top level Verilog file generated axi_qdsp_6061_controller_v1_0.v to add the Verilog I wrote to drive the QDSP-6061.
You can either import existing Verilog source files the same way you would in a normal Vivado project and simply instantiate it in the top level source file of the AXI4 peripheral project or you can write your own logic directly.
Since I needed to make some changes to the Verilog code of the QDSP-6061 driver, I chose to directly copy+paste it into the top level Verilog module axi_qdsp_6061_controller_v1_0.v
After copy+pasting my previous Verilog in, I added an enable signal that would keep the driver in the reset state so long as that enable signal was logic level low (zero). For the sake of getting this project post written and it not being any longer than it already is, I decided to leave the loadable string to configure the scrolling text for another day.
With the core functionality added to the top level file, it became obvious that the only signal that needed to interact with the AXI interface was the enable signal. So I added it as a port to the AXI interface module described by axi_qdsp_6061_controller_v1_0_S00_AXI.v
Again, towards the end of the file is where you add any custom logic and the port declaration towards the top is where you declare any signals being fed in from the logic in the top level file.
Recall that I configured the AXI4-Lite slave interface to have four registers to write/read. I decided that the most simplistic way to generate the enable signal for the main driver logic would be to have the enable set high when the C application writes the value 0xFFFFFFFF to the first register at offset 0x0.
Note: The registers are at a value of 4 offset from each other, so register zero is at offset 0x0, register one is at offset 0x4, register two is at offset 0x8, and register three is at offset 0x12.
Logic for setting enable signal high based on the value 0xFFFFFFFF being written to AXI slave interface control register at offset 0x0:
always @ (posedge S_AXI_ACLK)
if (S_AXI_ARESETN == 1'b0)
logic_enable <= 1'b0;
if (slv_reg0 == 32'hffffffff)
logic_enable <= 1'b1;
logic_enable <= logic_enable;
At this point, all of the custom logic for the AXI4 QDSP-6061 Driver IP has been added and we can move on to the final configuration.
Switching to the Package IP tab, there is a series of packaging steps. The first is simply the name, description, and version info that was set previously. This is where you can come back and edit the information as needed.
By default, the IP is only compatible with the FPGA chip that the Vivado project is targeted towards when the option Create and Package New IP is called. Since I selected the Create and Package New IP option from with the Vivado project I had created for the Arty board, the AXI4 QDSP-6061 Driver IP is only compatible with the Zynq-7000 chip by default. This can be changed in the Compatibility step of the packaging steps.
To make the AXI4 IP compatible with all Xilinx FPGAs, click the + button in the Compatibility step and select Add Family Explicitly...
In the Add Family pop-up window, select all desired FPGA chips you would like you AXI4 IP to be compatible with. I just checked the All Families and Parts option, but this will make the IP take up more hard drive space if you're concerned about that. I also recommend selecting the Production option for Life-Cycle as it's mostly likely that you'll be working with production level FPGA chips.
Going through the rest of the tabs, there isn't anything that needed to be edited for the AXI4 QDSP-6061 Driver IP. However you can see what all files this Create and Package New IP tool will generate, including the C application source files. You can also do cool things like add a custom logo to appear on the IP block when it's used in a Vivado block design.
The final step is to package the IP (my screenshot only says re-package IP because I forgot to grab a screenshot before I packaged it the first time through).
After clicking the Package IP button in the final packaging step, a pop-up window will ask if you'd like to close the Vivado project for the AXI peripheral. You can reopen this Vivado project at anytime to modify/update the IP by opening the IP Catalog from any Vivado Project that the repository containing the custom IP has been added to, and selecting Edit in IP Packager.
The original Vivado project that the Create and Package New IP tool will add the custom IP automatically in Settings > IP > Repository. But you'll need to add it manually to any new Vivado projects you want to use the IP in in the future.
With the AXI4 QDSP-6061 Driver IP itself completed, I turned back to the original Vivado project to replace the RTL module in the block design with the new AXI enabled module IP.
After deleting the RTL module, use the + button or right-clicking in the block design and select Add IP... and search for the AXI4 QDSP-6061 Driver IP. Double-click on it in the list to add it to the block design.
After a few moments, Vivado will detect the new AXI interface in the block design and the connection automation will pop up at the top of the block design window. Click on the Run Connection Automation hyperlink in the green banner and a pop-up window will appear showing how Vivado will connect the AXI4 QDSP-6061 Driver IP to the AXI bus with the Zynq processing system IP. Click OK to run the connections.
I also chose to add ILAs to the AXI interface of the AXI4 QDSP-6061 Driver IP and its output signals initially for troubleshooting purposes by right-clicking on each wire and selecting Debug, then running the connection automation that appeared to generate the ILAs.
And the new block design turned out like this:
Validate design (button with check box at the top of the block design window), then save and close the block design.
Now that the block design has been updated, run synthesis, implementation, and generate a new bitstream. If you haven't read any of my previous Vivado 2021.2 project posts, you need to click on the Generate Bitstream button, which will automatically run synthesis and implementation, in order to avoid the critical warning that the board definition files can't be found (but this warning is totally benign and can be ignored, it just irritates me so I avoid it).
Export the hardware platform after a new bitstream has been generated, by selecting File > Export > Export Hardware...
In the pop-up window, be sure to select the option to include the bitstream. I chose to export the hardware platform to overwrite the previous one as well.
Finally, launch Vitis by selecting Tools > Launch Vitis IDEUpdate Hardware Specification in Vitis Platform
Since I already had an existing Vitis workspace for this project, I reopened it and updated the platform project with the new hardware platform exported from Vivado (because Vitis does not automatically detect and update that it has been modified even though I over-wrote it).
Right click on the platform project in the Explorer window (recall that I named the platform project arty_z7 in my previous post) of the Vitis project and select Update Hardware Specification.
The directory path to the XSA hardware file exported from Vivado can be left the same since I just over-wrote the old one. Then click OK.
Clean the platform project once it shows that it's out-of-date. Once completed, immediately follow that up by running a clean the application project.
After both the platform and application projects have been cleaned, build the platform project then build the application project.
It's important that you always clean/build the platform project before the application project since the application project has dependencies on the platform project.
It's important to note that this method for getting the hardware platform updated in a Vitis workspace is usually always different with each version of Vitis. This the method I've found to work in 2021.2, but in 2019.2 there was no getting the hardware platform updated without closing and re-launching Vitis.Vitis Application C Code
Finally its time to write the C application to control the AXI4 QDSP-6061 Driver IP. This code will assert/de-assert the enable signal in the RTL by writing 0xFFFFFFFF to register zero in the IP block.
As I previously mentioned, there are four registers being written to at offsets 0x0, 0x4, 0x8, and 0x12. Since I coded the Verilog to set the enable signal to the bubble display driver high when all 32 bits of the register at offset 0x0 are set to 0xFFFFFFFF, the C code needs to write 0xFFFFFFFF to the register at offset 0x0.
The auto-generated source files for a custom AXI4 peripheral show up in a few different places in the platform project in Vitis, but you need to edit the source file at the following location for them to update globally and be available to the main function in the application project: /<platform project>/ps7_cortexa9_0/standalone_domain/bsp/ps7_cortexa9_0/libsrc/<custom AXI peripheral IP name>/src
Be sure to include the function declarations in the header file (axi_qdsp_6061_controller.h in this case), then write the body of the functions in the corresponding C file (axi_qdsp_6061_controller.c)
There is a self-test source file that is generated as well that you can use as a template. But you need to keep in mind that the code it auto-generated to be as generalized as possible. So it may throw warnings and info messages for your custom application (basically I'm warning any novices not to treat the auto-generated code in the self test as gold, it may be wrong for your application). For example, passing the the base address of the AXI4 QDSP-6061 Driver IP as a void typed pointer, caused all sorts of issues. So I changed it to be passed as a simple integer value typecast to a u32 in the actual write register function:
My main function turned out pretty simple since all that needs to happen is that one write of 0xFFFFFFFF to register zero at offset 0x0:
Save all of the files and build the application project.Debug Application
Launch a debug run application to test it. Plug in the Arty board to the host PC and then right-click on the application from the Explorer window and select Debug As > Launch on Hardware (Single Application Debug). This will open the debug view in Vitis and set a breakpoint right at the entry of the main function to allow for you to step through the application line-by-line.
If you added the ILAs to the block design, switch back to Vivado and open them in the Hardware Manager before stepping through the application in Vitis. In the Flow Navigator window of Vivado, select Open Hardware Manager > Open Target > Auto Connect and Vivado will open the ILA windows for you.
I set triggers on the write address channel valid (AWVALID), write data channel valid (WVALID), and write response channel valid (BVALID) signals for the AXI ILA window to catch the actual write transaction from the C application when I stepped through the register write function.
I also set triggers on the output lines of the AXI4 QDSP-6061 Driver IP to see when/if it started driving the cathode and anode lines to validate that writing the value 0xFFFFFFFF to register zero did indeed set the enable signal for the logic high.
And there you have it, that's a start-to-finish example of how to take your own custom RTL module and add an AXI interface wrapper to it for AXI4-Lite. I'll be going over how to do this for full AXI4 and AXI4-Stream in the coming months!