Systems / 2025
Five-Stage CPU Emulator
A C implementation of a pipelined processor with forwarding, instruction and data caches, and LRU replacement.

Overview
I implemented a processor emulator in C to make the behavior of a pipelined CPU concrete. The project models five execution stages and the coordination required to keep instructions, data, and control moving through them correctly.
The emulator includes separate instruction and data caching, a least-recently-used replacement policy, and value forwarding between pipeline stages to reduce unnecessary stalls and bubbles.
Problem and motivation
Pipelining improves throughput by overlapping work, but it also introduces dependencies that a sequential implementation can ignore. I wanted to understand those interactions at the level where one incorrect control decision can corrupt every instruction behind it.
The challenge was not simply executing instructions. It was preserving architectural state while several instructions occupied different stages at the same time.
Approach
I treated each stage as an explicit part of the machine state and advanced the pipeline one cycle at a time. That structure made hazards, stalls, cache behavior, and forwarding decisions visible instead of hiding them inside a single instruction function.
The implementation focused on four responsibilities:
- maintaining the state associated with each pipeline stage;
- forwarding available values rather than waiting for a full write-back;
- stalling only when a dependency could not be resolved safely; and
- modeling instruction and data cache access with LRU replacement.
Architecture
The five-stage flow separates fetch, decode, execute, memory, and write-back behavior. Pipeline registers preserve the information each downstream stage needs. Forwarding paths inspect in-flight destinations and provide newer values when the register file has not yet been updated.
Cache bookkeeping is kept separate from instruction semantics. This made it possible to reason about a cache hit or miss without coupling replacement policy to the rest of the processor.
Challenges and tradeoffs
The hardest part was sequencing state updates. A pipeline cycle conceptually reads the old state and produces the next state; updating shared structures too early creates order-dependent bugs. Keeping those phases distinct made the emulator easier to debug.
I favored explicit data flow over compact code. The result uses more structures, but it makes pipeline behavior traceable and reduces ambiguity when diagnosing a hazard.
Result
The completed emulator simulates a five-stage pipeline, uses instruction and data caches with LRU replacement, and forwards values between stages to reduce avoidable stalls. The source remains private.
What I learned
The project sharpened my understanding of temporal state: correctness depends not only on a value, but also on when that value becomes available and which stage is allowed to consume it. That lesson carries directly into concurrent and embedded systems.