.. _rtl_interface: Top-Level RTL Interface ---------------------- The LegUp-generated top-level RTL module can be instantiated and integrated with other HDL blocks, by following the protocols used by the LegUp's RTL interface. This section describes the types of interfaces that are supported by LegUp, the default interface for each data type in C/C++, and how to customize the interface. Module Control Interface ~~~~~~~~~~~~~~~~~~~~~~ The top-level Verilog module generated by LegUp is always associated with a module control interface. The table below shows the interface ports of the control interface. +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | Port Name | Direction | Description | +=============+===========+=======================================================================================================+ | `clock` | IN | The input clock signal to the RTL module. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | `reset` | IN | The input reset signal to the RTL module. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | `ready` | OUT | Indicates the readiness of the RTL module. | | | | `ready` is set to 1 when the RTL module is ready to start a new iteration (invocation) with a new set | | | | of inputs. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | `start` | IN | When `ready` is 1, setting `start` to 1 will start the execution of the RTL module; | | | | When `ready` is 0, the `start` signal is ignored by the RTL module. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | `finish` | OUT | `finish` is set to 1 for one clock cycle when the RTL module finishes. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ | `return_val`| OUT | Holds the valid return value when `finish` is asserted. | | | | This signal does not exist if the top-level function has a void return type. | +-------------+-----------+-------------------------------------------------------------------------------------------------------+ .. NOTE:: - The top-level module starts a new iteration of execution upon the handshaking when both `ready` and `start` are high at a positive edge clock. - For a circuit containing pipelined functions, the `ready` signal can be high before the previous invocations have finished, allowing multiple invocations to overlap and run in parallel. - For circuits without pipelined functions, the `ready` signal is only asserted when the last invocation has finish. - The scalar arguments (described in the next section) should be provided at the same time when the `start` signal is set to high. Timing Diagram (No Pipeline Functions) ++++++++++++++++++++++ .. image:: ./timing_diagrams/control_intf.png :align: center The timing diagram above illustrates the behaviour of the module control interface when the generated circuit contains no pipelined circuits. * First, the `ready` signal comes out high after reset (label 0). * When the `start` signal becomes high at the next cycle (label a), the handshaking between `ready` and `start` (label 1 & a) occurs and the top-level module starts running. - Notice that at the same time as the `start` signal is asserted (label a), the argument inputs (i.e., `arg_X` and `arg_Y`) should also be provided to the top-level module. The scalar argument inputs are sampled by the top-level module when the handshaking occurs and will be used as the input for the current invocation. * Then the `ready` signal goes to low at the next cycle (label 2) to indicate the top-level module cannot accept a new invocation. * When the `ready` signal is low, the `start` signal is ignored by the top-level module. For example, the `start` signal becomes high at label b. The `ready` signal from the top-level module is still low at this cycle and hence a new function invocation won't be started yet. * At label x, when the previous invocation finishes, the top-level module sets `finish` signal high for one clock cycle. In this clock cycle, the return value of the top-level function is available on the `return_val` port. * Meanwhile, the `ready` signal becomes high as the previous invocation finishes (label 3). A new handshaking with `start` occurs (label 3 & c), then the top-level function starts the second invocation and finishes at label y. Timing Diagram (Pipeline Functions) ++++++++++++++++++++++ .. image:: ./timing_diagrams/control_intf_func_pipeline.png :align: center The timing diagram above illustrates the module control interface when the generated circuit contains pipelined function(s). In this case, the LegUp circuit can overlap the execution of multiple invocations, by starting a new invocation with a new set of inputs before previous invocations have finished. * There are a total of five invocations (or five `ready` & `start` handshakings, label 0-4 & a-e). As shown in the diagram, the new invocations can start without waiting for the prior invocations to finish. * Although the LegUp circuit can process multiple invocations in parallel, there are times when the LegUp circuit cannot start a new invocation. Such case can happen when the pipeline initiation internal is not 1 (i.e., the LegUp circuit cannot start a new invocation every clock cycle), or when the circuit is stalled waiting for resource/data to become available (e.g., waiting to read from an input FIFO). When the LegUp circuit can not start a new invocation, the `ready` signal will be set to low. - For example, the `ready` signal is low for one cycle before label 2, postponing the start of the third invocation until the `ready` signal is back to high at label 2 & c. * The external logic of the LegUp circuit can also lower the `start` signal to delay the start of a new invocation, as shown in the cycle before label d. * The invocations are always finished in the same order as they started. That is, the handshakings at label 0 & a, 1 & b, 2 & c, 3 & d, 4 & e, are corresponding to the completion at label o, p, q, r, s, respectively. .. :scale: 60 % Scalar Argument ~~~~~~~~~~~~~~~~~~~~~~ Each scalar argument of the top-level function (e.g., void foo(**int scalar_arg**);) becomes an input port of the top-level RTL module. Valid argument values should be provided on these input ports when the `start` signal is asserted. If the input port can be hold valid and unchanged throughout the whole iteration of the execution, a `stable` option can be specified using the following pragma to inform LegUp and potentially save register usage in the generated module. .. code-block:: CPP #pragma LEGUP interface argument() type(simple) stable() Note that the "type" option is not configurable in the current release but still needs to be specified if you wish to add the stable option. If `stable` is false, the pragma is not necessary because LegUp assumes not stable by default. +---------------------+-----------+-------------------------------------------------------------------------------------------------------+ | Port Name | Direction | Description | +=====================+===========+=======================================================================================================+ | `` | IN | The input value of the scalar argument. This input port is sampled by the LegUp module when both | | | | `start` and `ready` signals are 1. | +---------------------+-----------+-------------------------------------------------------------------------------------------------------+ Pointer Argument and Shared Global Variable ~~~~~~~~~~~~~~~~~~~~~~ Pointer argument and global variables are considered as "memories" that maintain states in the circuit. The "memories" may be implemented inside or outside of the LegUp-generated circuit, depending on the specified interface type. For a global variable, a top-level RTL interface is only created if the variable is shared/accessed by both the software testbench and the HLS function. If the global variable is only accessed by the HLS function, the "memory" will be implemented inside the generated circuit and has no top-level interface. There are three interface types for pointer argument and shared global variables: memory, scalar memory, and AXI4 slave. As shown in the table below, the available and default interface types vary depending on the data type of the pointer or global variable. We will explain each interface type in the sections below. +-----------+--------------------------------------------------------------+ | Data Type | Interface Type | | +-------------------+--------------------+---------------------+ | | Memory | Scalar memory | AXI4 Slave | +===========+===================+====================+=====================+ | Array | Yes (default) | Yes | n/a | +-----------+-------------------+--------------------+---------------------+ | Struct | Yes (default) | Yes | Yes (global only) | +-----------+-------------------+--------------------+---------------------+ | Scalar | n/a | Yes (default) | n/a | +-----------+-------------------+--------------------+---------------------+ Memory Type ++++++++++++++++++++++ The memory interface can be used for array/struct arguments or shared global array/struct. The generated RTL interface can be connected to an external RAM module that stores the corresponding data. The pragma below specifies the memory interface type for a given argument or global variable, .. code-block:: CPP // For top-level function arguments: // Add at the beginning of the function definition #pragma LEGUP interface argument() type(memory) depth() // For shared global variables: // Add before the variable definition #pragma LEGUP interface variable() type(memory) depth() .. The default read latency is 1 clock cycle and can be overrided via the following TCL command: The array size can be specified or overridden (over the declared size in C++) by specifying the depth option. .. NOTE:: The specified depth will represent the total number of elements of the array. In the case of multi-dimensional array, the size of the outermost dimension will be overrided to (depth / COMBINE_DEPTH_OF_INNER_DIMENSIONS), and the sizes of inner dimensions will remain the same. For multi-dimensional arrays, the specified depth has to be a multiple of the combined depth of all inner dimensions. LegUp circuit expects a one cycle read latency, and two RAM ports will be created for each memory interface. Moreover, a true dual-port memory is expected, allowing both ports to perform independent reads/writes simultaneously. The same port will not have both read and write requests at the same cycle. The table below lists the signals of each RAM port, with "_a" and "_b" suffixes respectively. +---------------------------------------+-----------+--------------------------------------------------------------------------+ | Port Name | Direction | Description | +=======================================+===========+==========================================================================+ | `_address_` | OUT | The address pointing to the RAM entry that LegUp module wants to access. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ | `_enable_` | OUT | Read enable port. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ | `_out_` | IN | Read data port. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ | `_write_enable_` | OUT | Write enable port. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ | `_byteena_` | OUT | Byte-enable port. Only available if the memory requires writes to | | | | partial bytes of a memory word. | | | | Not available if all write operations update the whole memory words. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ | `_in_` | OUT | Write data port. | +---------------------------------------+-----------+--------------------------------------------------------------------------+ The timing diagram shows the memory interface behaviour expected by the LegUp-generated module. .. image:: ./timing_diagrams/memory_intf.png :align: center * At Cycle a, the top-level module performs a write operation to the connected external memory, by setting the `arg_write_enable` port to high and providing the address "A0" and write data "D0" on the `arg_address` and `arg_in` ports respectively. * At Cycle b, the top-level module issues a read from the connected external memory by setting `arg_address` to the read address "A0" and `arg_enable` to high. LegUp module expects the read data "D0" to be available on the `arg_out` port in the subsequent cycle (i.e., Cycle c). * At Cycle d, the top-level module writes "D1" to address "A1" * Keeping the address port the same and setting `arg_enable` high at the next cycle, the top-level module reads from address "A1" at Cycle e, and expects to receive "D1" at the right next cycle at Cycle f. Scalar Memory Type ++++++++++++++++++++++ The scalar memory interface is used by LegUp module to access external memory that has only one element, and can be thought of as a special memory interface that has no address port. The scalar memory interface is used by default for scalar pointer arguments and shared scalar global variables. When scalar memory interface is specified for an array data type, LegUp will automatically partition the array into individual elements and will error out if it fails to do so. The pragma below specifies the scalar memory interface, .. TODO: output_registered option. .. code-block:: CPP // For top-level function arguments: // Add at the beginning of the function definition #pragma LEGUP interface argument() type(scalar_memory) // For shared global variables: // Add before the variable definition #pragma LEGUP interface variable() type(scalar_memory) +-----------------------------+-----------+--------------------------------------------------------------------------+ | Port Name | Direction | Description | +=============================+===========+==========================================================================+ | `_readdata` | IN | The input value of the argument. The signal is not sampled at the start | | | | of circuit execution. The external logic needs to keep the signal stable | | | | and valid at any given time during the circuit execution. | | | | This port is not available if the LegUp circuit never reads from the | | | | pointer argument (or global variable). | +-----------------------------+-----------+--------------------------------------------------------------------------+ | `_writedata` | OUT | The output value of the argument. The `writedata` port has valid value | | | | only when the `write_enable` signal is high. | | | | This port is not available if the LegUp circuit never writes to the | | | | pointer argument (or global variable). | +-----------------------------+-----------+--------------------------------------------------------------------------+ | `_write_enable` | OUT | Indicates the writedata is valid. | | | | This port is not available if the LegUp circuit never writes to the | | | | pointer argument (or global variable). | +-----------------------------+-----------+--------------------------------------------------------------------------+ Note that LegUp circuit expects a 0-cycle ready latency. This is, the `_readdata` port is expected to always hold the valid data for the pointer argument such that the LegUp circuit can use its value at any time. One way of using the scalar memory interface is to connect the scalar memory interface to a register outside of the LegUp module. The `readdata` port can be connected to the register itself. The `write_enable` and `writedata` ports will be used to update the register. .. image:: ./timing_diagrams/scalar_memory_intf.png :align: center The timing diagram gives an example of how the scalar memory interface would behave when it is connected to an external register. Initially the external register is holding a value of "D0" and providing the value to the `arg_readdata` port. At Cycle a, the top-level module writes to the register by asserting `arg_write_enable` and setting `arg_writedata` to "D1". Then at the next cycle, Cycle b, the register value is updated, and the `arg_readdata` is also immediately updated to the new value. Note that the scalar memory interface does not always have to be connected to an external register. One use case of the scalar memory interface could be to connect the `arg_readdata` port to an input signal that is changing while the LegUp circuit runs, allowing the LegUp circuit to read the up-to-date value from the input. Typically, the pointer argument is read-only by the LegUp circuit in such case. Similarly, the `arg_writedata` can be connected to an output that needs to be updated in real-time. In this case, the pointer argument is normally only written to by the LegUp circuit (with no read access). .. The output_registered option can be set to true in this case so that the arg_writedata is always holding the latest valid data. AXI4 Slave Type ++++++++++++++++++++++ In contrast to the memory and scalar memory interfaces, when the AXI4 slave interface is used, the "memories" for storing the data is inside the LegUp-generated RTL module rather than outside. The logic outside of LegUp module is responsible for initializing and/or retrieving the memory content before and/or after the execution of LegUp module. The pragma below specifies an AXI4 slave interface, .. // For top-level function arguments: .. #pragma LEGUP interface argument() type(scalar_memory) .. code-block:: CPP // For shared global variables: // Add before the variable definition #pragma LEGUP interface variable() type(axi_slave) concurrent_access(true|false) When the `concurrent_access` option is set to true (default to false), the external logic can read/write the AXI4 slave interface while the LegUp module is running. The concurrent access will however reduce the LegUp module's throughput to access the memory. After compilation, LegUp will generate a report file (``reports/axi_slave_memory_map.legup.rpt``) to specify the address map for each struct element. Here is an example struct and its corresponding memory map, .. code-block:: CPP struct SlaveLayout { ap_uint<8> arr[8]; ap_uint<32> a; ap_uint<32> b; ap_uint<64> sum_result; ap_uint<32> xor_result; ap_uint<32> or_result; }; #pragma LEGUP interface variable(gv) type(axi_slave) concurrent_access(true) SlaveLayout gv; The corresponding address map report (``reports/axi_slave_memory_map.legup.rpt``) is shown below. .. code-block:: text Address Map for AXI Slave Interface: gv +--------------+-----------+-------------------+----------+ | Word Address | Bit Range | Variables | Removed? | +--------------+-----------+-------------------+----------+ | 0 | 63 : 56 | memory.arr[7] | | | | 55 : 48 | memory.arr[6] | | | | 47 : 40 | memory.arr[5] | | | | 39 : 32 | memory.arr[4] | | | | 31 : 24 | memory.arr[3] | | | | 23 : 16 | memory.arr[2] | | | | 15 : 8 | memory.arr[1] | | | | 7 : 0 | memory.arr[0] | | | 1 | 63 : 32 | memory.b | | | | 31 : 0 | memory.a | | | 2 | 63 : 0 | memory.sum_result | | | 3 | 63 : 32 | memory.or_result | | | | 31 : 0 | memory.xor_result | | | 4 | 0 : 0 | slave_memory_ctrl | | +--------------+-----------+-------------------+----------+ - Note that the first column in the report shows the word-address -- multiply by 8 to get the byte-address. - The last column will indicate the struct elements that are optimized away from compilation because the LegUp module does not access them. - Notice that the last element in the table, ``slave_memory_ctrl``, is not part of the struct definition. This is a special status control register for the LegUp module. Writing to the address of ``slave_memory_ctrl`` will start the LegUp module (if the module was not running), and reading the register can poll the status, a value of 1 indicates the LegUp module has finished running, and 0 otherwise. .. NOTE:: There are several limitations in the current release, - A LegUp module can have at most one AXI4 slave interface, and the AXI4 slave interface type can only be specified for a global variable with a struct data type. If multiple data need to be placed behind the AXI4 slave interface, you can define a new struct type to include all the data, then instantiate a global variable with the struct type, and specify the above pragma for the global variable. - The AXI4 slave interface only supports the AXI44-lite protocol with additional support for incremental bursting. - Byte-enable write (via WSTRB port) has to be aligned to the elements in the struct. For example, you can update a 'short' type integer in the struct (without updating its nearby data that is packed in the same word) by asserting the corresponding WSTRB bits based on the mapped address of the 'short' type integer; but you cannot update partial bytes of the 'short' type integer. The WSTRB bits corresponding to different bytes of a struct element have to be all 1s or all 0s. - The AXI4 slave interface always uses 32-bit address and 64-bit data width. - The SW/HW Co-Simulation is only supported if the top level function is not pipelined. - When AXI4 slave interface is used, the top-level function must use void return type. legup::FIFO Argument ~~~~~~~~~~~~~~~~~~~~~~ LegUp provides a :ref:`streaming_lib` which includes a FIFO template class for inferring the AXI4-stream like, ready-valid-data (RVD) interface. When a ``legup::FIFO`` type argument is used by the top-level function, a corresponding RVD interface is always generated. The RVD interface is useful to transfer data from an upstream producer to a downstream consumer. The upstream sends the data along with a valid signal to indicate the data validity, while the downstream controls a ready signal to indicate its readiness to consume the data. A data is only transferred when both valid and ready signals are high at a positive clock edge. .. image:: ./timing_diagrams/rvd_intf.png :align: center As shown in the above timing diagram, three sets of data are transferred at the 2rd, 4th, and 6th positive clock edges. No data transfer occurs at the 3rd positive clock edge because the upstream does not assert the valid signal. The data "D2" is also not transferred at the 5th positive clock edge because the downstream back-pressures the upstream by lowering the ready signal. The ``legup::FIFO`` template class is declared as .. code-block:: CPP template class FIFO; The template argument ``T`` defines the data type, which can be a scalar data type or a struct of multiple scalar types. The template argument ``pack`` defines whether the scalar elements inside the struct should be packed into a single data port. When ``pack`` is false, each scalar element has its own data port, and all scalar elements in the struct share the same pair of ready and valid ports. An ``legup::FIFO`` argument must be either write-only or read-only. The data and valid ports always have the same direction (output if write-only, input if read-only), while the ready port has the opposite direction. The following table shows the interface ports of the ``legup::FIFO`` argument depending on the template parameters. .. FIXME: Having trouble figuring out the struct indentation below that would work for both html and pdf. +------------------------------------------------------------------+-------------------------------------------------+ | Template Parameter | Port Name | +==================================================================+=================================================+ | ``T`` is a scalar data type (``pack`` is ignored) | | | | | | _valid | | | | _ready | +-------------------------------------------+----------------------+-------------------------------------------------+ | ``T`` is a struct of scalars, e.g., | ``pack`` = false | | _data | | | | | _keep | | struct MyAxiStream { | | | _last | | ap_uint<32> data; | | | _valid | | ap_uint<8> keep; | | | _ready | | ap_uint<1> last; +----------------------+-------------------------------------------------+ | }; | ``pack`` = true | | // 41-bit wide. | | | | | _valid | | | | | _ready | +-------------------------------------------+----------------------+-------------------------------------------------+ In the struct type example above, the ``ap_uint`` template class is from :ref:`ap_lib`, which allows you to define custom bit-width integers. If you are familiar with the AXI4-stream interface, you may already notice that the struct example (non-pack case) yields an AXI4-stream interface. Indeed, this is how to infer AXI4-stream interface using ``legup::FIFO`` library. Inferring AXI4 Interfaces Using legup::FIFO ++++++++++++++++++++++ In addition to inferring an AXI4-stream interface as shown in the example above, ``legup::FIFO`` can also be used to implement a custom AXI4 slave or AXI4 master. The AXI4 interface protocol has 5 channels, read address (AR), read data (R), write address (AW), write data (W), and write response (B). Each channel is an AXI4 stream interface and can be described in C++ as a ``legup::FIFO`` object. For example, the read address channel has an address signal and a length signal. The AXI4 channel can be implemented as following in C++ to get the corresponding AR channel in the RTL interface. .. code-block:: CPP struct RdAddrSignals { uint32_t addr; uint8_t len; }; void MyTopFunctoin (legup::FIFO ar) { RdAddrSignals ar_sig; ar_sig.addr = 0x2000; ar_sig.len = 7; // 8-beat burst. ar.write(ar_sig); } .. code-block:: Verilog module MyTopFunction ( input clock, input reset, input ar_ready, output ar_valid, output [31:0] ar_addr, output [7:0] ar_len ); C++ Library for Custom AXI Master Interface ++++++++++++++++++++++ LegUp provides a C++ library for implementing the AXI4 master interfaces. The library defines the AXI4 master interface in C++ and provides several API functions for typical operations. For advanced users hoping to have more fine-grained custom control, or additional AXI4 interface signals that are not included in the library, the library can serve as a reference implementation for customization (create your own AXI4 master library based on LegUp's ``axi_interface.hpp`` header file). To create an AXI4 master interface using LegUp's library, include the header file: .. code-block:: c #include To add an AXI4 master interface, you will need to 1. Create an instance of the ``AxiInterface`` class and specify the address width, data width and wstrb width through template parameters. 2. Pass the created instance by reference to the top-level function. E.g., ``void MyTop(AxiInterface, /* DATA: */ ap_uint<64>, /* WSTRB: */ ap_uint<8>> &master);`` 3. Use the utility functions (APIs) defined in the header to control the AXI master interface. Below are the API functions to access the AXI master interface. .. code-block:: cpp template void axi_m_read_req(AxiInterface &m, T_ADDR byte_addr, ap_uint<9> burst_len); template T_DATA axi_m_read_data(AxiInterface &m); template void axi_m_write_req(AxiInterface &m, T_ADDR byte_addr, ap_uint<9> burst_len); template void axi_m_write_data(AxiInterface &m, T_DATA val, T_WSTRB strb, bool last); template ap_uint<2> axi_m_write_resp(AxiInterface &m); The read and write operations are independent and therefore can be executed in parallel at the same time. Same as the AXI4 slave interface, this AXI4 master interface library only supports the AXI4-lite protocol with additional support for bursting. SW/HW Co-Simulation is supported for AXI master, but requires modeling the AXI slave's responses to the AXI master in software before the kernel is called. An example of an AXI4 master interface tested with CoSim is shown below. .. code-block:: CPP #include #include void simple_master(AxiInterface, ap_uint<64>, ap_uint<8>> &master) { #pragma LEGUP function top ap_uint<9> remaining = AXIM_MAX_BURST_LEN; ap_uint<32> r_addr = 0; ap_uint<32> w_addr = AXIM_MAX_BURST_LEN * 8; #pragma LEGUP loop pipeline for (; remaining != 0; --remaining) { bool is_last = remaining == 1; if (remaining == AXIM_MAX_BURST_LEN) { // Request to read data in burst. axi_m_read_req, ap_uint<64>, ap_uint<8>>(master, r_addr, AXIM_MAX_BURST_LEN); // Request to write data in burst. axi_m_write_req, ap_uint<64>, ap_uint<8>>(master, w_addr, AXIM_MAX_BURST_LEN); } // Write back the data we read + 1. ap_uint<64> data = axi_m_read_data, ap_uint<64>>(master); axi_m_write_data, ap_uint<64>, ap_uint<8>>(master, data + 1, 0xFF, is_last); // After the last write, read the response code. if (is_last) ap_uint<2> bresp = axi_m_write_resp(master); } } int main() { AxiInterface, ap_uint<64>, ap_uint<8>> axi_if(AXIM_MAX_BURST_LEN); // Prepare the data to be read by the AXI master. for (int i = 0; i < AXIM_MAX_BURST_LEN; i++) { RdDataSignals> r_sig; r_sig.data = i; r_sig.resp = 0; r_sig.last = i == AXIM_MAX_BURST_LEN - 1; axi_if.r.write(r_sig); } // Prepare the write response for the write from AXI master. WrRespSignals b_sig; axi_if.b.write(b_sig); // Run the top-level function that will be synthesize to hardware. simple_master(axi_if); bool failed = false; // Clear the write and read request. ap_uint<32> r_addr = axi_if.ar.read().addr; ap_uint<32> w_addr = axi_if.aw.read().addr; // Check that the read and write addresses were as expected. failed |= r_addr != 0; failed |= w_addr != AXIM_MAX_BURST_LEN * 8; // Read all of write data. for (int i = 0; i < AXIM_MAX_BURST_LEN; ++i) { // Check that write data is i + 1. failed |= axi_if.w.read().data != i + 1; } // Now that all FIFOs have been cleared, the AXI interface could be prepared // for more calls to the kernel.. if (!failed) printf("PASS!\n"); else printf("FAILED!\n"); return failed; } .. AXI Slave Interface (Beta) .. -------------------------- .. LegUp allows generating an AXI slave interface for the top-level function in hardware. The slave interface is implemented by using FIFOs in the streaming library. To specify a slave interface, you will need to .. .. 1. define a struct for the slave memory in a header file. .. 2. instantiate a global variable of the struct above. .. 3. specify a top-level function in the Tcl configuration file. .. 4. add ``set_argument_interface_type axi_s`` in the Tcl configuration file. .. .. There are several additional optional arguments that can come after axi_s in the config. .. .. ====================== ========================== .. Argument Explanation .. ====================== ========================== .. -wstrb_mode Mode can be one of *none*, *element_wise_aligned*, or *element_wise_aligned_with_invalid_detection*. Element-wise aligned WSTRB only enables writing to an element of the AXI struct if all corresponding WSTRB bits are 1. Invalid detection will return an error (2, SLVERR) through the BRESP channel if element un-aligned WSTRB is detected. .. -concurrent_access Enables reading and writing to the slave interface while the slave is running. Note: this reduces throughput for access to the slave memory. .. ====================== ========================== .. .. The AXI slave interface support is a beta feature with several limitations: .. .. * Only one AXI slave interface can be generated for each LegUp project. .. * The interface only supports the AXI-lite protocol with additional support for bursting. .. * The interface always uses 32-bit address and 64-bit data width. .. * SW/HW Co-Simulation is only supported if the top level function is not pipelined. .. .. We will improve and stablize this feature in the later releases. Please contact support@legupcomputing.com if you require more details about this feature.