A configurable cache memory simulator written in Python, built for a Computer Architecture course at Universidade de Caxias do Sul. It replays a trace of memory accesses against a simulated cache and reports hit rates, main memory traffic, and average access time.
Cache size, block size, associativity, write policy, and replacement policy are all configurable, so the same trace can be run under different configurations to measure how each parameter affects performance.
- Python 3.8+
- No external dependencies (standard library only)
git clone https://github.com/YasmimAr/cache-simulator.git
cd cache-simulatorpython3 cache_simulator.py <write> <block> <lines> <assoc> <hit> <repl> <t_read> [t_write]The trace file is not passed as an argument. The program always reads
oficial.cache from the same directory as the script.
| # | Parameter | Description |
|---|---|---|
| 1 | write |
Write policy: 0 = write-through, 1 = write-back |
| 2 | block |
Block/line size in bytes (power of 2) |
| 3 | lines |
Total number of cache lines (power of 2) |
| 4 | assoc |
Associativity (power of 2, must be ≤ number of lines) |
| 5 | hit |
Hit time, in ns |
| 6 | repl |
Replacement policy: LRU or RANDOM |
| 7 | t_read |
Main memory read time, in ns |
| 8 | t_write |
(optional) Main memory write time, in ns. Defaults to t_read |
Total cache size is block × lines bytes. Setting assoc to 1 gives a
direct-mapped cache; setting it equal to lines gives a fully associative
cache. Every mapping scheme is therefore reachable from the same simulator by
varying a single parameter.
Running with no arguments (or the wrong number of them) prints the usage text.
oficial.cache contains one memory access per line:
<hex_address> <operation>
hex_address— 32-bit address in hexadecimaloperation—Rfor read,Wfor write
Blank lines are ignored. Malformed lines abort the run and report the offending line number. Example:
00003A1C R
00003A20 W
0000F004 R
Write-back, 8 KB cache (64 lines × 128 bytes), 4-way, 1 ns hit time, 10 ns main memory:
python3 cache_simulator.py 1 128 64 4 1 LRU 10Write-through, 64-byte blocks, 4096 lines, 2-way, 10 ns hit time, 80 ns main memory:
python3 cache_simulator.py 0 64 4096 2 10 LRU 80The simulator echoes the configuration it ran with, then reports the results:
PASTE REAL OUTPUT HERE
The simulator was used to run a series of experiments over a fixed 51,200-access trace, varying one parameter at a time. The full study, including the remaining experiments and complete result tables, is in the report linked below.
Cache size fixed at 8 KB, 128-byte blocks, write-back, LRU:
| Ways | Hit rate | Avg. access time | MM reads | MM writes |
|---|---|---|---|---|
| 1 | 74.96% | 19.03 ns | 12,822 | 2,048 |
| 2 | 87.94% | 11.24 ns | 6,176 | 1,026 |
| 4 | 92.93% | 8.24 ns | 3,621 | 515 |
| 8 | 89.93% | 10.04 ns | 5,154 | 1,026 |
| 16 | 99.91% | 4.05 ns | 44 | 4 |
| 32 | 99.91% | 4.05 ns | 44 | 4 |
| 64 | 99.91% | 4.05 ns | 44 | 4 |
Conflict misses drop sharply as associativity grows, and the curve flattens completely at 16 ways, where the working set stops colliding. The dip at 8 ways is not a measurement error: set-associative LRU is not a stack algorithm, so adding ways can occasionally increase misses for a particular access pattern.
Cache size fixed at 8 KB, write-through, LRU, 2-way:
| Block size | Hit rate | Avg. access time | MM reads |
|---|---|---|---|
| 8 B | 84.83% | 13.10 ns | 1,621 |
| 16 B | 84.83% | 13.10 ns | 1,621 |
| 64 B | 80.90% | 15.46 ns | 3,633 |
| 256 B | 74.97% | 19.02 ns | 6,673 |
| 1 KB | 64.00% | 25.60 ns | 12,288 |
| 4 KB | 56.00% | 30.40 ns | 16,384 |
Larger blocks exploit spatial locality, but with a fixed 8 KB capacity they also reduce the number of blocks resident at once. On this trace the second effect dominates throughout, and the smallest blocks win.
Same configurations run under both write policies, LRU replacement:
| Cache | Block | Ways | WT total | WB total |
|---|---|---|---|---|
| 8 KB | 64 B | 2 | 9,777 | 6,196 |
| 8 KB | 64 B | 4 | 8,755 | 3,641 |
| 8 KB | 128 B | 2 | 10,783 | 7,202 |
| 8 KB | 128 B | 4 | 9,250 | 4,136 |
| 16 KB | 64 B | 2 | 6,200 | 3,641 |
| 16 KB | 64 B | 4 | 6,200 | 3,130 |
| 16 KB | 128 B | 2 | 7,206 | 4,647 |
| 16 KB | 128 B | 4 | 6,184 | 3,114 |
Write-through propagates every write to main memory, so its write count is pinned at 6,144 — one per write in the trace — regardless of configuration. Write-back coalesces repeated writes to the same block into a single memory write when the line is evicted, averaging 771 writes across these runs, an 8× reduction in write traffic.
8 KB cache, 128-byte blocks, 16-way, write-back, LRU: 99.91% hit rate at 4.05 ns average access time. This is the best result under a performance-first criterion; a design weighing hardware cost would not necessarily pick 16-way.
The full technical report — methodology, all result tables, and discussion — is
available in docs/report.pdf. It is written in Portuguese.
A few modeling decisions worth stating explicitly, since simulators differ on these and results are not comparable across tools otherwise:
- Allocation policy is tied to write policy. Write-back uses write-allocate: a write miss fetches the block into the cache and marks it dirty. Write-through uses no-write-allocate: a write miss goes straight to main memory without allocating a line. This is the conventional pairing, but the two dimensions are independent in principle.
- Write-backs are assumed to be buffered. Evicting a dirty line counts as main memory traffic, but adds no latency to the access being served.
- Dirty lines are flushed at the end of the run. Every valid dirty line still resident when the trace finishes counts as one additional main memory write, so the reported write traffic accounts for all modified data rather than only what was evicted mid-run.
- Addresses are treated as 32 bits wide.
RANDOMis not seeded, so runs using random replacement vary between executions.LRUis fully deterministic.
This was a university assignment, kept close to its original form. It simulates a single level of cache with a unified instruction/data path, and models timing as a simple sum of hit and main memory latencies — no pipelining, no prefetching, no multi-level hierarchy.
Yasmim Arsego and Eduardo Machado Lopes — Fundamentos de Arquitetura de Computadores, Universidade de Caxias do Sul, 2026.