aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rv64cpu/rtl/core/csr_unit.sv499
-rw-r--r--rv64cpu/rtl/core/include/csr_pkg.sv7
-rw-r--r--rv64cpu/rtl/core/richard_core.sv42
-rw-r--r--rv64cpu/tb/CMakeLists.txt20
-rw-r--r--rv64cpu/tb/unit/tb_csr.cc335
5 files changed, 893 insertions, 10 deletions
diff --git a/rv64cpu/rtl/core/csr_unit.sv b/rv64cpu/rtl/core/csr_unit.sv
new file mode 100644
index 0000000..1749790
--- /dev/null
+++ b/rv64cpu/rtl/core/csr_unit.sv
@@ -0,0 +1,499 @@
+// Richard CPU
+// Copyright (C) 2026 Richard Qin
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// File path: rtl/core/csr_unit.sv
+
+module csr_unit
+ import rv64_pkg::*;
+ import csr_pkg::*;
+#(
+ parameter logic [XLEN-1:0] HART_ID = '0,
+ parameter logic [XLEN-1:0] VENDOR_ID = '0,
+ parameter logic [XLEN-1:0] ARCH_ID = '0,
+ parameter logic [XLEN-1:0] IMPL_ID = '0,
+ parameter logic [XLEN-1:0] MISA_VALUE = 64'h8000_0000_0050_112D
+)(
+ input logic clk,
+ input logic rst,
+ input logic csr_req,
+ input logic [2:0] csr_op,
+ input logic [11:0] csr_addr,
+ input logic [XLEN-1:0] csr_rs1_data,
+ input logic trap_illegal_instr,
+ input logic trap_is_ecall,
+ input logic trap_is_ebreak,
+ input logic trap_is_mret,
+ input logic trap_is_sret,
+ input logic [XLEN-1:0] trap_epc,
+ output logic [XLEN-1:0] csr_rdata,
+ output logic [XLEN-1:0] csr_mstatus,
+ output logic [XLEN-1:0] csr_sstatus,
+ output logic [XLEN-1:0] csr_medeleg,
+ output logic [XLEN-1:0] csr_mideleg,
+ output logic [XLEN-1:0] csr_mtvec,
+ output logic [XLEN-1:0] csr_stvec,
+ output logic [XLEN-1:0] csr_mepc,
+ output logic [XLEN-1:0] csr_sepc,
+ output logic [XLEN-1:0] csr_satp,
+ output logic [1:0] csr_priv_mode,
+ output logic [XLEN-1:0] csr_trap_vector,
+ output logic csr_trap_to_s_mode,
+ output logic csr_illegal_access
+);
+
+ typedef enum logic [1:0] {
+ PRIV_MODE_U = 2'b00,
+ PRIV_MODE_S = 2'b01,
+ PRIV_MODE_M = 2'b11
+ } priv_mode_t;
+
+ localparam int MSTATUS_MIE_BIT = 3;
+ localparam int MSTATUS_MPIE_BIT = 7;
+ localparam int MSTATUS_MPP_LO = 11;
+ localparam int MSTATUS_MPP_HI = 12;
+ localparam int SSTATUS_SIE_BIT = 1;
+ localparam int SSTATUS_SPIE_BIT = 5;
+ localparam int SSTATUS_SPP_BIT = 8;
+
+ // Machine-mode CSRs
+ logic [XLEN-1:0] mstatus_reg;
+ logic [XLEN-1:0] medeleg_reg;
+ logic [XLEN-1:0] mideleg_reg;
+ logic [XLEN-1:0] mie_reg;
+ logic [XLEN-1:0] mtvec_reg;
+ logic [XLEN-1:0] mcounteren_reg;
+ logic [XLEN-1:0] mscratch_reg;
+ logic [XLEN-1:0] mepc_reg;
+ logic [XLEN-1:0] mcause_reg;
+ logic [XLEN-1:0] mtval_reg;
+ logic [XLEN-1:0] mip_reg;
+
+ // Supervisor-mode CSRs
+ logic [XLEN-1:0] sstatus_reg;
+ logic [XLEN-1:0] sie_reg;
+ logic [XLEN-1:0] stvec_reg;
+ logic [XLEN-1:0] scounteren_reg;
+ logic [XLEN-1:0] sscratch_reg;
+ logic [XLEN-1:0] sepc_reg;
+ logic [XLEN-1:0] scause_reg;
+ logic [XLEN-1:0] stval_reg;
+ logic [XLEN-1:0] sip_reg;
+ logic [XLEN-1:0] satp_reg;
+
+ // Counters
+ logic [XLEN-1:0] mcycle_reg;
+ logic [XLEN-1:0] minstret_reg;
+ logic [XLEN-1:0] time_reg;
+
+ // Floating-point CSRs
+ logic [4:0] fflags_reg;
+ logic [2:0] frm_reg;
+
+ priv_mode_t priv_mode_q;
+
+ logic [11:0] csr_addr_canonical;
+ logic is_write_cmd;
+ logic is_set_cmd;
+ logic is_clear_cmd;
+ logic has_non_zero_data;
+ logic csr_write_en;
+ logic trap_event;
+ logic [5:0] trap_cause_index;
+ logic trap_delegated_to_s;
+ logic [XLEN-1:0] trap_cause_value;
+ logic [XLEN-1:0] trap_vector_target;
+ logic [XLEN-1:0] trap_vector_q;
+ logic csr_addr_supported;
+ logic csr_write_illegal;
+ logic [XLEN-1:0] csr_read_data;
+ logic trap_to_s_q;
+
+ // Canonicalize addresses, decode operations, and prepare CSR/trap metadata.
+ assign csr_addr_canonical = canonical_addr(csr_addr);
+ assign is_write_cmd = (csr_op == 3'b001) || (csr_op == 3'b101);
+ assign is_set_cmd = (csr_op == 3'b010) || (csr_op == 3'b110);
+ assign is_clear_cmd = (csr_op == 3'b011) || (csr_op == 3'b111);
+ assign has_non_zero_data = |csr_rs1_data;
+ assign csr_addr_supported = csr_is_supported(csr_addr_canonical);
+ assign csr_write_en = csr_req & csr_addr_supported & (is_write_cmd | ((is_set_cmd | is_clear_cmd) & has_non_zero_data));
+ assign trap_event = trap_illegal_instr | trap_is_ecall | trap_is_ebreak;
+ assign trap_cause_value = compute_trap_cause(priv_mode_q, trap_illegal_instr, trap_is_ecall, trap_is_ebreak);
+ assign trap_cause_index = trap_cause_value[5:0];
+ assign trap_delegated_to_s = trap_event && (priv_mode_q != PRIV_MODE_M) && medeleg_reg[trap_cause_index];
+ assign trap_vector_target = trap_delegated_to_s ? stvec_reg : mtvec_reg;
+ assign csr_read_data = read_csr(csr_addr_canonical);
+ assign csr_rdata = csr_read_data;
+ assign csr_mstatus = mstatus_reg;
+ assign csr_sstatus = sstatus_reg;
+ assign csr_medeleg = medeleg_reg;
+ assign csr_mideleg = mideleg_reg;
+ assign csr_mtvec = mtvec_reg;
+ assign csr_stvec = stvec_reg;
+ assign csr_mepc = mepc_reg;
+ assign csr_sepc = sepc_reg;
+ assign csr_satp = satp_reg;
+ assign csr_priv_mode = priv_mode_q;
+ assign csr_trap_vector = trap_vector_q;
+ assign csr_trap_to_s_mode = trap_to_s_q;
+ assign csr_write_illegal = (csr_req && (is_write_cmd || is_set_cmd || is_clear_cmd) && !csr_is_writable(csr_addr_canonical));
+ assign csr_illegal_access = csr_req && (!csr_addr_supported || csr_write_illegal);
+
+ // Main CSR state machine: counts cycles, processes CSR writes, and reacts to traps/returns.
+ always_ff @(posedge clk) begin
+ if (rst) begin
+ mstatus_reg <= '0;
+ medeleg_reg <= '0;
+ mideleg_reg <= '0;
+ mie_reg <= '0;
+ mtvec_reg <= '0;
+ mcounteren_reg <= '0;
+ mscratch_reg <= '0;
+ mepc_reg <= '0;
+ mcause_reg <= '0;
+ mtval_reg <= '0;
+ mip_reg <= '0;
+ sstatus_reg <= '0;
+ sie_reg <= '0;
+ stvec_reg <= '0;
+ scounteren_reg <= '0;
+ sscratch_reg <= '0;
+ sepc_reg <= '0;
+ scause_reg <= '0;
+ stval_reg <= '0;
+ sip_reg <= '0;
+ satp_reg <= '0;
+ mcycle_reg <= '0;
+ minstret_reg <= '0;
+ time_reg <= '0;
+ fflags_reg <= '0;
+ frm_reg <= '0;
+ priv_mode_q <= PRIV_MODE_M;
+ trap_vector_q <= '0;
+ trap_to_s_q <= 1'b0;
+ end else begin
+ mcycle_reg <= mcycle_reg + 64'd1;
+ time_reg <= time_reg + 64'd1;
+ if (!trap_event) begin
+ minstret_reg <= minstret_reg + 64'd1;
+ end
+
+ if (csr_write_en && csr_is_writable(csr_addr_canonical)) begin
+ unique case (csr_addr_canonical)
+ CSR_MSTATUS: begin
+ logic [XLEN-1:0] next_val;
+ next_val = csr_write_next(mstatus_reg, csr_rs1_data, csr_op);
+ mstatus_reg <= next_val;
+ sstatus_reg <= mirror_mstatus_to_sstatus(sstatus_reg, next_val);
+ end
+ CSR_MEDELEG: medeleg_reg <= csr_write_next(medeleg_reg, csr_rs1_data, csr_op);
+ CSR_MIDELEG: mideleg_reg <= csr_write_next(mideleg_reg, csr_rs1_data, csr_op);
+ CSR_MIE: mie_reg <= csr_write_next(mie_reg, csr_rs1_data, csr_op);
+ CSR_MTVEC: mtvec_reg <= csr_write_next(mtvec_reg, csr_rs1_data, csr_op);
+ CSR_MSCRATCH: mscratch_reg <= csr_write_next(mscratch_reg, csr_rs1_data, csr_op);
+ CSR_MEPC: mepc_reg <= csr_write_next(mepc_reg, csr_rs1_data, csr_op);
+ CSR_MCAUSE: mcause_reg <= csr_write_next(mcause_reg, csr_rs1_data, csr_op);
+ CSR_MTVAL: mtval_reg <= csr_write_next(mtval_reg, csr_rs1_data, csr_op);
+ CSR_MIP: mip_reg <= csr_write_next(mip_reg, csr_rs1_data, csr_op);
+ CSR_MCOUNTEREN: mcounteren_reg <= csr_write_next(mcounteren_reg, csr_rs1_data, csr_op);
+ CSR_SSTATUS: begin
+ logic [XLEN-1:0] next_sstatus;
+ next_sstatus = csr_write_next(sstatus_reg, csr_rs1_data, csr_op);
+ sstatus_reg <= next_sstatus;
+ mstatus_reg <= apply_sstatus_to_mstatus(mstatus_reg, next_sstatus);
+ end
+ CSR_SIE: sie_reg <= csr_write_next(sie_reg, csr_rs1_data, csr_op);
+ CSR_STVEC: stvec_reg <= csr_write_next(stvec_reg, csr_rs1_data, csr_op);
+ CSR_SCOUNTEREN: scounteren_reg <= csr_write_next(scounteren_reg, csr_rs1_data, csr_op);
+ CSR_SSCRATCH: sscratch_reg <= csr_write_next(sscratch_reg, csr_rs1_data, csr_op);
+ CSR_SEPC: sepc_reg <= csr_write_next(sepc_reg, csr_rs1_data, csr_op);
+ CSR_SCAUSE: scause_reg <= csr_write_next(scause_reg, csr_rs1_data, csr_op);
+ CSR_STVAL: stval_reg <= csr_write_next(stval_reg, csr_rs1_data, csr_op);
+ CSR_SIP: sip_reg <= csr_write_next(sip_reg, csr_rs1_data, csr_op);
+ CSR_SATP: satp_reg <= csr_write_next(satp_reg, csr_rs1_data, csr_op);
+ CSR_MCYCLE: mcycle_reg <= csr_write_next(mcycle_reg, csr_rs1_data, csr_op);
+ CSR_MINSTRET: minstret_reg <= csr_write_next(minstret_reg, csr_rs1_data, csr_op);
+ CSR_FFLAGS: begin
+ logic [XLEN-1:0] next_flags;
+ next_flags = csr_write_next({{(XLEN-5){1'b0}}, fflags_reg}, csr_rs1_data, csr_op);
+ fflags_reg <= next_flags[4:0];
+ end
+ CSR_FRM: begin
+ logic [XLEN-1:0] next_frm;
+ next_frm = csr_write_next({{(XLEN-3){1'b0}}, frm_reg}, csr_rs1_data, csr_op);
+ frm_reg <= next_frm[2:0];
+ end
+ CSR_FCSR: begin
+ logic [XLEN-1:0] next_fcsr;
+ next_fcsr = csr_write_next(compose_fcsr(), csr_rs1_data, csr_op);
+ fflags_reg <= next_fcsr[4:0];
+ frm_reg <= next_fcsr[7:5];
+ end
+ default: ;
+ endcase
+ end
+
+ if (trap_event) begin
+ trap_vector_q <= trap_vector_target;
+ trap_to_s_q <= trap_delegated_to_s;
+ // Update privilege-specific CSRs based on whether the trap was delegated.
+ if (trap_delegated_to_s) begin
+ logic [XLEN-1:0] next_sstatus;
+ next_sstatus = sstatus_reg;
+ next_sstatus[SSTATUS_SPIE_BIT] = sstatus_reg[SSTATUS_SIE_BIT];
+ next_sstatus[SSTATUS_SIE_BIT] = 1'b0;
+ next_sstatus[SSTATUS_SPP_BIT] = (priv_mode_q == PRIV_MODE_S);
+ sstatus_reg <= next_sstatus;
+ mstatus_reg <= apply_sstatus_to_mstatus(mstatus_reg, next_sstatus);
+ sepc_reg <= trap_epc;
+ scause_reg <= trap_cause_value;
+ stval_reg <= trap_illegal_instr ? trap_epc : '0;
+ priv_mode_q <= PRIV_MODE_S;
+ end else begin
+ logic [XLEN-1:0] next_mstatus;
+ next_mstatus = mstatus_reg;
+ next_mstatus[MSTATUS_MPIE_BIT] = mstatus_reg[MSTATUS_MIE_BIT];
+ next_mstatus[MSTATUS_MIE_BIT] = 1'b0;
+ next_mstatus[MSTATUS_MPP_HI:MSTATUS_MPP_LO] = encode_priv_mode(priv_mode_q);
+ mstatus_reg <= next_mstatus;
+ sstatus_reg <= mirror_mstatus_to_sstatus(sstatus_reg, next_mstatus);
+ mepc_reg <= trap_epc;
+ mcause_reg <= trap_cause_value;
+ mtval_reg <= trap_illegal_instr ? trap_epc : '0;
+ priv_mode_q <= PRIV_MODE_M;
+ end
+ end else begin
+ if (trap_is_mret) begin
+ priv_mode_t return_mode;
+ logic [XLEN-1:0] next_mstatus;
+ return_mode = decode_priv_mode(mstatus_reg[MSTATUS_MPP_HI:MSTATUS_MPP_LO]);
+ next_mstatus = mstatus_reg;
+ next_mstatus[MSTATUS_MIE_BIT] = mstatus_reg[MSTATUS_MPIE_BIT];
+ next_mstatus[MSTATUS_MPIE_BIT] = 1'b1;
+ next_mstatus[MSTATUS_MPP_HI:MSTATUS_MPP_LO] = encode_priv_mode(PRIV_MODE_U);
+ mstatus_reg <= next_mstatus;
+ sstatus_reg <= mirror_mstatus_to_sstatus(sstatus_reg, next_mstatus);
+ priv_mode_q <= return_mode;
+ end
+
+ if (trap_is_sret) begin
+ logic [XLEN-1:0] next_sstatus;
+ next_sstatus = sstatus_reg;
+ next_sstatus[SSTATUS_SIE_BIT] = sstatus_reg[SSTATUS_SPIE_BIT];
+ next_sstatus[SSTATUS_SPIE_BIT] = 1'b1;
+ next_sstatus[SSTATUS_SPP_BIT] = 1'b0;
+ sstatus_reg <= next_sstatus;
+ mstatus_reg <= apply_sstatus_to_mstatus(mstatus_reg, next_sstatus);
+ priv_mode_q <= (sstatus_reg[SSTATUS_SPP_BIT]) ? PRIV_MODE_S : PRIV_MODE_U;
+ end
+ end
+ end
+ end
+
+ // Map CSR aliases (cycle/instret) to their canonical machine addresses.
+ function automatic logic [11:0] canonical_addr(input logic [11:0] addr);
+ case (addr)
+ CSR_CYCLE: canonical_addr = CSR_MCYCLE;
+ CSR_INSTRET: canonical_addr = CSR_MINSTRET;
+ default: canonical_addr = addr;
+ endcase
+ endfunction
+
+ // Apply the CSR op semantics (write/set/clear) for a single register.
+ function automatic logic [XLEN-1:0] csr_write_next(
+ input logic [XLEN-1:0] current_val,
+ input logic [XLEN-1:0] write_val,
+ input logic [2:0] op_code
+ );
+ logic [XLEN-1:0] next_val;
+ unique case (op_code)
+ 3'b001, 3'b101: next_val = write_val; // CSRRW / CSRRWI
+ 3'b010, 3'b110: next_val = current_val | write_val; // CSRRS / CSRRSI
+ 3'b011, 3'b111: next_val = current_val & ~write_val; // CSRRC / CSRRCI
+ default: next_val = current_val;
+ endcase
+ return next_val;
+ endfunction
+
+ function automatic bit csr_is_writable(input logic [11:0] addr);
+ case (addr)
+ CSR_MISA,
+ CSR_MVENDORID,
+ CSR_MARCHID,
+ CSR_MIMPID,
+ CSR_MHARTID,
+ CSR_TIME: csr_is_writable = 1'b0;
+ default: csr_is_writable = 1'b1;
+ endcase
+ endfunction
+
+ function automatic bit csr_is_supported(input logic [11:0] addr);
+ bit supported;
+ unique case (addr)
+ CSR_MSTATUS,
+ CSR_MISA,
+ CSR_MEDELEG,
+ CSR_MIDELEG,
+ CSR_MIE,
+ CSR_MTVEC,
+ CSR_MSCRATCH,
+ CSR_MEPC,
+ CSR_MCAUSE,
+ CSR_MTVAL,
+ CSR_MIP,
+ CSR_MCOUNTEREN,
+ CSR_MVENDORID,
+ CSR_MARCHID,
+ CSR_MIMPID,
+ CSR_MHARTID,
+ CSR_SSTATUS,
+ CSR_SIE,
+ CSR_STVEC,
+ CSR_SCOUNTEREN,
+ CSR_SSCRATCH,
+ CSR_SEPC,
+ CSR_SCAUSE,
+ CSR_STVAL,
+ CSR_SIP,
+ CSR_SATP,
+ CSR_MCYCLE,
+ CSR_MINSTRET,
+ CSR_CYCLE,
+ CSR_INSTRET,
+ CSR_TIME,
+ CSR_FFLAGS,
+ CSR_FRM,
+ CSR_FCSR:
+ supported = 1'b1;
+ default:
+ supported = 1'b0;
+ endcase
+ return supported;
+ endfunction
+
+ function automatic logic [XLEN-1:0] apply_sstatus_to_mstatus(
+ input logic [XLEN-1:0] mstatus_cur,
+ input logic [XLEN-1:0] sstatus_val
+ );
+ logic [XLEN-1:0] next_val;
+ next_val = mstatus_cur;
+ next_val[SSTATUS_SIE_BIT] = sstatus_val[SSTATUS_SIE_BIT];
+ next_val[SSTATUS_SPIE_BIT] = sstatus_val[SSTATUS_SPIE_BIT];
+ next_val[SSTATUS_SPP_BIT] = sstatus_val[SSTATUS_SPP_BIT];
+ return next_val;
+ endfunction
+
+ function automatic logic [XLEN-1:0] mirror_mstatus_to_sstatus(
+ input logic [XLEN-1:0] sstatus_cur,
+ input logic [XLEN-1:0] mstatus_val
+ );
+ logic [XLEN-1:0] next_val;
+ next_val = sstatus_cur;
+ next_val[SSTATUS_SIE_BIT] = mstatus_val[SSTATUS_SIE_BIT];
+ next_val[SSTATUS_SPIE_BIT] = mstatus_val[SSTATUS_SPIE_BIT];
+ next_val[SSTATUS_SPP_BIT] = mstatus_val[SSTATUS_SPP_BIT];
+ return next_val;
+ endfunction
+
+ // Pack the discrete FP status bits into the architectural FCSR layout.
+ function automatic logic [XLEN-1:0] compose_fcsr();
+ logic [XLEN-1:0] value;
+ value = '0;
+ value[4:0] = fflags_reg;
+ value[7:5] = frm_reg;
+ return value;
+ endfunction
+
+ function automatic priv_mode_t decode_priv_mode(input logic [1:0] encoded);
+ case (encoded)
+ 2'b00: decode_priv_mode = PRIV_MODE_U;
+ 2'b01: decode_priv_mode = PRIV_MODE_S;
+ default: decode_priv_mode = PRIV_MODE_M;
+ endcase
+ endfunction
+
+ function automatic logic [1:0] encode_priv_mode(input priv_mode_t mode);
+ case (mode)
+ PRIV_MODE_U: encode_priv_mode = 2'b00;
+ PRIV_MODE_S: encode_priv_mode = 2'b01;
+ default: encode_priv_mode = 2'b11;
+ endcase
+ endfunction
+
+ function automatic logic [XLEN-1:0] compute_trap_cause(
+ input priv_mode_t mode,
+ input logic illegal_instr,
+ input logic is_ecall,
+ input logic is_ebreak
+ );
+ logic [XLEN-1:0] cause_val;
+ cause_val = '0;
+ if (illegal_instr) begin
+ cause_val = EXC_INST_ILLEGAL;
+ end else if (is_ebreak) begin
+ cause_val = EXC_BREAKPOINT;
+ end else if (is_ecall) begin
+ case (mode)
+ PRIV_MODE_U: cause_val = EXC_ECALL_FROM_U;
+ PRIV_MODE_S: cause_val = EXC_ECALL_FROM_S;
+ default: cause_val = EXC_ECALL_FROM_M;
+ endcase
+ end
+ return cause_val;
+ endfunction
+
+ function automatic logic [XLEN-1:0] read_csr(input logic [11:0] addr);
+ logic [XLEN-1:0] data_out;
+ unique case (addr)
+ CSR_FFLAGS: data_out = {{(XLEN-5){1'b0}}, fflags_reg};
+ CSR_FRM: data_out = {{(XLEN-3){1'b0}}, frm_reg};
+ CSR_FCSR: data_out = compose_fcsr();
+ CSR_MSTATUS: data_out = mstatus_reg;
+ CSR_MISA: data_out = MISA_VALUE;
+ CSR_MEDELEG: data_out = medeleg_reg;
+ CSR_MIDELEG: data_out = mideleg_reg;
+ CSR_MIE: data_out = mie_reg;
+ CSR_MTVEC: data_out = mtvec_reg;
+ CSR_MSCRATCH: data_out = mscratch_reg;
+ CSR_MEPC: data_out = mepc_reg;
+ CSR_MCAUSE: data_out = mcause_reg;
+ CSR_MTVAL: data_out = mtval_reg;
+ CSR_MIP: data_out = mip_reg;
+ CSR_MCOUNTEREN: data_out = mcounteren_reg;
+ CSR_MVENDORID: data_out = VENDOR_ID;
+ CSR_MARCHID: data_out = ARCH_ID;
+ CSR_MIMPID: data_out = IMPL_ID;
+ CSR_MHARTID: data_out = HART_ID;
+ CSR_SSTATUS: data_out = sstatus_reg;
+ CSR_SIE: data_out = sie_reg;
+ CSR_STVEC: data_out = stvec_reg;
+ CSR_SCOUNTEREN: data_out = scounteren_reg;
+ CSR_SSCRATCH: data_out = sscratch_reg;
+ CSR_SEPC: data_out = sepc_reg;
+ CSR_SCAUSE: data_out = scause_reg;
+ CSR_STVAL: data_out = stval_reg;
+ CSR_SIP: data_out = sip_reg;
+ CSR_SATP: data_out = satp_reg;
+ CSR_MCYCLE: data_out = mcycle_reg;
+ CSR_MINSTRET: data_out = minstret_reg;
+ CSR_CYCLE: data_out = mcycle_reg;
+ CSR_INSTRET: data_out = minstret_reg;
+ CSR_TIME: data_out = time_reg;
+ default: data_out = '0;
+ endcase
+ return data_out;
+ endfunction
+
+endmodule
diff --git a/rv64cpu/rtl/core/include/csr_pkg.sv b/rv64cpu/rtl/core/include/csr_pkg.sv
index 53165c9..a225da2 100644
--- a/rv64cpu/rtl/core/include/csr_pkg.sv
+++ b/rv64cpu/rtl/core/include/csr_pkg.sv
@@ -28,6 +28,13 @@ package csr_pkg;
} csr_op_t;
// ---------------------------------
+ // Floating-Point CSRs
+ // ---------------------------------
+ localparam logic [11:0] CSR_FFLAGS = 12'h001; // Floating-Point accrued exception flags
+ localparam logic [11:0] CSR_FRM = 12'h002; // Floating-Point dynamic rounding mode
+ localparam logic [11:0] CSR_FCSR = 12'h003; // Combined fflags/frm register
+
+ // ---------------------------------
// Machine Mode CSRs (M-Mode)
// ---------------------------------
localparam logic [11:0] CSR_MVENDORID = 12'hF11; // Vendor ID
diff --git a/rv64cpu/rtl/core/richard_core.sv b/rv64cpu/rtl/core/richard_core.sv
index 5b4acbc..ae73e77 100644
--- a/rv64cpu/rtl/core/richard_core.sv
+++ b/rv64cpu/rtl/core/richard_core.sv
@@ -154,6 +154,19 @@ module richard_core
logic [11:0] csr_addr;
logic [XLEN-1:0] csr_rs1_data;
logic [XLEN-1:0] csr_rdata; // Result from CSR unit
+ logic [1:0] csr_priv_mode_cur;
+ logic [XLEN-1:0] csr_mtvec_base;
+ logic [XLEN-1:0] csr_stvec_base;
+ logic [XLEN-1:0] csr_mstatus_value;
+ logic [XLEN-1:0] csr_sstatus_value;
+ logic [XLEN-1:0] csr_medeleg_value;
+ logic [XLEN-1:0] csr_mideleg_value;
+ logic [XLEN-1:0] csr_mepc_value;
+ logic [XLEN-1:0] csr_sepc_value;
+ logic [XLEN-1:0] csr_satp_value;
+ logic [XLEN-1:0] csr_trap_vector_value;
+ logic csr_trap_to_s_mode_flag;
+ logic csr_illegal_access_flag;
// (11) Exception / Trap bus
logic trap_illegal_instr, trap_is_ecall, trap_is_ebreak, trap_is_mret, trap_is_sret;
@@ -551,6 +564,33 @@ module richard_core
.trap_epc (trap_epc)
);
- assign csr_rdata = 64'b0;
+ csr_unit u_csr_unit (
+ .clk (clk),
+ .rst (rst),
+ .csr_req (csr_req),
+ .csr_op (csr_op),
+ .csr_addr (csr_addr),
+ .csr_rs1_data (csr_rs1_data),
+ .trap_illegal_instr (trap_illegal_instr),
+ .trap_is_ecall (trap_is_ecall),
+ .trap_is_ebreak (trap_is_ebreak),
+ .trap_is_mret (trap_is_mret),
+ .trap_is_sret (trap_is_sret),
+ .trap_epc (trap_epc),
+ .csr_rdata (csr_rdata),
+ .csr_mstatus (csr_mstatus_value),
+ .csr_sstatus (csr_sstatus_value),
+ .csr_medeleg (csr_medeleg_value),
+ .csr_mideleg (csr_mideleg_value),
+ .csr_mtvec (csr_mtvec_base),
+ .csr_stvec (csr_stvec_base),
+ .csr_mepc (csr_mepc_value),
+ .csr_sepc (csr_sepc_value),
+ .csr_satp (csr_satp_value),
+ .csr_priv_mode (csr_priv_mode_cur),
+ .csr_trap_vector (csr_trap_vector_value),
+ .csr_trap_to_s_mode (csr_trap_to_s_mode_flag),
+ .csr_illegal_access (csr_illegal_access_flag)
+ );
endmodule
diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt
index 1b6173a..48a4185 100644
--- a/rv64cpu/tb/CMakeLists.txt
+++ b/rv64cpu/tb/CMakeLists.txt
@@ -160,15 +160,15 @@ add_verilator_target(
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_hazard_unit.cc
)
#
-# add_verilator_target(
-# NAME tb_csr
-# TOP_MODULE csr_unit
-# SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
-# ${RTL_CORE_DIR}/include/csr_pkg.sv
-# ${RTL_CORE_DIR}/csr_unit.sv
-# INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
-# TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_csr.cc
-# )
+add_verilator_target(
+ NAME tb_csr
+ TOP_MODULE csr_unit
+ SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
+ ${RTL_CORE_DIR}/include/csr_pkg.sv
+ ${RTL_CORE_DIR}/csr_unit.sv
+ INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
+ TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_csr.cc
+)
# ═══════════════════════════════════════════════════════════════════════════════════
# Core Integration Test
@@ -192,6 +192,8 @@ add_verilator_target(
${RTL_CORE_DIR}/mem_wb_reg.sv
${RTL_CORE_DIR}/wb_stage.sv
${RTL_CORE_DIR}/hazard_unit.sv
+ ${RTL_CORE_DIR}/include/csr_pkg.sv
+ ${RTL_CORE_DIR}/csr_unit.sv
${RTL_CORE_DIR}/richard_core.sv
INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/core/tb_core.cc
diff --git a/rv64cpu/tb/unit/tb_csr.cc b/rv64cpu/tb/unit/tb_csr.cc
new file mode 100644
index 0000000..95d6be7
--- /dev/null
+++ b/rv64cpu/tb/unit/tb_csr.cc
@@ -0,0 +1,335 @@
+// Richard CPU
+// Copyright (C) 2026 Richard Qin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// File path: tb/unit/tb_csr.cc
+// Description: CSR unit testbench
+
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+#include <verilated.h>
+
+#include "Vcsr_unit.h"
+
+namespace {
+constexpr uint32_t CSR_OP_WRITE = 0b001;
+constexpr uint32_t CSR_OP_SET = 0b010;
+constexpr uint32_t CSR_OP_CLEAR = 0b011;
+
+constexpr uint16_t CSR_MSTATUS = 0x300;
+constexpr uint16_t CSR_MISA = 0x301;
+constexpr uint16_t CSR_MEDELEG = 0x302;
+constexpr uint16_t CSR_MIDELEG = 0x303;
+constexpr uint16_t CSR_MIE = 0x304;
+constexpr uint16_t CSR_MTVEC = 0x305;
+constexpr uint16_t CSR_MCOUNTEREN= 0x306;
+constexpr uint16_t CSR_MSCRATCH = 0x340;
+constexpr uint16_t CSR_MEPC = 0x341;
+constexpr uint16_t CSR_MCAUSE = 0x342;
+constexpr uint16_t CSR_MTVAL = 0x343;
+constexpr uint16_t CSR_MCYCLE = 0xB00;
+constexpr uint16_t CSR_MINSTRET = 0xB02;
+constexpr uint16_t CSR_SSTATUS = 0x100;
+constexpr uint16_t CSR_SIE = 0x104;
+constexpr uint16_t CSR_STVEC = 0x105;
+constexpr uint16_t CSR_SCOUNTEREN= 0x106;
+constexpr uint16_t CSR_SSCRATCH = 0x140;
+constexpr uint16_t CSR_SEPC = 0x141;
+constexpr uint16_t CSR_SCAUSE = 0x142;
+constexpr uint16_t CSR_STVAL = 0x143;
+constexpr uint16_t CSR_SIP = 0x144;
+constexpr uint16_t CSR_SATP = 0x180;
+constexpr uint16_t CSR_CYCLE = 0xC00;
+constexpr uint16_t CSR_INSTRET = 0xC02;
+constexpr uint16_t CSR_FFLAGS = 0x001;
+constexpr uint16_t CSR_FRM = 0x002;
+constexpr uint16_t CSR_FCSR = 0x003;
+
+constexpr uint64_t EXC_INST_ILLEGAL = 2ULL;
+constexpr uint64_t EXC_ECALL_FROM_U = 8ULL;
+constexpr uint64_t EXC_ECALL_FROM_S = 9ULL;
+
+constexpr int MSTATUS_MIE_BIT = 3;
+constexpr int MSTATUS_MPIE_BIT = 7;
+constexpr int MSTATUS_MPP_LO = 11;
+constexpr int MSTATUS_MPP_HI = 12;
+constexpr int SSTATUS_SIE_BIT = 1;
+constexpr int SSTATUS_SPIE_BIT = 5;
+constexpr int SSTATUS_SPP_BIT = 8;
+
+constexpr uint64_t PRIV_ENCODE_U = 0ULL;
+constexpr uint64_t PRIV_ENCODE_S = 1ULL;
+constexpr uint64_t PRIV_ENCODE_M = 3ULL;
+}
+
+void tick(Vcsr_unit* dut) {
+ dut->clk = 0;
+ dut->eval();
+ dut->clk = 1;
+ dut->eval();
+ dut->clk = 0;
+ dut->eval();
+}
+
+void drive_defaults(Vcsr_unit* dut) {
+ dut->clk = 0;
+ dut->rst = 0;
+ dut->csr_req = 0;
+ dut->csr_op = 0;
+ dut->csr_addr = 0;
+ dut->csr_rs1_data = 0;
+ dut->trap_illegal_instr = 0;
+ dut->trap_is_ecall = 0;
+ dut->trap_is_ebreak = 0;
+ dut->trap_is_mret = 0;
+ dut->trap_is_sret = 0;
+ dut->trap_epc = 0;
+}
+
+void apply_reset(Vcsr_unit* dut) {
+ dut->rst = 1;
+ tick(dut);
+ dut->rst = 0;
+ tick(dut);
+}
+
+void clear_trap_lines(Vcsr_unit* dut) {
+ dut->trap_illegal_instr = 0;
+ dut->trap_is_ecall = 0;
+ dut->trap_is_ebreak = 0;
+ dut->trap_is_mret = 0;
+ dut->trap_is_sret = 0;
+ dut->trap_epc = 0;
+}
+
+uint64_t read_csr(Vcsr_unit* dut, uint16_t addr) {
+ dut->csr_addr = addr;
+ dut->csr_req = 0;
+ dut->csr_op = 0;
+ dut->csr_rs1_data = 0;
+ dut->eval();
+ return dut->csr_rdata;
+}
+
+void csr_op(Vcsr_unit* dut, uint16_t addr, uint32_t op, uint64_t data) {
+ dut->csr_addr = addr;
+ dut->csr_op = op;
+ dut->csr_rs1_data = data;
+ dut->csr_req = 1;
+ tick(dut);
+ dut->csr_req = 0;
+ dut->csr_op = 0;
+ dut->csr_rs1_data = 0;
+ dut->csr_addr = 0;
+}
+
+void advance_cycles(Vcsr_unit* dut, int cycles) {
+ for (int i = 0; i < cycles; ++i) {
+ tick(dut);
+ }
+}
+
+void force_priv_mode_via_mret(Vcsr_unit* dut, uint64_t encoded_mode) {
+ uint64_t mstatus = read_csr(dut, CSR_MSTATUS);
+ mstatus &= ~(0x3ULL << MSTATUS_MPP_LO);
+ mstatus |= (encoded_mode << MSTATUS_MPP_LO);
+ csr_op(dut, CSR_MSTATUS, CSR_OP_WRITE, mstatus);
+ dut->trap_is_mret = 1;
+ tick(dut);
+ dut->trap_is_mret = 0;
+}
+
+int main(int argc, char** argv) {
+ Verilated::commandArgs(argc, argv);
+ Vcsr_unit* dut = new Vcsr_unit;
+
+ drive_defaults(dut);
+ apply_reset(dut);
+ clear_trap_lines(dut);
+
+ std::cout << "----------------------------------\n";
+ std::cout << " CSR Unit Testbench\n";
+ std::cout << "----------------------------------\n";
+
+ // Reset sanity
+ assert(read_csr(dut, CSR_MSTATUS) == 0);
+ assert(read_csr(dut, CSR_MIE) == 0);
+ assert(read_csr(dut, CSR_MEPC) == 0);
+ assert(read_csr(dut, CSR_SSTATUS) == 0);
+ assert(read_csr(dut, CSR_SATP) == 0);
+ std::cout << "[PASS] Reset clears CSR state" << std::endl;
+
+ // CSRRW basic write
+ csr_op(dut, CSR_MTVEC, CSR_OP_WRITE, 0x1020ULL);
+ assert(read_csr(dut, CSR_MTVEC) == 0x1020ULL);
+ csr_op(dut, CSR_MSCRATCH, CSR_OP_WRITE, 0xCAFEBABEULL);
+ assert(read_csr(dut, CSR_MSCRATCH) == 0xCAFEBABEULL);
+ std::cout << "[PASS] CSRRW updates mutable CSRs" << std::endl;
+
+ // CSRRS / CSRRC behavior
+ csr_op(dut, CSR_MIE, CSR_OP_WRITE, 0);
+ csr_op(dut, CSR_MIE, CSR_OP_SET, 0xBuLL);
+ assert(read_csr(dut, CSR_MIE) == 0xBuLL);
+ csr_op(dut, CSR_MIE, CSR_OP_CLEAR, 0x3ULL);
+ assert(read_csr(dut, CSR_MIE) == 0x8ULL);
+ csr_op(dut, CSR_MIE, CSR_OP_SET, 0x0ULL);
+ assert(read_csr(dut, CSR_MIE) == 0x8ULL);
+ std::cout << "[PASS] CSRRS/CSRRC honor masks and ignore zero data" << std::endl;
+
+ // Read-only CSRs ignore write attempts
+ const uint64_t misa_before = read_csr(dut, CSR_MISA);
+ csr_op(dut, CSR_MISA, CSR_OP_WRITE, 0ULL);
+ assert(read_csr(dut, CSR_MISA) == misa_before);
+ std::cout << "[PASS] Read-only CSRs reject writes" << std::endl;
+
+ // Canonical aliases for cycle/minstret
+ csr_op(dut, CSR_CYCLE, CSR_OP_WRITE, 0x40ULL);
+ assert(read_csr(dut, CSR_MCYCLE) == 0x40ULL);
+ advance_cycles(dut, 5);
+ assert(read_csr(dut, CSR_MCYCLE) == 0x45ULL);
+ const uint64_t instret_before = read_csr(dut, CSR_MINSTRET);
+ advance_cycles(dut, 3);
+ assert(read_csr(dut, CSR_MINSTRET) == instret_before + 3);
+ std::cout << "[PASS] Cycle/instret counters tick correctly" << std::endl;
+
+ // Trap should freeze minstret for that cycle
+ uint64_t mstatus_cfg = read_csr(dut, CSR_MSTATUS) | (1ULL << MSTATUS_MIE_BIT);
+ csr_op(dut, CSR_MSTATUS, CSR_OP_WRITE, mstatus_cfg);
+ const uint64_t instret_pre_trap = read_csr(dut, CSR_MINSTRET);
+ dut->trap_illegal_instr = 1;
+ dut->trap_epc = 0xDEADULL;
+ tick(dut);
+ dut->trap_illegal_instr = 0;
+ dut->trap_epc = 0;
+ assert(read_csr(dut, CSR_MINSTRET) == instret_pre_trap);
+ advance_cycles(dut, 1);
+ assert(read_csr(dut, CSR_MINSTRET) == instret_pre_trap + 1);
+ std::cout << "[PASS] Trap cycle does not increment minstret" << std::endl;
+
+ // Trap bookkeeping for illegal instruction
+ assert(read_csr(dut, CSR_MEPC) == 0xDEADULL);
+ assert(read_csr(dut, CSR_MCAUSE) == EXC_INST_ILLEGAL);
+ assert(read_csr(dut, CSR_MTVAL) == 0xDEADULL);
+ const uint64_t mstatus_after_trap = read_csr(dut, CSR_MSTATUS);
+ assert(((mstatus_after_trap >> MSTATUS_MIE_BIT) & 1ULL) == 0ULL);
+ assert(((mstatus_after_trap >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
+ assert(((mstatus_after_trap >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_M);
+ std::cout << "[PASS] Illegal trap updates mepc/mcause/mtval/mstatus" << std::endl;
+
+ // mret restore flow
+ dut->trap_is_mret = 1;
+ tick(dut);
+ dut->trap_is_mret = 0;
+ const uint64_t mstatus_after_mret = read_csr(dut, CSR_MSTATUS);
+ assert(((mstatus_after_mret >> MSTATUS_MIE_BIT) & 1ULL) == 1ULL);
+ assert(((mstatus_after_mret >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
+ assert(((mstatus_after_mret >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
+ std::cout << "[PASS] mret restores interrupt enable bits" << std::endl;
+
+ // sret restore flow
+ uint64_t sstatus_cfg = (1ULL << SSTATUS_SIE_BIT) | (1ULL << SSTATUS_SPP_BIT);
+ csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, sstatus_cfg);
+ dut->trap_is_sret = 1;
+ tick(dut);
+ dut->trap_is_sret = 0;
+ const uint64_t sstatus_after_sret = read_csr(dut, CSR_SSTATUS);
+ assert(((sstatus_after_sret >> SSTATUS_SIE_BIT) & 1ULL) == 0ULL);
+ assert(((sstatus_after_sret >> SSTATUS_SPIE_BIT) & 1ULL) == 1ULL);
+ assert(((sstatus_after_sret >> SSTATUS_SPP_BIT) & 1ULL) == 0ULL);
+ std::cout << "[PASS] sret updates supervisor status bits" << std::endl;
+
+ // Ecall causes depend on current privilege mode
+ force_priv_mode_via_mret(dut, PRIV_ENCODE_S);
+ dut->trap_is_ecall = 1;
+ dut->trap_epc = 0x600ULL;
+ tick(dut);
+ dut->trap_is_ecall = 0;
+ dut->trap_epc = 0;
+ assert(read_csr(dut, CSR_MCAUSE) == EXC_ECALL_FROM_S);
+ assert(read_csr(dut, CSR_MTVAL) == 0ULL);
+ assert(read_csr(dut, CSR_MEPC) == 0x600ULL);
+ assert(((read_csr(dut, CSR_MSTATUS) >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_S);
+
+ force_priv_mode_via_mret(dut, PRIV_ENCODE_U);
+ dut->trap_is_ecall = 1;
+ dut->trap_epc = 0x700ULL;
+ tick(dut);
+ dut->trap_is_ecall = 0;
+ dut->trap_epc = 0;
+ assert(read_csr(dut, CSR_MCAUSE) == EXC_ECALL_FROM_U);
+ assert(read_csr(dut, CSR_MTVAL) == 0ULL);
+ assert(read_csr(dut, CSR_MEPC) == 0x700ULL);
+ assert(((read_csr(dut, CSR_MSTATUS) >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
+ std::cout << "[PASS] Ecall encodes mcause per privilege" << std::endl;
+
+ // Trap delegation from U to S via medeleg
+ csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
+ csr_op(dut, CSR_MEDELEG, CSR_OP_SET, (1ULL << EXC_ECALL_FROM_U));
+ csr_op(dut, CSR_STVEC, CSR_OP_WRITE, 0x4000ULL);
+ force_priv_mode_via_mret(dut, PRIV_ENCODE_U);
+ dut->trap_is_ecall = 1;
+ dut->trap_epc = 0x900ULL;
+ tick(dut);
+ dut->trap_is_ecall = 0;
+ dut->trap_epc = 0;
+ assert(read_csr(dut, CSR_SEPC) == 0x900ULL);
+ assert(read_csr(dut, CSR_SCAUSE) == EXC_ECALL_FROM_U);
+ assert(read_csr(dut, CSR_STVAL) == 0ULL);
+ assert(dut->csr_priv_mode == PRIV_ENCODE_S);
+ assert(dut->csr_trap_to_s_mode);
+ assert(dut->csr_trap_vector == 0x4000ULL);
+ std::cout << "[PASS] medeleg routes ecall to supervisor" << std::endl;
+ force_priv_mode_via_mret(dut, PRIV_ENCODE_M);
+
+ // Basic supervisor CSRs touching for completeness
+ csr_op(dut, CSR_SIE, CSR_OP_WRITE, 0x55ULL);
+ csr_op(dut, CSR_SSCRATCH, CSR_OP_WRITE, 0xAAULL);
+ csr_op(dut, CSR_SEPC, CSR_OP_WRITE, 0x1234ULL);
+ csr_op(dut, CSR_SCAUSE, CSR_OP_WRITE, 0x21ULL);
+ csr_op(dut, CSR_SIP, CSR_OP_WRITE, 0x3ULL);
+ csr_op(dut, CSR_SATP, CSR_OP_WRITE, 0x8000000000000000ULL);
+ assert(read_csr(dut, CSR_SIE) == 0x55ULL);
+ assert(read_csr(dut, CSR_SSCRATCH) == 0xAAULL);
+ assert(read_csr(dut, CSR_SEPC) == 0x1234ULL);
+ assert(read_csr(dut, CSR_SCAUSE) == 0x21ULL);
+ assert(read_csr(dut, CSR_SIP) == 0x3ULL);
+ assert(read_csr(dut, CSR_SATP) == 0x8000000000000000ULL);
+ std::cout << "[PASS] Supervisor space CSR read/write" << std::endl;
+
+ // Writing SSTATUS should mirror MSTATUS subset bits
+ csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, (1ULL << SSTATUS_SIE_BIT));
+ assert(((read_csr(dut, CSR_MSTATUS) >> SSTATUS_SIE_BIT) & 1ULL) == 1ULL);
+ csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, 0ULL);
+ assert(((read_csr(dut, CSR_MSTATUS) >> SSTATUS_SIE_BIT) & 1ULL) == 0ULL);
+ std::cout << "[PASS] SSTATUS write mirrors MSTATUS" << std::endl;
+
+ // Floating-point CSR block
+ csr_op(dut, CSR_FFLAGS, CSR_OP_WRITE, 0x1FULL);
+ assert((read_csr(dut, CSR_FFLAGS) & 0x1FULL) == 0x1FULL);
+ csr_op(dut, CSR_FRM, CSR_OP_WRITE, 0x7ULL);
+ assert((read_csr(dut, CSR_FRM) & 0x7ULL) == 0x7ULL);
+ csr_op(dut, CSR_FCSR, CSR_OP_WRITE, 0xABULL);
+ assert((read_csr(dut, CSR_FFLAGS) & 0x1FULL) == (0xABULL & 0x1FULL));
+ assert((read_csr(dut, CSR_FRM) & 0x7ULL) == ((0xABULL >> 5) & 0x7ULL));
+ std::cout << "[PASS] FCSR/FM/FFLAGS read/write" << std::endl;
+
+ std::cout << "----------------------------------\n";
+ std::cout << "All CSR unit tests passed!\n";
+ std::cout << "----------------------------------\n";
+
+ delete dut;
+ return 0;
+}