Stack Overflow: View Question
Tags: caching, vhdl, cpu-architecture, fpga
Score: -2 | Views: 107
The asker is building a classic 5-stage RISC pipeline (IF/ID/EX/MEM/WB) in VHDL, and it works when the instruction memory behaves as a combinational ROM — present an address, get the instruction back in the same cycle. The trouble starts when they swap in a real block RAM-backed instruction cache. On every FPGA fabric (Xilinx, Intel, Lattice, Gowin), BRAM is fundamentally synchronous: the address is latched on a clock edge, and the data appears on the output port one cycle later. That extra cycle of latency breaks the textbook fetch stage, where the PC and the instruction were assumed to update in lockstep.
What makes this interesting is that the fix isn't just "add a register" — it forces you to re-think where the IF/ID boundary actually lives and how stalls, branches, and resets propagate.
Why it's hard
- The PC and the fetched instruction are now out of phase. When the PC is X on cycle N, the instruction at address X doesn't appear at the BRAM data port until cycle N+1.
- Branches and jumps from EX/MEM need to redirect the PC, and the in-flight fetch (already addressed BRAM last cycle) must be squashed without corrupting the IF/ID register.
- Stalls from load-use hazards have to hold both the PC and the BRAM address constant, otherwise you fetch — and lose — an instruction during the bubble.
- Reset is awkward: BRAM contents around address 0 will be read on the first real cycle, so you typically need to inject a NOP into IF/ID for one cycle after reset.
A direction toward a solution
Treat the BRAM's internal output register as the IF/ID pipeline register itself, rather than adding another flip-flop after it. Then:
- Drive the BRAM address with the next PC (the combinational mux output), not the registered PC. The address-latch flip-flop inside the BRAM is your PC register.
- On a branch mispredict from EX, assert a one-cycle
flush_IF that forces the instruction emerging from BRAM into a NOP at the ID stage (mux at the IF/ID output, not before it).
- On a stall, drive the BRAM's
ena (clock-enable) low so both the address register and the output register hold. This keeps the fetched instruction parked at the ID input until the hazard clears.
- Add a 1-bit "valid" flag through IF/ID so post-reset and post-flush cycles inject NOPs cleanly.
Gotchas
- Don't try to "go around" the BRAM with a bypass mux on miss-predict — you can't, the data isn't there yet. You must squash, not substitute.
- Cache misses (not just BRAM latency) need a separate
i_ready handshake that stalls the whole pipeline; conflating the two will bite you when you add a real cache controller.
- Watch for inferred vs. instantiated BRAM: some tools won't infer block RAM unless you use the exact read-first/write-first template, and a registered output adds a second cycle of latency you may not want.