HomeRISC-V from ScratchDay 10
DAY 10 · PHASE 2 — BUILD THE CPU

The ALU — Arithmetic Logic Unit

By EcrioniX · Updated Jun 11, 2026

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.

1. ALU operations and encoding

A 4-bit alu_op signal (generated by the control unit, Day 11) selects the operation. We define the encoding like this:

alu_opOperationResultUsed by
0000ADDA + Badd, addi, lw, sw, auipc
0001SUBA − Bsub, beq, bne, blt, bge
0010ANDA & Band, andi
0011ORA | Bor, ori
0100XORA ^ Bxor, xori
0101SLTA < B (signed) ? 1 : 0slt, slti
0110SLTUA < B (unsigned) ? 1 : 0sltu, sltiu
0111SLLA << B[4:0]sll, slli
1000SRLA >> B[4:0] (logical)srl, srli
1001SRAA >>> B[4:0] (arithmetic)sra, srai

2. Port table

PortDirWidthMeaning
ainput32operand A — from register file rs1
binput32operand B — from rs2 or immediate (selected by ALUSrc mux)
alu_opinput4operation select — from control unit
resultoutput32computed result — goes to DMEM address or write-back
zerooutput11 when result==0 — used by branch unit for BEQ/BNE

3. alu.v

alu.v — RV32I Arithmetic Logic Unit
// 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

4. Testbench — all 10 operations

tb_alu.v — testbench
`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
endmodule
expected output
ok   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

✅ SRA tip

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.

🎯 Day 10 takeaways

FAQ

What operations does the RISC-V ALU perform?

ADD, SUB, AND, OR, XOR, SLT, SLTU, SLL, SRL, SRA — selected by a 4-bit alu_op from the control unit.

What is the zero flag?

A 1-bit output that is 1 when result==0. Used by the branch unit — BEQ branches when A-B=0 (zero=1).

SRA vs SRL?

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.

Previous
← Day 9: Register file

← Full roadmap