본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 98일차 - SoC Design(AXI, SystemVerilog를 활용한 Simulation, button Interrupt, switch Interrupt)

by Graffitio 2023. 11. 23.
[Harman 세미콘 아카데미] 98일차 - SoC Design(AXI, SystemVerilog를 활용한 Simulation, button Interrupt, switch Interrupt)
728x90
반응형
[AXI Protocol]

 

📌 Block Design

 

이번엔 얘부터 추가해준다.

 

AXI Verfication IP더블클릭

 

VIP : verification IP라고, 검증된 IP라는 뜻

 

clk끼리, reset끼리 연결해주자

 

 

Master로 셋팅해줄 것

 

Window - Address Editor 에서 Auto Assign Address 해줄 것

 

다 됐으면 Wrapping

 


 

📌 Simulation

 

 

SystemVerilog를 써볼 것이다.

 

현장가면 검증할 때, SystemVerilog를 대부분 사용하므로

미리 맛 좀 보고 가자.

가장 큰 차이점은

C++처럼 객체(Class)를 만들 수 있다는 장점이 있다.

Class의 장점은 다른 사람이 만들어 놓은 것을 가져다 쓰기 편하다는 것이다.

(Class의 Include가 가능)

그러다보니 가져다 쓸 만한 것들도 많이 만들어 놨음

가장 대표적인 것이바로

UVM

 

Verilog vs SystemVerilog
Verilog SystemVerilog
include import
reg bit
wire logic

 

얘네를 Import

 

import axi_vip_pkg::*; // C의 include와 같은 것
// 이 패키지에 있는 모든 것을 전부(*) 가져다 쓰겠다는 의미
import FND_IP_test_axi_vip_0_0_pkg::*;

 

import axi_vip_pkg::*; // AXI VIP 패키지를 모두 가져옴
// 이 패키지에 있는 모든 것을 전부(*) 가져다 쓰겠다는 의미
import FND_IP_test_axi_vip_0_0_pkg::*; // FND IP 테스트 AXI VIP 패키지를 모두 가져옴

module tb_FND_IP();
  // 입력은 reg로, 출력은 wire로
  bit aclk_0 = 0; // SystemVerilog에서는 bit가 reg
  bit aresetn_0 = 0;
  logic [3:0] com_0; // SystemVerilog에서는 wire가 logic
  logic [7:0] seg_7_0;
  
  xil_axi_ulong base_reg = 32'h0x44a00000; // AXI VIP의 베이스 레지스터 주소
  xil_axi_prot_t prot = 0; // AXI VIP의 프로토콜
  xil_axi_resp_t resp; // AXI VIP의 응답

  bit [31:0] test_data1 = 32'h0000000f; // 테스트 데이터 1
  bit [31:0] test_data2 = 32'h00000008; // 테스트 데이터 2
  bit [31:0] test_data3 = 32'h00000037; // 테스트 데이터 3
  
//////////////////////// DUT Instance //////////////////////////
  FND_IP_test_wrapper DUT(
    .aclk_0,
    .aresetn_0,
    .com_0,
    .seg_7_0
  ); // 디바이스 언더 테스트(DUT) 모듈 인스턴스화
////////////////////////////////////////////////////////////////
  
  FND_IP_test_axi_vip_0_0_mst_t master_agent; // AXI VIP 마스터 에이전트 인스턴스

  always begin #5 aclk_0 = ~aclk_0; end // 클럭 주기마다 클럭 반전

  initial begin
    master_agent = new("master vip agent", DUT.FND_IP_test_i.axi_vip_0.inst.IF); // AXI VIP 마스터 에이전트 생성
    master_agent.start_master(); // 마스터 에이전트 시작
    
    #100
    aresetn_0 = 1; #50 // 리셋 해제 후 50 단위의 딜레이
    master_agent.AXI4LITE_WRITE_BURST(base_reg, prot, test_data1, resp); #400; // AXI VIP에 대한 쓰기 버스트 동작 후 400 단위의 딜레이
    $stop; // 시뮬레이션 종료
  end

endmodule

 

그렇게 시뮬레이션을 진행하면,

 

오류 발생

 

X값만 출력된 이유

 

FND IP에서 링카운터 모듈을 보면, temp의 초기화가 안 돼 있다.

얘도 0으로 시뮬레이션 초기화해줘야 한다.

 

 

 

 

 

 


 

[Interrupt]

 

📌 Block Design

 

 

 

Interrupt Controller 체크
그렇게 Interrupt 및 UART, 4 Push Button 추가

 

Interrupt  포트 생성

 

Interrupt 포트 연결
UART도 같이 연결헤주고 Wrapping

 


 

📌 Main Code

 

Platform Project

 

Application Project

 

#include <stdio.h>          // 표준 입력/출력 함수
#include "platform.h"       // 플랫폼에 특화된 정의 및 초기화
#include "xil_printf.h"      // Xilinx printf 함수
#include "xparameters.h"     // 하드웨어 플랫폼의 매개변수 정의
#include "xgpio.h"           // 하드웨어용 일반 I/O 함수
#include "xintc.h"           // 인터럽트 컨트롤러 함수

#define BTN_ID XPAR_AXI_GPIO_BTN_DEVICE_ID  // 버튼용 GPIO 컨트롤러의 ID
#define BTN_CHANNEL 1                        // 버튼용 GPIO 채널
#define BTN_MASK 0b1111                      // 버튼에 대한 마스크, 하위 4비트만 사용

XGpio btn_device;                         // 버튼용 XGpio 드라이버의 인스턴스
XIntc intc;                              // 인터럽트 컨트롤러의 인스턴스

int main()
{
    init_platform();                    // 플랫폼 초기화 (하드웨어 및 소프트웨어)

    printf("Start!!\n\r");              // 프로그램 시작을 나타내는 메시지 출력

    int btn_data = 0;                   // 버튼 데이터를 저장하는 변수
    XGpio_Config *cfg_ptr_btn_intc;     // 버튼 GPIO의 구성을 가리키는 포인터

    cfg_ptr_btn_intc = XGpio_LookupConfig(BTN_ID);  // 버튼 GPIO의 구성 조회
    XGpio_CfgInitialize(&btn_device, cfg_ptr_btn_intc, cfg_ptr_btn_intc->BaseAddress);  // 버튼 GPIO 초기화
    XGpio_SetDataDirection(&btn_device, BTN_CHANNEL, BTN_MASK);  // 버튼 GPIO의 데이터 방향 설정

    while(1)
    {
        print("Hello World\n\r");      // 콘솔에 "Hello World" 출력
        btn_data = XGpio_DiscreteRead(&btn_device, BTN_CHANNEL);  // GPIO에서 버튼 데이터 읽기
        xil_printf("btn data : %x \n\r", btn_data);  // 버튼 데이터를 16진수 형식으로 출력
        MB_Sleep(1000);                 // 1000밀리초 동안 대기
    }

    cleanup_platform();                 // 종료 전에 리소스 정리 및 해제
    return 0;                           // 성공적인 실행을 나타내기 위해 0 반환
}

 

cfg_ptr이란?

 

cfg_ptr는 일반적으로 "configuration pointer"의 줄임말로 사용된다.

이것은 프로그램이 특정 하드웨어 또는 소프트웨어 구성을 나타내는 구성(configuration) 정보에 대한 포인터이다..

구성 정보는 특정 기능 또는 하드웨어 장치의 동작을 제어하기 위한 설정과 관련된 데이터를 포함합니다.

 

 

sei()(set Interrupt), cli()(Clear Interrupt) 함수를 호출했던 것 처럼

 

#include <stdio.h>          // 표준 입력/출력 함수
#include "platform.h"       // 플랫폼에 특화된 정의 및 초기화
#include "xil_printf.h"      // Xilinx printf 함수
#include "xparameters.h"     // 하드웨어 플랫폼의 매개변수 정의
#include "xgpio.h"           // 하드웨어용 일반 I/O 함수
#include "xintc.h"           // 인터럽트 컨트롤러 함수
#include "xil_exception.h"

//////////////////////// User Define ////////////////////////

#define BTN_ID XPAR_AXI_GPIO_BTN_DEVICE_ID  // 버튼용 GPIO 컨트롤러의 ID
#define BTN_CHANNEL 1                        // 버튼용 GPIO 채널
#define BTN_MASK 0b1111                      // 버튼에 대한 마스크, 하위 4비트만 사용

#define BTN_VEC_ID XPAR_INTC_0_GPIO_0_VEC_ID // VEC_ID는 이름을 바꿔도 처음 아이디 그대로 나온다.
#define INTC_ID XPAR_INTC_0_DEVICE_ID // Interrupt Controller의 디바이스 ID

////////////////////////////////////////////////////////////////////////////////

XGpio btn_device;                         // 버튼용 XGpio 드라이버의 인스턴스
XIntc intc;                              // 인터럽트 컨트롤러의 인스턴스

//////////// Function Prototype Declaration ////////////

void BTN_ISR(void *CallBackRef);

////////////////////////////////////////////////////////


int main()
{
    init_platform();                    // 플랫폼 초기화 (하드웨어 및 소프트웨어)

    printf("Start!!\n\r");              // 프로그램 시작을 나타내는 메시지 출력

    int btn_data = 0;                   // 버튼 데이터를 저장하는 변수
    XGpio_Config *cfg_ptr_btn_intc;     // 버튼 GPIO의 구성을 가리키는 포인터

    cfg_ptr_btn_intc = XGpio_LookupConfig(BTN_ID);  // 버튼 GPIO의 구성 조회
    XGpio_CfgInitialize(&btn_device, cfg_ptr_btn_intc, cfg_ptr_btn_intc->BaseAddress);  // 버튼 GPIO 초기화
    XGpio_SetDataDirection(&btn_device, BTN_CHANNEL, BTN_MASK);  // 버튼 GPIO의 데이터 방향 설정

    //////////////////////// intc 초기화 ////////////////////////
    XIntc_Initialize(&intc, INTC_ID);
    XIntc_Connect(&intc, BTN_VEC_ID,
    		(XInterruptHandler)BTN_ISR, (void *)&btn_device);
    		// 인터럽트가 발생하면 이 아이디에 대해서 VEC_ID로 가고 함수의 주소를 찾아서 그 함수로 간다.

    //----------------------- intc Enable -----------------------//
    XIntc_Enable(&intc, BTN_VEC_ID); // Interrupt 활성화
    XIntc_Start(&intc, XIN_REAL_MODE);

    XGpio_InterruptEnable(&btn_device, BTN_CHANNEL); // 게별 인터럽트 활성화

    XGpio_InterruptGlobalEnable(&btn_device); // Global 인터럽트 활성화

    //-------------------- mblaze 인터럽트 할성화 --------------------//
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
    		(Xil_ExceptionHandler)XIntc_InterruptHandler, &intc);
    Xil_ExceptionEnable();
    ////////////////////////////////////////////////////////

    while(1)
    {
        print("Hello World\n\r");      // 콘솔에 "Hello World" 출력
        btn_data = XGpio_DiscreteRead(&btn_device, BTN_CHANNEL);  // GPIO에서 버튼 데이터 읽기
        xil_printf("btn data : %x \n\r", btn_data);  // 버튼 데이터를 16진수 형식으로 출력
        MB_Sleep(1000);                 // 1000밀리초 동안 대기
    }

    cleanup_platform();                 // 종료 전에 리소스 정리 및 해제
    return 0;                           // 성공적인 실행을 나타내기 위해 0 반환
}



void BTN_ISR(void *CallBackRef)
{
	printf("btn Interrupt\n\r");
	XGpio *GpioPtr = (XGpio *)CallBackRef;
	// 이 CallBackRef가 누가 인터럽트를 발생시켰는지 알려준다.
	// 그래서 이 주소를 받아서 클리어해주는 것이 좋다.
//	if(XGpio_DiscreteRead(GpioPtr, BTN_CHANNEL)) // 버튼을 누를 때만 동작하게 하고 싶다면?
	if(XGpio_DiscreteRead(GpioPtr, BTN_CHANNEL) & 0b0010) // 왼쪽 버튼만 인터럽트로 사용하고 싶다면?
	{
		printf("btn Left Pushed\n\r");
	}
	XGpio_InterruptClear(GpioPtr, BTN_CHANNEL);
	// 여기서는 Interrupt가 발생해도 Flag가 0으로 초기화되지 않는다.
	// 한 번 ISR이 동작하면, 계속 ISR이 동작해버림
	// 그래서 한 번 동작하고 꺼주는 함수를 추가해줘야 한다.

	return;
}

 

아무 버튼이나 눌러도 인터럽트 발생
좌측 버튼만 인터럽트 활성화

 

 

void BTN_ISR(void *CallBackRef)
{
	printf("btn Interrupt\n\r");
	XGpio *GpioPtr = (XGpio *)CallBackRef;
	// 이 CallBackRef가 누가 인터럽트를 발생시켰는지 알려준다.
	// 그래서 이 주소를 받아서 클리어해주는 것이 좋다.
//	if(XGpio_DiscreteRead(GpioPtr, BTN_CHANNEL)) // 버튼을 누를 때만 동작하게 하고 싶다면?
	if(XGpio_DiscreteRead(GpioPtr, BTN_CHANNEL) & 0b0010) // 왼쪽 버튼만 인터럽트로 사용하고 싶다면?
	{
		printf("btn Left Pushed\n\r");
	}
	else printf("btn Left released\n\r");
	XGpio_InterruptClear(GpioPtr, BTN_CHANNEL);
	// 여기서는 Interrupt가 발생해도 Flag가 0으로 초기화되지 않는다.
	// 한 번 ISR이 동작하면, 계속 ISR이 동작해버림
	// 그래서 한 번 동작하고 꺼주는 함수를 추가해줘야 한다.

	return;
}

 

버튼을 눌렀다 뗄 때도 인터럽트 활성화

 


 

[Switch로 Interrupt 동작]

 

📌 Block Design

 

 


 

📌 Main Code

 

#include <stdio.h>          // 표준 입력/출력 함수
#include "platform.h"       // 플랫폼에 특화된 정의 및 초기화
#include "xil_printf.h"      // Xilinx printf 함수
#include "xparameters.h"     // 하드웨어 플랫폼의 매개변수 정의
#include "xgpio.h"           // 하드웨어용 일반 I/O 함수
#include "xintc.h"           // 인터럽트 컨트롤러 함수
#include "xil_exception.h"

//////////////////////// User Define ////////////////////////

#define SWITCH_ID XPAR_AXI_GPIO_SWITCH_DEVICE_ID  // 스위치용 GPIO 컨트롤러의 ID
#define SWITCH_CHANNEL 1                        // 스위치용 GPIO 채널
#define SWITCH_MASK 0b1111                      // 스위치에 대한 마스크, 하위 4비트만 사용

#define SWITCH_VEC_ID XPAR_INTC_0_GPIO_0_VEC_ID // VEC_ID는 이름을 바꿔도 처음 아이디 그대로 나온다.
#define INTC_ID XPAR_INTC_0_DEVICE_ID            // Interrupt Controller의 디바이스 ID

////////////////////////////////////////////////////////////////////////////////

XGpio switch_device;                           // 스위치용 XGpio 드라이버의 인스턴스
XIntc intc;                                    // 인터럽트 컨트롤러의 인스턴스

//////////// 함수 프로토타입 선언 ////////////

void SWITCH_ISR(void *CallBackRef);

////////////////////////////////////////////////////////

int main()
{
    init_platform();                          // 플랫폼 초기화 (하드웨어 및 소프트웨어)

    printf("Start!!\n\r");                    // 프로그램 시작을 나타내는 메시지 출력

    int switch_data = 0;                       // 스위치 데이터를 저장하는 변수
    XGpio_Config *cfg_ptr_switch_intc;         // 스위치 GPIO의 구성을 가리키는 포인터

    cfg_ptr_switch_intc = XGpio_LookupConfig(SWITCH_ID);  // 스위치 GPIO의 구성 조회
    XGpio_CfgInitialize(&switch_device, cfg_ptr_switch_intc, cfg_ptr_switch_intc->BaseAddress);
    // 스위치 GPIO 초기화
    XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1);  // 스위치 GPIO의 데이터 방향 설정

    //////////////////////// 인터럽트 컨트롤러 초기화 ////////////////////////
    XIntc_Initialize(&intc, INTC_ID);
    XIntc_Connect(&intc, SWITCH_VEC_ID, (XInterruptHandler)SWITCH_ISR, (void *)&switch_device);
    // 인터럽트가 발생하면 이 아이디에 대해서 VEC_ID로 가고 함수의 주소를 찾아서 그 함수로 간다.

    //----------------------- 인터럽트 활성화 -----------------------//
    XIntc_Enable(&intc, SWITCH_VEC_ID);          // Interrupt 활성화
    XIntc_Start(&intc, XIN_REAL_MODE);

    XGpio_InterruptEnable(&switch_device, SWITCH_CHANNEL); // 개별 인터럽트 활성화

    XGpio_InterruptGlobalEnable(&switch_device);          // Global 인터럽트 활성화

    //-------------------- mblaze 인터럽트 활성화 --------------------//
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XIntc_InterruptHandler, &intc);
    Xil_ExceptionEnable();
    ////////////////////////////////////////////////////////

    while(1)
    {
        print("Hello World\n\r");                  // 콘솔에 "Hello World" 출력
        switch_data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
        // GPIO에서 스위치 데이터 읽기
        xil_printf("switch data : %x \n\r", switch_data);
        // 스위치 데이터를 16진수 형식으로 출력
        MB_Sleep(1000);                             // 1000밀리초 동안 대기
    }

    cleanup_platform();                           // 종료 전에 리소스 정리 및 해제
    return 0;                                     // 성공적인 실행을 나타내기 위해 0 반환
}



void SWITCH_ISR(void *CallBackRef)
{
    printf("switch Interrupt\n\r");
    XGpio *GpioPtr = (XGpio *)CallBackRef;
    // 이 CallBackRef가 누가 인터럽트를 발생시켰는지 알려준다.
    // 그래서 이 주소를 받아서 클리어해주는 것이 좋다.
    if(XGpio_DiscreteRead(GpioPtr, SWITCH_CHANNEL)) // 스위치을 누를 때만 동작하게 하고 싶다면?
//  if(XGpio_DiscreteRead(GpioPtr, SWITCH_CHANNEL) & 0b0010) // 왼쪽 버튼만 인터럽트로 사용하고 싶다면?
    {
        printf("Switch On\n\r"); // 스위치가 켜졌다고 출력
    }
    else printf("Switch Off\n\r"); // 스위치가 꺼진 것을 출력
    XGpio_InterruptClear(GpioPtr, SWITCH_CHANNEL);
    // 여기서는 Interrupt가 발생해도 Flag가 0으로 초기화되지 않는다.
    // 한 번 ISR이 동작하면, 계속 ISR이 동작해버림
    // 그래서 한 번 동작하고 꺼주는 함수를 추가해줘야 한다.

    return;
}

 

 


 

728x90
반응형