Skip to content

Add multi-layered L1/L2 data cache and per-block shared memory#61

Open
mouryadwarapudii wants to merge 1 commit into
adam-maj:masterfrom
mouryadwarapudii:feature/multi-layer-cache-shared-memory
Open

Add multi-layered L1/L2 data cache and per-block shared memory#61
mouryadwarapudii wants to merge 1 commit into
adam-maj:masterfrom
mouryadwarapudii:feature/multi-layer-cache-shared-memory

Conversation

@mouryadwarapudii

@mouryadwarapudii mouryadwarapudii commented Jul 1, 2026

Copy link
Copy Markdown

Summary

  • Implements the first item from the README's Advanced Functionality / Next Steps list: a multi-layered data cache and per-block shared memory.
  • Adds a generic, parameterized, non-blocking set-associative cache module (src/cache.sv) used for both a private per-core L1 (flushed on each new block, avoiding cross-core coherence complexity) and a shared L2 (persists per kernel) sitting in front of the existing memory controller.
  • Adds an on-chip shared memory scratchpad (src/shared_memory.sv + src/shared_lsu.sv) local to each core, with two new ISA instructions LDS/STS so threads in a block can exchange data without round-tripping through global memory. The existing lockstep scheduler gives this implicit barrier semantics for free.
  • README updated (Architecture/Memory, ISA, Simulation, Advanced Functionality, Next Steps) to document the new design and its tradeoffs (e.g. L1 is intentionally non-coherent across cores, relying on the per-block flush instead of a snooping/invalidation protocol).

Testing done

All four kernel-level tests were run locally with Icarus Verilog 13.0 + cocotb 1.9.2 + sv2v, via the project's existing make test_% flow (make compilesv2viverilogvvp w/ cocotb). Results:

Test Result Cycles What it checks
make test_matadd PASS 7,900,001 ns (was 4,475,001 ns pre-cache) Existing 1x8 matrix-add kernel, unmodified - confirms no functional regression from the new cache/memory path. Cycle count is higher than baseline because every access now pays cold-miss + line-fill latency once, since this kernel has little data reuse.
make test_matmul PASS 15,975,001 ns (was 12,300,001 ns pre-cache) Existing 2x2 matrix-multiply kernel (uses CMP/BRnzp looping), unmodified - confirms branching/looping control flow is unaffected.
make test_shared_memory PASS 3,425,001 ns New kernel: all 4 threads in a block each STS their own %threadIdx into shared memory, then each LDSs a different thread's slot (3 - threadIdx) and stores it to global memory. Verifies cross-thread visibility of shared memory and that the lockstep scheduler acts as an implicit barrier (every thread's write is guaranteed visible before any thread's read).
make test_cache PASS 11,400,001 ns New kernel: all 4 threads independently loop, each reading the same global memory address 5 times. Verifies both functional correctness (right value every time) and, by reading dut.cores[0].core_l1_hit_count / core_l1_miss_count directly, that the L1 cache actually recorded ≥1 miss (the cold load) and ≥4 hits (the repeat loads) rather than just happening to produce correct results some other way.

Beyond the kernel-level tests, correctness of the cache's line-fill and multi-channel arbitration logic was verified in isolation with standalone Icarus Verilog testbenches (not part of this PR, used only for local debugging) driving the cache module directly with concurrent read/write requests and a simple synchronous backing-memory model, both single-cache and chained L1→L2 configurations, before wiring it into the full GPU. This is how two real races were caught and fixed:

  1. A single-cache race where advancing to the next word's address in a line-fill while keeping mem_read_valid continuously asserted let the downstream memory answer with stale data for one cycle.
  2. The same race resurfacing at the L1↔L2 boundary specifically (not visible in the single-cache test) because of the extra pass-through register hop between the two cache instances - fixed by waiting for ready to actually deassert before reissuing the next word's request, instead of a fixed one-cycle wait.

How to test this PR

Prerequisites (see README's Simulation section):

brew install icarus-verilog
pip3 install cocotb
# install sv2v from https://github.com/zachjs/sv2v/releases and put it on $PATH
mkdir -p build

Then, from the repo root:

make test_matadd          # existing kernel - should still pass unmodified
make test_matmul          # existing kernel - should still pass unmodified
make test_shared_memory   # new: LDS/STS + cross-thread shared memory
make test_cache           # new: L1 cache hit/miss behavior on repeated reads

Each target prints a cocotb PASS/FAIL summary line and writes a full cycle-by-cycle execution trace + before/after data-memory dump to test/logs/log_<timestamp>.txt, in the same format the existing test_matadd/test_matmul logs use.

To inspect cache behavior directly (e.g. while modifying src/cache.sv), the per-core L1 stats are reachable from a cocotb test via dut.cores[<i>].core_l1_hit_count / core_l1_miss_count, and the shared L2's via the top-level dut.l2_hit_count / dut.l2_miss_count ports - test/test_cache.py shows a working example of reading these mid-simulation (they get reset along with the rest of the core's state right when its last block finishes, so they need to be sampled during the run rather than after dut.done).

Implements the first item from the README's advanced-functionality list:
a non-blocking, set-associative L1 (per-core, flushed per block) and L2
(shared, persists per kernel) cache hierarchy in front of the memory
controller, plus an on-chip shared memory scratchpad with new LDS/STS
instructions so threads in a block can exchange data without round-tripping
through global memory. Existing matadd/matmul kernels pass unmodified;
new test_shared_memory and test_cache kernels cover the new functionality.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant