저번 포스팅에서 Reg File을 설계했습니다. 이번에는 ALU를 설계할게요.
ALU 블록 다이어그램

다들 한번씩은 설계해보셨죠??
Combinational Logic을 공부할때 한번씩 공부할것 같습니다.
간단하지만 굉장히 중요한 회로입니다.
ALU 이론
ALU는 입력된 두 개의 데이터를 받아서, 제어 신호가 지시하는 대로 논리 연산을 수행하고 결과를 내보냅니다.
이때 clock이 낀다면 연산 속도가 굉장히 느릴겁니다.
이미 연산이 끝났는데 clock 신호를 기다린다면, 연산 이후의 동작도 1cycle씩 계속 밀릴겁니다.
그렇기에 ALU는 Combinational Logic 덩어리로 설계합니다.
우리가 만들 RV32I 프로세서의 ALU는 다음 4가지 종류의 연산을 처리할 수 있어야합니다.
1. Arithmetic Operation: ADD, SUB
2. Logical Operation: AND, OR, XOR
3. Shift Operation: SLL(Shift Left Logical), SRL, SRA(Shift Right Arithmetic)
SRL과 SRA의 차이
SRL은 >> 연산자를 이용하여 MSB의 빈칸을 0으로 채웁니다.
SRA는 >>> 연산자를 이용하여 MSB의 빈칸을 부호 비트로 채웁니다.
4. Comparison Operation: SLT (Set Less Than), SLTU
SLT (signed): src_a < src_b 이면 1, 아니면 0 (부호 감안)
SLTU (unsigned): src_a < src_b 이면 1, 아니면 0 (부호 없이)
Operation은 총 10개네요. 자연스럽게 alu_op 신호는 4bit를 차용합니다.

ALU 설계
alu.v 코드를 작성해봅시다. always @(*), case 문법을 이용합니다.
`timescale 1ns / 1ps
module alu(
input wire [31:0] src_a ,
input wire [31:0] src_b ,
input wire [3:0] alu_op ,
output reg [31:0] alu_result ,
output wire zero_flag
);
// 1. ALU Operation Logic
always @(*) begin
case (alu_op)
// Arithmetic
4'b0000: alu_result = src_a + src_b; // ADD
4'b0001: alu_result = src_a - src_b; // SUB
// Logical
4'b0010: alu_result = src_a & src_b; // AND
4'b0011: alu_result = src_a | src_b; // OR
4'b0100: alu_result = src_a ^ src_b; // XOR
// Shift
4'b0101: alu_result = src_a << src_b[4:0]; // SLL
4'b0110: alu_result = src_a >> src_b[4:0]; // SRL
4'b0111: alu_result = $signed(src_a) >>> src_b[4:0]; // SRA
// Comparison
4'b1000: begin // SLT
if ($signed(src_a) < $signed(src_b)) begin
alu_result = 32'd1;
end
else begin
alu_result = 32'd0;
end
end
4'b0000: begin // SLTU
if (src_a < src_b) begin
alu_result = 32'd1;
end
else begin
alu_result = 32'd0;
end
end
default: alu_result = 32'b0;
endcase
end
// 2. Zero Flag
assign zero_flag = (alu_result == 32'b0) ? 1'b1 : 1'b0;
endmodule
Shift에 대해 좀 더 설명하겠습니다.
왜 src_b[4:0]을 하는지에 대해...
기본적으로 프로세서의 Shift 연산자는 Shifting할 값과 그 양을 인풋으로 받습니다.
짱구를 굴려봅시다.
src_b는 shift 양을 값으로 받는데, 데이터 크기가 32bit 이므로 그 양을 결정하는 bit는 5bit가 됩니다.
11111이면 src_a의 모든 데이터 bit를 shift하겠네요. (LSB가 MSB가 됨)

ALU는 TB도 해봅시다.
tb 코드는 제미나이를 이용할게요.
ALU TestBench
`timescale 1ns / 1ps
module tb_alu;
// ========================================================================
// 1. Signal Declaration
// ========================================================================
reg [31:0] src_a_r ; // Stimulus reg A
reg [31:0] src_b_r ; // Stimulus reg B
reg [3:0] alu_op_r ; // Stimulus reg Op
wire [31:0] alu_result_w ; // Output wire Result
wire zero_flag_w ; // Output wire Zero
integer err_cnt = 0 ; // Error Counter
// ========================================================================
// 2. Instantiate the DUT
// ========================================================================
alu u_alu (
.src_a_i (src_a_r ),
.src_b_i (src_b_r ),
.alu_op_i (alu_op_r ),
.alu_result_o (alu_result_w),
.zero_flag_o (zero_flag_w )
);
// ========================================================================
// 3. ALU Operation Constants
// ========================================================================
localparam OP_ADD = 4'b0000 ;
localparam OP_SUB = 4'b0001 ;
localparam OP_AND = 4'b0010 ;
localparam OP_OR = 4'b0011 ;
localparam OP_XOR = 4'b0100 ;
localparam OP_SLL = 4'b0101 ;
localparam OP_SRL = 4'b0110 ;
localparam OP_SRA = 4'b0111 ;
localparam OP_SLT = 4'b1000 ;
localparam OP_SLTU = 4'b1001 ;
// ========================================================================
// 4. Test Procedure
// ========================================================================
initial begin
$display("==================================================");
$display(" Starting ALU Verification (Self-Checking)");
$display("==================================================");
// TC 1: Arithmetic
run_test(32'd10, 32'd20, OP_ADD, 32'd30, 1'b0, "ADD: 10 + 20");
run_test(32'd100, 32'd100, OP_SUB, 32'd0, 1'b1, "SUB: 100 - 100 (Zero)");
run_test(32'd10, 32'd20, OP_SUB, -32'd10, 1'b0, "SUB: 10 - 20 (Neg)");
// TC 2: Logic
run_test(32'hAA, 32'h55, OP_AND, 32'h00, 1'b1, "AND: AA & 55");
run_test(32'hAA, 32'h55, OP_OR, 32'hFF, 1'b0, "OR : AA | 55");
run_test(32'hAA, 32'h55, OP_XOR, 32'hFF, 1'b0, "XOR: AA ^ 55");
// TC 3: Shift
run_test(32'd1, 32'd31, OP_SLL, 32'h80000000, 1'b0, "SLL: 1 << 31");
run_test(32'hFFFFFFFF, 32'd31, OP_SRL, 32'd1, 1'b0, "SRL: -1 >> 31");
run_test(-32'd4, 32'd1, OP_SRA, -32'd2, 1'b0, "SRA: -4 >>> 1");
run_test(32'hFFFFFFFF, 32'd31, OP_SRA, 32'hFFFFFFFF, 1'b0, "SRA: -1 >>> 31");
// TC 4: Comparison
run_test(32'hFFFFFFFF, 32'd1, OP_SLT, 32'd1, 1'b0, "SLT : -1 < 1 (Signed)");
run_test(32'hFFFFFFFF, 32'd1, OP_SLTU, 32'd0, 1'b1, "SLTU: -1 < 1 (Unsigned)");
// Final Report
$display("==================================================");
if (err_cnt == 0) $display(" SUCCESS: All Tests Passed! :)");
else $display(" FAILURE: Found %d Errors :(", err_cnt);
$display("==================================================");
$finish;
end
// ========================================================================
// Task: Automated Checking
// ========================================================================
task run_test;
input [31:0] i_a ;
input [31:0] i_b ;
input [3:0] i_op ;
input [31:0] exp_res ;
input exp_zero ;
input [255:0] test_name ;
begin
src_a_r = i_a ;
src_b_r = i_b ;
alu_op_r = i_op ;
#10; // Wait for logic
if ((alu_result_w !== exp_res) || (zero_flag_w !== exp_zero)) begin
$display("[FAIL] %0s", test_name);
$display(" Got: Res=0x%h, Z=%b", alu_result_w, zero_flag_w);
err_cnt = err_cnt + 1;
end else begin
$display("[PASS] %0s", test_name);
end
end
endtask
endmodule
결과

waveform 안봐도 되는게 진짜 좋네요.
항상 AI의 발전에 감사합니다.
'Project > RISC-V CPU Architecture Design' 카테고리의 다른 글
| [7] RISC-V Control Unit 설계 (1) - Main Decoder (0) | 2026.01.05 |
|---|---|
| [6] Immediate Generator 설계 (0) | 2026.01.03 |
| [4] Single-Cycle 프로세서 Register File 설계 (0) | 2026.01.02 |
| [3] RISC-V 프로젝트 기획 (0) | 2026.01.02 |
| [2] 16bit CPU Design 이후 프로젝트 방향 feat. RISC-V (0) | 2024.11.15 |