AXI4 memory-mapped (m_axi
) interfaces allow kernels to read and write data in global
memory (DDR, HBM, PLRAM). Memory-mapped interfaces are a convenient way of sharing data across
different elements of the accelerated application, such as between the host and kernel, or
between kernels on the accelerator card. Refer to Vitis-HLS-Introductory-Examples/Interface/Memory on
Github for examples of some of these concepts.
m_axi
interfaces
are listed below:- The interface has a separate and independent read and write channels
- It supports burst-based accesses with potential performance of ~17 GB/s
- It provides support for outstanding transactions
In the Vitis Kernel flow the m_axi
interface is assigned by default to pointer and array
arguments. In this flow it supports the following default features:
- Pointer and array arguments are automatically mapped to the
m_axi
interface - The default mode of operation is
offset=slave
in the Vitis flow and should not be changed - All pointer and array arguments are mapped to a single interface bundle to conserve device resources, and ports share read and write access across the time it is active
- The default alignment in the Vitis flow is set to 64 bytes
- The maximum read/write burst length is set to 16 by default
m_axi
interface is specified it has the
following default features:- The default operation mode is offset=off but you can change it as described in Offset and Modes of Operation
- Assigned pointer and array arguments are mapped to a single interface bundle to conserve device resources, and share the interface across the time it is active
- The default alignment in Vivado IP flow is set to 1 byte
- The maximum read/write burst length is set to 16 by default
In both the Vivado IP flow and Vitis kernel flow, the INTERFACE pragma or directive can be used to modify default values as needed. Some customization can help improve design performance as described in Optimizing AXI System Performance.
You can use an AXI4 master interface on array or pointer/reference arguments, which Vitis HLS implements in one of the following modes:
- Individual data transfers
- Burst mode data transfers
With individual data transfers, Vitis HLS reads or writes a single element of data for each address. The following example shows a single read and single write operation. In this example, Vitis HLS generates an address on the AXI interface to read a single data value and an address to write a single data value. The interface transfers one data value per address.
void bus (int *d) {
static int acc = 0;
acc += *d;
*d = acc;
}
With burst mode transfers, Vitis HLS reads
or writes data using a single base address followed by multiple sequential data samples, which
makes this mode capable of higher data throughput. Burst mode of operation is possible when
you use the C memcpy
function or a pipelined for
loop. Refer to AXI Burst Transfers for more information.
memcpy
function is only supported for synthesis when used to
transfer data to or from a top-level function argument specified with an AXI4 master interface.The following example shows a copy of burst mode using the memcpy
function. The top-level function argument a
is specified as an AXI4
master interface.
void example(volatile int *a){
//Port a is assigned to an AXI4 master interface
#pragma HLS INTERFACE mode=m_axi depth=50 port=a
#pragma HLS INTERFACE mode=s_axilite port=return
int i;
int buff[50];
//memcpy creates a burst access to memory
memcpy(buff,(const int*)a,50*sizeof(int));
for(i=0; i < 50; i++){
buff[i] = buff[i] + 100;
}
memcpy((int *)a,buff,50*sizeof(int));
}
When this example is synthesized, it results in the interface shown in the following figure.
The following example shows the same code as the preceding example but uses a
for
loop to copy the data out:
void example(volatile int *a){
#pragma HLS INTERFACE mode=m_axi depth=50 port=a
#pragma HLS INTERFACE mode=s_axilite port=return
//Port a is assigned to an AXI4 master interface
int i;
int buff[50];
//memcpy creates a burst access to memory
memcpy(buff,(const int*)a,50*sizeof(int));
for(i=0; i < 50; i++){
buff[i] = buff[i] + 100;
}
for(i=0; i < 50; i++){
#pragma HLS PIPELINE
a[i] = buff[i];
}
}
When using a for
loop to implement burst
reads or writes, follow these requirements:
- Pipeline the loop
- Access addresses in increasing order
- Do not place accesses inside a conditional statement
- For nested loops, do not flatten loops, because this inhibits the burst operation
for
loop unless the ports are bundled in different AXI
ports. The following example shows how to perform two reads in burst mode using different AXI
interfaces.In the following example, Vitis HLS
implements the port reads as burst transfers. Port a
is
specified without using the bundle
option and is implemented
in the default AXI interface. Port b
is specified using a
named bundle and is implemented in a separate AXI interface called d2_port
.
void example(volatile int *a, int *b){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE mode=m_axi depth=50 port=a
#pragma HLS INTERFACE mode=m_axi depth=50 port=b bundle=d2_port
int i;
int buff[50];
//copy data in
for(i=0; i < 50; i++){
#pragma HLS PIPELINE
buff[i] = a[i] + b[i];
}
...
}