rv32g-core

Pipelined, synthesizable, and fabricable RISC-V core in SystemVerilog+Yosys+OpenROAD
Log | Files | Refs | LICENSE

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:
Mrtl/rv32g_hart.sv | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mrtl/rv32g_pkg.sv | 11++++++++---
Mrtl/stage_decode.sv | 9+++++++--
Mrtl/stage_memory.sv | 15+++++++++++----
Mrtl/stage_writeback.sv | 3++-
Mrtl/tb_rv32g.sv | 18+++++++++---------
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