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.
| 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 |
CSRs are accessed with three atomic read-modify-write instructions. All three return the old CSR value in rd before modifying it:
// 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
When an exception occurs (illegal instruction, misaligned address, ecall), the hardware automatically:
mepcmcause (e.g., 2 = illegal instruction, 11 = ECALL from M-mode)mstatus (disables further interrupts)mtvecThe 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
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.
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.
Machine Return — restores the PC from mepc, re-enables interrupts (restores MIE from MPIE), and returns from the trap handler to the interrupted code.