In my recent , I created a simple High Level Synthesis (HLS) IP block to which filtering and effects could be added. To ensure this IP core would interface with the I2S TX and RX IP cores from Xilinx, I needed to create AXI streaming interfaces on the HLS IP block.
Doing this got me thinking a little about how we perform interfacing using Vivado HLS, so in this blog I am going to explain how we control what interface our HLS IP block uses.
We are going to start right at the beginning with a simple HLS IP block which performs simple addition. For all these examples we are going to be targeting the Zedboard.
In HLS, the interfaces on the synthesized block are defined by the arguments we pass to our C/C++ function along with any return parameters. The direction depends upon how the are used in the function; though if we use pointers or arrays, these can be used as IO.
In addition to the inputs and outputs, an ap_cntrl interface will also be created in the synthesised HLS IP block. This control interface provides clock, reset and handshaking, for example, enabling the block to be started (ap_start), reporting when a result is ready (ap_done), when the block is ready to take a new input (ap_ready), and if the block is idle (ap_idle).
However, even the behavior of the ap_cntrl interface depends upon our solution setting and the code itself.
For the first example, we are going to use a slow clock at 100 ns. The first stage of the HLS process is scheduling, which is when the HLS tool assigns operations to clock cycles. If the clock period is long enough, then it may not even need registers, resulting in a combinatorial design.
The clock period defined in the solution settings will therefore have an impact on the synthesis and the interfacing. The very simple code below when synthesized with a clock period of 100 ns will result in a combinatorial implementation.
As such, there is no need for the handshaking signals. If you examine the output VHDL or Verilog, you will see while the signals exist, they simply assign the inputs and constants to the outputs. Another indication the synthesized block is combinatorial is there is no clock or reset input.
If we change the clock period to one of 5 ns, we will see the reported latency increase as the HLS tool inserts a register stage. This is also reflected in the interfaces with the clock and reset now being present and the behavior of the handshaking signals being updated to correctly implement the required handshaking.
This simple ap_cntrl port therefore gives us the ability to work with the HLS IP block in our design, and for that reason is called the block level protocol.
Of course, for many applications we want to be able to interface our HLS IP block with designs which use AXI or memory interfaces.
This is where the port level protocols come in and for these the type of C construct used for the variable becomes important. Port level protocols enable us to define for each port on the HLS block a specific interface protocol such as AXI Lite, AXI, FIFO, BRAM etc.
By updating the simple example above to use arrays, we can modify the interfaces to use FIFO type interfaces. We define this interface type using the pragma:
#pragma HLS interface ap_fifo depth = port =
To define the interface type, we can either type these in by hand as above or use the directives window on the right-handside of the HLS window.
As this is a port level interface, we need to define this for each of the interfaces in the HLS block.
When we run this through HLS, we will see the synthesis report generated which defines the interfaces implemented. As you can see, the FIFO interfaces have been implemented.
FIFO interfaces are interesting when we want to stream data between HLS modules.
Often, however, we want to implement interfaces which use a flavor of AXI (s_axilite, axis, s_axi, m_axi). We can use the same approach to implement AXIS and AXI master and slave interfaces.
If we are working with a heterogeneous SoC, such as the Zynq or Zynq MPSoC, we may want to use a AXI lite for control of the HLS IP block and not the discrete block interface. We can do this by using the pragma as below:
#pragma s_axilite port=return bundle=cmd
This will create not only a AXI lite interface that allows us to control the HLS IP core, it will generate the software drivers necessary to work with the HLS IP core in SDK as well.
If there is no need to stop and start the HLS IP core, then we can use the pragma:
#pragma HLS interface ap_ctrl_none port=return
Understanding how we can control the interfaces on our HLS IP blocks means that when we are working with HLS, we can ensure our IP block easily integrates with the rest of our design saving time and effort in creation of conversion blocks.
See My FPGA / SoC Projects: Adam Taylor on Hackster.io
Get the Code: ATaylorCEngFIET (Adam Taylor)
Access the MicroZed Chronicles Archives with over 280 articles on the Zynq / Zynq MpSoC updated weekly at MicroZed Chronicles.