The ALU is the engine of the CPU — the piece that actually computes. Every add, subtract, compare, shift, and logical operation passes through here. Build it right and you can execute most of the RV32I instruction set. This lesson builds alu.v with all 10 operations and a zero flag, then tests every single one.
A 4-bit alu_op signal (generated by the control unit, Day 11) selects the operation. We define the encoding like this:
| alu_op | Operation | Result | Used by |
|---|---|---|---|
| 0000 | ADD | A + B | add, addi, lw, sw, auipc |
| 0001 | SUB | A − B | sub, beq, bne, blt, bge |
| 0010 | AND | A & B | and, andi |
| 0011 | OR | A | B | or, ori |
| 0100 | XOR | A ^ B | xor, xori |
| 0101 | SLT | A < B (signed) ? 1 : 0 | slt, slti |
| 0110 | SLTU | A < B (unsigned) ? 1 : 0 | sltu, sltiu |
| 0111 | SLL | A << B[4:0] | sll, slli |
| 1000 | SRL | A >> B[4:0] (logical) | srl, srli |
| 1001 | SRA | A >>> B[4:0] (arithmetic) | sra, srai |
| Port | Dir | Width | Meaning |
|---|---|---|---|
| a | input | 32 | operand A — from register file rs1 |
| b | input | 32 | operand B — from rs2 or immediate (selected by ALUSrc mux) |
| alu_op | input | 4 | operation select — from control unit |
| result | output | 32 | computed result — goes to DMEM address or write-back |
| zero | output | 1 | 1 when result==0 — used by branch unit for BEQ/BNE |
// RV32I ALU — 10 operations, zero flag
module alu (
input wire [31:0] a, // operand A (rs1)
input wire [31:0] b, // operand B (rs2 or imm)
input wire [3:0] alu_op, // operation select
output reg [31:0] result, // computed result
output wire zero // 1 when result == 0 (for branches)
);
wire [4:0] shamt = b[4:0]; // shift amount is lower 5 bits of B
always @(*) begin
case (alu_op)
4'b0000: result = a + b; // ADD
4'b0001: result = a - b; // SUB
4'b0010: result = a & b; // AND
4'b0011: result = a | b; // OR
4'b0100: result = a ^ b; // XOR
4'b0101: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; // SLT
4'b0110: result = (a < b) ? 32'd1 : 32'd0; // SLTU
4'b0111: result = a << shamt; // SLL
4'b1000: result = a >> shamt; // SRL
4'b1001: result = $signed(a) >>> shamt; // SRA
default: result = 32'd0;
endcase
end
assign zero = (result == 32'd0);
endmodule`timescale 1ns/1ps
module tb_alu;
reg [31:0] a,b; reg [3:0] alu_op;
wire [31:0] result; wire zero;
alu dut(.a(a),.b(b),.alu_op(alu_op),.result(result),.zero(zero));
integer errors=0;
task chk(input [31:0]r,input [31:0]er,input zf,input ezf);
if(r!==er||zf!==ezf)begin $display("FAIL res=%0h(exp=%0h) z=%b(exp=%b)",r,er,zf,ezf);errors=errors+1;end
else $display("ok res=%0h z=%b",r,zf);
endtask
initial begin
a=32'd15; b=32'd7;
alu_op=4'b0000;#1;chk(result,32'd22,zero,0); // ADD 15+7=22
alu_op=4'b0001;#1;chk(result,32'd8, zero,0); // SUB 15-7=8
alu_op=4'b0010;#1;chk(result,32'h7,zero,0); // AND 15&7=7
alu_op=4'b0011;#1;chk(result,32'hF,zero,0); // OR 15|7=15
alu_op=4'b0100;#1;chk(result,32'h8,zero,0); // XOR 15^7=8
alu_op=4'b0101;#1;chk(result,32'd0,zero,1); // SLT 15<7? no ->0, zero=1
a=32'd3;b=32'd7;
alu_op=4'b0101;#1;chk(result,32'd1,zero,0); // SLT 3<7? yes ->1
a=32'd1;b=32'd3;
alu_op=4'b0111;#1;chk(result,32'd8,zero,0); // SLL 1<<3=8
a=32'd8;b=32'd1;
alu_op=4'b1000;#1;chk(result,32'd4,zero,0); // SRL 8>>1=4
a=32'hFFFFFFFC;b=32'd2;
alu_op=4'b1001;#1;chk(result,32'hFFFFFFFF,zero,0); // SRA -4>>>2=-1
// zero flag test
a=32'd5;b=32'd5;alu_op=4'b0001;#1;
if(zero!==1)begin $display("FAIL zero should be 1 for 5-5");errors=errors+1;end
else $display("ok zero=1 for 5-5=0");
if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
$finish;
end
endmoduleok res=00000016 z=0 ok res=00000008 z=0 ok res=00000007 z=0 ok res=0000000f z=0 ok res=00000008 z=0 ok res=00000000 z=1 ok res=00000001 z=0 ok res=00000008 z=0 ok res=00000004 z=0 ok res=ffffffff z=0 ok zero=1 for 5-5=0 ALL TESTS PASSED
The Verilog >>> operator only sign-extends when the left operand is declared or cast as $signed(). Without that cast it behaves like SRL. Always use $signed(a) >>> shamt for arithmetic right shift.
alu_op selects one of 10 operations; the control unit drives it.result==0; used by branches to check equality/inequality.$signed() cast in Verilog.b[4:0] (lower 5 bits, range 0–31).ADD, SUB, AND, OR, XOR, SLT, SLTU, SLL, SRL, SRA — selected by a 4-bit alu_op from the control unit.
A 1-bit output that is 1 when result==0. Used by the branch unit — BEQ branches when A-B=0 (zero=1).
SRL fills vacated bits with zeros. SRA fills with the sign bit, preserving the sign of a two's complement number. Use $signed() cast in Verilog for SRA.