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.
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.
| Format | Used by | Immediate bits in instruction | Signed range |
|---|---|---|---|
| I | addi, lw, jalr | inst[31:20] | −2048 to +2047 |
| S | sw, sh, sb | inst[31:25] | inst[11:7] | −2048 to +2047 |
| B | beq, bne, blt | inst[31,7,30:25,11:8] × 2 | −4096 to +4094 |
| U | lui, auipc | inst[31:12] placed at bits 31:12 | large constant |
| J | jal | inst[31,19:12,20,30:21] × 2 | ±1 MB range |
// 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
endmoduleThe control unit decodes the opcode (bits 6:0) and outputs all the signals that steer the datapath:
| Signal | Width | Meaning |
|---|---|---|
| reg_write | 1 | 1 = write result to register file (rd) |
| alu_src | 1 | 0 = ALU B from rs2; 1 = ALU B from immediate |
| mem_read | 1 | 1 = read from data memory (load instructions) |
| mem_write | 1 | 1 = write to data memory (store instructions) |
| branch | 1 | 1 = instruction may take a branch |
| jump | 1 | 1 = unconditional jump (jal/jalr) |
| wb_sel | 2 | 00=ALU result, 01=DMEM data, 10=PC+4, 11=upper imm |
| alu_op | 4 | operation for ALU (from Day 10 encoding) |
| imm_sel | 3 | immediate format select for ImmGen |
// 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`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
endmoduleok 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
alu_src (rs2 vs imm), reg_write, mem_read/write, branch, jump, wb_sel.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).
Reads the opcode and drives all datapath control signals: RegWrite, ALUSrc, MemRead, MemWrite, Branch, Jump, WBSel, ALUOp, ImmSel.
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.