A direct-mapped 256-byte cache: eight 32-byte lines, an 8-bit data path and 16-bit byte addresses. Implements read/write hits, write allocation, clean refill and dirty eviction into a byte-addressed backing-memory interface.
Started in COE758 (Digital Systems Engineering, fall 2024). The October 2026 refresh replaces the faulty submitted control path with a synchronous controller and automated data verification. This repository demonstrates RTL simulation and synthesis elaboration, not physical SDRAM control or a verified FPGA build.
Requires GHDL 5.1.1 (or compatible) and Bash:
bash scripts/check.sh
# Or use Docker with the pinned GHDL image:
bash scripts/check-docker.shThe fixed-seed test completes 1,221 CPU transactions, including 192 hits / 1,029 misses, and verifies every byte of the 64 KiB backing memory after dirty lines are evicted. It checks refills, all byte offsets/indices, conflict misses, write allocation, old-tag dirty writeback, readback, maximum addresses, memory stalls, captured CPU requests and reset invalidation.
Retained check output · Verification details · Provenance
Address: [tag: 15..8] [index: 7..5] [offset: 4..0].
Tag, valid and dirty storage accompany each line. One byte transfers per accepted
backing-memory beat; a dirty conflict writes 32 bytes using the evicted tag, then
refills 32 bytes using the pending address. The write/read completes after refill.
stateDiagram-v2
[*] --> IDLE
IDLE --> LOOKUP: capture CPU request
LOOKUP --> COMPLETE: valid tag hit
LOOKUP --> WRITEBACK: dirty conflict
LOOKUP --> REFILL: invalid or clean miss
WRITEBACK --> REFILL: 32 accepted bytes
REFILL --> COMPLETE: 32 accepted bytes
COMPLETE --> IDLE: completion pulse
| Interface | Contract |
|---|---|
cpu_valid, cpu_addr, cpu_write, cpu_wdata |
Captured on a rising edge in IDLE. One outstanding transaction; no pipelining. |
cpu_ready, cpu_rdata |
One-cycle completion pulse; read data is valid with that pulse. |
mem_valid, mem_write, mem_addr, mem_wdata |
Hold stable until a rising edge with mem_ready=1. |
mem_ready, mem_rdata |
Memory accepts the byte and provides read data at that edge. No separate delayed response channel. |
reset |
Synchronous cache invalidation; cancels an in-flight request and discards dirty contents. Not a flush operation. |
debug_hit |
Indicates whether the last lookup was a hit. |
The CPU may pulse valid for an idle acceptance edge, or hold valid until completion and deassert it before the next edge. Input changes while busy cannot alter the captured transaction. Issue the next request after observing completion. A stalled memory beat must eventually become ready; the controller does not contain a timeout.
The testbench's backing memory is a behavioral byte array with deterministic stalls,
not an SDRAM device controller. The course-provided CPU_gen in the archive is a
stimulus generator, not an instruction-executing CPU.
The submitted FSM crashes on entry to refill because a negative transfer offset
is passed to to_unsigned. It also increments a counter outside rising_edge,
and the controller derives hit/address signals in an incomplete clock-only process.
The refresh uses one rising-edge FSM, captured requests, bounded transfer counters,
and explicit ready/valid memory handshakes. Data/tag storage is consolidated with
the controller; it is a documented replacement, not a drop-in patch to the old CPU
or memory interfaces. The original source remains under archive/ for comparison.
GHDL synthesis elaboration passes. No target device synthesis, place-and-route, physical SDRAM operation or new board run is claimed. The original iSim report's 730/1,380 ns miss figures belong to the old model and are not refreshed measurements. GitHub Actions runs the same reference-memory checks on each push and pull request.