Skip to content
STIMSMITH

SOURCE ARCHIVE

SHA256: ad249381fa58854427e8f05d59740a6bde8254944c0eabcd4e96168b6ec6a5b2
TYPE: text/html
SIZE: 20.3 KB
FETCHED: 6/26/2026, 10:02:34 PM
EXTRACTOR: http-html
CHARS: 8,301

EXTRACTED CONTENT

8,301 chars

DAY 21 · PHASE 4 — ADVANCED & REAL HARDWARE

CSRs, Traps & Exceptions

By EcrioniX · Updated 2026-06-11

The pipeline is complete. Now we add the privilege layer — the mechanism that makes a CPU safe and supervisable. Control and Status Registers (CSRs) give software a way to control processor behaviour. Traps give the CPU a way to handle errors and system calls. Together they form the foundation of operating system support.

Key M-Mode CSRs

CSR Name Address Purpose
mstatus 0x300 Global interrupt enable (MIE bit[3]), previous interrupt state (MPIE bit[7])
mtvec 0x305 Trap handler base address — PC jumps here on any trap
mepc 0x341 Machine Exception PC — saves the PC of the trapped instruction
mcause 0x342 Cause of the trap (bit[31]=interrupt/exception, bits[30:0]=cause code)
mscratch 0x340 Scratch register for trap handler software use
mip 0x344 Machine Interrupt Pending — which interrupts are waiting
mie 0x304 Machine Interrupt Enable — which interrupts are enabled

CSR Instructions

CSRs are accessed with three atomic read-modify-write instructions. All three return the old CSR value in rd before modifying it:

  • CSRRW rd, csr, rs1 — atomically swap: rd = CSR; CSR = rs1
  • CSRRS rd, csr, rs1 — set bits: rd = CSR; CSR = CSR | rs1
  • CSRRC rd, csr, rs1 — clear bits: rd = CSR; CSR = CSR & ~rs1
// csr_file.v — M-mode Control and Status Register file
// Supports CSRRW, CSRRS, CSRRC and hardware trap signals
module csr_file (
    input         clk, rst,
    // CSR instruction interface
    input  [11:0] csr_addr,     // 12-bit CSR address from instruction
    input  [ 2:0] csr_op,      // 001=CSRRW, 010=CSRRS, 011=CSRRC
    input         csr_we,      // 1 = write CSR
    input  [31:0] csr_wdata,   // write data (from rs1)
    output reg [31:0] csr_rdata, // read data (old value, to rd)
    // Hardware trap interface
    input         trap,         // exception/interrupt asserted
    input  [31:0] trap_pc,     // PC of trapped instruction
    input  [31:0] trap_cause,  // mcause code
    output [31:0] mtvec_out,   // trap vector — where to jump on trap
    output [31:0] mepc_out,    // saved PC — used by MRET
    // MRET
    input         mret          // 1 = MRET instruction executing
);
    // ── CSR storage ───────────────────────────────────────────────
    reg [31:0] mstatus;   // 0x300
    reg [31:0] mie;       // 0x304
    reg [31:0] mtvec;     // 0x305
    reg [31:0] mscratch;  // 0x340
    reg [31:0] mepc;      // 0x341
    reg [31:0] mcause;    // 0x342
    reg [31:0] mip;       // 0x344

    assign mtvec_out = mtvec;
    assign mepc_out  = mepc;

    // ── CSR Read (combinatorial) ──────────────────────────────────
    always @(*) begin
        case (csr_addr)
            12'h300: csr_rdata = mstatus;
            12'h304: csr_rdata = mie;
            12'h305: csr_rdata = mtvec;
            12'h340: csr_rdata = mscratch;
            12'h341: csr_rdata = mepc;
            12'h342: csr_rdata = mcause;
            12'h344: csr_rdata = mip;
            default: csr_rdata = 32'h0;
        endcase
    end

    // ── CSR Write (synchronous) ───────────────────────────────────
    task do_csr_write;
        input [31:0] old_val;
        input [31:0] wdata;
        input [ 2:0] op;
        output reg [31:0] new_val;
        begin
            case (op)
                3'b001: new_val = wdata;           // CSRRW
                3'b010: new_val = old_val | wdata; // CSRRS
                3'b011: new_val = old_val & ~wdata; // CSRRC
                default: new_val = old_val;
            endcase
        end
    endtask

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            mstatus  <= 32'h0000_1800; // MPP=11 (M-mode)
            mie      <= 0; mtvec    <= 0;
            mscratch <= 0; mepc     <= 0;
            mcause   <= 0; mip      <= 0;
        end else if (trap) begin
            // Hardware trap: save PC, cause; jump to handler
            mepc     <= trap_pc;
            mcause   <= trap_cause;
            // Clear MIE, save to MPIE
            mstatus  <= {mstatus[31:8], mstatus[3], mstatus[6:4], 1'b0, mstatus[2:0]};
        end else if (mret) begin
            // Return from trap: restore MIE from MPIE
            mstatus <= {mstatus[31:8], 1'b1, mstatus[6:4], mstatus[7], mstatus[2:0]};
        end else if (csr_we) begin
            // Software CSR write
            case (csr_addr)
                12'h300: do_csr_write(mstatus, csr_wdata, csr_op, mstatus);
                12'h304: do_csr_write(mie,     csr_wdata, csr_op, mie);
                12'h305: do_csr_write(mtvec,   csr_wdata, csr_op, mtvec);
                12'h340: do_csr_write(mscratch,csr_wdata, csr_op, mscratch);
                12'h341: do_csr_write(mepc,    csr_wdata, csr_op, mepc);
                12'h342: do_csr_write(mcause,  csr_wdata, csr_op, mcause);
                default: ; // read-only or unimplemented CSR
            endcase
        end
    end
endmodule

Trap Handling Flow

When an exception occurs (illegal instruction, misaligned address, ecall), the hardware automatically:

  1. Saves the current PC to mepc
  2. Writes the exception code to mcause (e.g., 2 = illegal instruction, 11 = ECALL from M-mode)
  3. Clears the MIE bit in mstatus (disables further interrupts)
  4. Jumps the PC to the address stored in mtvec

The trap handler reads mcause to determine what happened, services the event, and executes MRET to return.

// tb_csr.v — Test CSRRW and trap handling
`timescale 1ns/1ps
module tb_csr;
    reg clk=0, rst=1;
    always #5 clk=~clk;

    // Direct instantiation of csr_file for unit testing
    reg [11:0] csr_addr;
    reg [2:0]  csr_op;
    reg        csr_we, trap, mret;
    reg [31:0] csr_wdata, trap_pc, trap_cause;
    wire [31:0] csr_rdata, mtvec_out, mepc_out;

    csr_file dut (
        .clk(clk), .rst(rst),
        .csr_addr(csr_addr), .csr_op(csr_op),
        .csr_we(csr_we), .csr_wdata(csr_wdata), .csr_rdata(csr_rdata),
        .trap(trap), .trap_pc(trap_pc), .trap_cause(trap_cause),
        .mtvec_out(mtvec_out), .mepc_out(mepc_out), .mret(mret)
    );

    initial begin
        $dumpfile("tb_csr.vcd"); $dumpvars(0, tb_csr);
        csr_we=0; trap=0; mret=0; csr_op=3'b001;
        @(posedge clk); @(posedge clk); rst=0;

        // Write mtvec = 0x8000
        csr_addr=12'h305; csr_wdata=32'h8000; csr_we=1; csr_op=3'b001;
        @(posedge clk); csr_we=0;
        @(posedge clk);
        if(mtvec_out===32'h8000) $display("PASS: mtvec=0x8000");
        else $display("FAIL: mtvec=%h",mtvec_out);

        // Trigger a trap (ecall, cause=11)
        trap=1; trap_pc=32'h100; trap_cause=32'd11;
        @(posedge clk); trap=0;
        @(posedge clk);
        if(mepc_out===32'h100) $display("PASS: mepc=0x100");
        else $display("FAIL: mepc=%h",mepc_out);

        $finish;
    end
endmodule

Day 21 Takeaways

  • CSRs are 32-bit special-purpose registers accessed by 12-bit addresses — completely separate from the integer register file.
  • CSRRW/CSRRS/CSRRC always return the old value atomically — this enables safe read-modify-write sequences.
  • On a trap, the CPU hardware saves PC → mepc, cause → mcause, and jumps to mtvec. Software handles the rest.
  • MRET restores the PC from mepc — without it, you cannot return from a trap handler.
  • M-mode is the highest privilege level. S-mode (supervisor) and U-mode (user) are added for OS support (Day 25).

FAQ

What are RISC-V CSRs?

Control and Status Registers are special 32-bit registers that control CPU behavior (interrupt enable, trap vector) and record status (exception cause, saved PC). They are accessed with CSRRW/CSRRS/CSRRC — not normal load/store instructions.

What happens when a RISC-V trap occurs?

Hardware saves PC to mepc, writes cause to mcause, clears MIE in mstatus, and jumps to mtvec. Software reads mcause, handles the event, and executes MRET to return.

What is MRET?

Machine Return — restores the PC from mepc, re-enables interrupts (restores MIE from MPIE), and returns from the trap handler to the interrupted code.

← Full roadmap