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

[Harman 세미콘 아카데미] 95일차 - SoC Design(SoC Review, MMIO, FND Control, FND module Instance)

by Graffitio 2023. 11. 20.
[Harman 세미콘 아카데미] 95일차 - SoC Design(SoC Review, MMIO, FND Control, FND module Instance)
728x90
반응형
[SoC Review]

 

📌 SoC 개요

 

우리가 만든 선풍기같은 것도 시스템이지 않느냐?

시스템은 '계'라는 뜻이니, 맞기는 하다.

 

하지만 SoC에서의 시스템은 다른 의미이다.

CPU, RAM 등 컴퓨터 시스템을 의미한다는 것

컴퓨터 자체를 칩 안에 집어 넣었다는 뜻

모바일 기술 발전을 가장 막고 있는 것이 바로 배터리 성능

CPU는 얼마든지 더 성능을 올릴 수 있다.

AP 같은 것들을 더 좋은 걸 쓰면 됨

근데 그러면 1~2시간만에 배터리 다 소모됨.

 

시스템에 다 넣는 것은 문제가 아닌다.

IoT라던가, 가전제품이라던가, 예전에는 시스템을 쓰지 않았던 기기들이

요즘에는 시스템을 다 쓰기 시작했다.

TV나 휴대폰 뜯어보면, 기판 하나 들어있고 끝. → System on a Chip 

 

그런 기기들의 요구가 점점 늘어날수록 SoC에 대한 수요가 더욱 더 늘어날 수 밖에 없다.

자체적으로 생산하는 회사도 많아졌고 그만큼 인력 수요도 같이 늘어났다.

 


 

📌 억까의 가능성

 

코드 작성 시, Wrapping, Implementation 중 어느 단계에서도 문제는 발생할 수 있다.

아직까지 Vivado Vitis가 완벽하지는 않다.

Xilinx는 Chip 만드는 회사이므로 소프트웨어적으로 완벽하지 않아.

우리는 하드웨어를 만들고 있으니, 그냥 억까로 안 될 수도 있다는 것을 알고 있자.


 

📌 Memory Mapped I/O

 

 

cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);

 

ㆍ IP들은 메모리도 아닌데, 왜 주소가 필요할까?

    우리가 CPU만들 때 버스 라인이 있던 것을 기억해보자.

    주변기기들끼리 연결된 버스가 있다.

    이 것도 제어를 해줘야 해. 이 기능을 하는 것이 AXI Interconnect

    GPIO에 접근할 때, 메모리로 접근하게 된다.

    그래서 주소 방식으로 접근을 하게 됨.

    대신 GPIO에 접근할 때는 메모리 주소가 아니라 따로 각각의 주소가 있다.

 

    예시)

    GPIO에 1111을 줘라. 라는 명령을 AXI Interconnect에 전달

    AXI Interconnect가 해당 GPIO에 맞는 주소에 1111을 전달

    GPIO에서 1111 출력

    CPU 입장에서는 그냥 다 메모리

    하지만 시스템에서 동작하는 것은 GPIO

    UART도 마찬가지로 CPU 입장에서는 메모리

    우리가 쓰고자 하는 것을 UDR0에 쓰면 이 것을 한 비트씩 통신 모듈에서 출력

 

    이렇게 하는 것을 Memory Mapped I/O 라고 한다.

    그래서 다 주소가 있는 것

 

 

절차 지향 프로그래밍을 하고 있지만, 약간의 객체를 만들 수 있다.

 


 

[FND Control]

 

📌 코드 최적화

입력값이 변경됐을 때만 동작하도록 코드를 최적화시켜주자.

이렇게 해주면, 시스템 부하를 줄여 동작을 빠르게 해줄 수 있다.

 

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"

#define LED_ID          	XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID       	XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID				XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL     	1
#define SWITCH_CHANNEL  	1
#define FND_COM_CHANNEL     1
#define FND_SEG7_CHANNEL    2


int main()
{
    init_platform();

    print("Start!\n\r");

    XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
    XGpio led_device; // gpio 객체
    XGpio switch_device; // gpio 객체
    XGpio fnd_device; // gpio 객체
    u32 data = 0;
    u32 old_data = 0;
    u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
    unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성

    ////////////////////////////////////// Initialize Led Device //////////////////////////////////////
    /////// LED ///////
    cfg_ptr_led = XGpio_LookupConfig(LED_ID);
    XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
    XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
    // XGpio_CfgInitialize(InstancePtr, Config, EffectiveAddr)
    // XGpio_SetDataDirection(InstancePtr, Channel, DirectionMask)
    //-----------------------------------------------------------------------------------------------//
    /////// SWITCH ///////
    cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
    XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
    XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// FND ///////
    cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
    XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
    XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
    XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    while(1)
    {
//    	print("Hello World\n\r"); // UART Test
//    	led_data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
//    	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_data);
//    	XGpio_DiscreteWrite(&fnd_device, FND_DISP_CHANNEL, fnd_data);
    	old_data = data;
    	data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);

    	if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
    	{
    		old_data = data;
        	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
        	FND[0] = ~fnd_value[0xf & data];
        	FND[1] = ~fnd_value[(0xf & data>>4)];
        	FND[2] = ~fnd_value[(0xf & data>>8)];
        	FND[3] = ~fnd_value[(0xf & data>>12)];
        	// 이렇게 따로 코드를 분류해주면, 데이터가 바뀔 때만 갱신할 수 있다.
        	// 스위치 입력이 달라졌을 때만, LED 출력을 바꿔줌
        	// 시스템의 부하를 줄여, 좀 더 빠르게 동작시킬 수 있다.
    	}

    	for(int i = 0;i<4;i++)
    	{
//    		FND[i] = ~fnd_value[(0xf & data>>(i*4))];
    		XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
//    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, ~fnd_value[(0xf & data>>(i*4))]);
    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
    		MB_Sleep(1);
    	}
    }
    cleanup_platform();
    return 0;
}

 


 

📌 코드의 함수화

 

[함수로 만들어주는 이유]

코드의 가독성재사용성을 높이기 위해 함수로 만들어 주자.

고급 언어를 쓰는 이유가 바로 가독성때문인데, 그 것을 더 극대화해주기 위해 함수를 쓴다.

요즘에는 시스템이 하도 커져서 혼자서는 절대 못 해.

내가 알아보기 쉽게 하는 것은 물론, 팀원들도 알아보기 쉽게 작성해주는 것이 좋다.

→ 퇴근 시간을 더욱 빠르게!

 

void FND_update()
{
    FND[0] = ~fnd_value[0xf & data];
    FND[1] = ~fnd_value[(0xf & data>>4)];
    FND[2] = ~fnd_value[(0xf & data>>8)];
    FND[3] = ~fnd_value[(0xf & data>>12)];
}

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

if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
    old_data = data;
    XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
    FND_update();
}

 


 

📌 Counting Code

 

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"

#define LED_ID          	XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID       	XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID				XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL     	1
#define SWITCH_CHANNEL  	1
#define FND_COM_CHANNEL     1
#define FND_SEG7_CHANNEL    2

int main()
{
    init_platform();

    print("Start!\n\r");

    XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
    XGpio led_device; // gpio 객체
    XGpio switch_device; // gpio 객체
    XGpio fnd_device; // gpio 객체
    u32 data = 0;
    u32 old_data = 0;
    u32 FND_data = 0;
    u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
    unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성

    void FND_update(unsigned data)
    {
    	FND[0] = ~fnd_value[0xf & data];
    	FND[1] = ~fnd_value[(0xf & data>>4)];
    	FND[2] = ~fnd_value[(0xf & data>>8)];
    	FND[3] = ~fnd_value[(0xf & data>>12)];
    	return;
    }


    ////////////////////////////////////// Initialize Led Device //////////////////////////////////////
    /////// LED ///////
    cfg_ptr_led = XGpio_LookupConfig(LED_ID);
    XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
    XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// SWITCH ///////
    cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
    XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
    XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// FND ///////
    cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
    XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
    XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
    XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    while(1)
    {
    	old_data = data;
    	data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);

    	if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
    	{
    		old_data = data;
        	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
    	}
    	FND_update(FND_data++);

    	for(int i = 0;i<4;i++)
    	{
    		XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
    		MB_Sleep(1);
    	}
    }
    cleanup_platform();
    return 0;
}

 

 

10진수로 바꿔보자.

 

void FND_update_dec(unsigned data)
{
    FND[0] = ~fnd_value[data%10];
    FND[1] = ~fnd_value[data/10%10];
    FND[2] = ~fnd_value[data/100%10];
    FND[3] = ~fnd_value[data/1000%10];
    return;
}

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

FND_update_dec(FND_data++);

 


 

📌 Porting

 

1. FND FONT

 

    다른 보드를 쓸 때는 최상위비트가 다른 곳에 연결될 수도 있잖아.

    그러면 다시 또 코드를 만들어 줘야 하는데, 그러면 재사용성이 떨어진다.

    재사용성을 위해 폰트를 만들어 주자.

    이러한 작업을 Porting이라 한다.

 

// FND 폰트를 위한 Define
#define FND_A  0
#define FND_B  1
#define FND_C  2
#define FND_D  3
#define FND_E  4
#define FND_F  5
#define FND_G  6
#define FND_P  7 // dot point

unsigned int FND_FONT[] = {
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P,  // 0
    1<<FND_B | 1<<FND_C, 								    // 1
    1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G,	    	     		    // 2
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, 				    // 3
    1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, 						    // 4
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 				    // 5
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // 6
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, 						    // 7
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 	    // 8
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 			    // 9

    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // A
    1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G,				    // B
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, 						    // C
    1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, 				    // D
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 				    // E
    1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G 						    // F
};

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

void FND_update_hex(unsigned data)
{
    FND[0] = ~FND_FONT[0xf & data];
    FND[1] = ~FND_FONT[(0xf & data>>4)];
    FND[2] = ~FND_FONT[(0xf & data>>8)];
    FND[3] = ~FND_FONT[(0xf & data>>12)];
    return;
}

void FND_update_dec(unsigned data)
{
    FND[0] = ~FND_FONT[data%10];
    FND[1] = ~FND_FONT[data/10%10];
    FND[2] = ~FND_FONT[data/100%10];
    FND[3] = ~FND_FONT[data/1000%10];
    return;
}

 

 

2. FND DIGIT

 

    시프트 연산도 결국에는 연산

    CPU가 계속 일해야 된다는 의미

    굳이 그렇게 고생시킬 필요가 있나?

    얘도 변수로 만들어줘서 필요할 때마다 불러오자

    그러면 처음 변수 만들 때 한 번만 연산하고 그 이후에는 그냥 갖다 쓰면 됨

 

unsigned int FND_digit[] = {
		~(1<<0),
		~(1<<1),
		~(1<<2),
		~(1<<3),
};

 

얘도 Porting해주자.

 

// FND Digit을 위한 Define
#define FND_DIGIT_0     0
#define FND_DIGIT_10    1
#define FND_DIGIT_100   2
#define FND_DIGIT_1000  3

unsigned int FND_digit[] = {
		~(1<<FND_DIGIT_0),
		~(1<<FND_DIGIT_10),
		~(1<<FND_DIGIT_100),
		~(1<<FND_DIGIT_1000),
};

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

XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);

 

정상적으로 잘 동작한다.

 

[Full Code]

더보기
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"

#define LED_ID          	XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID       	XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID				XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL     	1
#define SWITCH_CHANNEL  	1
#define FND_COM_CHANNEL     1
#define FND_SEG7_CHANNEL    2

// FND 폰트를 위한 Define
#define FND_A  0
#define FND_B  1
#define FND_C  2
#define FND_D  3
#define FND_E  4
#define FND_F  5
#define FND_G  6
#define FND_P  7 // dot point

// FND Digit을 위한 Define
#define FND_DIGIT_0     0
#define FND_DIGIT_10    1
#define FND_DIGIT_100   2
#define FND_DIGIT_1000  3



unsigned int FND_FONT[] = {
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P,  // 0
    1<<FND_B | 1<<FND_C, 								    // 1
    1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G,	    	     		    // 2
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, 				    // 3
    1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, 						    // 4
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 				    // 5
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // 6
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, 						    // 7
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 	    // 8
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 			    // 9

    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // A
    1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G,				    // B
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, 						    // C
    1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, 				    // D
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 				    // E
    1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G 						    // F
};

//unsigned int FND_digit[] = {
//		~(1<<0),
//		~(1<<1),
//		~(1<<2),
//		~(1<<3),
//};

unsigned int FND_digit[] = {
		~(1<<FND_DIGIT_0),
		~(1<<FND_DIGIT_10),
		~(1<<FND_DIGIT_100),
		~(1<<FND_DIGIT_1000),
};


int main()
{
    init_platform();

    print("Start!\n\r");

    XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
    XGpio led_device; // gpio 객체
    XGpio switch_device; // gpio 객체
    XGpio fnd_device; // gpio 객체
    u32 data = 0;
    u32 old_data = 0;
    u32 FND_data = 0;
    u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
    unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성

//    void FND_update(unsigned data)
//    {
//    	FND[0] = ~fnd_value[0xf & data];
//    	FND[1] = ~fnd_value[(0xf & data>>4)];
//    	FND[2] = ~fnd_value[(0xf & data>>8)];
//    	FND[3] = ~fnd_value[(0xf & data>>12)];
//    	return;
//    }

    void FND_update_hex(unsigned data)
    {
    	FND[0] = ~FND_FONT[0xf & data];
    	FND[1] = ~FND_FONT[(0xf & data>>4)];
    	FND[2] = ~FND_FONT[(0xf & data>>8)];
    	FND[3] = ~FND_FONT[(0xf & data>>12)];
    	return;
    }

    void FND_update_dec(unsigned data)
    {
    	FND[0] = ~FND_FONT[data%10];
    	FND[1] = ~FND_FONT[data/10%10];
    	FND[2] = ~FND_FONT[data/100%10];
    	FND[3] = ~FND_FONT[data/1000%10];
    	return;
    }


    ////////////////////////////////////// Initialize Led Device //////////////////////////////////////
    /////// LED ///////
    cfg_ptr_led = XGpio_LookupConfig(LED_ID);
    XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
    XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
    // XGpio_CfgInitialize(InstancePtr, Config, EffectiveAddr)
    // XGpio_SetDataDirection(InstancePtr, Channel, DirectionMask)
    //-----------------------------------------------------------------------------------------------//
    /////// SWITCH ///////
    cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
    XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
    XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// FND ///////
    cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
    XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
    XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
    XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    while(1)
    {
//    	print("Hello World\n\r"); // UART Test
//    	led_data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
//    	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_data);
//    	XGpio_DiscreteWrite(&fnd_device, FND_DISP_CHANNEL, fnd_data);
    	old_data = data;
    	data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);

    	if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
    	{
    		old_data = data;
        	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);

        	// 이렇게 따로 코드를 분류해주면, 데이터가 바뀔 때만 갱신할 수 있다.
        	// 스위치 입력이 달라졌을 때만, LED 출력을 바꿔줌
        	// 시스템의 부하를 줄여, 좀 더 빠르게 동작시킬 수 있다.
    	}
    	FND_update_hex(FND_data++);
//    	FND_update_dec(FND_data++);

    	for(int i = 0;i<4;i++)
    	{
//    		FND[i] = ~fnd_value[(0xf & data>>(i*4))];
//    		XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
    		XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);
//    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, ~fnd_value[(0xf & data>>(i*4))]);
    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
    		MB_Sleep(1);
    	}
    }
    cleanup_platform();
    return 0;
}

 


 

📌 헤더파일과 소스파일로 분할

 

ifndef : if not define

만약 define 되어 있지 않으면, endif까지 define해라.

이렇게 쓰면 한 번만 define되겠지?

헤더파일을 여기 저기에 넣어도 한 번만 실행하도록 하는 코드

 

FND.h

#ifndef FND_H_ // FND_H_ 가 Define되어 있지 않다면, endif까지 Define해라.

#define FND_H_ // 여기서 Define했으니, 이후 여기 저기에 넣어도 Define 안 됨.

////////////// Define //////////////
// FND 폰트를 위한 Define
#define FND_A  0
#define FND_B  1
#define FND_C  2
#define FND_D  3
#define FND_E  4
#define FND_F  5
#define FND_G  6
#define FND_P  7 // dot point

// FND Digit을 위한 Define
#define FND_DIGIT_0     0
#define FND_DIGIT_10    1
#define FND_DIGIT_100   2
#define FND_DIGIT_1000  3
//////////////// End /////////////////


////////////// Variable //////////////
unsigned int FND_FONT[];
unsigned int FND_digit[];
unsigned int FND[4];
//////////////// End /////////////////


////////////// Prototype //////////////
void FND_update_hex(unsigned data);
void FND_update_dec(unsigned data);
//////////////// End /////////////////

#endif

 

FND.c

#include "FND.h"

//////////////////////////////////////////// Init Variable //////////////////////////////////////////
unsigned int FND_FONT[] = {
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P,  // 0
    1<<FND_B | 1<<FND_C, 								    // 1
    1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G,	    	     		    // 2
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, 				    // 3
    1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, 						    // 4
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 				    // 5
    1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // 6
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, 						    // 7
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 	    // 8
    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, 			    // 9

    1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, 			    // A
    1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G,				    // B
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, 						    // C
    1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, 				    // D
    1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, 				    // E
    1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G 						    // F
};

unsigned int FND_digit[] = {
		~(1<<FND_DIGIT_0),
		~(1<<FND_DIGIT_10),
		~(1<<FND_DIGIT_100),
		~(1<<FND_DIGIT_1000),
};
///////////////////////////////////////////////// End ///////////////////////////////////////////////


//////////////////////////////////////////// Init Function //////////////////////////////////////////
void FND_update_hex(unsigned data)
{
	FND[0] = ~FND_FONT[0xf & data];
	FND[1] = ~FND_FONT[(0xf & data>>4)];
	FND[2] = ~FND_FONT[(0xf & data>>8)];
	FND[3] = ~FND_FONT[(0xf & data>>12)];
	return;
}

void FND_update_dec(unsigned data)
{
	FND[0] = ~FND_FONT[data%10];
	FND[1] = ~FND_FONT[data/10%10];
	FND[2] = ~FND_FONT[data/100%10];
	FND[3] = ~FND_FONT[data/1000%10];
	return;
}
///////////////////////////////////////////////// End ///////////////////////////////////////////////

 

helloworld.c

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"

#include "FND.h"

#define LED_ID              XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID           XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID		    XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL         1
#define SWITCH_CHANNEL      1
#define FND_COM_CHANNEL     1
#define FND_SEG7_CHANNEL    2

int main()
{
    init_platform();

    print("Start!\n\r");

    XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
    XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
    XGpio led_device; // gpio 객체
    XGpio switch_device; // gpio 객체
    XGpio fnd_device; // gpio 객체
    u32 data = 0;
    u32 old_data = 0;
    u32 FND_data = 0;
    u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};

    ////////////////////////////////////// Initialize Led Device //////////////////////////////////////
    /////// LED ///////
    cfg_ptr_led = XGpio_LookupConfig(LED_ID);
    XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
    XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// SWITCH ///////
    cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
    XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
    XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
    //-----------------------------------------------------------------------------------------------//
    /////// FND ///////
    cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
    XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
    XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
    XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    while(1)
    {
    	old_data = data;
    	data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);

    	if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
    	{
    		old_data = data;
        	XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
    	}
    	FND_update_hex(FND_data++);
//    	FND_update_dec(FND_data++);

    	for(int i = 0;i<4;i++)
    	{
    		XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);
    		XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
    		MB_Sleep(1);
    	}
    }
    cleanup_platform();
    return 0;
}

정상적으로 잘 동작한다.

 


 

[FND Controller module]

 

📌 나만의 IP 생성

 

우리가 만든 FND Cntr 모듈을 얹어서 사용해보자.

AXI Interconnect와 연결할 수 있는 단자를 만들어 줘야 한다.

 

새로 블록디자인을 만들어 주자.

이제까지는 이미 IP catalog에 만들어져 있는 IP를 썼지만,

이번에는 우리가 만든 모듈을 불러올 것이다.

IP : 지적 자산

 

 

새로 만들 거니까, Create New 에 체크

 

 

레지스터를 8개로 늘려주자
우리가 만든 것은 손을 좀 봐야 하므로, Edit IP

 

이렇게 IP를 만들기 위한 프로젝트가 새로 열린다.

 

이제 AXI 통신 모듈에 FND_Cntr 모듈을 붙여주면 된다. 

 


FND Cntr 모듈을 가져와서 High 모듈에 붙여주자.

 

여기다 붙여주면 된다.

 

여기엔 입출력 포트를 넣어주면 된다

 

// Users to add ports here
output [3:0] com,
output [7:0] seg_7,
// User ports ends
// Do not modify the ports beyond this line

 

Value는 메모리로 받아야 한다(MMIO이기 때문, 그 주소에 대한 메모리를 넣어준다.)

 

// Implement memory mapped register select and read logic generation
// Slave register read enable is asserted when valid address is available
// and the slave is ready to accept the read address.
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
always @(*)
begin
      // Address decoding for reading registers
      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
        3'h0   : reg_data_out <= slv_reg0;
        3'h1   : reg_data_out <= slv_reg1;
        3'h2   : reg_data_out <= slv_reg2;
        3'h3   : reg_data_out <= slv_reg3;
        3'h4   : reg_data_out <= slv_reg4;
        3'h5   : reg_data_out <= slv_reg5;
        3'h6   : reg_data_out <= slv_reg6;
        3'h7   : reg_data_out <= slv_reg7;
        default : reg_data_out <= 0;
      endcase
end

 

위 코드는 메모리에 대한 주소를 작성한 코드(메모리명 : slv_reg0~7)

 

하위 묘듈을 수정해줬으니, TOP module에서도 넣어줘야 한다.

 

여기도 포트 추가

 

 

 

이걸 눌러주면, 프로젝트가 꺼질 것이다.

 

이제 우리가 만든 IP를 불러와서 붙여주고, xdc 파일에서 연결만 해주면 된다.

 

 

 

출력 단자를 만들고 싶은 포트에 우클릭한 후, Make External 을 클릭해 출력 포트를 만들어 준다.

 

Wrapping하면, com과 seg 뒤에 _0이 붙은 것을 볼 수 있다.

 

##7 Segment Display
set_property -dict { PACKAGE_PIN W7   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[0]}]
set_property -dict { PACKAGE_PIN W6   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[1]}]
set_property -dict { PACKAGE_PIN U8   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[2]}]
set_property -dict { PACKAGE_PIN V8   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[3]}]
set_property -dict { PACKAGE_PIN U5   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[4]}]
set_property -dict { PACKAGE_PIN V5   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[5]}]
set_property -dict { PACKAGE_PIN U7   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[6]}]

set_property -dict { PACKAGE_PIN V7   IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[7]}]

set_property -dict { PACKAGE_PIN U2   IOSTANDARD LVCMOS33 } [get_ports {com_0[3]}]
set_property -dict { PACKAGE_PIN U4   IOSTANDARD LVCMOS33 } [get_ports {com_0[2]}]
set_property -dict { PACKAGE_PIN V4   IOSTANDARD LVCMOS33 } [get_ports {com_0[1]}]
set_property -dict { PACKAGE_PIN W4   IOSTANDARD LVCMOS33 } [get_ports {com_0[0]}]

 

xdc 파일에서 다 바꿔주자.

 

 


 

📌 Vitis Project 생성 및 Main code 작성

 

1. Export Hardware

2. Create Platform Project

3. Create Application Project

 

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"


int main()
{
    init_platform();

    print("Start!\n\r");
    int data = 0;
//    volatile unsigned int *FND_cntr = (volatile unsigned int *) XPAR_MYIP_FND_CNTR_0_S00_AXI_BASEADDR;
    // 이렇게 줘도 되고, 아래처럼 줘도 된다.
    volatile unsigned int *FND_cntr = (volatile unsigned int *) 0x44A00000;
    // FND_cntr의 포인터변수를 volatile unsigned int * 라는 데이터타입으로 임시 변환
    // *FND_cntr의 값은 0x44A00000

    while(1)
    {
    	FND_cntr[0] = data++; // [0] 배열은 포인터
    	// 온전성 검사(Sanity Check)해보자.
    	xil_printf("%X\n\r", FND_cntr[7]);
    	MB_Sleep(100);
    }

    cleanup_platform();
    return 0;
}

 

정상적으로 카운팅된다.
온전성 검사도 잘 진행되는 중

 


 

728x90
반응형