Two-Level CPU Cache Simulator with LRU Eviction
Built for BuildCored Orcas — 30-Day Build Challenge
Simulates a two-level CPU cache hierarchy (L1 + L2) with LRU eviction. Each memory access is classified as:
- 🟢 L1 Hit — found in L1 (1 cycle)
- 🟡 L2 Hit — found in L2 (5 cycles)
- 🔴 Miss — went all the way to RAM (20 cycles)
Hit rates per level are displayed in real time as accesses are simulated.
# 1. Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# 2. Install dependencies
pip install rich numpy
pip install mediapipe==0.10.18
# 3. Run the simulator
python main.pyEvery time you open a new terminal, run
source venv/bin/activatebefore running the script.
| Tool | Purpose |
|---|---|
| Python | Cache simulation logic, LRU algorithm |
rich |
Terminal UI — colors, tables, live refresh |
numpy |
(Available for pattern generation/analysis) |
A real CPU has multiple levels of cache:
CPU Core
└── L1 Cache (4–64 KB, ~1 cycle)
└── L2 Cache (256 KB – 4 MB, ~5 cycles)
└── RAM (GBs, ~100 cycles)
└── Storage (TBs, millions of cycles)
On an ARM Cortex-M0+ (common in firmware/embedded):
- Cache miss = 10–100× more cycles than a hit
- Writing cache-friendly code is the #1 firmware performance trick
When the cache is full and needs to load a new address, it evicts (removes) the Least Recently Used entry — the one that hasn't been accessed the longest.
Think of it like a desk with 4 folders (L1 size = 4):
- You can keep 4 files on your desk at once
- When you need a 5th file, you put away whichever you haven't touched the longest
- If you always cycle through exactly 5 files, you'll always have to go to the filing cabinet (RAM)
Pattern: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, ...]
Why it gives 0% L1 hits:
L1 can hold exactly 4 addresses (L1_SIZE = 4). If we cycle through 5 unique addresses, the cache is always one address too small to hold the entire working set.
Step 1: access 0 → MISS (load 0 → cache: {0})
Step 2: access 1 → MISS (load 1 → cache: {0,1})
Step 3: access 2 → MISS (load 2 → cache: {0,1,2})
Step 4: access 3 → MISS (load 3 → cache: {0,1,2,3}) ← FULL
Step 5: access 4 → MISS (evict 0, load 4 → cache: {1,2,3,4})
Step 6: access 0 → MISS (evict 1, load 0 → cache: {2,3,4,0})
... this repeats forever — always a miss!
This is called cache thrashing — a working set just slightly larger than the cache causes constant eviction and re-loading.
Real-world analog: A firmware loop that touches 5 separate data structures when the cache only holds 4. Performance tanks.
Pattern: [0, 1, 0, 1, 0, 1, ...]
Why it gives ~100% L1 hits:
We only access 2 unique addresses (well within L1_SIZE = 4). After the first 2 "cold misses" (first ever access to each address), both addresses permanently live in L1. Every subsequent access is an L1 hit.
Access 0 → MISS (cold miss — first time seeing 0, load into cache)
Access 1 → MISS (cold miss — first time seeing 1, load into cache)
Access 0 → HIT! (0 is in L1)
Access 1 → HIT! (1 is in L1)
Access 0 → HIT! (0 is still in L1)
... every access from here: HIT
Over 20 accesses: 2 cold misses + 18 hits = 90% hit rate (approaches 100% as n grows).
Real-world analog: A tight inner loop with just a counter and an accumulator variable. The compiler keeps them in CPU registers (even faster than L1!). This is what "hot path" optimization means in firmware.
- Cache sim accepts a memory access sequence
- Each access classified as L1 hit, L2 hit, or miss
- L1 and L2 hit rates displayed in real time
- At least 10 accesses simulated (default patterns use 20)
- 0% L1 hit rate pattern (Option 5)
- 100% L1 hit rate pattern (Option 6)
- 60-second screen recording
- README filled in ← you're reading it!
In v2.0, you'll profile real memory access patterns on an ARM Cortex-M0+ and compare them to this simulator's predictions. The goal: validate that the LRU model here matches real cache behavior, and identify firmware hot paths to optimize.