Vivado example (Also includes other AXI slaves, PWM decode and DSHOT150 generation)
For the past several weeks been working on using DMA (microblaze softcore ) with an AXI Stream interface connected to the FTDI serial interface. Here is a diagram from Vivado:
The AXI stream wraps an UART, (From nandland) and supports 12M baud. The AXI stream also supports TLAST on RX stream. TLAST is used to terminate the DMA and start a new transaction. Instead of the processor handling each byte using interrupts or polling, there will be one interrupt per packet, i.e. like an Ethernet packet. The serial protocol uses a similar encoding as SLIP or PPP, so, there is a unique SOP byte (start of packet) and EOP byte (end of packet). At this time the current rate is 3.4Mbps total or 1.7Mpbs for each RX/TX line.
Currently working on moving packet encoding, CRC handling to the FPGA to increase performance and off load the soft-core processor. The code is on github. in the axi_dma_stream_microblaze directory. The top level, resource is a modified libftdi library, added an API to allow reading what ever is in the current buffer. FTDI library API ftdi_set_event_char is used to assist in flushing the USB buffer when an EOP is received. There is a simple example, loopback_test.cpp that sends a "ping" packet that is echo'ed back to measure round trip time. Also, an async message to toggle the board LEDs.
Microblaze software
The microblaze is configured to run FreeRTOS and in the vitis directory there is a DMA driver that has 4 RX buffer descriptors and 8 RX buffers. The other 4 are used to swap between the driver and user application, allowing a zero-copy. For TX there is two buffers that can be allocated at the app level to allow zero-copy usage.
For example of processing an RX DMA buffer at the application level:
static void prvRxTask( [[maybe_unused]] void *pvParameters )
{
for( ;; )
{
uint8_t *rxPkt;
size_t lenOfPkt;
if ( true == wait_rx_pkt( rxPkt, lenOfPkt,portMAX_DELAY )) <-- DMA packet buffer is queued to the application
{
gProtocol.onRecv(rxPkt, lenOfPkt);
freeRxPkt(rxPkt); <-- DMA packet is queued back to the DMA buffer...
}
}
}
For TX:
.....
if (allocTx(gpTxPkt, gTxPktMaxSize)) <-- allocate a TX DMA buffer with a requested size..
...
rc = wait_tx_pkt(gpTxPkt, gTxPktSize); <- queue the packet to DMA driver.. in this case it blocks until sent.