Control Unit을 설계해 봅시다.
Control Unit은 CPU Architecture의 핵심이라고 생각합니다.
우선 Control Unit의 하위 블록들을 좀 설정해 봅시다.
RISC V의 경우 Main Decoder와 ALU Decoder가 필요합니다.
Control Unit 블록 다이어그램

Main Decoder
Main Decoder는 OP Code를 Decoding 합니다.
자연스럽게 input은 OP Code 7 bits를 받아옵니다.
output은 입력된 OP Code를 Decoding 하여 여러 신호를 출력합니다.
우리가 만들 프로세서의 OP Code를 살펴봅시다.

ISA를 보면 뭐가 많습니다... 하위 7bit를 눈 빠지게 쳐다보면 결국 아래와 같습니다.
OP Code 종류
- R-Type
- OP Code: 0110011
- ISA: ADD, SUB, SLL, SLT, XOR, SRL, SRA, OR, AND
- I-Type (ALU)
- OP Code: 0010011
- ISA: ADDI, SLTI, XORI, ORI, ANDI 등
- I-Type (Load)
- OP Code: 0000011
- ISA: LB, LH, LW, LBU, LHU
- S-Type
- OP Code: 0100011
- ISA: SB, SH, SW
- B-Type
- OP Code: 1100011
- ISA: BEQ, BNE, BLT, BGE, BLTU, BGEU
- U-Type (LUI)
- OP Code: 0110111
- ISA: LUI
- U-Type (AUIPC)
- OP Code: 0010111
- ISA: AUIPC
- J-Type (JAL)
- OP Code: 1101111
- ISA: JAL
- J-Type (JALR)
- OP Code: 1100111
- ISA: JALR
위 9개의 OP Code를 Decoding 하여 아래의 신호를 조절합니다.
- ALU의 두 번째 입력 결정
- Register Data(0) vs ImmGen의 상수(1)
- 저장할 값이 어디서 왔는지 결정
- ALU의 결과(0) vs 메모리 리딩값(1)
- 쓰기 동작 여부 결정
- 안 쓴다(0) vs 쓴다(1)
- ALU Decoder에게 알려주는 control signal (2bit)
총 7 bits이며, case 문으로 간단히 구성하면 알아서 Decoder를 만들어주겠지만...
공부하시는 분들은 Main Decoder의 내부 회로 logic gate를 살펴보는 것도 좋습니다.
직접 그리기는 좀 어려우니까 Vivado Implementation 이후 schematic을 까보면 좋겠네요.
이러한 logic gate를 이해하기 위해선 앞선 OP Code가 어떻게 구성되는지와 "중복되는 값들이 있는지" 여부로 결정됩니다.
Main Decoder 설계

앞서 말한 대로 부분 부분 쪼개줍니다.
gate-level modeling 혹은 Dataflow 모델링을 이용하면 굉장히 오래 걸리겠으나...
우리는 Behavioral Modeling을 통해 쉽게 설계할 수 있습니다. Verilog의 큰 장점이 되겠네요. (Tool 의존도가 크지만 ㅎㅎ)
case문을 이용한 코드는 아래와 같습니다.
Main Decoder 코드
`timescale 1ns / 1ps
module main_decoder (
input wire [6:0] op_i , // Opcode input
output reg reg_write_o , // Register write enable
output reg [2:0] imm_src_o , // Immediate source select
output reg alu_src_o , // ALU source B select
output reg mem_write_o , // Memory write enable
output reg [1:0] result_src_o , // Register writeback select
output reg branch_o , // Branch instruction flag
output reg jump_o , // Jump instruction flag
output reg [1:0] alu_op_o , // ALU operation hint
output reg alu_asrc_o // ALU source A select (AUIPC)
);
always @(*) begin
// Default values
reg_write_o = 1'b0 ; imm_src_o = 3'b000 ; alu_src_o = 1'b0 ; mem_write_o = 1'b0 ;
result_src_o = 2'b00 ; branch_o = 1'b0 ; jump_o = 1'b0 ; alu_op_o = 2'b00 ;
alu_asrc_o = 1'b0 ;
case (op_i)
7'b0110011: begin // R-type
reg_write_o = 1'b1 ; alu_op_o = 2'b10 ;
end
7'b0010011: begin // I-type ALU
reg_write_o = 1'b1 ; imm_src_o = 3'b000 ; alu_src_o = 1'b1 ; alu_op_o = 2'b10 ;
end
7'b0000011: begin // Load
reg_write_o = 1'b1 ; imm_src_o = 3'b000 ; alu_src_o = 1'b1 ; result_src_o = 2'b01 ;
end
7'b0100011: begin // Store
imm_src_o = 3'b001 ; alu_src_o = 1'b1 ; mem_write_o = 1'b1 ;
end
7'b1100011: begin // Branch
imm_src_o = 3'b010 ; branch_o = 1'b1 ; alu_op_o = 2'b01 ;
end
7'b1101111: begin // JAL
reg_write_o = 1'b1 ; imm_src_o = 3'b100 ; jump_o = 1'b1 ; result_src_o = 2'b10 ;
end
7'b1100111: begin // JALR
reg_write_o = 1'b1 ; imm_src_o = 3'b000 ; alu_src_o = 1'b1 ; jump_o = 1'b1 ;
result_src_o = 2'b10 ;
end
7'b0110111: begin // LUI
reg_write_o = 1'b1 ; imm_src_o = 3'b011 ; alu_src_o = 1'b1 ; alu_op_o = 2'b11 ;
end
7'b0010111: begin // AUIPC
reg_write_o = 1'b1 ; imm_src_o = 3'b011 ; alu_src_o = 1'b1 ; alu_asrc_o = 1'b1 ;
end
default: ;
endcase
end
endmodule
Test Bench
TB 돌려야겠죠? 제미나이한테 또또 부탁합니다.
`timescale 1ns / 1ps
module tb_main_decoder();
// ---------------------------------------------------------
// 1. Signal Declaration
// ---------------------------------------------------------
reg [6:0] op_r ; // Stimulus reg for Opcode
wire reg_write_w ; // Output wire for RegWrite
wire [2:0] imm_src_w ; // Output wire for ImmSrc
wire alu_src_w ; // Output wire for ALUSrc
wire mem_write_w ; // Output wire for MemWrite
wire [1:0] result_src_w ; // Output wire for ResultSrc
wire branch_w ; // Output wire for Branch
wire jump_w ; // Output wire for Jump
wire [1:0] alu_op_w ; // Output wire for ALUOp
wire alu_asrc_w ; // Output wire for ALU_ASrc
// ---------------------------------------------------------
// 2. DUT Instantiation
// ---------------------------------------------------------
main_decoder u_main_decoder (
.op_i (op_r ),
.reg_write_o (reg_write_w ),
.imm_src_o (imm_src_w ),
.alu_src_o (alu_src_w ),
.mem_write_o (mem_write_w ),
.result_src_o (result_src_w ),
.branch_o (branch_w ),
.jump_o (jump_w ),
.alu_op_o (alu_op_w ),
.alu_asrc_o (alu_asrc_w )
);
// ---------------------------------------------------------
// 3. Test Procedure
// ---------------------------------------------------------
initial begin
$display("------------------------------------------------------------");
$display("Opcode | RW | ImmS | ASrc | MW | ResS | Br | Jp | ALUOp | AASrc");
$display("------------------------------------------------------------");
// R-type
op_r = 7'b0110011; #10;
$display("R-type | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// I-ALU
op_r = 7'b0010011; #10;
$display("I-ALU | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// Load
op_r = 7'b0000011; #10;
$display("Load | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// Store
op_r = 7'b0100011; #10;
$display("Store | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// Branch
op_r = 7'b1100011; #10;
$display("Branch | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// JAL
op_r = 7'b1101111; #10;
$display("JAL | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// JALR
op_r = 7'b1100111; #10;
$display("JALR | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// LUI
op_r = 7'b0110111; #10;
$display("LUI | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
// AUIPC
op_r = 7'b0010111; #10;
$display("AUIPC | %b | %b | %b | %b | %b | %b | %b | %b | %b",
reg_write_w, imm_src_w, alu_src_w, mem_write_w, result_src_w, branch_w, jump_w, alu_op_w, alu_asrc_w);
$display("------------------------------------------------------------");
$finish;
end
endmodule
결과 확인

짠~ 다음 포스트에서는 ALU Decoder를 설계하고 상위 블록에서 묶어서 Control Unit을 만들도록 하겠습니다.
별로 안 남았네요. 이제 각 부분에서 MUX 좀 끼워주고, PC Counter 설계하고, Memory 연결하면 끝날 것 같습니다.
'Project > RISC-V CPU Architecture Design' 카테고리의 다른 글
| [9] PC Unit, Memory 및 Single Cycle Processor 설계 (0) | 2026.01.06 |
|---|---|
| [8] RISC-V Control Unit 설계 (2) - ALU Decoder (0) | 2026.01.06 |
| [6] Immediate Generator 설계 (0) | 2026.01.03 |
| [5] ALU 설계 (0) | 2026.01.02 |
| [4] Single-Cycle 프로세서 Register File 설계 (0) | 2026.01.02 |