[Mission]
📌 Project Summary
Project name
: FPGA환경에서 Verilog 언어를 통한 4bit CPU 구현 프로젝트
Mission
i) 실제 CPU와 같이 Fetch-Decode-Execute- Writeback 사이클을 반복하여 연산하도록 구현
ii) 사칙연산, 논리연산
iii) 시프트연산, 비교연산
iv) 테스트벤치 작성을 통한 시뮬레이션으로 기능 검증
프로젝트 기간 및 팀원
기간 : 4 Days
팀원 : 1명
[Result]
📌 Operation
📌 Function & Block Diagram
✅ 연산 파트
: 사칙연산, 논리연산, 시프트연산, 비교연산을 수행합니다.
- ALU : 각 연산의 명령어(op_sub, op_and 등)와 ACC와 BREG에 저장된 데이터를
입력받아 연산을 수행합니다. 또한 Zero Flag, Sign Flag, Carry Flag의
Flag를 통해 비교연산과 곱셈, 뺼셈, 나눗셈 연산 등을 수행할 수 있도록
설계하였습니다. 최종 연산 결과는 ACC로 전송된 후 데이터버스를 지나
출력됩니다.
- ACC : 연산에 필요한 데이터 4bit, 연산 결과를 저장할 4bit 총 8bit가 필요하므로
4bit ACC 두 개를 이어 붙여 8bit ACC를 구현하였습니다.
데이터버스로부터 연산에 사용할 첫 번째 데이터 및 명령어를 입력받고
좌/우 시프트 기능을 통해 시프트연산과 곱셈 나눗셈이 가능하도록 설계했습니다.
- BREG : 연산에 사용할 두 번째 데이터를 저장하는 레지스터이며, breg_inen의
enable 신호가 인가될 때만 데이터 버스로부터 데이터를 입력받습니다.
✅ 제어 파트
: 명령어의 주소 포인팅을 통해 ROM으로부터 명령어를 불러오며,
Hexa code 형태로 저장된 명령어를 디코딩하고 Control Signal로
변환합니다. 해당 신호들을 통해 CPU를 제어할 수 있습니다.
- PC : 프로그램 카운터는 이전 값을 불러오는 기능이 포함된 Nbit Half Adder를
인스턴스하여 구현했습니다. PC 모듈은 사용할 명령어의 주소까지 카운팅하고
해당 명령어의 주소를 데이터버스를 통해 MAR로 전달합니다.
- MAR : 메모리 주소 레지스터인 MAR은 PC를 통해 지정된 명령어의 주소를 저장합니다.
- ROM : 각종 명령어들이 Hexa code 형태로 저장되어 있으며, MAR로부터 사용할
명령어의 주소를 입력받아 해당되는 명령어를 MDR로 출력합니다.
- MDR : ROM으로부터 명령어를 전달받아 데이터버스를 통해 IR로 전달합니다.
- IR & Control Block : 사용할 명령어가 임시로 저장되는 레지스터이며,
이후 Control Block에서 디코딩 및 Control Signal로의 변환을 거친 후
해당 신호를 통해 CPU를 제어합니다.
✅ 레지스터 파트
: 시스템의 제어, 출력, 임시 데이터 등의 저장에 사용되는
레지스터들로 구성된 파트입니다.
- TMPREG : 중간 계산 결과 또는 임시 데이터를 저장하는 레지스터
- CREG : 시스템의 제어 및 설정을 담당하는 레지스터
- RREG : PC나 스택포인터, 범용 데이터 저장의 역할을 하는 레지스터
- OUTREG : 데이터 출력과 관련된 작업을 수행하는 데 사용되는 레지스터
✅ 데이터 버스
: 모든 데이터는 데이터 버스를 통해 전송되며,
데이터 충돌을 방지하기 위해 Enable 신호를 받은 데이터만
데이터 버스를 통해 전송될 수 있도록 설계했습니다.
✅ CPU 연산 과정
: CPU에서는 프로그램 실행을 위해 ROM에서 명령어를 순차적으로
인출 – 해독 – 실행하는 명령어 사이클을 반복하며, 마지막에 쓰기가 추가된
총 4단계로 CPU의 연산이 이루어집니다.
📌 Improve
<Testbench & Simulation>
module tb_block_alu_acc();
reg clk, reset_p, acc_high_reset_p;
reg rd_en, acc_in_select;
reg [1:0] acc_high_select, acc_low_select; // acc의 mode를 선택
reg op_add, op_sub, op_mul, op_div, op_and;
reg [3:0] bus_data, bus_reg_data;
wire sign_flag, zero_flag;
wire [7:0] acc_data; // 최종적인 연산의 결과 / 얘는 BUS로만 간다.(상위 4bit, 하위 4bit)
block_alu_acc DUT(
clk, reset_p, acc_high_reset_p,
rd_en, acc_in_select,
acc_high_select, acc_low_select, // acc의 mode를 선택
op_add, op_sub, op_mul, op_div, op_and,
bus_data, bus_reg_data,
sign_flag, zero_flag,
acc_data // 최종적인 연산의 결과 / 얘는 BUS로만 간다.(상위 4bit, 하위 4bit)
);
initial begin
clk = 0;
reset_p = 1;
rd_en = 1;
acc_high_reset_p = 0;
acc_in_select = 0;
bus_data = 4'b0111; bus_reg_data = 4'b0010;
acc_high_select = 0; acc_low_select = 0;
op_add = 0; op_sub = 0; op_mul = 0; op_div = 0; op_and = 0;
end
always #4 clk = ~clk;
// 나눗셈
initial begin
#8;
reset_p = 0; #8;
// BUS의 data를 high에 넣고 low에서 로드한 뒤, high 클리어
acc_in_select = 1; // BREG 값 로드
acc_high_select = 2'b11; #8; // high에 로드.
acc_in_select = 0; acc_high_select = 2'b00; // 값 받았으면, 꺼주고
acc_low_select = 2'b11; #8; // high 값을 low로 보내주고
acc_low_select = 2'b00; // 값 받았으면 꺼준다.
acc_high_reset_p = 1; #8; // 상위 4bit만 리셋하고 한 클락 보냄
acc_high_reset_p = 0;
// 여기까지 ACC : 0000 0111, BREG : 0010
// 나눗셈 시작
// 빼기 + 좌시프트의 연속
// 빼기는 acc_high_data - bus_reg_data 이 것의 뺄셈이 가능한 지 불가능한 지 여부에 따라 결정한다.
// 뺀 결과가 음수가 나온다는 것이 빼기가 불가능하다는 의미.
acc_low_select = 2'b10; acc_high_select = 2'b10; #8;// 좌시프트 먼저 하고
// 좌시프트를 먼저 하는 이유 : 첫 자리는 어짜피 빼기해도 음수가 나오므로 데이터가 유지된다. 따라서 생략 가능
// 여기까지 ACC : 0000 1110, BREG : 0010
acc_low_select = 2'b00; // 값 받았으니 꺼주고
op_div = 1; #8; // 0000 - 0010 하면, 뺼셈이 불가능하므로 cout = 0이고, 데이터 유지
// 뺼 수 있으면, carry_flag = cout = 1 / 뺼 수 없으면, carry가 발생하고, cout = 0
// 여기까지 ACC : 0000 1110, BREG : 0010
op_div = 0; // 시프트하기전에 op_div 꺼주고
acc_low_select = 2'b10; acc_high_select = 2'b10; #8;// 좌시프트 먼저 하고
// 여기까지 ACC : 0001 1100, BREG : 0010
acc_low_select = 2'b00; // 값 받았으니 꺼주고
op_div = 1; #8; // 0001 - 0010 하면, 뺼셈이 불가능하므로 cout = 0이고, 데이터 유지
// 여기까지 ACC : 0001 1100, BREG : 0010
op_div = 0; // 시프트하기전에 op_div 꺼주고
acc_low_select = 2'b10; acc_high_select = 2'b10; #8;// 좌시프트 먼저 하고
// 여기까지 ACC : 0011 1000, BREG : 0010
acc_low_select = 2'b00; // 값 받았으니 꺼주고
op_div = 1; #8; // 0011 - 0010 하면, 뺼셈이 가능하므로 cout = 1이므로 빼고 그 값을 로드
// 여기까지 ACC : 0001 1000, BREG : 0010
op_div = 0; // 시프트하기전에 op_div 꺼주고
acc_low_select = 2'b10; acc_high_select = 2'b10; #8;// 좌시프트 먼저 하고
// 여기까지 ACC : 0011 0001, BREG : 0010
acc_low_select = 2'b00; // 값 받았으니 꺼주고
op_div = 1; #8; // 0011 - 0010 하면, 뺼셈이 가능하므로 cout = 1이므로 빼고 그 값을 로드
// 여기까지 ACC : 0001 0001, BREG : 0010
op_div = 0; // 시프트하기전에 op_div 꺼주고
acc_low_select = 2'b10; acc_high_select = 2'b00; #8; // acc_low만 좌시프트 한 번 해준다.
// 여기까지 ACC : 0001 0011, BREG : 0010
acc_low_select = 2'b00; #8;
$stop;
end
endmodule
✅ 개선 전
: 이제까지는 대부분 직관적으로 결과를 볼 수 있는 Peripheral device를 사용하는 모듈의 설계를 진행했기 때문에
따로 Testbench를 만들어서 시뮬레이션하는 경우는 드물었다.
✅ 개선 후
: 이번 CPU 구현 프로젝트는 기능과 시퀀스가 복잡할 뿐더러 직관적으로 기능이 구현되는 것을 볼 수 없어,
각 모듈별로 Testbench를 작성하여 시뮬레이션을 진행했고, 이를 통해 오류를 사전에 검출하여
쉽게 디버깅할 수 있었다.
📌 Wrap - up
<꼼꼼한 설계 습관 - Simulation>
각 모듈마다 시뮬레이션을 진행해 발생할 오류를 사전에 검출해내는 과정의 중요성을
다시금 깨닫게 된 프로젝트였다.
실무에서 맡게 될 프로젝트는 지금과는 비교할 수 없을 정도로 복잡하고
큰 규모를 가지고 있으며, '잘 되겠지' 라는 안일한 마음으로 시뮬레이션을 소홀히 한다면
그 것이 얼마나 큰 나비효과를 불러올 지 모를 일이다.
그런 의미에서 이번 프로젝트는 좀 더 꼼꼼한 설계 습관을 체득할 수 있는 좋은 기회가 되었다.
.<Calculator>
다양한 연산이 가능한 CPU를 활용할 방안을 모색하던 중,
4 x 4 Keypad와 FND를 사용해 계산기를 만들면 좋겠다는 생각이 들었다.
CPU 모듈과 Keypad, FND 모듈을 각각 인스턴스하여 입출력 Wiring만 해준다면,
충분히 구현할 수 있을 것이므로 차기 Small Project로 진행할 예정입니다.
[Full Code]
프로젝트 관련 코드는 아래 링크 참조
Fin
'# Programming > - FPGA Project' 카테고리의 다른 글
[FPGA Project] Multi-function Fan(UART-Bluetooth, PWM, FSM, Timer, WNS/TNS) (0) | 2023.10.02 |
---|---|
[FPGA Project] Multi Function Clock(다기능 시계 구현 프로젝트) (0) | 2023.10.02 |