Sollen im FPGA z. B. Audio-, Video- oder Netzwerkinterfaces genutzt werden, so wird in den meisten Fällen auf ein AXI-Stream Interface zurückgegriffen um den jeweiligen IP-Core für das Interface mit dem Prozessor oder der programmierbaren Logik zu verbinden. Um eine ununterbrochene Datenversorgung für das AXI-Stream Interface zu gewährleisten, wird in der Regel auf einen AXI-Stream FIFO oder einen AXI-DMA zurückgegriffen. Beide IP-Cores werden einmalig konfiguriert und übertragen dann die Daten entweder direkt aus dem Hauptspeicher über das AXI-Stream Interface (DMA) oder aus einem lokalen Speicher (FIFO).
In diesem Beispiel möchte ich zeigen, wie ein AXI-Stream FIFO genutzt werden kann um Daten vom Processing System über das AXI-Stream Interface zu übertragen. Der FIFO stellt in diesem Beispiel sowohl den Master (über den Sendekanal), als auch den Slave (über den Empfangskanal) dar. Der FIFO kann auf zwei verschiedene Arten betrieben werden:
In diesem Beispiel soll der Store-and-Forward Modus genutzt werden. Das dazu gehörige Design sieht folgenermaßen aus:
Wobei der FIFO wie folgt konfiguriert wird:
Hinweis:
Der Transmit Control Kanal wird für bestimmte VLAN Features bei den AXI Ethernet IP-Cores genutzt und kann daher in diesem Beispiel ignoriert werden.
Die AXI-Stream Ports können zudem noch zusätzliche Einstellungen erweitert werden:
Für die Software wird ein neues Projekt angelegt und der entsprechende Header eingefügt um den FIFO nutzen zu können:
#include "xllfifo.h"
Dann kann der FIFO auch schon initialisiert werden:
FIFO_ConfigPtr = XLlFfio_LookupConfig(XPAR_FIFO_DEVICE_ID); if(FIFO_ConfigPtr == NULL) { xil_printf("[ERROR] Invalid FIFO configuration!\r\n"); return XST_FAILURE; } if(XLlFifo_CfgInitialize(&Fifo, FIFO_ConfigPtr, FIFO_ConfigPtr->BaseAddress) != XST_SUCCESS) { xil_printf("[ERROR] FIFO Initialization failed!\n\r"); return XST_FAILURE; }
Wenn die Interrupts genutzt werden sollen, müssen zusätzlich noch der GIC initialisiert und die Interrupts konfiguriert werden:
GIC_ConfigPtr = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID); if(GIC_ConfigPtr == NULL) { xil_printf("[ERROR] Invalid GIC configuration!\n\r"); return XST_FAILURE; } XScuGic_CfgInitialize(&GIC, GIC_ConfigPtr, GIC_ConfigPtr->CpuBaseAddress); XScuGic_SetPriorityTriggerType(&GIC, XPAR_FABRIC_LLFIFO_0_VEC_ID, 0xA0, 0x03); if(XScuGic_Connect(&GIC, XPAR_FABRIC_LLFIFO_0_VEC_ID, (Xil_ExceptionHandler)FifoHandler, &Fifo) != XST_SUCCESS) { xil_printf("[ERROR] Can not connect GpioHandler!\n\r"); return XST_FAILURE; } XScuGic_Enable(&GIC, XPAR_FABRIC_LLFIFO_0_VEC_ID); Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &GIC); Xil_ExceptionEnable(); XLlFifo_IntClear(&Fifo, 0xFFFFFFFF); XLlFifo_IntEnable(&Fifo, XLLF_INT_ALL_MASK);
Als nächstes werden der Empfangs- und der Sendekanal des FIFO zurückgesetzt, damit sie sich in einem definierten Zustand befinden:
XLlFifo_TxReset(&Fifo); XLlFifo_RxReset(&Fifo);
Jetzt müssen noch Daten für den Transfer erzeugt werden. Sämtliche gesendeten und empfangenen Daten werden in einem entsprechenden Buffer gespeichert. Bei Bedarf können die Daten dann in den FIFO geschrieben werden, sodass der FIFO die Daten dann an einen angeschlossenen AXI-Stream Slave sendet oder Daten, die von einem angeschlossenen AXI-Stream Master empfangen wurden können bei Bedarf aus dem FIFO ausgelesen werden.
u32 SourceBuffer[64]; u32 DestinationBuffer[64] xil_printf("[INFO] Generate data!\n\r"); for(u32 i = 0x00; i < 64; i++) { SourceBuffer[i] = i * 2; }
Hinweis:
Die Übertragung der Daten zum FIFO geschieht immer wortweise, wobei die Länge eines Wortes der Datenbreite des AXI-Interfaces entspricht. Bei einem AXI-Lite Interface sind es immer 32 Bit und bei einem AXI-Interface kann die Busbreite auf bis zu 512 Bit umgestellt werden.
Sobald eine Übertragung gestartet werden soll, müssen die einzelnen Datenwörter über die Funktion XLlFifo_TxPutWord
in den FIFO geschrieben werden.
for(u32 i = 0x00; i < 64; i++) { if(XLlFifo_iTxVacancy(&Fifo)) { XLlFifo_TxPutWord(&Fifo, SourceBuffer[i]); } }
Über das TDFV-Register (Transmit Data FIFO Vacancy) kann die Software prüfen wie viele Daten noch in den FIFO geschrieben werden können. Der Wert kann über die Funktion XLlFifo_iTxVacancy
ausgelesen werden und ist 0, wenn der FIFO voll ist. Wenn alle Daten in den FIFO kopiert wurden, kann die Übertragung gestartet werden, indem die Anzahl der zu übertragenden Bytes mit der Funktion XLlFifo_iTxSetLen
in das TLF-Register geschrieben wird.
XLlFifo_iTxSetLen(&Fifo, 64 * sizeof(u32));
Wenn keine Interrupts genutzt werden, muss die Funktion XLlFifo_IsTxDone
verwendet werden, um auf das Ende der Übertragung zu warten.
while(!(XLlFifo_IsTxDone(&Fifo)));
Anschließend kann die Funktion XLlFifo_iRxOccupancy
genutzt werden, um das RDFO-Register (Receive Data FIFO Occupancy) auszulesen. Wenn der Wert größer als 0 ist, sind neue Pakete empfangen worden.
while(XLlFifo_iRxOccupancy(&Fifo)) { }
Falls neue Pakete empfangen worden sind, muss die Anzahl der empfangenen Datenbytes aus dem RLF-Register (Receive Length FIFO) ausgelesen werden um die Anzahl der empfangenen Datenwörter zu bestimmen. Mit diesem Wert können die einzelnen Datenwörter dann aus dem FIFO ausgelesen werden.
u32 ReceiveLength = (XLlFifo_iRxGetLen(InstancePtr)) / sizeof(u32); for(u32 i = 0x00; i < ReceiveLength; i++) { u32 RxWord = XLlFifo_RxGetWord(&Fifo); DestinationBuffer[i] = RxWord; xil_printf(" Data %lu: %lu\n\r", i, RxWord); }
Alternativ können auch der Callbacks des Interrupts verwendet werden um auf das Ende einer Übertragung zu warten, bzw. auf ein empfangenes Paket zu reagieren.
static void FifoHandler(void* CallbackRef) { XLlFifo* InstancePtr = (XLlFifo*)CallbackRef; u32 Pending = XLlFifo_IntPending(InstancePtr); while(Pending) { if(Pending & XLLF_INT_RC_MASK) { xil_printf(" Receive complete!\n\r"); while(XLlFifo_iRxOccupancy(InstancePtr)) { u32 ReceiveLength = (XLlFifo_iRxGetLen(InstancePtr)) / sizeof(u32); for(int i = 0; i < ReceiveLength; i++) { u32 RxWord = XLlFifo_RxGetWord(InstancePtr); DestinationBuffer[i] = RxWord; xil_printf(" Data %lu: %lu\n\r", i, RxWord); } } XLlFifo_IntClear(InstancePtr, XLLF_INT_RC_MASK); } else if(Pending & XLLF_INT_ERROR_MASK) { xil_printf(" FIFO Error...\n\r"); XLlFifo_IntClear(InstancePtr, XLLF_INT_ERROR_MASK); } else { XLlFifo_IntClear(InstancePtr, Pending); } Pending = XLlFifo_IntPending(InstancePtr); } }
In dem Callback wird zuerst über die Funktion XLlFifo_IntPending
das IS-Register (Interrupt Status) ausgelesen. Durch einen Vergleich mit den Bitmasken XLLF_INT_RC_MASK
und XLLF_INT_ERROR_MASK
wird ermittelt ob ein neues Paket empfangen wurde oder ob ein Fehler vorliegt.
Falls ein neues Paket empfangen wurde, wird die Anzahl der empfangenen Datenbytes aus dem RLF-Register ausgelesen werden um die Anzahl der empfangenen Datenwörter zu bestimmen. Mit diesem Wert können die einzelnen Datenwörter dann aus dem FIFO ausgelesen werden.
Dann wird das jeweilige Interruptbit über die Funktion XLlFifo_IntClear
gelöscht, ein neuer Wert aus dem IS-Register ausgelesen und die ganze Abfrage wird erneut durchgeführt. Dieser Vorgang wiederholt sich so lange, bis alle Interrupts behandelt wurden und kein Interruptbit mehr gesetzt ist.
Das komplette Projekt steht in meinem GitHub-Repository zum Download bereit.
Schreibe einen Kommentar