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

Immediate Generation & the Control Unit

By EcrioniX · Updated Jun 11, 2026

Two modules today — both purely combinational, both critical. ImmGen extracts the scattered immediate bits from any instruction format and sign-extends them to 32 bits. The Control Unit reads the opcode and drives every mux and enable in the datapath. Together they are the brain that makes the datapath execute the right operation for every instruction.

1. ImmGen — immediate generator

Recall from Day 3: RISC-V has 5 instruction formats (I, S, B, U, J) and each scatters the immediate bits differently across the 32-bit instruction word. ImmGen's job is to reassemble them into a single sign-extended 32-bit value.

FormatUsed byImmediate bits in instructionSigned range
Iaddi, lw, jalrinst[31:20]−2048 to +2047
Ssw, sh, sbinst[31:25] | inst[11:7]−2048 to +2047
Bbeq, bne, bltinst[31,7,30:25,11:8] × 2−4096 to +4094
Ului, auipcinst[31:12] placed at bits 31:12large constant
Jjalinst[31,19:12,20,30:21] × 2±1 MB range
immgen.v — immediate generator
// Immediate Generator — extracts and sign-extends immediates
// imm_sel: 000=I, 001=S, 010=B, 011=U, 100=J
module immgen (
    input  wire [31:0] instr,    // full 32-bit instruction word
    input  wire [2:0]  imm_sel,  // format select from control unit
    output reg  [31:0] imm       // sign-extended immediate
);
    always @(*) begin
        case (imm_sel)
            3'b000: imm = {{20{instr[31]}}, instr[31:20]};           // I
            3'b001: imm = {{20{instr[31]}}, instr[31:25], instr[11:7]}; // S
            3'b010: imm = {{19{instr[31]}}, instr[31], instr[7],     // B
                           instr[30:25], instr[11:8], 1'b0};
            3'b011: imm = {instr[31:12], 12'b0};                     // U
            3'b100: imm = {{11{instr[31]}}, instr[31], instr[19:12], // J
                           instr[20], instr[30:21], 1'b0};
            default: imm = 32'd0;
        endcase
    end
endmodule

2. Control Unit

The control unit decodes the opcode (bits 6:0) and outputs all the signals that steer the datapath:

SignalWidthMeaning
reg_write11 = write result to register file (rd)
alu_src10 = ALU B from rs2; 1 = ALU B from immediate
mem_read11 = read from data memory (load instructions)
mem_write11 = write to data memory (store instructions)
branch11 = instruction may take a branch
jump11 = unconditional jump (jal/jalr)
wb_sel200=ALU result, 01=DMEM data, 10=PC+4, 11=upper imm
alu_op4operation for ALU (from Day 10 encoding)
imm_sel3immediate format select for ImmGen
control.v — control unit
// RISC-V Control Unit — decodes opcode, drives datapath
module control (
    input  wire [6:0] opcode,
    input  wire [2:0] funct3,
    input  wire [6:0] funct7,
    output reg        reg_write,
    output reg        alu_src,
    output reg        mem_read,
    output reg        mem_write,
    output reg        branch,
    output reg        jump,
    output reg  [1:0] wb_sel,   // 00=ALU,01=MEM,10=PC+4,11=Uimm
    output reg  [3:0] alu_op,
    output reg  [2:0] imm_sel
);
    localparam R   = 7'b0110011;  // R-type
    localparam I   = 7'b0010011;  // I-type ALU
    localparam LOAD= 7'b0000011;  // loads
    localparam STOR= 7'b0100011;  // stores
    localparam BR  = 7'b1100011;  // branches
    localparam JAL = 7'b1101111;  // jal
    localparam JALR= 7'b1100111;  // jalr
    localparam LUI = 7'b0110111;  // lui
    localparam AUIP= 7'b0010111;  // auipc

    always @(*) begin
        // defaults — safe values
        {reg_write,alu_src,mem_read,mem_write,branch,jump}=6'b0;
        wb_sel=2'b00; alu_op=4'b0000; imm_sel=3'b000;
        case (opcode)
            R:    begin reg_write=1; alu_src=0; wb_sel=2'b00;
                    case({funct7[5],funct3})
                        4'b0000: alu_op=4'b0000; // ADD
                        4'b1000: alu_op=4'b0001; // SUB
                        4'b0111: alu_op=4'b0010; // AND
                        4'b0110: alu_op=4'b0011; // OR
                        4'b0100: alu_op=4'b0100; // XOR
                        4'b0010: alu_op=4'b0101; // SLT
                        4'b0011: alu_op=4'b0110; // SLTU
                        4'b0001: alu_op=4'b0111; // SLL
                        4'b0101: alu_op=4'b1000; // SRL
                        4'b1101: alu_op=4'b1001; // SRA
                        default: alu_op=4'b0000;
                    endcase end
            I:    begin reg_write=1; alu_src=1; wb_sel=2'b00; imm_sel=3'b000;
                    case(funct3)
                        3'h0: alu_op=4'b0000; // ADDI
                        3'h7: alu_op=4'b0010; // ANDI
                        3'h6: alu_op=4'b0011; // ORI
                        3'h4: alu_op=4'b0100; // XORI
                        3'h2: alu_op=4'b0101; // SLTI
                        3'h3: alu_op=4'b0110; // SLTIU
                        3'h1: alu_op=4'b0111; // SLLI
                        3'h5: alu_op=(funct7[5])?4'b1001:4'b1000; // SRAI/SRLI
                        default: alu_op=4'b0000;
                    endcase end
            LOAD: begin reg_write=1; alu_src=1; mem_read=1; wb_sel=2'b01; imm_sel=3'b000; alu_op=4'b0000; end
            STOR: begin alu_src=1; mem_write=1; imm_sel=3'b001; alu_op=4'b0000; end
            BR:   begin branch=1; imm_sel=3'b010;
                    alu_op=(funct3==3'h4||funct3==3'h5)?4'b0101:4'b0001; end
            JAL:  begin reg_write=1; jump=1; wb_sel=2'b10; imm_sel=3'b100; end
            JALR: begin reg_write=1; alu_src=1; jump=1; wb_sel=2'b10; imm_sel=3'b000; alu_op=4'b0000; end
            LUI:  begin reg_write=1; wb_sel=2'b11; imm_sel=3'b011; end
            AUIP: begin reg_write=1; alu_src=1; wb_sel=2'b00; imm_sel=3'b011; alu_op=4'b0000; end
            default: ;
        endcase
    end
endmodule
tb_control.v — quick smoke test
`timescale 1ns/1ps
module tb_control;
    reg [6:0]op; reg[2:0]f3; reg[6:0]f7;
    wire rw,as,mr,mw,br,jp; wire[1:0]wbs; wire[3:0]ao; wire[2:0]is;
    control dut(.opcode(op),.funct3(f3),.funct7(f7),
        .reg_write(rw),.alu_src(as),.mem_read(mr),.mem_write(mw),
        .branch(br),.jump(jp),.wb_sel(wbs),.alu_op(ao),.imm_sel(is));
    integer errors=0;
    task chk(input e_rw,e_as,e_mr,e_mw,e_br,e_jp);
        if({rw,as,mr,mw,br,jp}!=={e_rw,e_as,e_mr,e_mw,e_br,e_jp})
            begin $display("FAIL op=%b signals=%b%b%b%b%b%b",op,rw,as,mr,mw,br,jp);errors=errors+1;end
        else $display("ok   op=%b rw=%b as=%b mr=%b mw=%b br=%b jp=%b",op,rw,as,mr,mw,br,jp);
    endtask
    initial begin
        f3=0;f7=0;
        op=7'b0110011;#1;chk(1,0,0,0,0,0); // R-type
        op=7'b0010011;#1;chk(1,1,0,0,0,0); // I-type ALU
        op=7'b0000011;#1;chk(1,1,1,0,0,0); // LOAD
        op=7'b0100011;#1;chk(0,1,0,1,0,0); // STORE
        op=7'b1100011;#1;chk(0,0,0,0,1,0); // BRANCH
        op=7'b1101111;#1;chk(1,0,0,0,0,1); // JAL
        if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
        $finish;
    end
endmodule
expected output
ok   op=0110011 rw=1 as=0 mr=0 mw=0 br=0 jp=0
ok   op=0010011 rw=1 as=1 mr=0 mw=0 br=0 jp=0
ok   op=0000011 rw=1 as=1 mr=1 mw=0 br=0 jp=0
ok   op=0100011 rw=0 as=1 mr=0 mw=1 br=0 jp=0
ok   op=1100011 rw=0 as=0 mr=0 mw=0 br=1 jp=0
ok   op=1101111 rw=1 as=0 mr=0 mw=0 br=0 jp=1
ALL TESTS PASSED

🎯 Day 11 takeaways

FAQ

What is the immediate generator?

It reassembles and sign-extends the immediate field from a 32-bit instruction word. Different instruction formats scatter the immediate bits differently, so ImmGen handles all five formats (I/S/B/U/J).

What does the control unit do?

Reads the opcode and drives all datapath control signals: RegWrite, ALUSrc, MemRead, MemWrite, Branch, Jump, WBSel, ALUOp, ImmSel.

What is sign extension?

Expanding a short integer to 32 bits by filling upper bits with the sign bit (MSB), preserving the value's sign in two's complement arithmetic.

Previous
← Day 10: The ALU

← Full roadmap