Digital Design / Module 12: Projects and interview preparation
66. Major project: small RISC-style CPU
Design a working processor of your own.
See it move
The goal
Design a small processor of your own. It touches almost every topic in this course: register files, an ALU, FSMs or , memories, and hazards. Few things will impress an interviewer more than explaining a CPU you built yourself.
Start with an instruction set
Keep it small and original, and write it down first: instruction formats, registers, and behaviour. The animation above shows a tiny teaching machine: four registers and five instructions, enough to run a real loop.
The classic stages
- Fetch: read the instruction at the program counter.
- Decode: work out the operation, and read registers.
- Execute: use the ALU, or decide a branch.
- Memory: load or store data.
- Write back: put the result in a register.
Step 1: single cycle
All five stages happen in one long clock cycle. It's simple, and easy to get right. It's slow, as the clock must fit the longest instruction. Get all your instructions working here first.
Step 2: pipeline
Put registers between stages, to run a higher clock. One instruction finishes per cycle, in the ideal case. Then come the hazards:
| Hazard | Example | Fix |
|---|---|---|
| Data | Next instruction reads a result not written yet | Forward from a later stage |
| Load-use | Instruction reads a value still coming from memory | Stall one cycle |
| Control | Branch changes the next instruction | Flush, or predict |
Verifying it
- Write test programs with known final register values, and check them automatically.
- Build a simple instruction-set simulator in Python, and compare both, instruction by instruction.
- Test hazards directly: back-to-back dependent instructions, branches after loads, and so on.
Common mistake
Starting with the pipeline. Debugging a pipeline when instructions aren't correct yet is painful. Build the single-cycle version and a good test suite first. Those same tests will then catch your pipeline bugs immediately.
Key ideas
- Fetch, decode, execute, memory, write back.
- Begin single-cycle; then pipeline it.
- Handle hazards by forwarding and stalling.
Your exercise
Interview questions
coreData hazard solutions?
Forwarding where possible, otherwise a stall.
basicName the five classic stages of a simple pipelined CPU and what each one does.
Fetch reads the instruction at the program counter. Decode works out the operation and reads the source registers. Execute uses the ALU, or decides a branch. Memory loads or stores data if the instruction needs it. Write back puts the result into the destination register.
basicEach of five stages takes 3 ns of logic, and each register adds 0.3 ns. What clock period does a single-cycle design need, and what does a five-stage pipeline need? What is the ideal speed-up?
Single cycle has to fit all five stages plus one register: 5 x 3 + 0.3 = 15.3 ns. The pipeline only has to fit one stage plus one register: 3 + 0.3 = 3.3 ns. Ideally one instruction finishes per cycle in both, so the speed-up is 15.3 / 3.3, about 4.6. It is less than 5 because every stage pays the register overhead, and hazards reduce it further.
coreA five-stage pipeline runs 30 instructions. There are 2 load-use stalls and 5 taken branches, each flushing 2 instructions. How many cycles does it take?
The first instruction needs 5 cycles to finish, and each later one adds 1, so 30 instructions take 30 + 4 = 34 cycles with no hazards. Each load-use stall adds 1 cycle, so +2. Each taken branch flushes 2 instructions, so +10. The total is 34 + 2 + 10 = 46 cycles.
coreIn this forwarding mux, why is the EX/MEM result checked before the MEM/WB result?
always @(*) begin
if (exmem_we && exmem_rd == idex_rs1)
alu_a = exmem_result; // one stage ahead
else if (memwb_we && memwb_rd == idex_rs1)
alu_a = memwb_result; // two stages ahead
else
alu_a = idex_rs1_val; // register file
endIf two instructions in flight both write the register being read, the one in EX/MEM is the more recent in program order, so its value is the correct one. Checking MEM/WB first would forward an older value and break programs like two back-to-back writes to the same register followed by a read. Only if the nearer stage is not writing that register does the older one apply, and only if neither is does the register file value apply.
coreWhy can forwarding not remove the stall after a load whose result is used by the next instruction?
The loaded value only exists at the end of the memory stage. The next instruction needs it at the start of execute, which is the same clock cycle, so there is nothing yet to forward. The pipeline has to hold fetch and decode for one cycle and send a bubble down the pipe. One cycle later, the value is in the write-back stage and forwarding can supply it.
coreYour pipelined CPU gives a wrong final register value on a test program. How do you find the faulty instruction quickly?
Compare against a reference, either a single-cycle version of the same CPU or an instruction-set model, instruction by instruction rather than only at the end. Log each retired instruction with its program counter, destination register and value in both, and stop at the first difference. That points straight at the instruction where a hazard was mishandled. Final values alone can even match by luck while intermediate results are wrong.
advancedBranches are decided in execute and cost 2 cycles when taken. How could you reduce that, and what does it cost?
Moving the branch decision into decode cuts the penalty to 1 cycle, because only one wrong instruction has been fetched. That needs a comparator and target adder in decode, and forwarding paths into decode, which lengthen that stage and may need new stalls when the branch depends on a value still being computed. Branch prediction is another option, fetching the predicted path and flushing only on a wrong guess. Each technique adds hardware that must match the simple reference core exactly.
advancedIn a five-stage pipeline, an instruction in decode reads a register in the same cycle that write back writes it. What must the register file do, and what happens if it does not?
The register file should write first and read second within the cycle, or bypass the incoming write data to the read port, so decode sees the new value. If it returns the old value, the instruction three behind the writer gets stale data, and you need a third forwarding path to cover that distance. Many designs choose write-before-read to keep the forwarding logic to two sources. Whatever you choose, state it in the design and test that exact spacing.