Immediate Generator

The IMMGEN is a small Verilog module in a single-cycle RISC-V core whose sole job is to pull out the immediate (constant) field from a 32-bit instruction and sign-extend it to 32 bits. It centralizes all of the different RISC-V immediate formats (I, S, B, U, J) into one place.

What Does it Look Like?

We can have a big picture at the start so it is easier to visualize it. You can visualize the immgen as just a box that takes instruct and imm_sel and outputs imm_out.

              ┌──────────┐
 instr[31:0]─►│          │
 imm_sel ─--─►│  IMMGEN  │─► imm_out[31:0]
              └──────────┘

In verilog, it is described as something like:

module immgen (
  input  wire [31:0] instr,
  input  wire [2:0]  imm_sel,
  output reg  [31:0] imm_out
);
  // Precompute all formats
  wire [31:0] i_imm = {{20{instr[31]}}, instr[31:20]};
  wire [31:0] s_imm = {{20{instr[31]}}, instr[31:25], instr[11:7]};
  wire [31:0] b_imm = {{19{instr[31]}}, instr[31], instr[7],
                       instr[30:25], instr[11:8], 1'b0};
  wire [31:0] u_imm = {instr[31:12], 12'b0};
  wire [31:0] j_imm = {{11{instr[31]}}, instr[31], instr[19:12],
                       instr[20], instr[30:21], 1'b0};

	// Selecting and passing the correct format 
  always @(*) begin
    case (imm_sel)
      3'b000: imm_out = i_imm;  // I-type
      3'b001: imm_out = s_imm;  // S-type
      3'b010: imm_out = b_imm;  // B-type
      3'b011: imm_out = u_imm;  // U-type (LUI/AUIPC)
      3'b100: imm_out = j_imm;  // J-type (JAL)
      default: imm_out = 32'b0;
    endcase
  end
endmodule

What Exactly Does it Do?

But you can just recap it as 2 stages:

  1. Precomputing all formats:
    • Take the instruction via instr this is the main data we will be manipulating.
    • It is sliced so that the immediate part is extracted from the instruction and saved to i_imm, s_imm, b_imm, u_imm, and j_imm.
    • Now we have the bits that we want. But since we sliced them, it will be shorter than our original instruction. So we sign-extend them - which means we make it 32 bits (as long as the original instruction) and make sure the sign stays the same.
  2. Selecting and passing the correct format
    • We also received the imm_sel, we can just write a case that we pass our precomputed immediate.

What is Its Role?

Okay, so now we know what it is, what it looks like, and what it does. Now why do we even need it? How does it work with the rest of the CPU? Well, in short, everywhere that needs an immediate. But below are some examples:

  • ALU operations: For arithmetic-immediate (ADDI, SLTI, etc.) and logic-immediate (ANDI, ORI), the ALU’s second operand comes from IMMGEN.

  • Memory access: Load/store instructions use IMMGEN’s output as the address offset (base register + immediate).

  • Branches & jumps: The branch-target and jump-target calculations add the sign-extended offset from IMMGEN to the PC.

  • PC-relative instructions: AUIPC and LUI rely on the U-type immediate for building large constants or PC offsets.

Connections

  • Inputs:

    • instr[31:0] from the instruction memory.

    • imm_sel[2:0] from the main control unit, decoded from the instruction’s opcode/funct fields.

  • Output:

    • imm_out[31:0] which then fans out to the ALU, branch-adder, or PC-update logic.

More about Immediate Formats:

TypeEncoding bitsImmediate bits (before sign-extend)Used for
I-type[31:20]imm[11:0]load/store offsets, ALU immediates, jalr
S-type[31:25] | [11:7]imm[11:5] | imm[4:0]store offsets
B-type[31] | [7] | [30:25] | [11:8]imm[12] | imm[11] | imm[10:5] | imm[4:1] | 0branch offsets (×2)
U-type[31:12]imm[31:12] | 12’b0upper immediates (lui, auipc)
J-type[31] | [19:12] | [20] | [30:21]imm[20] | imm[19:12] | imm[11] | imm[10:1] | 0jump offsets (jal) (×2)

There is also R-type instructions but they do not have any immediate bits.

See also: Instruction Formats

References:

https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/notebooks/RISCV/RISCV_CARD.pdf