Microblze로 동작하는 FPGA의 Hardware Platform을 생성하는 과정을 알아보자.
1. Vivado에서 MicroBlaze 하드웨어 플랫폼 설계
우선 Vivado에서 새로운 프로젝트를 생성하고 MicroBlaze를 포함한 설계를 한다.
Vivado에서 IP Integrator를 사용하여 MicroBlaze 프로세서를 시스템에 추가한다.
프로세서를 추가한 후, "Run Block Automation"을 누르면 MicroBlaze와 다른 필수 IP(예: 인터럽트 컨트롤러, 메모리 컨트롤러, 클록 및 리셋 로직 등)가 자동으로 연결된다.
생성된 IP들은 다음과 같은 역할을 담당하게 된다.
clk_wiz는 FPGA에서 클록 신호를 생성하고, 클록 주파수를 변경하거나 클록을 복제하는 역할을 한다.
- 다양한 주파수의 클록 생성 (예: 입력 클록에서 출력 클록을 생성)
- 클록의 주파수를 변환 (예: PLL을 사용하여 클록의 주파수를 높이거나 낮춤)
- 클록에 대한 다양한 옵션을 설정 (예: 클록의 위상, 주파수, 분주기 등)
Peripheral IP를 추가한다. 우선 GPIO, UART만 연결한다. 각 IP에서는 BAUDRATE, GPIO에 관련된 설정을 할 수 있다.
연결이 완료된 하드웨어 플랫폼은 다음과 같은 형태를 가진다. 연결된 IP에 따라 VIVADO에서 ADDRESS MAP 또한 설정된 모습을 볼 수 있다.
이후, 모듈들의 인터페이스 (AXI 인터페이스, APB 인터페이스)를 통합하기 위해 HDL Wrapper를 생성한다.
생성된 HDL Wrapper 파일을 보게 되면, FPGA의 I/O 포트가 정의되어있는 것을 볼 수 있다. 이를 XDC 파일에 적용하여 실제 FPGA의 핀과 매핑 시켜준다.
2. Vivado에서 하드웨어 플랫폼 내보내기
Vivado에서 하드웨어 설계를 완료하고 비트스트림을 생성한 후, 해당 프로젝트를 Vitis로 내보내야 한다. Bitstream 생성 후, Vivado에서 "Export Hardware" 옵션을 사용하여 하드웨어 플랫폼을 생성한다. (Include bitstream 옵션을 선택하여 비트스트림 파일도 함께 내보낸다)
Vitis를 실행한 후, "Create Application Project"를 선택하여 새 애플리케이션 프로젝트를 시작한다. 하드웨어 플랫폼을 선택할 때 Vivado에서 내보낸 .xsa 파일을 선택 후, MicroBlaze 프로세서에 맞는 어플리케이션을 선택한다. 우선 동작 여부를 확인하기 위해 "Hello world" 어플리케이션으로 설정한다.
"Hello world" 어플리케이션은 기본적으로 printf() 함수를 사용하고 있어 동작 여부를 UART를 통해 확인할 수 있다. Vitis에서 빌드를 수행하고, 생성된 실행 파일을 FPGA에 다운로드 후, UART 동작을 확인해 보자.
3. Vitis를 이용한 LED 점등
Xilinx Vivado 환경에서 FPGA 설계 및 하드웨어 설정에 대한 여러 파라미터와 정의된 값을 포함한 "xparameters.h"를 자동으로 생성해준다. 이를 이용하면 Vivado에서 설정된 Memory Map과 같은 변수가 정의되어 있는 것을 확인할 수 있다.
/* Definitions for peripheral AXI_GPIO_0 */
#define XPAR_AXI_GPIO_0_BASEADDR 0x40000000
#define XPAR_AXI_GPIO_0_HIGHADDR 0x4000FFFF
#define XPAR_AXI_GPIO_0_DEVICE_ID 0
#define XPAR_AXI_GPIO_0_INTERRUPT_PRESENT 0
#define XPAR_AXI_GPIO_0_IS_DUAL 0
/******************************************************************/
/* Canonical definitions for peripheral AXI_GPIO_0 */
#define XPAR_GPIO_0_BASEADDR 0x40000000
#define XPAR_GPIO_0_HIGHADDR 0x4000FFFF
#define XPAR_GPIO_0_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define XPAR_GPIO_0_INTERRUPT_PRESENT 0
#define XPAR_GPIO_0_IS_DUAL 0
/******************************************************************/
/* Definitions for driver UARTLITE */
#define XPAR_XUARTLITE_NUM_INSTANCES 1U
/* Definitions for peripheral AXI_UARTLITE_0 */
#define XPAR_AXI_UARTLITE_0_DEVICE_ID 0U
#define XPAR_AXI_UARTLITE_0_BASEADDR 0x40600000U
#define XPAR_AXI_UARTLITE_0_HIGHADDR 0x4060FFFFU
#define XPAR_AXI_UARTLITE_0_BAUDRATE 9600U
#define XPAR_AXI_UARTLITE_0_USE_PARITY 0U
#define XPAR_AXI_UARTLITE_0_ODD_PARITY 0U
#define XPAR_AXI_UARTLITE_0_DATA_BITS 8U
이제 GPIO 레지스터에 접근하여 LED 점등을 해보자. 필자는 GPIO 포트를 LED에 매핑해놓았기에, LED 점등을 위해서는 아래 두 레지스터 설정이 필요하다.
- GPIO OUTPUT MODE 설정
- GPIO DATA REGISTER 설정
Xilinx의 공식문서를 확인해보면, 0x0000 번지는 데이터 레지스터, 0x0004 번지가 Mode 설정 레지스터인 것을 확인할 수 있다. 코딩의 용이성을 위해 이를 토대로 GPIO_CONTROL_REG와 GPIO_DATA_REG를 define하고, 이를 토대로 소프트웨어를 구성한다.
추가로 사용된 <sleep.h> 와 usleep() 함수는 C 언어에서 사용되는 시간 지연 기능을 제공하는 함수이다.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "sleep.h"
#define GPIO_CONTROL_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0X04)
#define GPIO_DATA_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0X00)
int main()
{
init_platform();
print("Hello World\n\r");
print("Successfully ran Hello World application\n\r");
GPIO_CONTROL_REG = 0x0000; // GPIO OUTPUT으로 설정
while(1)
{
GPIO_DATA_REG = 0xffff;
usleep(300000);
GPIO_DATA_REG = 0x0000;
usleep(300000);
}
return 0;
}
4. LED SHIFT
버튼이 눌렸을때 LED를 좌로 쉬프트하고, 눌리지 않았을 때는 우로 쉬프트하는 프로그램을 작성해보자. 이 경우에는 BUTTON 입력을 INPUT으로 받아야 하기 때문에, Vivado 환경에서 INPUT을 위한 GPIO를 추가하고 .xsa 파일을 다시 생성해준다.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "sleep.h"
#define GPIO_LED_CONTORL_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0x04)
#define GPIO_LED_DATA_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0x00)
#define GPIO_BUTTON_CONTORL_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0x0c)
#define GPIO_BUTTON_DATA_REG *(volatile uint32_t *)(XPAR_GPIO_0_BASEADDR + 0x08)
int main()
{
print("Hello World\n\r");
GPIO_LED_CONTORL_REG = 0x0000;
GPIO_BUTTON_CONTORL_REG = 0x000f;
GPIO_LED_DATA_REG = 0x01;
while(1)
{
if(GPIO_BUTTON_DATA_REG & 0x01){
GPIO_LED_DATA_REG = (GPIO_LED_DATA_REG >> 15) | (GPIO_LED_DATA_REG << 1);
usleep(300000);
} else{
GPIO_LED_DATA_REG = (GPIO_LED_DATA_REG << 15) | (GPIO_LED_DATA_REG >> 1);
usleep(300000);
}
}
return 0;
}
GPIO_LED_DATA_REG = (GPIO_LED_DATA_REG >> 7) | (GPIO_LED_DATA_REG << 1)
단계별 비트 변화 (8비트 예시)
- 초기값: 0000_0001 (0x01)
- GPIO_LED_DATA_REG << 1: 0000_0010 (왼쪽으로 이동, 최하위 비트 0 채움)
- GPIO_LED_DATA_REG >> 7: 0000_0000 (최상위 비트가 0이므로 0)
- OR 결과: 0000_0010 | 0000_0000 = 0000_0010 (0x02)
- 결과: 2번째 LED 켜짐.
- 다음 상태: 0000_0010 (0x02)
- GPIO_LED_DATA_REG << 1: 0000_0100
- GPIO_LED_DATA_REG >> 7: 0000_0000
- OR 결과: 0000_0100 | 0000_0000 = 0000_0100 (0x04)
- 결과: 3번째 LED 켜짐.
- 다음 상태: 0000_0100 (0x04)
- GPIO_LED_DATA_REG << 1: 0000_1000
- GPIO_LED_DATA_REG >> 7: 0000_0000
- OR 결과: 0000_1000 | 0000_0000 = 0000_1000 (0x08)
- 결과: 4번째 LED 켜짐.
- 계속 진행:
- 0000_1000 → 0001_0000 (0x10)
- 0001_0000 → 0010_0000 (0x20)
- 0010_0000 → 0100_0000 (0x40)
- 0100_0000 → 1000_0000 (0x80)
- 최상위 비트에서 로테이션:
- 1000_0000 (0x80)
- GPIO_LED_DATA_REG << 1: 0000_0000 (최상위 비트 사라짐)
- GPIO_LED_DATA_REG >> 7: 0000_0001 (최상위 비트 1이 최하위로 이동)
- OR 결과: 0000_0000 | 0000_0001 = 0000_0001 (0x01)
- 결과: 다시 처음으로 돌아감.
- 1000_0000 (0x80)
동작 패턴
- LED가 왼쪽으로 이동: 0000_0001 → 0000_0010 → 0000_0100 → ... → 1000_0000 → 0000_0001 (반복).
- 300ms마다 한 칸씩 이동하며, 끝에 도달하면 처음으로 돌아옴.
'AMBA BUS > Vitis' 카테고리의 다른 글
Vitis - 06. FND 제어 (0) | 2025.03.12 |
---|---|
Vitis - 05. Common Code 작성 (0) | 2025.03.12 |
Vitis - 04. Driver Code 작성 (0) | 2025.03.10 |
Vitis - 03. 사용자 IP와 AXI4-Lite Interface 결합 (0) | 2025.03.10 |
Vitis - 02. XGpio (0) | 2025.03.10 |