Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Instruction Cache

Controller Architecture

The instruction cache controller (instruction_cache_controller.sv) manages the fetch path between the CPU and the DDR2 memory via the shared 128-bit Wishbone arbiter. It wraps the cache_memory_subsystem module and adds the hit/miss evaluation, miss handling, and pipeline handshake logic.

flowchart LR
    subgraph CPU
        FETCH["Fetch Stage"]
    end
    subgraph ICACHE["ICache Controller"]
        EVAL["Hit/Miss Eval"]
        MEM["Cache Memory Arrays"]
        FSM["FSM<br/>IDLE→EVAL→FETCH"]
    end
    subgraph DDR2_PATH["Memory Subsystem"]
        ARB["128-bit Arbiter"]
        BRIDGE["MIG Bridge"]
        DDR2["DDR2 SDRAM"]
    end

    FETCH --> EVAL
    EVAL --> MEM
    MEM --> FETCH
    EVAL --> FSM
    FSM --> ARB
    ARB --> BRIDGE
    BRIDGE --> DDR2
    DDR2 --> BRIDGE
    BRIDGE --> ARB
    ARB --> FSM

State Machine

The controller operates in three states:

stateDiagram-v2
    IDLE --> EVALUATE: cpu_bus.cyc && cpu_bus.stb
    EVALUATE --> IDLE: !cpu_bus.cyc (CPU abort)
    EVALUATE --> IDLE: cache_hit (update LRU)
    EVALUATE --> FETCH_DDR: cache_miss
    FETCH_DDR --> EVALUATE: mem_bus.ack (line fetched)
    FETCH_DDR --> IDLE: mem_bus.err (error)

IDLE

Waits for a valid CPU request. The Wishbone signals cyc and stb indicate the fetch stage is requesting an instruction at the current adr. On receiving both, the controller transitions to EVALUATE.

EVALUATE

In this state, the memory arrays have already been read (they are combinational/inferred-BRAM on the previous cycle). The hit/miss logic compares the tag from cpu_bus.adr[31:11] against both ways’ stored tags:

assign current_tag = { {(32-TAG_BITS){1'b0}}, cpu_bus.adr[31 : 32-TAG_BITS] };
assign hit_way0    = valid_out_way0 && (tag_out_way0 == current_tag);
assign hit_way1    = valid_out_way1 && (tag_out_way1 == current_tag);
assign cache_hit   = hit_way0 | hit_way1;

On hit: The controller selects the correct 32-bit word from the 128-bit line based on cpu_bus.adr[1:0], asserts cpu_bus.ack, updates the LRU state, and returns to IDLE for the next request:

case (cpu_bus.adr[1:0])
    2'b00: cpu_bus.dat_miso = active_line[31:0];
    2'b01: cpu_bus.dat_miso = active_line[63:32];
    2'b10: cpu_bus.dat_miso = active_line[95:64];
    2'b11: cpu_bus.dat_miso = active_line[127:96];
endcase

On miss: The controller initiates a 128-bit read on the memory bus, aligned to the 16-byte boundary of the requested address:

mem_bus.adr  <= {cpu_bus.adr[31:2], 2'b00};
mem_bus.cyc  <= 1'b1;
mem_bus.stb  <= 1'b1;
mem_bus.we   <= 1'b0;
mem_bus.sel  <= 16'hFFFF;

FETCH_DDR

Waits for the DDR2 access to complete. The memory bus is monitored for ack (success) or err (error):

  • On ack: The 128-bit data from DDR2 is written into the evicted cache way (determined by lru_evict_way), the tag is updated, and the valid bit is set. The controller returns to EVALUATE to re-read from cache (which now contains the requested line).
  • On err: The error is forwarded to the CPU, and the controller returns to IDLE.

The fill writes the full 128-bit line:

write_data <= mem_bus.dat_miso;
if (lru_evict_way == 1'b0) we_way0 <= 1'b1;
else                       we_way1 <= 1'b1;
state <= STATE_EVALUATE;

Interrupt Handling

Between IDLE and EVALUATE, if the CPU de-asserts cpu_bus.cyc (indicating an interrupt or pipeline flush), the controller aborts the pending access and returns to IDLE. This prevents stale cache operations after a control flow change, which otherwise led to hard faults, as the first instruction of the context switch code within the interrupt handler was being dropped.

Hit Latency

A cache hit completes in a single cycle: the address is presented in EVALUATE, the data is available at the end of the same cycle, and ack is returned to the CPU.