commit 57f97ea9a2d53e0e618a00e05e2eb24a5fc10c6b
parent 0b72748e55a5126780ae81c30ef9b3509c54ff93
Author: Vineet Kumar <git@vineetk.net>
Date: Thu, 6 Aug 2026 21:17:51 +0530
fix pipeline by handling hazards, aligning data-memory, and registered reads
Diffstat:
6 files changed, 176 insertions(+), 85 deletions(-)
diff --git a/rtl/rv32g_hart.sv b/rtl/rv32g_hart.sv
@@ -19,17 +19,17 @@ module rv32g_hart
output exception_t exception);
// x0 to x31
- reg_t regfile [0:31];
- reg_t next_pc;
+ reg_t regfile [0:31];
+ reg_t next_pc;
// pipeline registers
- ifid_reg_t ifid_reg, ifid_next;
- idex_reg_t idex_reg;
- exmem_reg_t exmem_reg;
- memwb_reg_t memwb_reg;
+ ifid_reg_t ifid_reg;
+ idex_reg_t idex_reg;
+ exmem_reg_t exmem_reg;
+ memwb_reg_t memwb_reg;
// temporary fetch signals
- logic [31:0] fetch_inst;
+ logic [31:0] fetch_addr, fetch_inst;
reg_t pc_reg;
// temporary decode signals
@@ -38,39 +38,46 @@ module rv32g_hart
logic [4:0] dec_rs1_num, dec_rs2_num, dec_rd_num;
// temporary execute signals
- reg_t ex_alu_out;
- logic ex_branch_taken;
+ reg_t ex_rs1_val, ex_rs2_val, ex_alu_out;
+ logic ex_branch_taken;
// temporary memory signals
- logic [31:0] mem_mem_out;
+ memop_t mem_memop;
+ logic [31:0] mem_mem_out;
+ reg_t exmem_value, memwb_value;
- // hazards
- logic if_stall;
+ // temporary writeback signals
+ logic wb_valid, wb_we;
- assign if_stall = !imem_valid;
+ // hazards and control flow
+ logic dec_uses_rs1, dec_uses_rs2;
+ logic load_use_stall;
+ logic redirect_valid;
+ reg_t redirect_target;
// =============
// === FETCH ===
// =============
-
+
assign pc_out = pc_reg;
+ // re-read decoded instruction on load-use stall
+ assign fetch_addr = load_use_stall ? ifid_reg.pc : pc_reg;
- stage_fetch u_fetch(.clk, .rstN, .flush_i(idex_reg.ctrl.fence_flush), .pc(pc_reg), .imem_addr, .imem_data, .inst(fetch_inst));
+ stage_fetch u_fetch(.clk, .rstN, .flush_i(idex_reg.valid && idex_reg.ctrl.fence_flush),
+ .pc(fetch_addr), .imem_addr, .imem_data, .inst(fetch_inst));
// IF/ID pipeline register
- always_comb begin
- ifid_next = ifid_reg; // holds by default
- if (imem_valid) begin
- ifid_next.pc = pc_reg;
- ifid_next.inst = fetch_inst;
- end
- end
always_ff @(posedge clk or negedge rstN) begin
if (!rstN) begin
pc_reg <= BASE_ADDR;
ifid_reg <= '0;
- end else if (!if_stall) begin
+ end else if (redirect_valid) begin
+ pc_reg <= redirect_target;
+ ifid_reg <= '0;
+ end else if (!load_use_stall) begin
pc_reg <= next_pc;
- ifid_reg <= ifid_next;
+ ifid_reg.valid <= 1'b1;
+ ifid_reg.pc <= pc_reg;
+ ifid_reg.inst <= fetch_inst;
end
end
@@ -78,51 +85,95 @@ module rv32g_hart
// === DECODE ===
// ==============
- stage_decode u_decode(.clk, .rstN, .regfile, .inst(ifid_reg.inst),
+ stage_decode u_decode(.clk, .rstN, .regfile, .inst(fetch_inst),
+ .wb_we, .wb_rd_num(memwb_reg.rd_num), .wb_data(memwb_value),
.pc_in(ifid_reg.pc), .pc_out(dec_pc),
.ctrl(dec_ctrl), .imm(dec_imm),
.rs1_num(dec_rs1_num), .rs2_num(dec_rs2_num),
.rs1_val(dec_rs1_val), .rs2_val(dec_rs2_val),
.rd_num(dec_rd_num));
+
+ // assign control hazard signals
+ always_comb begin
+ dec_uses_rs1 = dec_ctrl.aluop1src == ALUOP1_REG;
+ dec_uses_rs2 = dec_ctrl.aluop2src == ALUOP2_REG ||
+ dec_ctrl.memop == MEM_SB ||
+ dec_ctrl.memop == MEM_SH ||
+ dec_ctrl.memop == MEM_SW;
+
+ load_use_stall = idex_reg.valid && idex_reg.ctrl.wbsrc == WB_MEM &&
+ idex_reg.rd_num != 0 && ifid_reg.valid && imem_valid &&
+ ((dec_uses_rs1 && dec_rs1_num == idex_reg.rd_num) ||
+ (dec_uses_rs2 && dec_rs2_num == idex_reg.rd_num));
+ end
+
// ID/EX pipeline register
always_ff @(posedge clk) begin
if (!rstN) begin
// NOP by default
idex_reg <= '0;
idex_reg.pc <= BASE_ADDR;
- end else if (if_stall) begin
- // insert NOP
+ end else if (redirect_valid || load_use_stall) begin
+ // NOP bubble
idex_reg <= '0;
- idex_reg.pc <= pc_reg;
end else begin
// latch decode outputs in normal operation
+ idex_reg.valid <= ifid_reg.valid && imem_valid;
idex_reg.ctrl <= dec_ctrl;
idex_reg.pc <= dec_pc;
idex_reg.imm <= dec_imm;
- idex_reg.rs1_val <= dec_rs1_val;
- idex_reg.rs2_val <= dec_rs2_val;
idex_reg.rs1_num <= dec_rs1_num;
idex_reg.rs2_num <= dec_rs2_num;
idex_reg.rd_num <= dec_rd_num;
end
end
- // always_ff @(posedge clk) begin
- // // TODO handle exceptions. might need moving.
- // if (idex_reg.ctrl.exception != EXC_NONE) begin
- // end
- // end
- // TODO find out what else needs to be done in between stages like exceptions
-
// ===============
// === EXECUTE ===
// ===============
+ // select EX stage operands via forwarding from EX/MEM and MEM/WB pipeline registers
+ always_comb begin
+ unique case (exmem_reg.wbsrc)
+ WB_PC: exmem_value = exmem_reg.pc + 4;
+ WB_ALU: exmem_value = exmem_reg.alu_out;
+ WB_IMM: exmem_value = exmem_reg.imm;
+ default: exmem_value = '0;
+ endcase
+
+ unique case (memwb_reg.wbsrc)
+ WB_PC: memwb_value = memwb_reg.pc + 4;
+ WB_ALU: memwb_value = memwb_reg.alu_out;
+ WB_MEM: memwb_value = mem_mem_out;
+ WB_IMM: memwb_value = memwb_reg.imm;
+ default: memwb_value = '0;
+ endcase
+
+ ex_rs1_val = (idex_reg.rs1_num == 0) ? '0 : dec_rs1_val;
+ ex_rs2_val = (idex_reg.rs2_num == 0) ? '0 : dec_rs2_val;
+
+ if (idex_reg.rs1_num != 0) begin
+ if (exmem_reg.valid && exmem_reg.wbsrc != WB_NONE &&
+ exmem_reg.wbsrc != WB_MEM && exmem_reg.rd_num == idex_reg.rs1_num)
+ ex_rs1_val = exmem_value;
+ else if (wb_we && memwb_reg.rd_num == idex_reg.rs1_num)
+ ex_rs1_val = memwb_value;
+ end
+
+ if (idex_reg.rs2_num != 0) begin
+ if (exmem_reg.valid && exmem_reg.wbsrc != WB_NONE &&
+ exmem_reg.wbsrc != WB_MEM && exmem_reg.rd_num == idex_reg.rs2_num)
+ ex_rs2_val = exmem_value;
+ else if (wb_we && memwb_reg.rd_num == idex_reg.rs2_num)
+ ex_rs2_val = memwb_value;
+ end
+ end
+
stage_execute u_execute(.clk,
.imm(idex_reg.imm),
.pc(idex_reg.pc),
- .rs1_val(idex_reg.rs1_val),
- .rs2_val(idex_reg.rs2_val),
+ .rs1_val(ex_rs1_val),
+ .rs2_val(ex_rs2_val),
.op(idex_reg.ctrl.aluop),
.op1src(idex_reg.ctrl.aluop1src),
.op2src(idex_reg.ctrl.aluop2src),
@@ -135,8 +186,9 @@ module rv32g_hart
exmem_reg <= '0;
exmem_reg.pc <= BASE_ADDR;
end else begin
+ exmem_reg.valid <= idex_reg.valid;
exmem_reg.alu_out <= ex_alu_out;
- exmem_reg.rs2_val <= idex_reg.rs2_val;
+ exmem_reg.rs2_val <= ex_rs2_val;
exmem_reg.pc <= idex_reg.pc;
exmem_reg.imm <= idex_reg.imm;
exmem_reg.rd_num <= idex_reg.rd_num;
@@ -145,20 +197,50 @@ module rv32g_hart
exmem_reg.memop <= idex_reg.ctrl.memop;
exmem_reg.wbsrc <= idex_reg.ctrl.wbsrc;
exmem_reg.pcsrc <= idex_reg.ctrl.pcsrc;
+ exmem_reg.exception <= idex_reg.ctrl.exception;
+ exmem_reg.fence_flush <= idex_reg.ctrl.fence_flush;
+ end
+ end
+
+ // resolve control transfers in EX stage and select the next PC
+ always_comb begin
+ redirect_valid = 1'b0;
+ redirect_target = idex_reg.pc + 4;
+
+ if (idex_reg.valid) begin
+ if (idex_reg.ctrl.fence_flush) begin
+ redirect_valid = 1'b1;
+ end else begin
+ unique case (idex_reg.ctrl.pcsrc)
+ PC_IMM: begin
+ if (idex_reg.ctrl.branchop == BRANCH_NONE || ex_branch_taken) begin
+ redirect_valid = 1'b1;
+ redirect_target = idex_reg.pc + idex_reg.imm;
+ end
+ end
+ PC_ALU: begin
+ redirect_valid = 1'b1;
+ redirect_target = ex_alu_out & ~32'b1;
+ end
+ default: ;
+ endcase
+ end
end
- // if (!dmem_valid && ctrl.memop != MEM_NONE) begin
- // // TODO stall
- // end
+
+ next_pc = redirect_valid ? redirect_target : pc_reg + 4;
end
- // TODO stall when dmem_valid is 0 and ctrl.memop != 0. find out what else needs to be done in between stages
// ==============
// === MEMORY ===
// ==============
- stage_memory u_mem(.memop(exmem_reg.memop),
+ assign mem_memop = exmem_reg.valid ? exmem_reg.memop : MEM_NONE;
+
+ stage_memory u_mem(.memop(mem_memop),
.alu_out(exmem_reg.alu_out),
.rs2_val(exmem_reg.rs2_val),
+ .resp_memop(memwb_reg.memop),
+ .resp_addr(memwb_reg.alu_out),
.mem_out(mem_mem_out),
.dmem_addr, .dmem_rdata, .dmem_wdata, .dmem_we, .dmem_be);
// MEM/WB pipeline register
@@ -167,44 +249,35 @@ module rv32g_hart
memwb_reg <= '0;
memwb_reg.pc <= BASE_ADDR;
end else begin
+ memwb_reg.valid <= exmem_reg.valid;
memwb_reg.alu_out <= exmem_reg.alu_out;
- memwb_reg.mem_out <= mem_mem_out;
memwb_reg.pc <= exmem_reg.pc;
memwb_reg.imm <= exmem_reg.imm;
memwb_reg.rd_num <= exmem_reg.rd_num;
+ memwb_reg.memop <= exmem_reg.memop;
memwb_reg.wbsrc <= exmem_reg.wbsrc;
+ memwb_reg.exception <= exmem_reg.exception;
end
- // else ; // TODO I need to think of what else should happen
end
- // TODO find out what else needs to be done in between stages
// =================
// === WRITEBACK ===
// =================
- stage_writeback u_wb(.clk, .rstN, .regfile,
+ always_comb begin
+ wb_valid = memwb_reg.valid &&
+ (memwb_reg.wbsrc != WB_MEM || dmem_valid);
+ wb_we = wb_valid && memwb_reg.wbsrc != WB_NONE && memwb_reg.rd_num != 0;
+ end
+
+ stage_writeback u_wb(.clk, .rstN, .regfile, .valid(wb_valid),
.wbsrc(memwb_reg.wbsrc),
.rd_num(memwb_reg.rd_num),
.pc(memwb_reg.pc),
.imm(memwb_reg.imm),
.alu_out(memwb_reg.alu_out),
- .mem_out(memwb_reg.mem_out));
-
- // TODO move to writeback stage or as separate stage?
- always_comb begin
- unique case (exmem_reg.pcsrc)
- // normal case
- PC_SEQ: next_pc = exmem_reg.pc + 4;
- // branch or jal
- PC_IMM: begin
- if ((exmem_reg.branchop != BRANCH_NONE) ? exmem_reg.branch_taken : 1'b1) next_pc = exmem_reg.pc + exmem_reg.imm;
- else next_pc = exmem_reg.pc + 4;
- end
- // jalr
- PC_ALU: next_pc = exmem_reg.alu_out & ~32'b1; // spec says to clear LSB and /may/ raise an exception
- endcase
- end
+ .mem_out(mem_mem_out));
- assign exception = (idex_reg.ctrl.exception != EXC_NONE) ? idex_reg.ctrl.exception : EXC_NONE;
+ assign exception = memwb_reg.valid ? memwb_reg.exception : EXC_NONE;
endmodule: rv32g_hart
diff --git a/rtl/rv32g_pkg.sv b/rtl/rv32g_pkg.sv
@@ -180,22 +180,23 @@ package rv32g_pkg;
// pipeline registers
typedef struct packed {
+ logic valid;
reg_t pc;
logic [31:0] inst;
} ifid_reg_t;
typedef struct packed {
+ logic valid;
ctrl_t ctrl;
reg_t pc;
logic [31:0] imm;
- reg_t rs1_val;
- reg_t rs2_val;
logic [4:0] rs1_num;
logic [4:0] rs2_num;
logic [4:0] rd_num;
} idex_reg_t;
typedef struct packed {
+ logic valid;
reg_t alu_out;
reg_t rs2_val;
reg_t pc;
@@ -206,14 +207,18 @@ package rv32g_pkg;
memop_t memop;
wbsrc_t wbsrc;
pcsrc_t pcsrc;
+ exception_t exception;
+ logic fence_flush;
} exmem_reg_t;
typedef struct packed {
+ logic valid;
reg_t alu_out;
- reg_t mem_out;
reg_t pc;
reg_t imm;
logic [4:0] rd_num;
+ memop_t memop;
wbsrc_t wbsrc;
+ exception_t exception;
} memwb_reg_t;
endpackage: rv32g_pkg
diff --git a/rtl/stage_decode.sv b/rtl/stage_decode.sv
@@ -4,6 +4,9 @@ module stage_decode
input logic rstN,
input logic [31:0] inst,
input logic [31:0] regfile [0:31],
+ input logic wb_we,
+ input logic [4:0] wb_rd_num,
+ input logic [31:0] wb_data,
output ctrl_t ctrl,
output logic [31:0] imm,
output logic [31:0] rs1_val,
@@ -276,8 +279,10 @@ module stage_decode
rs1_val <= '0;
rs2_val <= '0;
end else begin
- rs1_val <= regfile[rs1_num];
- rs2_val <= regfile[rs2_num];
+ rs1_val <= (rs1_num == 0) ? '0 :
+ ((wb_we && wb_rd_num == rs1_num) ? wb_data : regfile[rs1_num]);
+ rs2_val <= (rs2_num == 0) ? '0 :
+ ((wb_we && wb_rd_num == rs2_num) ? wb_data : regfile[rs2_num]);
end
end
diff --git a/rtl/stage_memory.sv b/rtl/stage_memory.sv
@@ -4,6 +4,9 @@ module stage_memory
input logic [31:0] alu_out,
input reg_t rs2_val,
+ input memop_t resp_memop,
+ input logic [31:0] resp_addr,
+
output logic [31:0] dmem_addr,
input logic [31:0] dmem_rdata,
output logic [31:0] dmem_wdata,
@@ -23,25 +26,28 @@ module stage_memory
dmem_be = 0;
mem_out = 0;
- unique case (alu_out[1:0])
+ unique case (resp_addr[1:0])
2'b00: bdata = dmem_rdata[7:0];
2'b01: bdata = dmem_rdata[15:8];
2'b10: bdata = dmem_rdata[23:16];
2'b11: bdata = dmem_rdata[31:24];
endcase
- unique case (alu_out[1])
+ unique case (resp_addr[1])
1'b0: hdata = dmem_rdata[15:0];
1'b1: hdata = dmem_rdata[31:16];
endcase
- unique case (memop)
- MEM_NONE: mem_out = '0;
+ unique case (resp_memop)
MEM_LB: mem_out = {{24{bdata[7]}}, bdata};
MEM_LH: mem_out = {{16{hdata[15]}}, hdata};
MEM_LW: mem_out = dmem_rdata;
MEM_LBU: mem_out = {24'b0, bdata};
MEM_LHU: mem_out = {16'b0, hdata};
+ default: mem_out = '0;
+ endcase
+
+ unique case (memop)
MEM_SB: begin
unique case (alu_out[1:0])
2'b00: begin
@@ -81,6 +87,7 @@ module stage_memory
dmem_wdata = rs2_val;
dmem_we = 1;
end
+ default: ;
endcase
end
diff --git a/rtl/stage_writeback.sv b/rtl/stage_writeback.sv
@@ -2,6 +2,7 @@ module stage_writeback
import rv32g_pkg::*;
(input logic clk,
input logic rstN,
+ input logic valid,
input wbsrc_t wbsrc,
input logic [4:0] rd_num,
input reg_t pc,
@@ -13,7 +14,7 @@ module stage_writeback
always_ff @(posedge clk or negedge rstN) begin
if (!rstN) begin
foreach (regfile[i]) regfile[i] <= '0;
- end else if (rd_num != 0) begin
+ end else if (valid && rd_num != 0) begin
unique case (wbsrc)
WB_NONE: ;
WB_PC: regfile[rd_num] <= pc + 4;
diff --git a/rtl/tb_rv32g.sv b/rtl/tb_rv32g.sv
@@ -78,30 +78,30 @@ module tb_rv32g
always_ff @(posedge clk or negedge rstN) begin
if (!rstN)
seen_first_real_inst <= 0;
- else if (dut.ifid_reg.inst != 32'b0)
+ else if (dut.fetch_inst != 32'b0)
seen_first_real_inst <= 1;
end
parameter int MAX_CYCLES = 10000;
always_ff @(posedge clk) begin
$display("IF: pc: %h, next_pc: %h, imem_valid: %d, imem[%h]: %h, inst: %h",
- dut.ifid_reg.pc, dut.next_pc, dut.imem_valid, dut.imem_addr, dut.imem_data, dut.ifid_reg.inst);
+ dut.ifid_reg.pc, dut.next_pc, dut.imem_valid, dut.imem_addr, dut.imem_data, dut.fetch_inst);
$display("ID: ctrl: %h, imm: %h, rs1_val: %h, rs2_val: %h, rd_num: %d",
- dut.idex_reg.ctrl, dut.idex_reg.imm, dut.idex_reg.rs1_val, dut.idex_reg.rs2_val, dut.idex_reg.rd_num);
+ dut.idex_reg.ctrl, dut.idex_reg.imm, dut.dec_rs1_val, dut.dec_rs2_val, dut.idex_reg.rd_num);
$display("EX: op: %h, op1src: %d, op2src: %d, branchop: %d, out: %h, branch_taken: %d, rs1_val: %h, rs2_val: %h",
dut.idex_reg.ctrl.aluop, dut.idex_reg.ctrl.aluop1src, dut.idex_reg.ctrl.aluop2src,
dut.idex_reg.ctrl.branchop, dut.exmem_reg.alu_out, dut.exmem_reg.branch_taken,
- dut.idex_reg.rs1_val, dut.idex_reg.rs2_val);
+ dut.ex_rs1_val, dut.ex_rs2_val);
$display("MEM: memop: %h, dmem_addr: %h, dmem_rdata: %h, dmem_wdata: %h, dmem_we: %d, dmem_be: %b, mem_out: %h",
- dut.exmem_reg.memop, dut.dmem_addr, dut.dmem_rdata, dut.dmem_wdata, dut.dmem_we, dut.dmem_be, dut.memwb_reg.mem_out);
+ dut.exmem_reg.memop, dut.dmem_addr, dut.dmem_rdata, dut.dmem_wdata, dut.dmem_we, dut.dmem_be, dut.mem_mem_out);
$display("WB: wbsrc: %d, rd_num: %d, pc: %h, imm: %h, alu_out: %h, mem_out: %h",
dut.memwb_reg.wbsrc, dut.memwb_reg.rd_num, dut.memwb_reg.pc, dut.memwb_reg.imm,
- dut.memwb_reg.alu_out, dut.memwb_reg.mem_out);
+ dut.memwb_reg.alu_out, dut.mem_mem_out);
if (dut.idex_reg.ctrl.exception != EXC_NONE) begin
unique case (dut.idex_reg.ctrl.exception)
EXC_NONE: ;
- EXC_ILL_INST: $display("Exception: illegal instruction, inst: %h", dut.ifid_reg.inst);
+ EXC_ILL_INST: $display("Exception: illegal instruction, inst: %h", dut.fetch_inst);
EXC_CALL: $display("Exception: ECALL");
EXC_BKPT: $display("Exception: BREAK");
endcase
@@ -116,9 +116,9 @@ module tb_rv32g
$finish();
end
- if (exception == EXC_ILL_INST && seen_first_real_inst && dut.ifid_reg.inst == 32'b0) begin
+ if (exception == EXC_ILL_INST && seen_first_real_inst && dut.fetch_inst == 32'b0) begin
$display("!!! Program terminated (exception ILL_INST at PC %h), inst: %h !!!",
- pc_out, dut.ifid_reg.inst);
+ pc_out, dut.fetch_inst);
$finish();
end