第05课 · ALU
算术逻辑单元RISC-VVerilator
📌 学习目标:设计 RISC-V 风格的 ALU,支持加、减、与、或、异或、移位、比较等操作,并通过 Verilator 全面验证。
一、ALU 是什么?
ALU(Arithmetic Logic Unit,算术逻辑单元)是 CPU 的执行核心,负责所有算术和逻辑运算。RISC-V 的 ALU 需要支持以下操作:
| 操作码 | 操作 | 功能 | RISC-V 指令 |
| 000 | ADD | A + B | ADD/ADDI/LW/SW |
| 001 | SUB | A - B | SUB |
| 010 | AND | A & B | AND/ANDI |
| 011 | OR | A | B | OR/ORI |
| 100 | XOR | A ^ B | XOR/XORI |
| 101 | SRL | A >> B(逻辑右移) | SRL/SRLI |
| 110 | SLL | A << B(逻辑左移) | SLL/SLLI |
| 111 | SLT | A < B ? 1 : 0(有符号比较) | SLT/SLTI |
二、ALU 设计要点
2.1 减法实现
A - B = A + (~B) + 1,利用补码运算,只需在加法器输入端加一个 MUX 和取反器。
2.2 移位实现
逻辑左移(SLL):低位补 0
逻辑右移(SRL):高位补 0
算术右移(SRA):高位补符号位
2.3 比较实现
SLT(Set on Less Than):如果 A < B(有符号),输出 1,否则输出 0。
通过减法结果的符号位来判断,但需要处理溢出情况。
三、Verilog 实现
module alu (
input [31:0] a,
input [31:0] b,
input [2:0] op,
output [31:0] result,
output zero
);
reg [31:0] result_r;
assign result = result_r;
assign zero = (result_r == 32'b0);
always @(*) begin
case (op)
3'b000: result_r = a + b;
3'b001: result_r = a - b;
3'b010: result_r = a & b;
3'b011: result_r = a | b;
3'b100: result_r = a ^ b;
3'b101: result_r = a >> b[4:0];
3'b110: result_r = a << b[4:0];
3'b111: begin
if ($signed(a) < $signed(b))
result_r = 32'd1;
else
result_r = 32'd0;
end
default: result_r = 32'b0;
endcase
end
endmodule
3.1 测试台
module tb_alu;
reg [31:0] a, b;
reg [2:0] op;
wire [31:0] result;
wire zero;
alu uut (.a(a), .b(b), .op(op), .result(result), .zero(zero));
integer pass = 0, fail = 0;
task check;
input [31:0] expected;
begin
if (result !== expected) begin
$display("FAIL: a=%0d b=%0d op=%b got=%0d exp=%0d", a, b, op, result, expected);
fail = fail + 1;
end else
pass = pass + 1;
end
endtask
initial begin
a = 32'd10; b = 32'd20; op = 3'b000; #10;
check(32'd30);
a = 32'hFFFFFFFF; b = 32'd1; op = 3'b000; #10;
check(32'd0);
a = 32'd50; b = 32'd20; op = 3'b001; #10;
check(32'd30);
a = 32'd5; b = 32'd10; op = 3'b001; #10;
check(32'hFFFFFFFB);
a = 32'hFF00FF00; b = 32'h0F0F0F0F; op = 3'b010; #10;
check(32'h0F000F00);
a = 32'hFF00FF00; b = 32'h0F0F0F0F; op = 3'b011; #10;
check(32'hFF0FFF0F);
a = 32'hFF00FF00; b = 32'h0F0F0F0F; op = 3'b100; #10;
check(32'hF00FF00F);
a = 32'h80000004; b = 32'd2; op = 3'b101; #10;
check(32'h20000001);
a = 32'd1; b = 32'd4; op = 3'b110; #10;
check(32'd16);
a = 32'hFFFFFFFE; b = 32'd1; op = 3'b111; #10;
check(32'd1);
a = 32'd5; b = 32'd3; op = 3'b111; #10;
check(32'd0);
a = 32'd10; b = 32'd10; op = 3'b001; #10;
if (zero !== 1'b1) begin
$display("FAIL: zero flag"); fail = fail + 1;
end else pass = pass + 1;
$display("========================================");
$display("ALU 测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ 加减与或移位比较全通过!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
四、Verilator 编译命令
verilator --cc alu.v --exe tb_alu.v \
--build --top-module tb_alu
./obj_dir/Vtb_alu
五、ALU 在 RISC-V 中的角色
- R-Type 指令:ADD, SUB, AND, OR, XOR, SRL, SLL, SLT — 两个寄存器操作数进入 ALU
- I-Type 指令:ADDI, ANDI, ORI, SLTI — 寄存器 + 立即数进入 ALU
- Load/Store:基地址 + 偏移量通过 ALU 的 ADD 计算地址
- Branch:ALU 做 SUB,zero 标志判断 BEQ/BNE
🤔 思考题:为什么 RISC-V 的 ALU 只需要一个 zero 标志位,而 x86 需要多个标志位(CF/ZF/SF/OF)?
💡 提示:RISC 的精简设计哲学 — 比较和分支用专用指令(SLT+BEQ),而不是依赖标志位
🏆 成就解锁:ALU 设计师
✅ Verilator 仿真验证通过
✅ ADD/SUB/AND/OR/XOR/SRL/SLL/SLT 全部验证正确
✅ 补码减法验证正确
✅ 有符号比较 SLT 验证正确
✅ Zero 标志位验证正确
🎯 下一目标:时序逻辑入门 → 第06课:触发器