이 Project는 대학교 3학년 때, 수강하였던 "시스템 온 칩(SoC)"라는 교과목으로 강의 수강 중 제출해야 되는 과제였다.
과제에 대한 제한 사항으로는 Basys3 FPGA Borad를 활용하여 간단한 1차 Project를 구성하는 것이였다.
따라서, 기본적인 연산기 기능을 만들어 최종 Project 구성에 도움이 되도록, +, - 연산기를 주제로 정하였다.
최종 Project를 구성하기 전, "7 Segments에 결과값을 출력하는 7bit 감산, 가산기"를 먼저 설계하여 최종 Project 구성에 사용하였다.
최종 Project를 상향식 설계를 통해 구성하기 위해, 핵심 기능인 감산, 가산이 가능한 7bit 연산기를 미리 만들었다.
Basys3의 16개의 Switch 중 15개를 사용하여 0번부터 6번까지 7bit의 연산 입력을 넣어주는 1ch Input Switch로 구성하였고, 7번부터 13번까지 동일하게 2ch Input Switch로 구성하였다.
14번은 1ch과 2ch의 결과값을 + 또는 - 연산을 할 수 있도록 선택할 수 있는 Selector Switch로 구성하였다.
1ch과 2ch에서 활성화된 Switch의 값과 Selector Switch의 활성화 여부를 LED를 통해 직관적으로 확인할 수 있도록 하였다. 이후 4개의 7 Segment를 통하여 연산한 결과값을 출력하도록 프로젝트를 진행하였다.
프로젝트는 LED, Switch, 7Segment가 이용되었으며, LED와 Switch는 GPIO를 이용하였고 7 Segment는 전용 모듈을 이용하였다.
각 모듈을 AHB-LITE 모듈, BUS에 연결함으로써 모든 모듈이 정상적으로 연결이 되게 하였다.
S/W 설계는 C를 이용하였으며, S/W가 H/W와 같이 맞물려 구동되며 결과를 보여주게 된다.
프로젝트에서 사용할 Switch, LED와 7Segment를 보드의 핀과 연결해주기 위해 .xdc파일을 다음과 같이 수정하였다.
주석 처리가 되어 있는 핀을 활성화함으로써 핀을 사용한다.
# Switches
set_property PACKAGE_PIN V17 [get_ports {sw[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sw[0]}]
ː
set_property PACKAGE_PIN T1 [get_ports {sw[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sw[14]}]
set_property PACKAGE_PIN R2 [get_ports RESET]
set_property IOSTANDARD LVCMOS33 [get_ports RESET]
# LEDs
set_property PACKAGE_PIN U16 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
ː
set_property PACKAGE_PIN L1 [get_ports {LED[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[15]}]
# 7 segment display
set_property PACKAGE_PIN W7 [get_ports {seg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}]
ː
set_property PACKAGE_PIN U7 [get_ports {seg[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
set_property PACKAGE_PIN V7 [get_ports dp]
set_property IOSTANDARD LVCMOS33 [get_ports dp]
set_property PACKAGE_PIN U2 [get_ports {an[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[0]}]
ː
set_property PACKAGE_PIN W4 [get_ports {an[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[3]}]
Bus를 통해 주고받는 입력과 출력값은 아래와 같다. 16번 스위치는 Reset으로 사용되기에 0부터 14까지의 15개의 Switch를 입력으로 받고 16개의 LED를 출력으로 한다.
module AHBLITE_SYS(
//CLOCKS & RESET
input wire CLK,
input wire RESET,
//TO SW BOARD
input wire [14:0] sw, //TO BOARD LEDs
output wire [15:0] LED,
// SEG
output wire [6:0] seg,
output wire [3:0] an,
output wire dp
);
"7 Segment"를 사용함에 있어 세그먼트의 출력값, 세그먼트의 번호, 세그먼트의 dp(Dot Point)를 출력할 수 있도록 하였다.
//SELECT SIGNALS
wire [3:0] MUX_SEL;
wire HSEL_MEM;
wire HSEL_LED;
wire HSEL_SW;
wire HSEL_SEG;
//SLAVE READ DATA
wire [31:0] HRDATA_MEM;
wire [31:0] HRDATA_LED;
wire [31:0] HRDATA_SW;
wire [31:0] HRDATA_SEG;
//SLAVE HREADYOUT
wire HREADYOUT_MEM;
wire HREADYOUT_LED;
wire HREADYOUT_SW;
wire HREADYOUT_SEG;
AHBMUX uAHBMUX (
ː
.HRDATA_S0(HRDATA_MEM),
.HRDATA_S1(HRDATA_LED),
.HRDATA_S2(HRDATA_SW),
.HRDATA_S3(HRDATA_SEG),
ː
.HREADYOUT_S0(HREADYOUT_MEM), .HREADYOUT_S1(HREADYOUT_LED), .HREADYOUT_S2(HREADYOUT_SW), .HREADYOUT_S3(HREADYOUT_SEG),
:
);
추가적으로 Slave들이 Master와 신호를 주고 받게 하기 위해야 위와 같은 부분들을 수정하였다.
//AHBLite Slave
AHBGPIO uAHB2LED (
//AHBLITE Signals
.HSEL(HSEL_LED),
.HCLK(HCLK),
.HRESETn(HRESETn),
.HREADY(HREADY),
.HADDR(HADDR),
.HTRANS(HTRANS[1:0]),
.HWRITE(HWRITE),
.HWDATA(HWDATA[31:0]),
.HRDATA(HRDATA_LED),
.HREADYOUT(HREADYOUT_LED),
//Sideband Signals
//.LED(LED[6:0])
.GPIOOUT(LED[14:0])
);
기존의 LED Module을 사용하지 않고 GPIO를 통해 LED를 연결하였으며, 16번 Reset 스위치를 제외하고 나머지 15개의 LED를 ".GPIOOUT(LED[14:0])" 출력으로 설정하여 LED를 연결하였다.
//AHBLite Slave
AHBGPIO uAHB2GPIO (
//AHBLITE Signals
.HSEL(HSEL_SW),
.HCLK(HCLK),
.HRESETn(HRESETn),
.HREADY(HREADY),
.HADDR(HADDR),
.HTRANS(HTRANS[1:0]),
.HWRITE(HWRITE),
.HWDATA(HWDATA[31:0]),
.HRDATA(HRDATA_SW),
.HREADYOUT(HREADYOUT_SW),
//Sideband Signals
.GPIOIN({1'b0, sw[14:0]}) // { , }concatenation operator
);
LED GPIO와 동일하게 Switch 또한, GPIO를 통해 .GPIOIN({1'b0, sw[14:0]})으로 입력값을 받았다.
AHB7SEGDEC uAHB7SEGDEC (
//AHBLITE Signals
.HSEL(HSEL_SEG),
.HCLK(HCLK),
.HRESETn(HRESETn),
.HREADY(HREADY),
.HADDR(HADDR),
.HTRANS(HTRANS[1:0]),
.HWRITE(HWRITE),
.HWDATA(HWDATA[31:0]),
.HRDATA(HRDATA_SEG),
.HREADYOUT(HREADYOUT_SEG),
//Sideband Signals
.seg(seg),
.an(an),
.dp(dp)
);
7 Segment Module을 사용하여 4개의 7 Segment를 제어하였으며 .seg(seg)를 통해 세그먼트의 LED에 출력할 값을 정한 후, .an(an)를 사용하여 4개의 세그먼트 중 사용할 세그먼트를 지정하였고, .dp(dp)를 사용하여 7 Segment에서 Dot Point를 사용할지에 대해 정할 수 있도록 구성하였다.
프로젝트에서 S/W 구성할 때, 어셈블리 언어만 사용하여 S/W를 구성하는 것이 아닌 C 언어도 함께 사용하기 위해 위와 같이 어셈블리어를 수정하였다. Reset_Handler가 작동하면 C언어의 main으로 이동한다.
ː
__Vectors
DCD 0x00000FFC ;4K Internal Memory
DCD Reset_Handler
ː
;Reset Handler
Reset_Handler PROC
GLOBAL Reset_Handler
ENTRY
IMPORT __main
LDR R0, =__main
BX R0 ;Branch to __main
ENDP
ː
main.c 헤더에 LED, Switch, 7 Segment를 사용하기 위해 각각의 주소값을 할당하였다. 이 주소 값은 H/W적으로 "MEM"에 할당된 부분이며 MEM 모듈을 열어보면 확인할 수 있다.
#define AHB_LED_BASE 0x50000000
#define AHB_SW_BASE 0x51000000
#define AHB_SEG_BASE 0x52000000
void resetSeg();
void setSeg();
unsigned int cut0to6();
unsigned int cut7to13();
unsigned int cut14();
헤더 부분에 정의한 함수들은 다음과 같은 기능을 수행한다.
void resetSeg() : 세그먼트를 초기화시켜주기 위한 함수
void setSeg() : 7 Segment에 연산 결과값을 출력시키기 위해 사용한 함수
unsigned int cut0to6() : 16bit 중 1부터 7bit까지 1ch Input Value로 구성
unsigned int cut7to13() : 16bit 중 8부터 14bit까지 2ch Input Value로 구성
unsigned int cut14() : 16bit 중 15bit만 +/- Selector Switch로 구성
아래의 그림을 살펴보면 사용할 GPIO의 주소를 할당하는 것과 "GPIO Base Address"에 0x04를 더한 주소를 이용하여 해당 GPIO핀에 INPUT 또는 OUTPUT을 설정하는 것을 알 수 있다.
GPIO를 통해 16개의 LED를 사용하기 때문에 "AHB_LED_BASE+4"에 0xFFFF을 넣어서 16개의 LED 모두 OUTPUT으로 설정한 것을 볼 수 있다.
int main(void) {
*(unsigned int*) (AHB_LED_BASE+4)=0xFFFF; resetSeg();
while (1) {
ː
}
resetSeg();
}
LED에 관한 설정을 마치고 난 후, 세그먼트 사용을 위해 초기화 함수를 사용하였으며 while문을 사용하여 polling 방식으로 +/- 연산기를 구현하였다.
ː
unsigned int temp_0to14;
unsigned int temp_0to6;
unsigned int temp_7to13;
unsigned int temp_14;
unsigned int result;
temp_0to14 = *(unsigned int*) AHB_SW_BASE;
*(unsigned int*) AHB_LED_BASE = temp_0to14;
temp_0to6 = cut0to6(temp_0to14);
temp_7to13 = cut7to13(temp_0to14);
temp_14 = cut14(temp_0to14);
ː
AHB_SW_BASE의 주소에 있는 reset Switch를 제외한 15bit Switch 입력값을 temp_0to14에 초기화하였다.
Switch 입력값에 맞추어 직관적으로 입력값을 확인할 수 있도록 AHB_LED_BASE의 주소에 초기화하여 주었다.
이후, Switch 입력값을 1ch, 2ch, +/- Selector로 구분하기 위한 cut0to6(), cut7to13(), cut14() 함수를 사용하였다.
위 함수의 구체적인 기능은 아래에서 다룬다.
ː
if (temp_14 == 0x01) {
result = temp_7to13 + temp_0to6;
setSeg(result, 0x00);
}
ː
+/- Selector가 0x00일 때 + 연산을, 0x01일 때 - 연산을 할 수 있도록 만들기 위해, if문을 사용하였다. 여기서는 +/- Selector가 1일때를 다루고 있다.
즉, +연산을 수행하는 코드이다. 0~6과 7~13을 더한 결과를 result에 저장하고 setSeg()함수를 호출하여 인자로 result, 0x00를 전달한다.
result는 출력하기 위한 결과이고 0x00는 7Segment 왼쪽에서 첫 번째에 표시되는 값을 지정한다.
setSeg()함수의 구체적인 기능은 아래에서 다룬다.
ː
else {
if (temp_7to13 == temp_0to6){ result = 0x00;
setSeg(result, 0x00);
}
else if (temp_7to13 > temp_0to6) {
result = temp_7to13 - temp_0to6;
setSeg(result, 0x00);
}
else if (temp_7to13 < temp_0to6) {
result = temp_0to6 - temp_7to13;
setSeg(result, 0x11);
}
}
ː
여기서는 +/- Selector가 0일 때를 다루고 있다. 1ch과 2ch의 값을 비교하는 기능을 하며 1ch = 2ch일 때, 1ch > 2ch일 때, 1ch < 2ch일 때로 구분하여 연산을 할 수 있도록 설계하였다.
1ch과 2ch의 차이가 0일 때는 setSeg()함수를 이용하여, 0을 출력하여 세그먼트에 출력되는 값이 "0000"이 되게 만들었다.
추가적으로 - 연산을 하였을 때 결과값이 0보다 크면 4번째 세그먼트에 - 부호를 붙여 -000으로 출력되게 구현하였다.
setSeg(result, 0x11)의 "0x11"이 – 에 해당하는 값이다.
이러한 과정을 거치는 이유는 7Segment에 음수값을 그대로 넣어주었을 때, 정상적인 값을 출력하지 못하기 때문이다.
위 코드는 4개의 7 Segment를 사용하기 위해 초기화하는 함수로 아래의 그림을 보면 AHB_SEG_BASE Address에 0x04를 더하여 1번부터 4번 세그먼트를 사용할 수 있는 것을 확인할 수 있다.
void resetSeg() {
*(unsigned int*) AHB_SEG_BASE = 0xFF;
*(unsigned int*) (AHB_SEG_BASE +0x04) = 0xFF;
*(unsigned int*) (AHB_SEG_BASE +0x08) = 0xFF;
*(unsigned int*) (AHB_SEG_BASE +0x0C) = 0xFF;
}
Main Function에서 1ch과 2ch의 연산 결과를 세그먼트로 출력하기 위해 사용하는 함수로, temp라는 연산 결과값과 set4t라는 4번째 세그먼트의 출력값을 정하는 변수들이 함수의 입력값으로 사용된다.
위의 그림을 참고하면 7 Segment에 사용되는 7개의 LED에 대한 파라미터를 확인할 수 있다.
7'h12라는 값을 세그먼트의 출력으로 초기화시켜주면 아래의 세그먼트에서 "G"에 대한 LED만 HIGH가 되어 - 부호를 출력할 수 있다.
따라서 set4th의 입력으로는 0x00과 0x11으로 두 개인데 0x00는 0이며, 0x11는 - 부호로 - 연산에 있어 결과값과 그에 따른 부호를 함께 출력할 수 있게 만든 것이다.
void setSeg(temp, set4th) {
*(unsigned int*) (AHB_SEG_BASE) = (temp) % 10;
*(unsigned int*) (AHB_SEG_BASE + 0x04) = (temp % 100) /10 ;
*(unsigned int*) (AHB_SEG_BASE + 0x08) = (temp %1000) / 100;
*(unsigned int*) (AHB_SEG_BASE + 0x0C) = set4th;
}
아래의 3 함수는 16bit를 1ch Input Data(7bit), 2ch Input Data(7bit), +/- Selector Data(1bit)로 잘라 주는 기능을 한다.
사용하는 bit만 1로 변경하고 나머지 비트를 0으로 처리 후, "& 연산"을 하는 방식을 사용한다.
따라서 1이 있는 자리를 제외한 나머지 자리가 0이 되게 되고 이 값을 각각의 bit에 맞게 shift해주면 잘라진 결과를 얻을 수 있다.
unsigned int cut0to6(temp){
temp = 0x7F & temp; // 0x7F : 0000 0000 0111 1111
return temp >> 0;
}
unsigned int cut7to13(temp){
temp = 0x3F80 & temp; // 0x3F80 : 0011 1111 1000 0000
return temp >> 7;
}
unsigned int cut14(temp){
temp = 0x4000 & temp; // 0x4000 : 0100 0000 0000 0000
return temp >> 14;
}
15bit Switch 중 1bit, 7bit, 7bit를 사용하여 7 Segments에 결과값을 출력하는 7bit +, - 연산기를 설계하였다. 설계한 결과 다음과 같이 잘 동작을 하였음을 확인할 수 있었다.
덧셈 연산
뺄셈 연산
양쪽의 값이 같을 때 뺄셈 연산
왼쪽(2ch)의 값이 더 큰 경우 오른쪽(1ch)의 값이 더 큰 경우 뺄셈 연산
프로젝트를 수행하며 ARM H/W에서 모듈과 버스의 관계에 대한 이해가 향상되었으며, 각 모듈에 대한 사용법을 확실히 익힐 수 있었다.
또한, "memory mapped I/O"를 이해해 외부 장치 컨트롤에 대한 이해가 높아졌다.