rv32g-core

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

commit b1da71976298dbebb4c5acef8b37d2973d44e47a
parent 564723b1aa15d081506e9ebba9c05fd5a9da6296
Author: Vineet Kumar <git@vineetk.net>
Date:   Mon,  6 Jul 2026 17:40:39 +0530

implement Zicsr

Diffstat:
MMakefile | 2+-
Artl/csr_regfile.sv | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mrtl/rv32g_hart.sv | 24+++++++++++++++++++++---
Mrtl/rv32g_pkg.sv | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mrtl/stage_decode.sv | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mrtl/stage_execute.sv | 26++++++++++++++++++++++++--
6 files changed, 350 insertions(+), 18 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ OBJDIR := obj_dir PKG_SRC := ${SRCDIR}/rv32g_pkg.sv TB_SRC := ${SRCDIR}/tb_rv32g.sv -SV_SRCS := ${SRCDIR}/rv32g_hart.sv ${wildcard ${SRCDIR}/stage_*.sv} ${SRCDIR}/unified_memory.sv +SV_SRCS := ${SRCDIR}/rv32g_hart.sv ${SRCDIR}/csr_regfile.sv ${wildcard ${SRCDIR}/stage_*.sv} ${SRCDIR}/unified_memory.sv TOP_SRC := ${SRCDIR}/rv32g_hart_top.sv CST_SRC := ${SRCDIR}/rv32g_hart_top.cst diff --git a/rtl/csr_regfile.sv b/rtl/csr_regfile.sv @@ -0,0 +1,123 @@ +module csr_regfile + import rv32g_pkg::*; + (input logic clk, + input logic rstN, + output logic illegal, + // read port + input logic [11:0] raddr, + input logic rden, + output reg_t rdata, + // write port + input logic [11:0] waddr, + input logic wren, + input reg_t wdata, + // xEPC shadows for xRET + output reg_t mepc_shadow); + + reg_t regfile [0:CSR_SIZE-1]; + logic [$clog2(CSR_SIZE)-1:0] reg_ridx, reg_widx; + logic illegal_rd, illegal_wr; + + // xEPC + reg_t mepc_reg; + + function automatic void decode_csr(input logic [11:0] addr, + output logic [$clog2(CSR_SIZE)-1:0] idx, + output logic illegal); + + illegal = '0; + unique case (addr) + // supervisor trap setup + CSR_SSTATUS: idx = CSR_REG_SSTATUS; + CSR_SIE: idx = CSR_REG_SIE; + CSR_STVEC: idx = CSR_REG_STVEC; + CSR_SCOUNTEREN: idx = CSR_REG_SCOUNTEREN; + // supervisor trap handling + CSR_SSCRATCH: idx = CSR_REG_SSCRATCH; + CSR_SEPC: idx = CSR_REG_SEPC; + CSR_SCAUSE: idx = CSR_REG_SCAUSE; + CSR_STVAL: idx = CSR_REG_STVAL; + CSR_SIP: idx = CSR_REG_SIP; + CSR_SCOUNTOVF: idx = CSR_REG_SCOUNTOVF; + // supervisor protection and translation + CSR_SATP: idx = CSR_REG_SATP; + // machine information registers + CSR_MVENDORID: idx = CSR_REG_MVENDORID; + CSR_MARCHID: idx = CSR_REG_MARCHID; + CSR_MIMPID: idx = CSR_REG_MIMPID; + CSR_MHARTID: idx = CSR_REG_MHARTID; + CSR_MCONFIGPTR: idx = CSR_REG_MCONFIGPTR; + // machine trap setup + CSR_MSTATUS: idx = CSR_REG_MSTATUS; + CSR_MISA: idx = CSR_REG_MISA; + CSR_MEDELEG: idx = CSR_REG_MEDELEG; + CSR_MIDELEG: idx = CSR_REG_MIDELEG; + CSR_MIE: idx = CSR_REG_MIE; + CSR_MTVEC: idx = CSR_REG_MTVEC; + CSR_MCOUNTEREN: idx = CSR_REG_MCOUNTEREN; + CSR_MSTATUSH: idx = CSR_REG_MSTATUSH; + CSR_MEDELEGH: idx = CSR_REG_MEDELEGH; + // machine trap handling + CSR_MSCRATCH: idx = CSR_REG_MSCRATCH; + CSR_MEPC: idx = CSR_REG_MEPC; + CSR_MCAUSE: idx = CSR_REG_MCAUSE; + CSR_MTVAL: idx = CSR_REG_MTVAL; + CSR_MIP: idx = CSR_REG_MIP; + CSR_MTINST: idx = CSR_REG_MTINST; + CSR_MTVAL2: idx = CSR_REG_MTVAL2; + // machine memory protection (subset) + CSR_PMPCFG0: idx = CSR_REG_PMPCFG0; + CSR_PMPADDR0: idx = CSR_REG_PMPADDR0; + // machine non-maskable interrupt handling + CSR_MNSCRATCH: idx = CSR_REG_MNSCRATCH; + CSR_MNEPC: idx = CSR_REG_MNEPC; + CSR_MNCAUSE: idx = CSR_REG_MNCAUSE; + CSR_MNSTATUS: idx = CSR_REG_MNSTATUS; + default: begin + idx = '0; + illegal = '1; + end + endcase + endfunction // decode_csr + + always_comb begin + decode_csr(raddr, reg_ridx, illegal_rd); + decode_csr(waddr, reg_widx, illegal_wr); + + // set illegal_wr when wren is enabled for read-only registers + unique case (waddr) + CSR_SCOUNTOVF, + CSR_MVENDORID, + CSR_MARCHID, + CSR_MIMPID, + CSR_MHARTID, + CSR_MCONFIGPTR: illegal_wr = '1; + default: ; + endcase + end + + // FIXME remove once hart is pipelined + // perform combinational read when rden + assign rdata = (rden && !illegal_rd) ? regfile[reg_ridx] : '0; + + always_ff @(posedge clk or negedge rstN) begin + if (!rstN) begin + mepc_reg <= '0; + end else begin + // // perform sequential read when rden + // if (rden && !illegal_rd) rdata <= regfile[reg_ridx]; + // else rdata <= '0; + + // perform sequential write when wren and not read-only + if (wren && !illegal_wr) begin + regfile[reg_widx] <= wdata; + if (waddr == CSR_MEPC) mepc_reg <= wdata; + end + end + end + + assign mepc_shadow = mepc_reg; + + assign illegal = (rden && illegal_rd) || (wren && illegal_wr); + +endmodule: csr_regfile diff --git a/rtl/rv32g_hart.sv b/rtl/rv32g_hart.sv @@ -30,6 +30,16 @@ module rv32g_hart reg_t alu_out; reg_t mem_out; logic branch_taken; + + // Zicsr + logic [11:0] csr_addr; + reg_t csr_rdata; + reg_t csr_out; + logic csr_illegal; + + csr_regfile u_csr(.clk, .rstN, .illegal(csr_illegal), .mepc_shadow(csr_mepc), + .raddr(csr_addr), .rden(ctrl.csr_rden), .rdata(csr_rdata), + .waddr(csr_addr), .wren(ctrl.csr_wren), .wdata(csr_out)); stage_fetch u_fetch(.clk, .rstN, .flush_i(ctrl.fence_flush), .pc, .imem_addr, .imem_data, .inst); always_ff @(posedge clk or negedge rstN) begin @@ -41,7 +51,7 @@ module rv32g_hart // end end - stage_decode u_decode(.inst, .regfile, .ctrl, .imm, .rs1_val, .rs2_val, .rd_num); + stage_decode u_decode(.inst, .regfile, .ctrl, .imm, .rs1_val, .rs2_val, .rd_num, .csr_addr); // TODO ID/EX pipeline register // always_ff @(posedge clk or negedge rstN) begin always_ff @(posedge clk) begin @@ -64,7 +74,14 @@ module rv32g_hart .op2src(ctrl.aluop2src), .branchop(ctrl.branchop), .out(alu_out), - .imm, .pc, .rs1_val, .rs2_val, .branch_taken); + .imm, .pc, .rs1_val, .rs2_val, .branch_taken, + .csrop(ctrl.csrop), + .csr_rden(ctrl.csr_rden), + .csr_wren(ctrl.csr_wren), + .csr_immen(ctrl.csr_immen), + .csr_imm(ctrl.csr_imm), + .csr_rdata(csr_rdata), + .csr_out(csr_out)); // TODO EX/MEM pipeline register // always_ff @(posedge clk or negedge rstN) begin always_ff @(posedge clk) begin @@ -108,6 +125,7 @@ module rv32g_hart endcase end - assign exception = ctrl.exception; + assign exception = (ctrl.exception != EXC_NONE) ? ctrl.exception : + (ctrl.csrop != CSR_NONE && csr_illegal) ? EXC_ILL_INST : EXC_NONE; endmodule: rv32g_hart diff --git a/rtl/rv32g_pkg.sv b/rtl/rv32g_pkg.sv @@ -58,7 +58,14 @@ package rv32g_pkg; FENCE_TSO, PAUSE, ECALL, - EBREAK} + EBREAK, + // Zicsr + CSRRW, + CSRRS, + CSRRC, + CSRRWI, + CSRRSI, + CSRRCI} inst_name_t; // based off of Unprivileged ISA Chapter 36 Table 72 (no reserved) @@ -158,23 +165,131 @@ package rv32g_pkg; WB_IMM // LUI writes back an immediate value } wbsrc_t; + localparam CSR_SIZE = 40; + typedef enum logic [2:0] {CSR_NONE, + CSR_RW, + CSR_RS, + CSR_RC + } csrop_t; + + // numbers from privileged spec 2.2 + typedef enum logic [11:0] {// supervisor trap setup + CSR_SSTATUS = 'h100, + CSR_SIE = 'h104, + CSR_STVEC = 'h105, + CSR_SCOUNTEREN = 'h106, + // supervisor trap handling + CSR_SSCRATCH = 'h140, + CSR_SEPC = 'h141, + CSR_SCAUSE = 'h142, + CSR_STVAL = 'h143, + CSR_SIP = 'h144, + CSR_SCOUNTOVF = 'hDA0, + // supervisor protection and translation + CSR_SATP = 'h180, + // machine information registers + CSR_MVENDORID = 'hF11, + CSR_MARCHID = 'hF12, + CSR_MIMPID = 'hF13, + CSR_MHARTID = 'hF14, + CSR_MCONFIGPTR = 'hF15, + // machine trap setup + CSR_MSTATUS = 'h300, + CSR_MISA = 'h301, + CSR_MEDELEG = 'h302, + CSR_MIDELEG = 'h303, + CSR_MIE = 'h304, + CSR_MTVEC = 'h305, + CSR_MCOUNTEREN = 'h306, + CSR_MSTATUSH = 'h310, + CSR_MEDELEGH = 'h312, + // machine trap handling + CSR_MSCRATCH = 'h340, + CSR_MEPC = 'h341, + CSR_MCAUSE = 'h342, + CSR_MTVAL = 'h343, + CSR_MIP = 'h344, + CSR_MTINST = 'h34A, + CSR_MTVAL2 = 'h34B, + // machine memory protection (subset) + CSR_PMPCFG0 = 'h3A0, + CSR_PMPADDR0 = 'h3B0, + // machine non-maskable interrupt handling + CSR_MNSCRATCH = 'h740, + CSR_MNEPC = 'h741, + CSR_MNCAUSE = 'h742, + CSR_MNSTATUS = 'h744 + } csr_t; + typedef enum logic [$clog2(CSR_SIZE)-1:0] {// supervisor trap setup + CSR_REG_SSTATUS, + CSR_REG_SIE, + CSR_REG_STVEC, + CSR_REG_SCOUNTEREN, + // supervisor trap handling + CSR_REG_SSCRATCH, + CSR_REG_SEPC, + CSR_REG_SCAUSE, + CSR_REG_STVAL, + CSR_REG_SIP, + CSR_REG_SCOUNTOVF, + // supervisor protection and translation + CSR_REG_SATP, + // machine information registers + CSR_REG_MVENDORID, + CSR_REG_MARCHID, + CSR_REG_MIMPID, + CSR_REG_MHARTID, + CSR_REG_MCONFIGPTR, + // machine trap setup + CSR_REG_MSTATUS, + CSR_REG_MISA, + CSR_REG_MEDELEG, + CSR_REG_MIDELEG, + CSR_REG_MIE, + CSR_REG_MTVEC, + CSR_REG_MCOUNTEREN, + CSR_REG_MSTATUSH, + CSR_REG_MEDELEGH, + // machine trap handling + CSR_REG_MSCRATCH, + CSR_REG_MEPC, + CSR_REG_MCAUSE, + CSR_REG_MTVAL, + CSR_REG_MIP, + CSR_REG_MTINST, + CSR_REG_MTVAL2, + // machine memory protection (subset) + CSR_REG_PMPCFG0, + CSR_REG_PMPADDR0, + // machine non-maskable interrupt handling + CSR_REG_MNSCRATCH, + CSR_REG_MNEPC, + CSR_REG_MNCAUSE, + CSR_REG_MNSTATUS + } csrreg_t; + // very rudimentary. privileged spec (chapter 3) defines this formally, but left to implementation // on unprivileged spec. typedef enum logic [1:0] {EXC_NONE, EXC_ILL_INST, // illegal instruction - EXC_CALL, // ECALL - EXC_BKPT // EBREAK + EXC_CALL, // ECALL + EXC_BKPT // EBREAK } exception_t; typedef struct packed { - aluop_t aluop; + aluop_t aluop; aluop1src_t aluop1src; aluop2src_t aluop2src; - memop_t memop; - pcsrc_t pcsrc; - branchop_t branchop; - wbsrc_t wbsrc; + memop_t memop; + pcsrc_t pcsrc; + branchop_t branchop; + wbsrc_t wbsrc; + csrop_t csrop; + logic csr_rden; + logic csr_wren; + logic csr_immen; + logic [4:0] csr_imm; exception_t exception; - logic fence_flush; + logic fence_flush; } ctrl_t; endpackage: rv32g_pkg diff --git a/rtl/stage_decode.sv b/rtl/stage_decode.sv @@ -6,7 +6,8 @@ module stage_decode output logic [31:0] imm, output logic [31:0] rs1_val, output logic [31:0] rs2_val, - output logic [4:0] rd_num); + output logic [4:0] rd_num, + output logic [11:0] csr_addr); logic [6:0] funct7; logic [4:0] rs2_num; @@ -23,6 +24,7 @@ module stage_decode funct3 = inst[14:12]; rd_num = inst[11:7]; opcode = opcode_t'(inst[6:0]); + csr_addr = inst[31:20]; end // parse the type for each instruction @@ -117,6 +119,13 @@ module stage_decode if (rs1_num == '0 && rd_num == '0 && inst[31:20] == '0) name = FENCE_I; else name = NAME_INVALID; end + // Zicsr + {7'b???????, 3'b001, OPCODE_SYSTEM}: name = CSRRW; + {7'b???????, 3'b010, OPCODE_SYSTEM}: name = CSRRS; + {7'b???????, 3'b011, OPCODE_SYSTEM}: name = CSRRC; + {7'b???????, 3'b101, OPCODE_SYSTEM}: name = CSRRWI; + {7'b???????, 3'b110, OPCODE_SYSTEM}: name = CSRRSI; + {7'b???????, 3'b111, OPCODE_SYSTEM}: name = CSRRCI; default: name = NAME_INVALID; endcase end @@ -130,8 +139,13 @@ module stage_decode pcsrc: PC_SEQ, branchop: BRANCH_NONE, wbsrc: WB_NONE, - fence_flush: 1'b0, - exception: EXC_NONE}; + csrop: CSR_NONE, + csr_rden: 1'b0, + csr_wren: 1'b0, + csr_immen: 1'b0, + csr_imm: inst[19:15], + exception: EXC_NONE, + fence_flush: 1'b0}; // the major opcodes share the same signals, just operation varies based on funct unique case (opcode) @@ -260,6 +274,46 @@ module stage_decode // Zifencei FENCE_I: ctrl.fence_flush = 1'b1; default: ctrl.exception = EXC_ILL_INST; + // Zicsr + CSRRW: begin + ctrl.csrop = CSR_RW; + ctrl.csr_rden = rd_num != 0; + ctrl.csr_wren = 1; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end + CSRRS: begin + ctrl.csrop = CSR_RS; + ctrl.csr_rden = 1; + ctrl.csr_wren = rs1_num != 0; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end + CSRRC: begin + ctrl.csrop = CSR_RC; + ctrl.csr_rden = 1; + ctrl.csr_wren = rs1_num != 0; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end + CSRRWI: begin + ctrl.csrop = CSR_RW; + ctrl.csr_rden = rd_num != 0; + ctrl.csr_wren = 1; + ctrl.csr_immen = 1; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end + CSRRSI: begin + ctrl.csrop = CSR_RS; + ctrl.csr_rden = 1; + ctrl.csr_wren = ctrl.csr_imm != 0; + ctrl.csr_immen = 1; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end + CSRRCI: begin + ctrl.csrop = CSR_RC; + ctrl.csr_rden = 1; + ctrl.csr_wren = ctrl.csr_imm != 0; + ctrl.csr_immen = 1; + ctrl.wbsrc = (rd_num != 0) ? WB_ALU : WB_NONE; + end endcase end diff --git a/rtl/stage_execute.sv b/rtl/stage_execute.sv @@ -9,11 +9,20 @@ module stage_execute input reg_t rs1_val, input reg_t rs2_val, output logic [31:0] out, - output logic branch_taken); + output logic branch_taken, + // Zicsr + input csrop_t csrop, + input logic csr_rden, + input logic csr_wren, + input logic csr_immen, + input logic [4:0] csr_imm, + input reg_t csr_rdata, + output reg_t csr_out); reg_t a; reg_t b; logic [4:0] shamt; + logic [31:0] csr_wdata; always_comb begin // assign inputs @@ -32,7 +41,11 @@ module stage_execute // perform computation unique case (op) - ALU_NONE: out = '0; + ALU_NONE: begin + // Zicsr handled inside ALU_NONE to avoid multiple drivers of out + if (csrop != CSR_NONE && csr_rden) out = csr_rdata; + else out = '0; + end ALU_ADD: out = a + b; ALU_SUB: out = $signed(a) - $signed(b); ALU_XOR: out = a ^ b; @@ -55,6 +68,15 @@ module stage_execute BRANCH_BLTU: branch_taken = (out[0] == 1'b1) ? 1 : 0; BRANCH_BGEU: branch_taken = (out[0] != 1'b1) ? 1 : 0; endcase + + // Zicsr + csr_wdata = (csr_immen) ? {{27{1'b0}}, csr_imm} : rs1_val; + unique case (csrop) + CSR_NONE: csr_out = 0; + CSR_RW: csr_out = csr_wdata; + CSR_RS: csr_out = csr_rdata | csr_wdata; + CSR_RC: csr_out = csr_rdata & ~csr_wdata; + endcase end endmodule: stage_execute