본문 바로가기
# Programming/- FPGA Project

[FPGA Project] CPU 구현 프로젝트(ALU, ACC, PC, MAR, MDR, ROM, Control Block, Data Bus)

by Graffitio 2023. 11. 8.
[FPGA Project] CPU 구현 프로젝트(ALU, ACC, PC, MAR, MDR, ROM, Control Block, Data Bus)
728x90
반응형
[Mission]

 

📌 Project Summary

 

Project name

   : FPGA환경에서 Verilog 언어를 통한 4bit CPU 구현 프로젝트

 

Mission

  i) 실제 CPU와 같이 Fetch-Decode-Execute- Writeback 사이클을 반복하여 연산하도록 구현

  ii) 사칙연산, 논리연산

  iii) 시프트연산, 비교연산

  iv) 테스트벤치 작성을 통한 시뮬레이션으로 기능 검증

 

프로젝트 기간 및 팀원

    기간 : 4 Days 

    팀원 : 1명

 


 

[Result]

 

📌 Operation

 

곱셈 연산 시뮬레이션

 

3 + 2 = 5

 


 

📌 Function & Block Diagram

 

CPU 구성도

 

✅ 연산 파트 

       : 사칙연산, 논리연산, 시프트연산, 비교연산을 수행합니다.

         - ALU : 각 연산의 명령어(op_sub, op_and )ACCBREG에 저장된 데이터를

                     입력받아 연산을 수행합니다. 또한 Zero Flag, Sign Flag, Carry Flag

                     Flag를 통해 비교연산과 곱셈, 뺼셈, 나눗셈 연산 등을 수행할 수 있도록

                     설계하였습니다. 최종 연산 결과는 ACC로 전송된 후 데이터버스를 지나

                     출력됩니다.

        - ACC : 연산에 필요한 데이터 4bit, 연산 결과를 저장할 4bit 8bit가 필요하므로

                     4bit ACC 두 개를 이어 붙여 8bit ACC를 구현하였습니다.

                     데이터버스로부터 연산에 사용할 첫 번째 데이터 및 명령어를 입력받고

                     /우 시프트 기능을 통해 시프트연산과 곱셈 나눗셈이 가능하도록 설계했습니다.

         - BREG : 연산에 사용할 두 번째 데이터를 저장하는 레지스터이며, breg_inen

                         enable 신호가 인가될 때만 데이터 버스로부터 데이터를 입력받습니다.

 

ALU blockdiagram
ACC blockdiagram

 

 

✅ 제어 파트 

      : 명령어의 주소 포인팅을 통해 ROM으로부터 명령어를 불러오며,

        Hexa code 형태로 저장된 명령어를 디코딩하고 Control Signal

        변환합니다. 해당 신호들을 통해 CPU를 제어할 수 있습니다.

        - PC : 프로그램 카운터는 이전 값을 불러오는 기능이 포함된 Nbit Half Adder

                 인스턴스하여 구현했습니다. PC 모듈은 사용할 명령어의 주소까지 카운팅하고

                 해당 명령어의 주소를 데이터버스를 통해 MAR로 전달합니다.

        - MAR : 메모리 주소 레지스터인 MARPC를 통해 지정된 명령어의 주소를 저장합니다.

        - ROM : 각종 명령어들이 Hexa code 형태로 저장되어 있으며, MAR로부터 사용할

                    명령어의 주소를 입력받아 해당되는 명령어를 MDR로 출력합니다.

        - MDR : ROM으로부터 명령어를 전달받아 데이터버스를 통해 IR로 전달합니다.

        - IR & Control Block : 사용할 명령어가 임시로 저장되는 레지스터이며,

                    이후 Control Block에서 디코딩Control Signal로의 변환을 거친 후

                    해당 신호를 통해 CPU를 제어합니다.

 

PC blockdiagram

 

 

✅ 레지스터 파트 

       : 시스템의 제어, 출력, 임시 데이터 등의 저장에 사용되는

         레지스터들로 구성된 파트입니다.

          - 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]

 

프로젝트 관련 코드는 아래 링크 참조

 

GitHub - Graffitio/Project_CPU

Contribute to Graffitio/Project_CPU development by creating an account on GitHub.

github.com

 


Fin

728x90
반응형