https://insoobaik.tistory.com/717
https://insoobaik.tistory.com/718
위 두 가지 내용을 바탕으로 32비트 RISC CPU의 RTL 설계를 진행할 예정이다.
첫번째 사진을 통해 Pipeline을 구성하기 위해 4단계 위치 각각에 FF이 필요하다.
두번째 사진을 통해 (1)Instruction Memory, (2)Control, (3)Registers, (4)ALU, (5)ALU Control, (6)Data Memory 총 6개의 Moudle을 구성할 것이다.
Control의 경우 RegDst, Branch, MemRead, MemToReg, ALUOp, MemWrite, ALUSrc, RegWrite 7개의 Flag와 ALUOp 1개의 Opcode의 값을 결정하게 된다.
Instruction
32bit RISC CPU의 32bit Instruction의 각 Bit는 위와 같이 구분할 것이다.
Cotrol을 통해 전달되는 Opcode에 따라 Output Flag를 위와 같이 설정할 것이다.
- R-Format : 6'h00
add $t0, $t1, $t2
(funct에 의해 add, sub, and가 정해진다.) $t1과 $t2의 값을 더하여 결과를 레지스터 $t0에 저장한다.
- LW : 6'h24
lw $t0, 4($t1)
$t1 + 4의 메모리 주소에서 데이터를 읽어 레지스터 $t0에 저장한다.
- SW : 6'h2B
sw $t0, 8($t1)
레지스터 $t0의 데이터를 메모리 주소 $t1 + 8에 저장한다.
- BEQ : 6'h04
beq $t0, $t1, target
$t0와 $t1의 값이 같으면, PC를 target 주소로 분기한다.
- addi : 6'h08
addi $t0, $t1, 10
$t1의 값에 즉시 값 10을 더하여 결과를 레지스터 $t0에 저장한다.
- andi : 6'h0C
andi $t0, $t1, 15
$t1의 값과 즉시 값 15를 AND 연산하여 결과를 레지스터 $t0에 저장한다.
각 Flag별 의미
- RegDst : R, I-Type 결정 R-Type의 1일 경우 rd 레지스터에, I-Type의 0일 경우 rt에 레지스터에 결과값이 저장된다.
- ALUSrc : ALU에서 계산될 src2 값의 출처를 정하기 위해 사용된다. (1일 경우 Sign-extend 이후 32bit OR 2일 경우Registers의 Read Data 2 중 하나)
- MemToReg : Registers의 Write data 값의 출처를 정하기 위해 사용된다. (0일 경우 ALU result OR 1일 경우 Write Back)
- RegWrite : 1일 경우 Registers의 내부 메모리에 Data 저장, 0일 경우 저장 x
- MemRead : Opcode가 lw일 때 1로 사용되며, Data memory에서 데이터를 읽어온다.
- MemWrite : Opcode가 sw일 때 1로 사용되며, Data memory에 값을 입력한다.
- Branch : ALU에서 Branch를 위해 사용되는 신호이며, ALU의 Zero output과 함께 & 연산을 통해 Branch 수행을 결정한다. (ALU 연산 결과가 0인 경우 zero output이 1로 나오고, Branch 신호도 1일 경우 Branch를 수행하게 된다.)
- ALUOp [1:0] : Rtype, lw, sw, beq, addi, andi를 구분하기 위한 2bit register
위와 같이 6개의 Opcode를 설정할 것이다.
Control Module 설계
Instruction으로 들어온 32bit 중 상위 6bit는 위와 같이 타입에 따라 구분하고 있다.
R-Format : 6'h00
LW : 6'h23
SW : 6'h2B
BEQ : 6'h04
+
addi : 6'h08
andi : 6'h0C
* R-Type의 경우 위에서 반복적으로 설명하였지만 rs, rt, rd 각각의 레지스터가 funct 값에 따라 연산하게 된다.
I-Type의 경우 하위 16bit가 직접적으로 연산을 수행하기 위한 값에 해당하면 최상위 1비트가 부호 비트이며, 이후 Sign-Extend에 의해 32bit로 확장된다.
위 표와 위에서 제시한 Flag를 조합하여 case문을 작성
핵심 Code
...
case(opcode)
R_Type:begin
ALUOp = 2'b10;
RegDst = 1'b1;
RegWrite = 1'b1;
end
lw:begin
ALUOp = 2'b00;
RegWrite = 1'b1;
// ALUSrc는 I-Type에서 주로 사용된다.
ALUSrc = 1'b1;
MemRead = 1'b1;
MemToReg = 1'b1;
end
sw:begin
ALUOp = 2'b00;
ALUSrc = 1'b1;
MemWrite = 1'b1;
end
beq:begin
ALUOp = 2'b01;
Branch = 1'b1;
end
addi, andi:begin
ALUOp = 2'b11;
RegWrite = 1'b1;
ALUSrc = 1'b1;
end
endcase
...
ALUContorl Module 설계
ALU Module을 통한 연산을 위해 ALUControl Module에서 Control Module로부터 전달받은 ALUOp 값과 Instruction 32bit 중 opcode와 funct 값을 통해 ALUCtrl 값을 ALU Module에 전달하여 특정 연산을 진행할 수 있도록 한다.
Control Module은 아래 ALUOp를 Output으로 전달한다.
ALUOp
10 | R_Type |
00 | lw, sw |
01 | beq |
11 | addi, andi |
위와 같이 값을 Output로 전달하기로 정의하였다.
ALUControl Module은 아래 ALUCtrl을 Output으로 전달한다.
ALUCtrl
00 | add |
01 | sub |
10 | and |
ALUCtrl의 경우 위와 같이 연산을 하도록 구현할 것이다.
* lw, sw는 Load, Store을 하기 위해서는 레지스터에 대한 add가 필요하고, Branch의 경우 레지스터의 값이 일치하는지 판별하기 위해 sub를 사용한다.
핵심 Code
always @ * begin
ALUCtrl = 0;
case(ALUOp)
2'b10:case(funct)
add_r : ALUCtrl = 4'b00; //add
sub_r : ALUCtrl = 4'b01; //sub
and_r : ALUCtrl = 4'b10; //and
endcase
2'b11:case(opcode)
add_i : ALUCtrl = 4'b00; //add
and_i : ALUCtrl = 4'b10; //and
endcase
2'b00 : ALUCtrl = 4'b00; //lw, sw는 Add Operation이 필요
2'b01 : ALUCtrl = 4'b01; //Branch는 Sub Operation이 필요
endcase
end
ALU Module 설계
ALU Module은 연산을 수행하기 위한 Module이다. Control, Registers, ALUControl로 부터 전달받은 레지스터 주소 값, Operation 등을 조합하여 연산을 수행한다.
zero Pin의 경우 Branch 명령을 수행하기 위해 두개의 레지스터 주소 값의 차이를 확인하여 Branch를 수행할지에 대한 Flag 신호에 해당한다.
ALUCtrl
00 | add |
01 | sub |
10 | and |
ALUControl Module에서 전달한 위 값을 그대로 실행한 뒤 result Pin을 통해 결과를 Data Memory에 전달하거나 Registers Module의 Write Data로 전달하게 된다.
핵심 Code
assign zero = result == 0;
마지막에 위와 같이 연산 결과 result가 0일 경우 sub 연산에 의해 두 레지스터의 주소가 동일한 경우에 해당하기 때문에 (물론 아예 동작하기 않은 상태에서도 result가 0일 수 있다.) zero Flag를 1로 전달한다.
DataMemory Module 설계
DataMemory의 경우 실제 Data를 포함하고 있는 Moduled에 해당한다. 전달 받은 레지스터의 주소값을 받아 해당 주소에 lw Load 값을 불러오거나, sw Store 값을 저장하기 위한 공간이다.
DataMemory Module의 경우 Data Memory를 가지고 있고 이는 순차 논리 회로로 이루어져 있기 때문에 Clock에 따라 동작한다.
clk, rstn 두개의 Port는 Clock에 필요하며, MemRead, MemWrite는 lw, sw 선택에 따라 결정된다.
그 외 Address, WriteData, ReadData 또한 Read, Write에 따라 해당 레지스터 주고에 접근하여 Data를 Read하거나 Write하게 된다.
WriteData의 경우 sw에 의해 레지스터 주소에 전달되 Data를 내부 Memory에 저장한다.
ReadData의 경우 lw에 의해 레지스터 주소에서 값을 읽을 때 Data가 입력되며, 그 외에 경우에는 x(unknown)을 전달한다.
핵심 Code
always @(posedge clk, negedge rstn)
if (!rstn) for (int i=0;i<64;i++) mem[i] <= 0;
else if (MemWrite) mem[Address[5:0]] <= WriteData;
assign ReadData = MemRead? mem[Address[5:0]]: 'bX;
Registers Module 설계
Registers Module은 내부에 값을 저장하는 순차 논리 회로를 포함하기 때문에 Clock과 Reset을 사용한다.
WriteReg는 Mux를 통해 input 값이 결정되는데, R-Type일 경우 rd값이 LW, SW일 경우 rt값이 들어간다. (R-Type은 목적지 레지스터가 별도로 있지만 LW, SW는 목적지 레지스터가 타겟 레지스터와 일치하기 때문이다.)
두번째로 RegWrite Flag를 사용하여 레지스터 저장 여부를 결정한다. R-Type, LW, SW, addi, andi의 경우 결과 값을 레지스터에 결과 값을 다시 저장하지만, SW의 경우 레지스터에 값을 저장하는 것이 아니라 레지스터의 값을 Memory에 저장하게 된다.
핵심 Code
always @(posedge clk, negedge rstn) begin
if(!rstn) begin
for(int i = 0; i<32; i++) begin
array[i] <= 0;
end
end
else if(RegWrite) begin
array[WriteReg] <= WriteData;
end
end
assign ReadData1 = array[rs];
assign ReadData2 = array[rt];
InstMem Module 설계
InstMem Module은 32bit 크기의 Instruction들을 저장하고 있는 메모리에 해당한다. Memory에 대한 Address가 들어오면 해당 Memory의 Data를 output으로 전달한다.
CPU는 주소 1당 1byte를 가리키고, 명령어는 4byte 크기를 가진다. 32bit 안에 opcode, rs, rt, rd, funct, imm와 같은 다양한 값을 포함하고 있으며, 임의로 해당 주소를 가지는 Memory Module을 생성한다.
Pipeline을 위한 D/FF Module 설계
이전에 설명한 Pipeline 구조를 만들기 위해서는 단계 사이마다 값을 저장할 수 있는 D/FF가 필요하다.
* D/FF 크기의 경우 상황에 따라 다를 수 있기 때문에 Parameter로 크기를 전달받아서 사용한다.
핵심 Code
module D_FF #(
parameter WIDTH=32
)(
input clk, rstn,
input en,
input [WIDTH-1:0] D,
output reg [WIDTH-1:0] Q
);
always @(posedge clk, negedge rstn)
if(!rstn) Q <= 0;
else if (en) Q <= D;
endmodule
cpu (TOP)Module 설계
CPU는 기본적으로 Clock만 전달되면 여러 Module을 통해 동작하게 된다.
cpu(top) Module에서는 CPU를 동작 시키기 위한 Module의 각 input에 대한 output을 연결해주는 역할을 한다.
cpu(top) Module은 CPU를 동작 시키기 위한 Module을 통합 시켜 동작시키고, Pipeline을 위한 D/FF Module을 통해 각 Module의 input, output 값을 전달한다.
'Semiconductor > RTL, Simulation' 카테고리의 다른 글
컴퓨터 구조 및 CPU 동작 원리 (4) - 32 Bit RISC CPU(Pipeline) Simulation 분석 (0) | 2024.10.17 |
---|---|
RTL - I2C (Master / Slave - Simulation, Code) (0) | 2024.08.17 |
RTL - Two Port SRAM / Dual Port SRAM 구조 및 설계 (0) | 2024.08.07 |
RTL - CACHE(Two Port SRAM) 동작 과정 및 설명 (0) | 2024.08.07 |
RTL - SRAM (`ifdef를 이용한 FPGA, ASIC 코드 분리) (0) | 2024.08.06 |