rv32g-core

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

commit a27f857278de6f860ba720e069848f206e54bd43
parent 56b8baf09f4372bae891e08a3a93fff1741fe48c
Author: Vineet Kumar <git@vineetk.net>
Date:   Tue, 14 Jul 2026 13:26:21 +0530

properly latch the pipeline registers

Diffstat:
Mrtl/rv32g_hart.sv | 104++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mrtl/rv32g_pkg.sv | 5+++--
Mrtl/stage_memory.sv | 3+--
Mrtl/tb_rv32g.sv | 3++-
4 files changed, 67 insertions(+), 48 deletions(-)

diff --git a/rtl/rv32g_hart.sv b/rtl/rv32g_hart.sv @@ -35,26 +35,28 @@ module rv32g_hart exmem_reg_t exmem_reg; memwb_reg_t memwb_reg; + // temporary fetch signals + logic [31:0] fetch_inst; + reg_t pc_reg; + + // temporary decode signals + ctrl_t dec_ctrl; + reg_t dec_pc, dec_imm, dec_rs1_val, dec_rs2_val; + logic [4:0] dec_rs1_num, dec_rs2_num, dec_rd_num; + logic [11:0] dec_csr_addr; + + // temporary execute signals + reg_t ex_alu_out, ex_csr_out; + logic ex_branch_taken; + + // temporary memory signals + logic [31:0] mem_mem_out; + // hazards logic if_stall; - logic csr_read_stall; - logic csr_read_stall_active; assign if_stall = !imem_valid; - // stall one cycle when csr_rden is active due to registered read - always_ff @(posedge clk or negedge rstN) begin - if (!rstN) begin - csr_read_stall_active <= 1'b0; - end else begin - if (idex_reg.ctrl.csr_rden && !csr_read_stall_active) - csr_read_stall_active <= 1'b1; - else if (csr_read_stall_active) - csr_read_stall_active <= 1'b0; - end - end - assign csr_read_stall = csr_read_stall_active; - // TODO move in between decode and execute for better organization? csr_regfile u_csr(.clk, .rstN, .addr(idex_reg.csr_addr), .rs1_num(idex_reg.rs1_num), .mepc_shadow(csr_mepc), .illegal(csr_illegal), @@ -64,10 +66,6 @@ module rv32g_hart // ============= // === FETCH === // ============= - - // temporary signals - logic [31:0] fetch_inst; - reg_t pc_reg; assign pc_out = pc_reg; @@ -86,7 +84,7 @@ module rv32g_hart if (!rstN) begin pc_reg <= BASE_ADDR; ifid_reg <= '0; - end else if (!if_stall && !csr_read_stall) begin + end else if (!if_stall) begin pc_reg <= next_pc; ifid_reg <= ifid_next; end @@ -96,12 +94,6 @@ module rv32g_hart // === DECODE === // ============== - // temporary signals - ctrl_t dec_ctrl; - reg_t dec_pc, dec_imm, dec_rs1_val, dec_rs2_val; - logic [4:0] dec_rs1_num, dec_rs2_num, dec_rd_num; - logic [11:0] dec_csr_addr; - stage_decode u_decode(.clk, .rstN, .regfile, .inst(ifid_reg.inst), .pc_in(ifid_reg.pc), .pc_out(dec_pc), .ctrl(dec_ctrl), .imm(dec_imm), @@ -115,9 +107,11 @@ module rv32g_hart if (!rstN) begin // NOP by default idex_reg <= '0; - end else if (if_stall || csr_read_stall) begin + idex_reg.pc <= pc_reg; + end else if (if_stall) begin // insert NOP idex_reg <= '0; + idex_reg.pc <= pc_reg; end else begin // latch decode outputs in normal operation idex_reg.ctrl <= dec_ctrl; @@ -129,6 +123,7 @@ module rv32g_hart idex_reg.rs2_num <= dec_rs2_num; idex_reg.rd_num <= dec_rd_num; idex_reg.csr_addr <= dec_csr_addr; + idex_reg.csr_rdata <= csr_rdata; end end @@ -163,18 +158,33 @@ module rv32g_hart .csr_wren(idex_reg.ctrl.csr_wren), .csr_immen(idex_reg.ctrl.csr_immen), .csr_imm(idex_reg.ctrl.csr_imm), - .csr_rdata(csr_rdata), + .csr_rdata(idex_reg.csr_rdata), .is_mret(idex_reg.ctrl.is_mret), - .out(exmem_reg.alu_out), - .branch_taken(exmem_reg.branch_taken), - .csr_out(exmem_reg.csr_out)); + .out(ex_alu_out), + .branch_taken(ex_branch_taken), + .csr_out(ex_csr_out)); // EX/MEM pipeline register always_ff @(posedge clk or negedge rstN) begin - if (!rstN) exmem_reg = '0; - else begin - $display("EX: op: %h, op1src: %d, op2src: %d, branchop: %d, out: %h, branch_taken: %d", + if (!rstN) begin + exmem_reg = '0; + exmem_reg.pc = idex_reg.pc; + end else begin + $display("EX: op: %h, op1src: %d, op2src: %d, branchop: %d, out: %h, branch_taken: %d, rs1_val: %h, rs2_val: %h", idex_reg.ctrl.aluop, idex_reg.ctrl.aluop1src, idex_reg.ctrl.aluop2src, - idex_reg.ctrl.branchop, exmem_reg.alu_out, exmem_reg.branch_taken); + idex_reg.ctrl.branchop, exmem_reg.alu_out, exmem_reg.branch_taken, + idex_reg.rs1_val, idex_reg.rs2_val); + + exmem_reg.alu_out = ex_alu_out; + exmem_reg.csr_out = ex_csr_out; + exmem_reg.rs2_val = idex_reg.rs2_val; + exmem_reg.pc = idex_reg.pc; + exmem_reg.imm = idex_reg.imm; + exmem_reg.rd_num = idex_reg.rd_num; + exmem_reg.branch_taken = ex_branch_taken; + exmem_reg.branchop = idex_reg.ctrl.branchop; + exmem_reg.memop = idex_reg.ctrl.memop; + exmem_reg.wbsrc = idex_reg.ctrl.wbsrc; + exmem_reg.pcsrc = idex_reg.ctrl.pcsrc; end // if (!dmem_valid && ctrl.memop != MEM_NONE) begin // // TODO stall @@ -185,19 +195,27 @@ module rv32g_hart // ============== // === MEMORY === // ============== - + stage_memory u_mem(.memop(exmem_reg.memop), .alu_out(exmem_reg.alu_out), .rs2_val(exmem_reg.rs2_val), - .mem_out(memwb_reg.mem_out), - .alu_out_passthrough(memwb_reg.alu_out), + .mem_out(mem_mem_out), .dmem_addr, .dmem_rdata, .dmem_wdata, .dmem_we, .dmem_be); // MEM/WB pipeline register always_ff @(posedge clk or negedge rstN) begin - if (!rstN) memwb_reg = '0; - else begin + if (!rstN) begin + memwb_reg = '0; + end else begin $display("MEM: memop: %h, dmem_addr: %h, dmem_rdata: %h, dmem_wdata: %h, dmem_we: %d, dmem_be: %b, mem_out: %h", exmem_reg.memop, dmem_addr, dmem_rdata, dmem_wdata, dmem_we, dmem_be, memwb_reg.mem_out); + + memwb_reg.alu_out = exmem_reg.alu_out; + memwb_reg.mem_out = mem_mem_out; + memwb_reg.csr_out = exmem_reg.csr_out; + memwb_reg.pc = exmem_reg.pc; + memwb_reg.imm = exmem_reg.imm; + memwb_reg.rd_num = exmem_reg.rd_num; + memwb_reg.wbsrc = exmem_reg.wbsrc; end // else ; // TODO I need to think of what else should happen end @@ -224,11 +242,11 @@ module rv32g_hart always_comb begin unique case (exmem_reg.pcsrc) // normal case - PC_SEQ: next_pc = pc_reg + 4; + 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 = pc_reg + memwb_reg.imm; - else next_pc = pc_reg + 4; + if ((exmem_reg.branchop != BRANCH_NONE) ? exmem_reg.branch_taken : 1'b1) next_pc = exmem_reg.pc + memwb_reg.imm; + else next_pc = exmem_reg.pc + 4; end // jalr PC_ALU: next_pc = memwb_reg.alu_out & ~32'b1; // spec says to clear LSB and /may/ raise an exception diff --git a/rtl/rv32g_pkg.sv b/rtl/rv32g_pkg.sv @@ -317,15 +317,16 @@ package rv32g_pkg; logic [4:0] rs1_num; logic [4:0] rs2_num; logic [4:0] rd_num; + reg_t csr_rdata; logic [11:0] csr_addr; } idex_reg_t; typedef struct packed { reg_t alu_out; - reg_t mem_out; reg_t csr_out; reg_t rs2_val; reg_t pc; + reg_t imm; logic [4:0] rd_num; logic branch_taken; branchop_t branchop; @@ -338,8 +339,8 @@ package rv32g_pkg; reg_t alu_out; reg_t mem_out; reg_t csr_out; - reg_t imm; reg_t pc; + reg_t imm; logic [4:0] rd_num; wbsrc_t wbsrc; } memwb_reg_t; diff --git a/rtl/stage_memory.sv b/rtl/stage_memory.sv @@ -11,8 +11,7 @@ module stage_memory // byte-enable signal is used so that memory controller doesn't overwrite adjacent bytes in word output logic [3:0] dmem_be, - output logic [31:0] mem_out, - output logic [31:0] alu_out_passthrough); + output logic [31:0] mem_out); logic [7:0] bdata; logic [15:0] hdata; diff --git a/rtl/tb_rv32g.sv b/rtl/tb_rv32g.sv @@ -83,7 +83,8 @@ module tb_rv32g parameter int MAX_CYCLES = 10000; always_ff @(posedge clk) begin if (exception == EXC_ILL_INST && seen_first_real_inst) begin - $display("!!! Program terminated (exception ILL_INST at PC %h), inst: %h !!!", pc_out, dut.ifid_reg.inst); + $display("!!! Program terminated (exception ILL_INST at PC %h), inst: %h, csrop: %d, csr_ill_rd: %d, csr_ill_wr: %d !!!", + pc_out, dut.ifid_reg.inst, dut.idex_reg.ctrl.csrop, dut.u_csr.illegal_rd, dut.u_csr.illegal_wr); $finish(); end