Using Vivado HLS we can of course, accelerate the development of our data path. There are times however, when using HLS that we want to interact with external memories such as DDR. Either to store data or to retrieve data already written by another function.
Using HLS to interact with an external DDR at first sounds a like it might be complicated. Nothing could be further from the truth as I am about to demonstrate.
To do this I am going to use my Arty board which contains both a Artix 35T FPGA and 256MB of DDR3L ideal for the demonstration. What this demonstration will create is a HLS IP block which can be included within our Vivado design and interface with DDR. The functionality of this block will be simple writing a pattern of numbers into the DDR memory.
To successfully use this block we will therefore, need a Vivado design which contains the following
- Memory Interface Generator configured for the DDR3L
- JTAG to AXI to allow debug verification access to the DDR3L
- AXI interconnect to allow the HLS block and the JTAG to AXI to access the MIG
- VIO to start the HLS block
- ILA to verify / debug internally the design
- The HLS test block itself
The idea behind this demonstration is the HLS block will write the data to the DDR and I will then be able to read the values written into the DDR using the JTAG to AXIS.
To crate the HLS block we use Vivado HLS and create a new project targeting the same Artix devices as on the Arty. All we need to do then is create our source files and test bench both of which will be very simple.
First the source file, to read or write from DDR memory we use the C function memcpy when used in software it allows the copying of data from a source to a destination. When we use it in HLS it also does the same however, the movement of data is based around the AXI4 memory mapped interface.
Of course, this interface is ideal for use with the MIG and other memory interfaces. As such the code we use to create the example HLS block is very simple
Looking in detail at the code, the function prototype includes a int pointer DDR, this will be the interface we use to write out the data to the DDR memory. If necessary, we could also use this interface to read from the DDR as well.
We to define this interface as a AXI4 master interface we use the pragma HLS INTERFACE with the type m_axi port =ddr associates the port ddr with the AXI interface. While the depth is used for co-simulation when pointers are used in place of arrays and should be set to the number values written /read.
The offset is how we control the address space and the addresses the AXI interface begins to access. This can be provided by one of three methods.
- Off – the default, it will start accesses from 0x00000000.
- Direct – this will create a port on the HLS module which allows the offset definition to be applied from within the Vivado block design.
- Slave – this will create a AXI 4 Lite interface with a register which defines the offset.
For this example, we will be using the direct approach as this allows us to show how a more logic-based design as opposed to a solution containing an embedded processing system using AXI Lite.
When it comes to verifying the design, we need to create a simple test bench which allows us to perform C based simulation and Co-Simulation. The test bench for this example simply has a 256-bit array passed to the HLS function which it fills.
The C Simulation allows us to test out the functionality of the code before we perform the HLS synthesis and co-simulation. Just like in any C debugging here we can use break points and examine the contents of variables.
With this completed the next step is to perform Synthesis and Co-Simulation, when you run Co-Simulation ensure you set the dump trace to port. This allows us to see the inputs and output waveforms of the HLS core.
The Co-Simulation applies the same C test bench inputs to the generated RTL and reports back on the pass / fail of the simulation.
Being happy with the co-simulation results the final step in Vivado HLS is to generate the IP core, and add it into our Vivado IP repository allowing its use.
Once our HLS core has been added into the Vivado design it is here that we can set the address the HLS core will use for its transactions. To do this I used a constant block.
With this completed we can then generate the bit stream, and once completed open the hardware manager and program the device.
Thanks to the VIO we can control the ap_start signal on the HLS core, this means before we start we can read the DDR memory using the JTAG to AXIS link and check that it is not set to our test values.
Satisfied the DDR memory is randomly initialised we can then start the HLS block using the VIO and check that it has run correctly writing its data to the DDR memory.
To verify the succesful writing of data, we can do this in two ways the first is to use internal ILA’s configured to trigger on AXI writes and completion of the HLS function.
The second is to use the JTAG to AXI bridge to read back the written addresses and confirm the values.
This demonstrates the write to DDR memory is successful, which enables us more flexibility within our HLS designs.
Watch out for!
- Ensure the HLS block is reset correctly.
- Ensure the ap_start signal is held high until ap_ready is asserted.
- Holding ap_start high will result in the core running again.
- Ensure you have the address range set correctly for your memory access in the hardware.
MicroZed Chronicles on GitHub
Want a Book