Skip to content

CacheController Component


Overview

The CacheController coordinates memory transactions between a RISC-V processor core and external main memory. It implements a 4-way set-associative blocking cache architecture containing 32 sets, with a line size of 8 words (32 bytes). The component automatically handles parallel way lookups, single-cycle hit execution, multi-cycle line-fill bursts upon a cache miss, and pseudo-LRU eviction updates.

  • Purpose in CPU: Bridges the operational frequency gap between high-speed CPU registers and high-latency main memory, minimizing pipeline stalls by storing local blocks of recently accessed memory.
  • Role in Datapath: Intercepts the processor's memory access stage, inspecting read/write requests, stalling the pipeline on cache misses, and steering data buses to return words or commit byte-masked store instructions.

  • Source: logisim/RiskVCache.circ


Interface

Inputs

Signal Width Description
CPU_Addr 32 Memory address issued by the processor core (Tag[31:10], Index[9:5], WordOffset[4:2], ByteOffset[1:0])
CPU_DataIn 32 32-bit store data originating from processor registers
CPUWE 1 Master write-enable strobe from the CPU indicating a store operation
WriteMask 4 Byte-level write-enable mask bits supporting byte, halfword, or word stores
MemReady 1 External memory interface flag verifying a valid word transmission during memory bursts
MemDataIn 32 32-bit word streaming from main memory during a cache line fill
clk 1 System global clock signal
rst 1 System synchronous reset line

Outputs

Signal Width Description
CPU_DataOut 32 Filtered/aligned 32-bit data word returned to the processor pipeline
CacheReady 1 Hardware status indicator used to stall (0) or clear (1) the CPU pipeline
MemAddr 32 Address bus routed to external memory during line fills or writebacks
MemRE 1 Memory Read Enable line asserted to initiate an external line-fill sequence
MemWE 1 Memory Write Enable line asserted during dirty line evictions

Output Logic (Core Definition)

Rule-based definition

  • When FSM_STATE == STATE_COLD (Power-on Reset):
  • Continuous evaluation occurs. If an immediate cache match is found, route NEW_FSM_STATE = STATE_IDLE. If a miss is registered, route NEW_FSM_STATE = STATE_ALLOC.
  • FSM_WE is forced high to exit the cold state on the first clock edge.

  • When FSM_STATE == STATE_IDLE:

  • CacheReady = GlobalCacheHit. If GlobalCacheHit == 1, the processor proceeds at full speed. If GlobalCacheHit == 0, the CPU pipeline stalls.
  • DataCtl = 0, forcing internal cache slots to accept CPU_DataIn and index words using CPU_Addr[4:2].
  • If GlobalCacheHit == 0, a transition is flagged: SignExtIdleToAloc = 1, driving NEW_FSM_STATE = STATE_ALLOC and updating the FSM register.
  • If GlobalCacheHit == 1, the tree re-encoder triggers LRU_WE = 1 to mark the hit way as Most Recently Used (MRU).

  • When FSM_STATE == STATE_ALLOC:

  • CacheReady = 0 is hardwired to freeze the processor pipeline during the block acquisition.
  • DataCtl = 1, switching cache data paths to accept MemDataIn and indexing word lanes using the internal ControllerWordOffset.
  • MemRE = 1, keeping the external memory read request line active.
  • Counter_Inc = MemReady, incrementing the burst counter from 0 to 7 every time memory asserts a data-valid flag.
  • When ControllerWordOffset == 7 and MemReady == 1, the transition triggers: SignExtAlocToIdle = 1, committing tag metadata, setting the line valid bit, updating the LRU array, and returning to STATE_IDLE.

Boolean expressions

isColdActive = (FSM_STATE == 0)
isIdleActive = (FSM_STATE == 1)
isAllocActive = (FSM_STATE == 2)

SignExtColdToIdle = GlobalCacheHit AND isColdActive
SignExtColdToAloc = NOT(GlobalCacheHit) AND isColdActive
SignExtIdleToAloc = NOT(GlobalCacheHit) AND isIdleActive
SignExtAlocToIdle = (ControllerWordOffset == 7) AND MemReady AND isAllocActive

FSM_WE = SignExtColdToIdle OR SignExtColdToAloc OR SignExtIdleToAloc OR SignExtAlocToIdle

STATE_CHANGE = (SignExtIdleToAloc AND STATE_ALLOC) OR
               (SignExtAlocToIdle AND STATE_IDLE)  OR
               (SignExtColdToIdle AND STATE_IDLE)  OR
               (SignExtColdToAloc AND STATE_ALLOC)

Counter_Reset = isColdActive OR isIdleActive
Counter_Inc   = isAllocActive AND MemReady

DataCtl = isAllocActive

Internal Design

The CacheController macro-component orchestrates the interface through structured combinational selection networks and synchronous tracking tables:

  • FSM State Core: Manages the active state using a 2-bit clock-gated state register. Because it uses conditional WE logic derived from transition equations, it implements next-state logic without the use of structural feedback multiplexers on its data port.
  • Data Path Multiplexing Architecture: Incorporates wide multiplexers handling the distribution of data and word addresses. The selection lines are driven by the DataCtl signal, dynamically re-routing access lanes away from the CPU toward the external memory bus during a block refill.
  • Hit Detection Consolidation: Takes the 4 individual CacheHit_N lines from the storage ways, feeding them into a 4-input OR gate to establish GlobalCacheHit. It concurrently drives a 4-to-1 data multiplexer to extract the correct 32-bit word from the matching cache line.

Operation

  1. CPU Request Phase: CPU_Addr and control lines settle. The 5-bit index (CPU_Addr[9:5]) isolates a set simultaneously within all four ways and the Pseudo-LRU module.
  2. Tag Matching Evaluation: In STATE_IDLE, the four ways output their stored tags combinationally. The tag comparators evaluate these lines against CPU_Addr[31:10]. If a valid hit occurs, data is passed to CPU_DataOut within the same cycle, and the LRU tree updates.
  3. Refill Sequence Initiation: On a cache miss, the state register locks into STATE_ALLOC on the next clock edge. CacheReady drops low, stalling the processor. The Pseudo-LRU matrix evaluates the tree switches and identifies the victim way, asserting its specific LineFillWay_N wire.
  4. Burst Transfer Execution: As main memory asserts MemReady, words 0 through 7 flow sequentially into the targeted data lanes of the victim way. The internal burst counter steps forward tracking the block fill.
  5. Metadata Commitment & Resume: Upon reaching the final word (ControllerWordOffset == 7), the controller closes the data lane, flushes the new tag into the Way's Tag RAM, sets the valid bit high, updates the LRU index status, and returns to STATE_IDLE to fulfill the CPU request.

Limitations / Assumptions

  • Blocking Operation Constancy: The cache controller handles requests strictly in a blocking layout. The CPU core is entirely stalled during line fills; no non-blocking lookups or hit-under-miss tracking can occur.
  • Write-Through Protocol Dependence: Assumes write transactions are written through to external data buffers concurrently if cache modifications alter lines, maintaining consistent core state alignments.
  • Ideal Memory Boundary: Assumes external memory correctly responds with sequential burst data matching lower offset fields 000 to 111 linearly during an allocation phase.

Implementation Notes (Logisim)

  • Assembled exclusively using native Logisim primitives, guaranteeing full compatibility without external macro dependencies.
  • Employs an asynchronous read layout across Tag and Valid RAM grids to ensure lookup hits or misses evaluate combinationally inside a single execution phase.
  • Buses use clean split networks and tri-state buffer banks where necessary to eliminate floating wire configurations during line-switching processes.

Submodules

1. CacheWay

The CacheWay submodule provides the primary storage fabric for the cache line arrays, mapping 32 distinct rows (sets). Each slot entry manages a 1-bit validation block, a 22-bit tag field, and an 8-word data line array.

  • Internal Data Grid: Comprises 8 discrete RAM nodes configured as \(32 \times 32\text{-bit}\) blocks. This structure enables individual word lane modifications via a internal 3-to-8 demultiplexer driven by the active WordOffset.
  • Gated Write Architecture: The demultiplexer activation line maps to: $\(\text{Demux\_Enable} = (\text{CacheHit} \cdot \text{CPUWE}) + \text{LineFill}\)$ This allows single-word CPU stores to alter individual data elements on a cache hit, or permits the FSM controller to sequentially stream words 0–7 during a burst block allocation phase.
  • Metadata Framing: Tag memory allocations and row validation entries are locked until the termination cycle of an allocation block: $\(\text{Metadata\_WE} = \text{LineFill} \cdot (\text{WordOffset} == 7)\)$ This gates updates until the full line is securely fetched, preventing tag corruption mid-burst.

2. Pseudo-LRU Eviction Router

Tracks block access history across the 32 cache sets using a binary tree tracking system (\(32 \times 3\text{-bit}\) RAM). It maps three directional tracking bits (B0 root, B1 left child, B2 right child) to isolate eviction targets.

  • Section 1: Storage Layer: Contains the \(32 \times 3\text{-bit}\) memory core driven by the 5-bit set index.
  • Section 2: Eviction Decoder: Translates tree directions into a 4-bit one-hot vector during miss processing: $\(\text{ActiveWay0} = \overline{B_0} \cdot \overline{B_1}, \quad \text{ActiveWay1} = \overline{B_0} \cdot B_1, \quad \text{ActiveWay2} = B_0 \cdot \overline{B_2}, \quad \text{ActiveWay3} = B_0 \cdot B_2\)$ These vectors are gated with isAllocActive to emit LineFillWay_N signals to the target cache slots.
  • Section 3 & 4: Re-Encoder Feedback: When an index is accessed (via hit or fill allocation), a tracking crossbar evaluates the active block path. It recalculates the updated tree configurations to point completely away from the active way, marking it Most Recently Used (MRU). Unvisited tree branches are mirrored forward directly from the RAM read lines to prevent state corruption: $\(\text{Next\_}B_0 = \text{AccessWay0} + \text{AccessWay1}\)$ $\(\text{Next\_}B_1 = \text{AccessWay0} + (\overline{\text{AccessWay1}} \cdot B_1)\)$ $\(\text{Next\_}B_2 = \text{AccessWay2} + (\overline{\text{AccessWay3}} \cdot B_2)\)$
  • Write Control: Commits values on the clock edge via the gated write strobe: $\(\text{LRU\_WE} = (\text{isIdleActive} \cdot \text{GlobalCacheHit}) + (\text{isAllocActive} \cdot (\text{ControllerWordOffset} == 7))\)$