프로젝트/전자공학종합설계

[전자공학종합설계] 3. 16bit MIPS single-cycle CPU design

아이스얼그레이 2022. 4. 4. 23:26

오랜만에 게시글을 작성하네요.

RTL 소스코드는 깃허브에 올려두었습니다. 블로그에 올리기에는 코드가 길어서 링크로 남깁니다.
https://github.com/Kim-Jiwan/16bit_MIPS_CPU

 

GitHub - Kim-Jiwan/16bit_MIPS_CPU

Contribute to Kim-Jiwan/16bit_MIPS_CPU development by creating an account on GitHub.

github.com

 

3월 22일 화요일 전자공학종합설계(이하 전프) 미팅 때 2주 동안 16bit MIPS를 RTL로 짜오라는 미션을 받았습니다. 사실 이 과제는 3학년 1학기 논리회로설계 과목의 final assignment입니다. 앞으로 1년 동안 프로세서 설계를 해나갈 건데 가장 기본인 16bit MIPS는 짜 봐야 하지 않겠나 하는 취지에서 주신 과제입니다.

 

16bit MIPS full datapath

위 사진이 16bit MIPS CPU의 full datapath입니다. CPU의 동작을 간단히 설명하면 instruction memory에서 명령어를 불러오고(fetch), register와 전후에서 명령어를 해독합니다.(decode) 명령어(instruction)에는 opcode라 불리는 연산 코드와 register 주소정보가 담겨있습니다. 그리고 ALU에서 data의 값이나 address를 연산합니다.(excute)

예를 들어, 1100010000000010 이러한 명령어를 고려해봅시다. 이런 binary code는 사람이 한 눈에 알아보기 어려우므로, 의미를 가지는 단위별로 끊어보겠습니다.

110_001_000_000_0010 이런 단위로 명령어가 나눠집니다. 하나하나 살펴봅시다.

110은 opcode 입니다. 3자리 수의 opcode에 따라 control signal이 결정됩니다. 110은 beq instruction을 위한 opcode 입니다. opcode가 110이면 beq를 수행하기 위한 연산을 ALU에서 수행합니다.

001, 000, 000은 각각 rs(source register), rt(tager register), rd(destination register)의 번지수입니다. 16bit MIPS CPU는 8개의 register를 가지는데, 이 8개를 표현하려면 3bit(2^3)가 필요합니다. 그래서 rs, rt, rd가 각각 3bit씩 차지하는 것입니다.

0010은 funct 이라는 code입니다. opcode에 따라 control signal unit이 ALUop라는 ctrl signal을 만들어 냅니다. 이때 2bit짜리 ALUop 만으로 표현할 수 없는 연산이 있는데, 이때는 funct code를 통해 구분하게 됩니다. 이해를 돕기 위해 ALU ctrl unit의 RTL을 첨부하였습니다.

module ALU_ctrl_unit (
    input   	    [3:0]   funct,
    input   	    [1:0]   ALU_op,

    output  reg     [2:0]   ALU_ctrl
);
    always @(funct or ALU_op) begin
        case(ALU_op)
            2'b00 : begin
				case (funct)
					4'b0000 : ALU_ctrl = 3'b000; // add
					4'b0001 : ALU_ctrl = 3'b001; // sub
					4'b0010 : ALU_ctrl = 3'b010; // and
					4'b0011 : ALU_ctrl = 3'b011; // or
					4'b0100 : ALU_ctrl = 3'b100; // slt shift left
					4'b0101 : ALU_ctrl = 3'b101; // mul
					default : ALU_ctrl = 3'bxxx; // default
				endcase
            end
            2'b01 : ALU_ctrl = 3'b001; // sub -> for zero check
            2'b10 : ALU_ctrl = 3'b100; // slt shift left for slti
            2'b11 : ALU_ctrl = 3'b000; // add for addi, lw, sw
        endcase
    end
endmodule


다만, beq instruction의 경우 연산결과가 0인지 확인하기만 하면 되므로 function code가 따로 필요 없습니다. 대신 beq instruction은 rd와 funct을 합친 7bit를 immediate value를 표현하는 데 사용합니다. 이 값은 branch 할 다음 PC를 계산하는 데 사용됩니다. 예를 들어 위 예시에서 immediate value가 2이므로 다음 Program counter는 PC_p2 + 2이 됩니다.

그래서 이진수 1100010000000010을 사람이 이해할 수 있는 말로 풀어보면 다음과 같습니다.

1. 110에 해당하는 ctrl signal을 생성해라.
2. 001(1), 000(0) 번지에 해당하는 register의 data를 output으로 내보내라.
3. zero가 1이 되어서 branch를 한다면 현재의 PC_p2 값에 2를 더해서 PC_next에 저장해라.

이해가 되시나요? Computer architecture에서 이러한 체계를 ISA(Instruction Set Architecture)라고 부릅니다. MIPS는 간단한 ISA중 하나입니다.


16bit MIPS CPU을 설계하는데 필요한 ISA와 computer architecture 내용은 과제와 함께 주셨습니다. 첫 주에는 감이 좀 안 잡혀서 금요일이나 되어서 설계를 시작했습니다. 처음에는 간단한 combination logic circuit을 먼저 설계했습니다.

위 사진에서 instruction memory, data memory, PC(program counter), register를 제외하고는 모두 combination logic circuit로 작성했습니다. 즉, 방금 서술한 4가지 module만 clk, rst signal에 의해 통제되고, 나머지는 input, output port에 의해 동작합니다.

그래서 위 4가지 module을 제외하고는 ISA를 잘 이해하고, 시간만 때려박으면 충분히 작성할 수 있는 RTL입니다. memory와 register module은 어떻게 동작하는지 감도 잘 안 오기도 하고 clk timing을 고려해줘야 하기 때문에 설계하는데 꾀나 애를 먹었습니다.

그리고 full datapath에서 보이는 모든 data가 이동하는 경로를 wire로 선언해줘야 합니다. 이 과정이 좀 노가다같은 느낌이 있는데, 나중에 debugging 할 때 시간을 뺏기지 않으려면 wire 변수명을 명확하게 설정해주어야 합니다. 이건 저도 알고 싶지 않았습니다... : -)

저 datapath를 처음 보면 이거 답 없는데? 하는 생각이 들겠지만 짜고 나면 생각보다 그 동작이 간단하다는 것을 알 수 있습니다.

simulation 결과와 Synthesis 결과를 보며 마무리하겠습니다.

PC를 보면 중간중간 branch or jump를 수행하는 것을 알 수 있습니다.
Synopsis VCS를 통해 파형을 확인
Design compiler를 통해 합성