Each stage owns one job. Fetch produces an instruction word, decode produces operands, execute produces a result, memory produces loaded data, and writeback retires it.
Stages communicate only through pipeline registers. No stage reaches into another stage directly.
Register | Data carried | Control carried |
|---|---|---|
IF/ID | Instruction word, PC, PC+4 | Valid bit |
ID/EX | Two operands, immediate, rs/rd names | ALU op, RegWrite, MemRead, MemWrite, Branch |
EX/MEM | ALU result, store data, rd name | RegWrite, MemRead, MemWrite, branch outcome |
MEM/WB | Loaded word or ALU result, rd name | RegWrite, result select |
Control travels with its instruction. A MemRead bit in ID/EX belongs to the instruction in that register, not to the stage.
This pairing is what makes stalls and flushes local decisions. Each register can be held or cleared without touching the others.
A stall freezes the PC and IF/ID and injects a bubble into ID/EX. The bubble is a register with control bits cleared, so it retires as a no-op.
A flush clears the wrong-path registers and gates side effects. A killed store must never write memory and a killed writeback must never write the register file.
Stall: hold PC and IF/ID, inject a bubble into ID/EX.
Flush: clear IF/ID and ID/EX on a taken branch, gate memory and register writes.
Bubble: valid 0 with control 0, safe to retire.
The PC and IF/ID hold, so the same instruction is fetched again. ID/EX takes the bubble while the load advances to MEM.
The instructions in IF/ID and ID/EX came from the wrong path, so both registers are cleared. Their pending writes are gated off.
A five-deep valid pipeline with kill, plus a testbench that checks steady-state count, five-cycle latency and kill clearing.
module pipe_valid (
input logic clk,
input logic rst_n,
input logic in_valid,
input logic kill,
output logic out_valid
);
logic [4:0] v;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) v <= 5'b0;
else begin
v[0] <= in_valid & ~kill;
v[1] <= v[0] & ~kill;
v[2] <= v[1] & ~kill;
v[3] <= v[2] & ~kill;
v[4] <= v[3] & ~kill;
end
end
assign out_valid = v[4];
endmodule