FPGA 실습(1)

본 게시물은 VIVADO 2018.3 Version을 기준으로 작성 되었읍니다.
실습은 microZed7020을 이용하여 회로를 구현하고 실습하였읍니다.

1. DMA 실습

Zynq와 DMA Interface를 이용하여 DMA Stream FIFO에 저장하고, FIFO에서 읽어와서 데이터를 비교하는 과정을 실습한다.

1.1 설계

- VIVADO 2018.3을 실행
- “Create Project”를 click
- “create a new Vivado Project” 창이 뜨면 “Next”를 click
- “project name” 입력창에 적당한 프로젝트 이름을 입력한다. 예; ex_dma_fifo
- “project location” 입력창에 적당한 디렉토리를 지정한다. 예: E:/FPGA
- “Next>”를 click
- “Project Type” 창에서 RTL_Project에 Mark하고 “Next>”를 click
- “Add Sources” 창이 뜨면 Target Lanuage를 VHDL로 선택하고 “Next>”를 click
- “Add Constraints” 창이 뜨면 “Next>”를 click
- “Default Part” 창이 뜨면 “Board” tab을 선택하고 “MicroZed 7020 Board”를 선택한 뒤 “Next>”를 click. 
*MicroZed Bd가 안보이면 MicroZed Bd data을 인터넷에서 다운 받아 C:\Xilinx\Vivado\2018.3\data\boards\board_files 디렉토리에 copy하여야 함.
- “New Project Summary” 창이 뜨면 요약 내용을 확인하고 “Finish”를 click
그러면 다음과 같은 통합 설계 창이 뜬다.


- 위 설계 도구 왼쪽에 PROJECT MANAGER라는 창에서 “Create Block Design”을 click
- Create Block Design 창이 뜨면 Design Name 입력 창에 적당한 이름을 입력한다. 기본 값은 design_1이며, 본 문서에서는 이를 그냥 사용한다. 디렉토리는 별도로 지정하지 않으면 앞에서 입력한 프로젝트 디렉토리에 새로운 디렉토리가 생성되어 저장된다. “OK”를 click
- 설계 도구 창 오른쪽 위쪽에 다음과 같은 Diagram 창이 나타난다.


- Diagram 창의 메뉴 바에 있는 “+”를 누르든지, 창 가운데 있는 “+”를 click 하면 다음과 같이 사용할 수 있는 IP를 선택할 수 있는 창이 출력된다.



- “Search” 입력란에 “zynq”를 입력하면 “Zynq Processing System”이 보이게 된다. 이것을 double click 한다. 그러면 잠시 뒤에 Zynq가 나타나며, 위쪽에 녹색 줄이 나타나며 “Run Clock Automation”이 보인다. 이것을 클릭한다.

- Run Block Automation 창이 뜨면 내용을 확인하고 “OK”를 click. MicroZed에 연결된 DDR과 GPIO를 입출력으로 표시한 다음과 같은 그림이 Diagram 창에 출력된다.

- 이제 Zynq Processor의 몇 가지 Option을 설정하기 위하여 위 그림을 Double click한다. 그러면 다음과 같이 Zynq 내부에 있는 프로세서의 구조가 출력되며 왼쪽에 Option을 설정할 수 있는 Menu들이 보인다.


- 위 그림을 보면 UART1, GPIO, SD0등등 Check 되어 있는 것을 볼 수 있는데, 이는 MicroZed 보드를 위해 미리 설정된 것들이다. 여기서 DMA를 시험하기 위해 AXIS 인터페이스를 활성화 하기 위해 그림의 아래쪽의 “High Performance ... Port”를 Double click 하면 다음과 같이 인터페이스를 설정할 수 있는 창이 뜬다.


- 위 그림에서 HP Slave AXI Interface에서 “S AXI HP0 Interface”를 check 한다.
- PL에 clock을 제공하기 위해 왼쪽 메뉴의 clock configuration을 누른다. 그러면 다음과 같이 click Configuration이라는 창이 보인다.


- 위 그림에서 PL Fablic Clocks를 누르면 FCLK_CLK0가 check 되어 있고, 100MHz의 클럭이 출력된다는 것을 확인할 수 있다.
- DMA로부터 Interrupt를 수신할 수 있게 하기 위해 왼쪽의 메뉴에서 Interrupts를 누르면 Interrupt 형상을 설정할 수 있는 다음과 같은 창이 출력된다.


- 위 그림에서 “Fabric Interrupts”를 check하고 “PL-PS Interrupt Ports” 밑의 IQR_F2P[15:]을 check 한다.
- 그리고 AXI Master Port를 설정하기 위해 Zynq Block Diagram으로 돌아가서 32b GP AXI Master Ports라고 되어 있는 Box를 두 번 클릭한다.


- 위 그림에서 M AXI GP0 interface를 check하고 “OK”를 클릭한다. 그러면 다이어그램 창의 zynq가 다음과 같이 변경되어 있다.


- 이제 DMA 블록을 추가하기 위해 다이어그램 창의 위부분에 있는 메뉴의 “+”를 클릭하면 zynq를 삽입할 때와 같이 IP List가 출력되고, search 입력란에 DMA를 입력하면 다음과 같은 IP list가 출력된다.


- 위 그림에서 AXI Direct Memory Access를 두 번 클릭한다. 그러면 Diagram 창에 다음과 같이 axi_dma가 삽입된 것을 확인할 수 있다.


- 위 그림에서 axi_dma의 선택 사항을 설정하기 위해 axi_dma_0를 두 번 클릭하면 다음과 같이 AXI DMA의 설정 창이 출력된다.


- 위 그림에서 Enable Scatter Gather Engine에 check되어 있는 것을 클릭하여 check를 없애 주고 “OK”를 클릭한다.
* Scatter Gather Engine은 DMA를 이용할때 Buffer Descripter를 이용하여 BD 링을 구성하여 버퍼를 구성하는 기능임. 본 문서에서는 직접 버퍼를 사용하기 때문에 선택 해제 한다.
- 이제 axi_dma에서 데이터를 수신하여 FIFO에 저장하고 FIFO에 데이터가 저장되면 다시 DMA로 데이터를 보내기 위해 DMA Stream FIFO IP를 추가하기 위해 메뉴의 “+”를 클릭하고 Search 입력창에 FIFO를 입력한다.


- 위 그림에서 AXI4-Stream Data FIFO를 두 번 클릭한다. 그러면 다이어그램 창에 다음과 같이 FIFO IP가 삽입된 것을 확인할 수 있다.


- 이제 다이어 그램에서 다음과 같이 인터페이스 단자들을 연결하기 위해 상단의 녹색바에 있는 “run connection automation”을 클릭한다. 그러면 아래 그림과 같이 AXI 인터페이스르 연결하기 위한 블록, AXIS 인터페이스를 연결하기 위한 블록, 클럭과 리셋을 연결하기 위한 블록이 생성되어 각 인터페이스를 자동으로 연결한다. 다이어그램이 정리되지 않은 상태로 보일 때는 다이그램 창에서 마우스 오르쫌 버튼을 누른 상태에서 Regeneration Layout을 선택하면 정리가 된다.


- 위 그림에서 FIFO를 다음 그림과 같이 axi_dma에 연결한다. 연결할 때는 연결하기 위한 포트에 마우스를 올려 놓으면 커서가 화살표로 변하게 되고 마우스를 클릭한 상태에서 연결하고자 하는 대상 인터페이스까지 드래그해서 마우스 클릭을 해제 하면 된다.


- 이제 추가로 연결되지 않은 클럭과 AXI 인터페이스를 연결하기 위해 run connection automation을 한번 더 누른다. 생성된 회로는 다음과 같다.


- 추가로 DMA interrupt를 zynq PS에 연결하기 위해 Concat이라는 IP를 추가하고 다음 그림과 같이 연결한다.


- 회로 설계는 완료 되었으며, 추가로 Address Map을 설정하기 위해 다이어그램 옆에 있는 Address Editor Tab을 클릭한다. Memory Map이 다음과 같이 설정되었는지 확인한다.


- 최종적으로 bd 설계에 문제가 없는지 확인하기 위해 다이어그램의 메뉴에서 버튼을 클릭한다. 그러면 크리티칼 에러가 있다고 출력된다. 원래는 에러를 수정하여야 하나 여기서는 무시하고 진행한다.
- 설계의 TOP Module을 생성하기 위해 Source 창에서 design_1(design_1.bd)를 선택하고 오른쪽 버튼을 눌러 “Create HDL wrapper”를 선택하고 OK를 클릭한다. 그러면 Source 창에 VHDL Source가 생성된다.
- 이제 설계도구 왼쪽의 메뉴에서 Run Synthesis, Run Implement, Generation Bitstream을 순서대로 수행한다. Generation Bitstream을 누르면 이 과정을 모두 자동으로 수행한다.

1.2 시험

설계된 하드웨어 회로를 시험하기 위해서 Xilinx SDK를 이용하여 Standalone OS기반 SW를 이용하여 시험하는 방법을 소개한다.
우선 소프트웨어의 실행 결과를 확인하기 위한 Serial Terminal SW가 필요하다.(예: Tera Term) MicroZed 보드의 UART 포트에 시리얼 케이블과 PC를 연결한 상태에서 시험을 수행하고 Serial Terminal의 Baud Rate를 115200으로 설정한다. 그리고 다시 VIVADO 개발 도구로 돌아가서 다음의 절차에 따라 시험을 수행한다.
- 구현된 하드웨어의 형상파일을 추출하기 위하여 Vivado 개발도구의 File 메뉴에서 Export -> Export Hardware를 수행한다. 그러면 아래의 pop up 창이 출력되는데, Include bitstream을 체크하고 “OK”를 클릭한다. 


- Vivado 개발도구의 File 메뉴에서 Launch SDK를 수행한다. 그러면 Export location과 Workspace를 설정할 수 있는 pop up 창이 나오는데 모두 default 상태로 두고 “OK”를 클릭한다. 그러면 SDK 도구 창이 수행되면서 하드웨어 설계 자료와 SW에 필요한 파일들이 프로젝트 디렉토리의 *.sdk 디렉토리에 생성되면서 SW 개발 환경이 구성된다.


- 새로운 응용 프로그램을 생성하기 위해 SDK 개발도구의 File 메뉴에서 New -> Appliction Project를 수행한다. 다음과 같은 창이 출력된다.


- Project name 입력란에 적당한 이름을 입력한다. 여기서는 testdma라고 입력하였다. 프로젝트 이름을 입력한 뒤에 “Next”를 클릭한다. 그러면 다음과 같은 예제 프로그램을 선택할 수 있는 창이 출력된다.



- Hello Wolrd를 선택하고 “Finish”를 누르면 새로운 프로젝트가 생성되고 src 밑에 helloworl.c 라는 프로그램이 생성된다. 이 프로그램은 MicroZed 보드의 UART 포트를 통해서 시리얼 터미널 프로그램에 “hello world”를 프린트하는 간단한 프로그램이다.
- 개발도구의 왼쪽 Project Explorer에서 프로젝트 이름을 우클릭하고 build project를 수행하여 프로그램의 build 한다.
- MicroZed 보드의 프로그램 선택 Jumper를 JTAG Download에 의한 프로그램으로 설정하고 UART Cable과 JTAG 프로그램 도구를 연결한 뒤 보드의 전원을 켜고 PC에 시리얼 터미널을 실행시킨다.
- SDK 개발도구의 상단 메뉴에서 Xilinx -> Program FPGA를 실행하면 다음과 같은 창이 나오고 “Program”을 클릭하면 FPGA에 bitstream을 프로그램밍하게 되며 보드의 파란 LED가 점등된다.
- 다시 SDK의 상단 메뉴에서 Run -> Run을 수행하면 SW가 다운로드 되어 실행되고 시리얼 터미널에 다음과 같이 Hello World가 출력되는 것을 확인할 수 있다.


- 이제 이 helloworld.c 프로그램을 DMA를 시험하기 위한 프로그램으로 변경해보자.
*/Xilinx/SDK/2018.3/data/embeddedsw/XilinxProcessorUPLib/drivers/axidma_v9_8/examples 디렉토리에서 xaxidma_example_simple_intr.c를 workspace의 src 디렉토리로 복사한다. 이 프로그램은 dma로 데이터를 전송하고, dma에서 데이터를 수신하면 인터럽트를 발생해서 인터럽트를 처리하고 전송한 데이터와 수신한 데이터를 비교하여 데이터 전송이 정상적으로 이루어 졌는지 확인하는 프로그램이다.
- 복사한 소스를 helloworld.c로 다시 복사한다. 그러면 이전에 hello world를 프린트 했던 프로그램 대신 복사된 프로그램이 SDK 개발도구에 Load 된다. 그리고 src 디렉토리에 예제 프로그램을 복사 했으면 그 프로그램을 삭제한다.
- 다시 프로젝트를 빌드해 보자. 이번에는 build project 대신 clean project를 선택한다.
- 그러면 개발도구의 콘솔 창에 컴파일 하는 과정이 출력되면서 컴파일이 성공했다고 출력된다. 이제 프로그램을 다시 실행시켜 본다. 다음과 같이 프로그램이 성공했다고 출력된다.


- 이제 프로그램을 조금 변경하여 전송된 데이터와 수신한 데이터를 프린트해서 확인하는 프로그램으로 변경하여 보자. 우선 프로그램의 다음 부분을 찾아서 최대 크기를 0x1000으로 변경한다.
#define MAX_PKT_LEN 0x100 -> 0x1000

- 그리고 main function에 보면 다음의 for loop가 있다.

/* Send a packet */
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;
}


/*
* Wait TX done and RX done
*/
while (!TxDone && !RxDone && !Error) {
/* NOP */
}

if (Error) {
xil_printf("Failed test transmit%s done, "
"receive%s done\r\n", TxDone? "":" not",
RxDone? "":" not");

goto Done;

}

/*
* Test finished, check data
*/
Status = CheckData(MAX_PKT_LEN, 0xC);
if (Status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
}

- 위의 프로그램을 보면 dma로부터 수신부터 하고 송신하는 루틴으로 구성되어 있는데 수신하는 부분은 인터럽트 핸들러로 보내고 송신하는 부분만 남겨야 하고, 송신 되는 데이터의 크기도 고정된 값이 아닌 순차적으로 변하는 값으로 해보자. 우선 수신하는 프로시듀어 call 하는 부분을 for loop 밖으로 빼고 for loop 밖에 있는 송신 데이터를 설정하는 부분을 for loop 안으로 보내어 다음과 같이 프로그램을 수정한다.

int main(void)
{ int Status;
XAxiDma_Config *Config;
int Tries = NUMBER_OF_TRANSFERS;
int Index,i;
u8 *TxBufferPtr;
u8 *RxBufferPtr;
u8 Value, start_value;
TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;
/* Initial setup for Uart16550 */
#ifdef XPAR_UARTNS550_0_BASEADDR
Uart550_Setup();
#endif
xil_printf("\r\n--- Entering main() --- \r\n");
Config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!Config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
/* Initialize DMA engine */
Status = XAxiDma_CfgInitialize(&AxiDma, Config);
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;
}
/* Set up Interrupt system  */
Status = SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);
if (Status != XST_SUCCESS) {
xil_printf("Failed intr setup\r\n");
return XST_FAILURE;
}
/* Disable all interrupts before setup */
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Initialize flags before start transfer test  */
TxDone = 0;
RxDone = 0;
Error = 0;
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/* Send a packet */
for(Index = 0; Index < Tries; Index ++) {
Value = (u8)(0x10 + Index);
TX_LEN = Index + 10;
start_value = Value;
xil_printf("TX_LEN = %d : ", TX_LEN);
for(i = 0; i < TX_LEN; i ++) {
TxBufferPtr[i] = Value;
Value = (Value + 1) & 0xFF;
}
/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
* is enabled
*/
Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, TX_LEN);
#ifdef __aarch64__
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, TX_LEN);
#endif
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
TX_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
* Wait TX done and RX done
*/
while (!TxDone && !RxDone && !Error) {
/* NOP */
}
if (Error) {
xil_printf("Failed test transmit%s done, "
"receive%s done\r\n", TxDone? "":" not",
RxDone? "":" not");
goto Done;
}
/*
* Test finished, check data
*/
while(!RxDone){
/* NOP */
}
Status = CheckData(RX_LEN, start_value);
if (Status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
}

그리고 수신데이를 확인하기 위해 CheckData 부분도 아래와 같이 변경한다.

static int CheckData(int Length, u8 StartValue)
{ u8 *RxPacket;
int Index = 0, rslt;
u8 Value;
RxPacket = (u8 *) RX_BUFFER_BASE;
Value = StartValue;
rslt = 0;
/* Invalidate the DestBuffer before receiving the data, in case the
* Data Cache is enabled
*/
#ifndef __aarch64__
Xil_DCacheInvalidateRange((UINTPTR)RxPacket, Length);
#endif
for(Index = 0; Index < Length; Index++) {
xil_printf("%02x ", RxPacket[Index]);
if((Index % 16) == 15) xil_printf("\r\n");
//if (RxPacket[Index] != Value) {
// xil_printf("Data error %d: %x/%x\r\n",
//     Index, RxPacket[Index], Value);
//rslt = Index;
//return XST_FAILURE;
//}
Value = (Value + 1) & 0xFF;
}
xil_printf("\r\n");
return rslt;
}

- 위 프로그램의 수행 결과는 다음과 같다.



댓글

이 블로그의 인기 게시물

르네사스 타겟보드를 이용한 CAN통신(3)