HLS часть 1 . AXIS.

Vivado HLS (High Level Synthesis) – САПР Xilinx, предназначенная для создания цифровых устройств с применением языков высокого уровня C/C++.

Немного про HLS читайте в предыдущей статье.

HLS. Часть 0.
HLS часть 1 . AXIS.
HLS часть 2. BRAM.

Протокол AXI-Stream используется, как стандартный интерфейс для соединения IP блоков, которые обмениваются данными. Интерфейс может использоваться для соединения одного главного IP, который генерирует данные, с другим IP, который принимает данные. Протокол также может быть использован для соединения большего количества главных и подчиненных IP. Протокол поддерживает несколько потоков данных, использующих общий набор шин, что позволяет реализовать внутренний обмен данными между IP блоками.

Интерфейс AXI-Stream использует термин передача пакета. Передачей называют обмен одиночных данных от главного к подчиненному IP. Передача определяется одиночным Handshake. Пакетом называют группу байт, передающихся вместе, что аналогично пакетной передаче в AXI. Пакет может состоять из одной или нескольких передач. Компоненты могут использовать пакеты для более эффективной передачи данных. Также существует понятие кадра, который состоит из целого числа пакетов.

Для того, чтобы передача данных состоялась, необходимо, чтобы на очередном такте были выставлены сигналы TVALID и TREADY. При этом главный IP не может ждать выставления TREADY перед выставлением TVALID. А когда сигнал TVALID установлен, он не может быть снят до тех пор, пока не будет выставлен TVALID. Подчиненный IP может ждать выставления TVALID до выставления TREADY. Также подчиненному компоненту разрешено менять состояние TREADY, если в этот момент TVALID снят.

У нас сегодня под капотом пример IP на HLS, которая генерирует данные для AXI-Stream.

1. Создаем новый проект.

2. Выбираем нужным нам SoC.

3. Меню конфигурации проекта.

4. Настраиваем рабочую частоту для IP, по умолчанию значение равно 10ns, можно время задавать через частоту (мне например так привычнее).

5. Вводим название проекта.

6. Вводим название top функции.

7. Исходный код проекта.

#include <ap_axi_sdata.h>
#include <hls_stream.h>
#define SIZE_STREAM 1024

struct axis {
    int  tdata;
    bool tlast;
};

void data_generation(axis outStream[SIZE_STREAM])
{
#pragma HLS INTERFACE axis port=outStream

    int i = 0;
    do{
        outStream[i].tdata = i;
        outStream[i].tlast = (i == (SIZE_STREAM - 1)) ? 1 : 0;
        i++;
    }while( i < SIZE_STREAM);
}

8. Сборка проекта.

9. Утилизация ресурсов FPGA.

10. Диаграмма планирования.

11. Упаковываем наш код в IP, далее делаем компиляцию C/C++ -> HDL (Verilog или HDL). Вводим название IP и заполняем поля описания ядра.

12. Нажимаем ОК для генерации выходного ядра.

13. Информация при генерировании проекта.

14. Загружаем IP в проект с Vivado (подробно из цикла статей про Zynq-7000 SoC — Xilinx. Z-turn Lite. Bare metal).

15. Необходимое IP.

16. Настраиваем AXI DMA для работы с нашим IP.

17. Итоговый проект в Vivado.

18. Создадим новый проект.

19. Создадим проект в SDK для проверки работы с нашим IP, будем использовать готовый пример от Xilinx.

20. Исходный код проекта (основной .c файл).

#include "xaxidma.h"
#include "xparameters.h"
#include "xdebug.h"

#if defined(XPAR_UARTNS550_0_BASEADDR)
#include "xuartns550_l.h"       /* to use uartns550 */
#endif

/******************** Constant Definitions **********************************/

/*
 * Device hardware build related constants.
 */

#define DMA_DEV_ID		XPAR_AXIDMA_0_DEVICE_ID

#ifdef XPAR_AXI_7SDDR_0_S_AXI_BASEADDR
#define DDR_BASE_ADDR		XPAR_AXI_7SDDR_0_S_AXI_BASEADDR
#elif XPAR_MIG7SERIES_0_BASEADDR
#define DDR_BASE_ADDR	XPAR_MIG7SERIES_0_BASEADDR
#elif XPAR_MIG_0_BASEADDR
#define DDR_BASE_ADDR	XPAR_MIG_0_BASEADDR
#elif XPAR_PSU_DDR_0_S_AXI_BASEADDR
#define DDR_BASE_ADDR	XPAR_PSU_DDR_0_S_AXI_BASEADDR
#endif

#ifndef DDR_BASE_ADDR
#warning CHECK FOR THE VALID DDR ADDRESS IN XPARAMETERS.H, \
		 DEFAULT SET TO 0x01000000
#define MEM_BASE_ADDR		0x01000000
#else
#define MEM_BASE_ADDR		(DDR_BASE_ADDR + 0x1000000)
#endif

#define TX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00300000)
#define RX_BUFFER_HIGH		(MEM_BASE_ADDR + 0x004FFFFF)

#define MAX_PKT_LEN		4096

#define TEST_START_VALUE	0xC

#define NUMBER_OF_TRANSFERS	10

/**************************** Type Definitions *******************************/


/***************** Macros (Inline Functions) Definitions *********************/


/************************** Function Prototypes ******************************/

#if (!defined(DEBUG))
extern void xil_printf(const char *format, ...);
#endif

int XAxiDma_SimplePollExample(u16 DeviceId);
static int CheckData(void);

/************************** Variable Definitions *****************************/
/*
 * Device instance definitions
 */
XAxiDma AxiDma;


/*****************************************************************************/
/**
* The entry point for this example. It invokes the example function,
* and reports the execution status.
*
* @param	None.
*
* @return
*		- XST_SUCCESS if example finishes successfully
*		- XST_FAILURE if example fails.
*
* @note		None.
*
******************************************************************************/
int main()
{
	int Status;

	xil_printf("\r\n--- Entering main() --- \r\n");

	/* Run the poll example for simple transfer */
	Status = XAxiDma_SimplePollExample(DMA_DEV_ID);

	if (Status != XST_SUCCESS) {
		xil_printf("XAxiDma_SimplePoll Example Failed\r\n");
		return XST_FAILURE;
	}

	xil_printf("Successfully ran XAxiDma_SimplePoll Example\r\n");

	xil_printf("--- Exiting main() --- \r\n");

	return XST_SUCCESS;

}

#if defined(XPAR_UARTNS550_0_BASEADDR)
/*****************************************************************************/
/*
*
* Uart16550 setup routine, need to set baudrate to 9600, and data bits to 8
*
* @param	None.
*
* @return	None
*
* @note		None.
*
******************************************************************************/
static void Uart550_Setup(void)
{

	/* Set the baudrate to be predictable
	 */
	XUartNs550_SetBaud(XPAR_UARTNS550_0_BASEADDR,
			XPAR_XUARTNS550_CLOCK_HZ, 9600);

	XUartNs550_SetLineControlReg(XPAR_UARTNS550_0_BASEADDR,
			XUN_LCR_8_DATA_BITS);

}
#endif

/*****************************************************************************/
/**
* The example to do the simple transfer through polling. The constant
* NUMBER_OF_TRANSFERS defines how many times a simple transfer is repeated.
*
* @param	DeviceId is the Device Id of the XAxiDma instance
*
* @return
*		- XST_SUCCESS if example finishes successfully
*		- XST_FAILURE if error occurs
*
* @note		None
*
*
******************************************************************************/
int XAxiDma_SimplePollExample(u16 DeviceId)
{
	XAxiDma_Config *CfgPtr;
	int Status;
	int Tries = NUMBER_OF_TRANSFERS;
	int Index;
	u8 *TxBufferPtr;
	u8 *RxBufferPtr;
	u8 Value;

	TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
	RxBufferPtr = (u8 *)RX_BUFFER_BASE;

	/* Initialize the XAxiDma device.
	 */
	CfgPtr = XAxiDma_LookupConfig(DeviceId);
	if (!CfgPtr) {
		xil_printf("No config found for %d\r\n", DeviceId);
		return XST_FAILURE;
	}

	Status = XAxiDma_CfgInitialize(&AxiDma, CfgPtr);
	if (Status != XST_SUCCESS) {
		xil_printf("Initialization failed %d\r\n", Status);
		return XST_FAILURE;
	}

//	if(XAxiDma_HasSg(&AxiDma)){
//		xil_printf("Device configured as SG mode \r\n");
//		return XST_FAILURE;
//	}

	/* Disable interrupts, we use polling mode
	 */
	XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
						XAXIDMA_DEVICE_TO_DMA);
	XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
						XAXIDMA_DMA_TO_DEVICE);

	Value = TEST_START_VALUE;

	for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
			TxBufferPtr[Index] = Value;

			Value = (Value + 1) & 0xFF;
	}
	/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
	 * is enabled
	 */
	Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
#ifdef __aarch64__
	Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
#endif

	for(Index = 0; Index < Tries; Index ++) {


		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
					MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
					MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		while ((XAxiDma_Busy(&AxiDma,XAXIDMA_DEVICE_TO_DMA)) ||
			(XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE))) {
				/* Wait */
		}
//
//		while (XAxiDma_Busy(&AxiDma,XAXIDMA_DEVICE_TO_DMA)) {
//				/*4096 Wait */
//		}


		Status = CheckData();
//		if (Status != XST_SUCCESS) {
//			return XST_FAILURE;
//		}

	}

	/* Test finishes successfully
	 */
	return XST_SUCCESS;
}



/*****************************************************************************/
/*
*
* This function checks data buffer after the DMA transfer is finished.
*
* @param	None
*
* @return
*		- XST_SUCCESS if validation is successful.
*		- XST_FAILURE otherwise.
*
* @note		None.
*
******************************************************************************/
static int CheckData(void)
{
	u8 *RxPacket;
	int Index = 0;
	u8 Value;

	RxPacket = (u8 *) RX_BUFFER_BASE;
	Value = TEST_START_VALUE;

	/* Invalidate the DestBuffer before receiving the data, in case the
	 * Data Cache is enabled
	 */
#ifndef __aarch64__
	Xil_DCacheInvalidateRange((UINTPTR)RxPacket, MAX_PKT_LEN);
#endif

	for(Index = 0; Index < MAX_PKT_LEN; Index++) {
		if (RxPacket[Index] != Value) {
			xil_printf("Data error %d: %x/%x\r\n",
			Index, (unsigned int)RxPacket[Index],
				(unsigned int)Value);

			return XST_FAILURE;
		}
		Value = (Value + 1) & 0xFF;
	}

	return XST_SUCCESS;
}

21. Результат нашей работы. Видим, что хотели, то и получили.