Skip to content

rtl/duck_coherency_ctrl.sv — MOESI Coherency Controller + L2

Replaces l2_cache.sv in the current design. Adds a 2-master simplified MOESI coherency layer on top of the same 4-way write-back L2 geometry: Master 0 is the CPU (same interface l2_cache exposed), Master 1 is the NPU DMA (read-only, one word at a time, weight-loading path).

Where it fits

Instantiated once by base_die_top.sv as coherency_inst. The CPU side is fed by base_die_top's mem_req/mem_rsp bridge FSM (standing in for riscv_core); the NPU DMA side is wired straight to system_top's top-level npu_dma_* ports.

Parameters (fixed, same as l2_cache.sv)

Name Value Meaning
SETS 16 Number of sets
WAYS 4 Associativity
LINE_WORDS 8 Words per cache line (32 bytes)

Total capacity: 16 × 4 × 32 B = 2 KiB. Address decomposition: [31:9] tag (23b), [8:5] set (4b), [4:2] word (3b), [1:0] byte offset (unused at word granularity).

Ports

Direction Name Width Description
in clk, rst_n 1, 1 Clock / reset
in cpu_read/cpu_write 1/1 CPU load/store request
in cpu_addr/cpu_wdata 32/32 CPU access address / write data
in cpu_byte_en 4 CPU store byte enables
out cpu_rdata 32 Combinational hit read data
out cpu_stall 1 High while miss FSM busy or on a miss
in npu_dma_read 1 NPU DMA read request
in npu_dma_addr 32 NPU DMA target address
out npu_dma_rdata 32 NPU DMA read data
out npu_dma_ack 1 1-cycle pulse: DMA data ready
out npu_dma_stall 1 1 whenever state != ST_IDLE — CPU has strict priority
out dram_read/dram_write 1/1 DRAM transaction request
out dram_addr/dram_wdata 32/32 DRAM address / write data
out dram_byte_en 4 Always 4'hF
in dram_rdata 32 DRAM read data
in dram_ack 1 1 cycle after request, per the DRAM model

Functionality — coherency protocol (simplified MOESI, 2-master)

  • CPU write to a line marked npu_present: clears npu_present (Invalidate — the NPU's copy is now stale).
  • NPU DMA read from an address with a CPU-dirty (Modified) line: (1) flush the entire dirty cacheline to DRAM (M→S transition), (2) clear the dirty flag (CPU line becomes Shared/Clean), (3) read the target word from DRAM for the NPU, (4) set npu_present for that line (NPU enters Shared state).
  • NPU DMA read from a clean/absent line: read directly from DRAM, no flush needed.
  • DRAM arbitration: CPU miss path has strict priority — NPU DMA is stalled (npu_dma_stall=1) while any CPU miss FSM state is active.

FSM states

State Path Purpose
ST_IDLE (0) shared Arbitrate CPU vs NPU; serve hits
ST_WB_START/ST_WB_WAIT (1/2) CPU miss Dirty victim writeback, 8 words
ST_FILL (3) CPU miss Burst fill from DRAM, 8 words
ST_DONE (4) CPU miss 1-cycle gap before re-serving the CPU
ST_NPU_FLUSH/ST_NPU_FLWAIT (5/6) NPU DMA Flush CPU-dirty line to DRAM (MOESI M→S)
ST_NPU_DRAM (7) NPU DMA Read target word from DRAM
ST_NPU_DONE (8) NPU DMA npu_dma_ack pulses this cycle
ST_NPU_GAP (9) NPU DMA 1-cycle gap after flush so dram_ack de-asserts before the DRAM read begins (avoids capturing a stale ack — see BUG-003)

Example — NPU DMA read that must flush first

CPU:  sw a1, 0(a0)          # writes 42 to 0x1000 -> L2 line Modified (dirty=1)
      jal x0,0              # halt

TB:   npu_dma_addr=0x1000, npu_dma_read=1
      ST_IDLE -> npu_cpu_dirty=1 (tag hit, dirty=1) -> ST_NPU_FLUSH
      ST_NPU_FLUSH/FLWAIT   # 8 words written to DRAM, dirty cleared
      ST_NPU_GAP            # let dram_ack settle
      ST_NPU_DRAM           # read word 0x1000 back from DRAM -> 42
      ST_NPU_DONE           # npu_dma_ack pulses, npu_dma_rdata=42

This exact scenario is tb_moesi_integration.py's test_moesi_cpu_write_npu_read — see tb_moesi_integration.md. It is currently failing (BUG-006 / UNSOLVED-001) despite the ST_NPU_GAP fix being present in this file.