Disclaimer: This tutorial is reproduced here from the FOS GitHub tutorials. To find about more on how to use FPGAs with Linux / partial reconfiguration / multi-tenancy please check the FOS GitHub page: https://github.com/khoapham/fos
A maintained version can be found at https://github.com/khoapham/fos/blob/master/compilation_flow/hls/README.md
IntroductionHere we provide a step-by-step tutorial on how to generate a static bitstream. We use the Sobel edge detection algorithm as an example to demonstrate the process. However, the steps remain the same for other modules you may to want to create.
SetupListed below are the tools that I used to generate the static bitstream of a Sobel algorithm:
Tools Used:- Vivado 2018.3
- Ubuntu 16.04.5 LTS
- Avnet Ultra96v1 Development Board
- OpenCL, OpenCV, C/C++
- Sobel OpenCL Code
- To create a new project go to File > New Project
- Choose a suitable name and location for the project.
- Next, enter the name of the main function in the program. In our case, this is krnl_sobel. From here you can import the source file. You can also import the source files later on in the process, I will point this out.
- The same applies to the testbench files.
- This screen allows to us to tailor our module to a certain architecture. Click the 3 dots in the 'Part Selection' area of the next window. From this window, you can pick a specific FPGA or board. In this tutorial, we are selecting a specific board to synthesize our module for. It should be noted that Vivado 2018.3 does not contain an entry for the Ultra96 platform. However, when designing the modules, we found that it works fine by using the ZCU102 platform as they both use the same ZYNQ FPGA.
- We don't need to worry about any of the options in the clock section of the window. You can choose a different name for your solution.
- The final thing to do is to select the Finish button.
- From here right-click on the source in the Explorer menu and select New File... From here you navigate to the directory that contains the source files you want to import. The same goes for 'Test Bench' in the same Explorer menu. It should be noted that you should include any header files or test data that the module needs to be tested.
- You can view and modify the source code by opening the file from the Source directory. For this tutorial, the source code will synthesize without modification. The 'Misc' section contains modifications that we made to source code, such that it would be compatible with the larger project.
- The button to start synthesis is the green triangle.
- If synthesis was successful, this tab should appear:
- This tab contains information about the interface that was generated. For example, bus widths for the slave and master AXI ports
- To create the RTL module to use in the Vivado block diagram, you just simply press the 'Export RTL' button.
- For this tutorial, you can leave these options as the defaults.
- To create a new project, go to File > Project > New...
- Navigate to the following screen by pressing next:
- Choose a suitable name and location for the new project before continuing
- For this tutorial select RTL Project option in the next menu
- The next window is one way of adding source files to the block design:
- For this tutorial, we use a different method. However, both are equal
- We will not be adding any constraints in this tutorial
- For the default part menu, we will be going to Boards and selecting the Ultra96v1 Evaluation Platform
- If all is well, you can click the Finish button and get started with a block diagram
- From this next window, you can add your sobel module. Just go to Tools > Settings... > IP > Repository
- From here, go to Add and then navigate to where you stored your sobel module. Note, only the folder needs to be selected, Vivado will automatically detect the IP inside. Once this is done, click Apply and OK
- From the Flow Navigator menu of the Vivado window, you can select the Create Block Design option to get started
- Keep everything the same except the design name, which can be changed at your discretion.
- From the Diagram section of the Vivado window, you can click the, or press CTRL + I, to add new IP to the diagram
- Start by getting the block that represents your processing system, PS. For this tutorial, we use the Zynq Ultrascale+ MPSoC PS. Then click the 'Run Block Automation' link from the pop up that appears. Make sure the PS is selected and click OK
- The next thing we want to set up is a slave and master AXI port to connect our PS to the Sobel module we created. To do this, double click on the PS block. Then go to, PS-PL Configuration > PS-PL Interfaces > Master Interface and select one of the options. Then go to Slave Interface > AXI HP and select one of the options
- Now we add the Sobel module we created. To do so navigate to the menu you would normally select the IP you want and search for the name of the top function that you specified in Vivado HLS. In our case, this is Krnl_sobel. Add this to the block design.
- Click the 'Run Connection Automation' link from the pop-up that appears. This will add in the necessary connection blocks that we need to be able to use the Sobel module. Make sure all the boxes are checked before pressing OK.
- Generating the bitstream is an easy task, but first, we need to validate our design. Select the Validate Design option from the top of the Diagram window or press F6. If this was done correctly, it should tell you that a slave AXI port was excluded.
- This can be fixed by going the Address Editor tab and opening the sobel module section and then the Excluded Address Segments. To fix the validation issue, simply right-click the excluded address segment and select Include Segment.
- Re-validate the design
- Before we can generate the bitstream we need to create an HDL wrapper for our design. This is easy to do. Go to the sources menu on the Vivado screen, right-click on the design file you want to create the wrapper for and select 'Create HDL Wrapper'. Keep all as default and select OK.
- Select Generate Bitstream from the Flow Navigator menu. Keep everything as default and click OK. This step will take some time, so go and grab a drink and come back.
- To find the bitstream, navigate to the directory you created for the project. For us, we will navigate to sobel.runs/impl_1/design_1_wrapper.bit. Note, sobel is the name of our directory, this will be replaced with whatever you named the directory.
- Once you've found the.bit file we need to convert the image file that we can load onto the FPGA. To do this, we use Xilinx's Bootgen. This is straightforward to do. Preferably in the same directory as you found the.bit file, create a file called bitstream.bif. Its contents should be as follows:
all:
{
design_1_wrapper.bit
}
- Once the bitstream.bif file is created, all you need to do is run the following command:
bootgen -image bitstream.bif -arch zynqmp -o bitstream.bin -w
MiscThis section contains helpful modifications that you may need or want during development
32 and 64 bit interfacesUsing the original Sobel OpenCL code, the data bus is 512 bits. For us, this is still usable but it could be changed. To do so, we changed the function parameter list to pass ints and int pointers, depending on the variable. We then took the input parameters and cast them into new variables of the original types. This allowed us to control the width of the data bus.
In order a 64 bit data bus, you just need to add an option to the configuration in HLS before synthesis. In Vivado HLS, click on the two yellow cogs called Solution Settings..., go to Add and select config_interface from the Command drop-down menu. Ensure that the m_axi_addr64 option is selected.
Now after synthesis, your data buses should be 64 bits wide
N.B. Remember that if you create a 64 bit interface, the modules internal registers will be 64 bits. You need to set those upper 32 bits to 0, otherwise, the module will use whatever value is stored in upper 32 bits and result in potentially arbitrary behaviour.
Master and Slave AXI Pragma in HLSMaster PragmasIf you find you need to map an argument to the memory port, the basic structure is as follows:
#pragma HLS INTERFACE m_axi port=<variable_name> offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi port=<variable_name> offset=slave bundle=gmem
Variable_name is a variable that denotes an array. It should be noted that you should only map arrays to the memory ports
Slave PragmasIf you find you need to map an argument to the control port, the basic structure is as follows:
#pragma HLS INTERFACE s_axilite port=<variable_name> bundle=control
#pragma HLS INTERFACE s_axilite port=<variable_name> bundle=control
What these allowed us to do was map variables, both input and output, to registers within the hardware module. Which in turn, allowed us to tell the module where to find the data it needed. It should be noted that a pragma should be created for return:
#pragma HLS INTERFACE s_axilite port=return bundle=control
#pragma HLS INTERFACE s_axilite port=return bundle=control
Known Issues- There is a bug in Vivado HLS 2018.3, such that, sometimes, you will have to create a new project in order to see the changes to the interface
- Can only test single work-group for OpenCL
Comments