commit b1da71976298dbebb4c5acef8b37d2973d44e47a
parent 564723b1aa15d081506e9ebba9c05fd5a9da6296
Author: Vineet Kumar <git@vineetk.net>
Date: Mon, 6 Jul 2026 17:40:39 +0530
implement Zicsr
Diffstat:
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