Day 7 gave you the full datapath map and the Program Counter. Now we build the other half of the Fetch stage: Instruction Memory (IMEM) — the ROM that holds your program. Wire PC + IMEM together and you have a stage that can continuously fetch instructions. That's the first piece of a working CPU.
IMEM is the simplest module in the CPU: give it an address, get back a 32-bit instruction. It is purely combinational — no clock, no write port. Think of it as a lookup table: index = address, value = machine code word.
| Port | Dir | Width | Meaning |
|---|---|---|---|
| addr | input | 32 | byte address of the instruction to fetch (from PC) |
| instr | output | 32 | 32-bit machine code word at that address |
We word-index into the array using addr[31:2] (shift right by 2 = divide by 4) because instructions are 4-byte aligned and we store one word per entry.
// Instruction Memory — combinational ROM, word-addressed
// Holds up to 256 instructions (1 KB). Pre-load with $readmemh.
module imem #(parameter DEPTH = 256) (
input wire [31:0] addr, // byte address from PC
output wire [31:0] instr // 32-bit instruction word
);
reg [31:0] mem [0:DEPTH-1];
// Load program at simulation start.
// Replace "program.hex" with your assembled .hex file.
initial $readmemh("program.hex", mem);
// Word-aligned read: divide byte address by 4
assign instr = mem[addr[31:2]];
endmodule// Instruction Fetch stage: PC register + IMEM
// pc_next is driven by the branch/jump logic (just PC+4 for now)
module if_stage (
input wire clk,
input wire rst,
input wire [31:0] pc_next, // next PC (PC+4 or branch target)
output wire [31:0] pc, // current PC
output wire [31:0] instr // fetched instruction
);
pc pc_reg (.clk(clk), .rst(rst), .pc_next(pc_next), .pc(pc));
imem imem_mod(.addr(pc), .instr(instr));
endmoduleWe pre-load IMEM with a small program encoded as hex words, then check that each instruction fetches in the right order:
00500093 // addi x1, x0, 5 (x1 = 5) 00A00113 // addi x2, x0, 10 (x2 = 10) 002081B3 // add x3, x1, x2 (x3 = 15) 00000013 // nop (addi x0,x0,0)
`timescale 1ns/1ps
module tb_if_stage;
reg clk=0, rst=1;
reg [31:0] pc_next;
wire[31:0] pc, instr;
if_stage dut(.clk(clk),.rst(rst),.pc_next(pc_next),.pc(pc),.instr(instr));
always #5 clk=~clk;
integer errors=0;
task check(input [31:0] epc, input [31:0] einstr);
if(pc!==epc||instr!==einstr)begin
$display("FAIL pc=%0h instr=%0h (exp pc=%0h instr=%0h)",pc,instr,epc,einstr);
errors=errors+1;
end else $display("ok pc=%0h instr=%0h",pc,instr);
endtask
initial begin
pc_next=0; @(posedge clk); #1; check(32'h0,32'h00500093); // reset, fetch instr 0
rst=0;
pc_next=32'h4; @(posedge clk); #1; check(32'h4,32'h00A00113);
pc_next=32'h8; @(posedge clk); #1; check(32'h8,32'h002081B3);
pc_next=32'hC; @(posedge clk); #1; check(32'hC,32'h00000013);
if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
$finish;
end
endmoduleok pc=00000000 instr=00500093 ok pc=00000004 instr=00a00113 ok pc=00000008 instr=002081b3 ok pc=0000000c instr=00000013 ALL TESTS PASSED
IMEM is a combinational ROM: addr in → instr out, no clock. Word-index with addr[31:2]. Wired with pc.v it becomes the complete Fetch stage — the CPU can now continuously fetch instructions from a stored program.
addr[31:2] (byte → word address).$readmemh to load a hex program file at sim start.A read-only memory (ROM) holding the program. The PC supplies the address; IMEM returns the 32-bit instruction word at that address.
We only ever read IMEM (never write during execution). A combinational ROM returns the instruction the same cycle the address is presented — no clock edge needed.
A Verilog system task that loads a memory array from a hex file at simulation time — used to pre-load the program into IMEM.