Total points: 25
Estimated time: 4 hours
Prerequisites: Lab 1.1 complete; full RV32I CPU running; Verilator simulation environment
Overview
This lab implements the trap mechanism from the RISC-V privileged architecture specification. You will add privilege modes (U and M), the six trap CSRs (mstatus, mtvec, mepc, mcause, mtval, mscratch), and ECALL/MRET support. Then you will write a minimal trap handler and a user-mode test program that transitions to supervisor mode, calls SYS_WRITE, and returns.
The deliverable is a working kernel + user program running under Verilator with verified mode transitions and a measured trap round-trip cycle count.
Part A: Hardware -- privilege modes and trap CSRs (10 pts)
A1: Mode register and mstatus (3 pts)
Add a 2-bit privilege mode register to your CPU state (priv register: 2'b11 = M-mode, 2'b00 = U-mode). Initialize to M-mode at reset.
Implement mstatus (CSR address 0x300) with the following bits:
- Bit 3:
MIE(machine interrupt enable; initialize to 0; write 0 in trap entry) - Bits 12:11:
MPP(machine previous privilege; saved on trap entry; restored on MRET)
Other mstatus bits can be read-as-zero for now.
A2: Trap CSRs (4 pts)
Implement the following CSRs as read/write registers:
mtvec(0x305): trap vector base address; lower 2 bits encode mode (00 = direct)mepc(0x341): machine exception program countermcause(0x342): machine trap causemtval(0x343): machine trap value (write 0 for ECALL)mscratch(0x340): machine scratch register
The six CSR instructions (CSRRW, CSRRS, CSRRC, CSRRWI, CSRRSI, CSRRCI) should already be partially implemented from the Zicsr stub in Lab 1.1; complete them now.
A3: ECALL and MRET (3 pts)
Implement ECALL trap delivery:
- Save PC to
mepc - Write cause code to
mcause(value 8 for U-mode ECALL; value 11 for M-mode ECALL) - Save current privilege level to
mstatus.MPP - Write 0 to
mstatus.MIE - Set
privto M-mode (2'b11) - Jump PC to
mtvec(direct mode: PC = mtvec & ~3)
Implement MRET:
- Set
privtomstatus.MPP - Set
mstatus.MIEto 1 (re-enable interrupts) - Jump PC to
mepc
Verify A3 with the rv32mi-p-* riscv-tests machine-level trap tests:
make rv32mi # expects PASS for csr, ma_fetch, ma_data, illegal, ma_addr, ecall
Part B: Software -- trap handler and user program (10 pts)
B1: Minimal trap handler (6 pts)
Write the trap handler in RISC-V assembly. The handler must:
- Save all 32 registers to a kernel stack frame. Use
mscratchto hold the kernel stack pointer while saving the usersp. - Read
mcause(viacsrr a0, mcause) and branch on the cause code. - Handle ECALL (mcause = 8) by dispatching on
a7(the syscall number saved in the frame). - Implement SYS_WRITE (a7 = 64): write the null-terminated string at the address in
a1to the simulator output port at address 0xFFFF0000. - Implement SYS_EXIT (a7 = 93): halt the simulation by writing the exit code to 0xFFFF0008 and entering an infinite loop.
- Handle illegal instruction (mcause = 2): write a fault message and call SYS_EXIT(-1).
- Restore all 32 registers from the frame.
- Advance
mepcby 4 (past the ECALL instruction). - Execute MRET to return to user mode.
Skeleton to start from (trap_handler.S):
.global _trap_start
_trap_start:
csrrw sp, mscratch, sp # swap sp and mscratch; sp now = kernel stack
addi sp, sp, -132 # 32 regs * 4 = 128 + 4 for mcause
sw x1, 4(sp)
# ... save x2-x31 ...
# NOTE: x2 (original user sp) is in mscratch now; save it manually:
csrr t0, mscratch
sw t0, 8(sp)
csrr a0, mcause
sw a0, 0(sp) # save cause at top of frame
call trap_dispatch # C function handles dispatch
lw x1, 4(sp)
# ... restore x2-x31 ...
lw t0, 8(sp) # restore original user sp
csrw mscratch, t0 # (in case we return to user mode)
csrr t0, mepc
addi t0, t0, 4
csrw mepc, t0
addi sp, sp, 132
csrrw sp, mscratch, sp # restore user sp
mret
B2: User-mode test program (4 pts)
Write a user-mode program in RISC-V assembly that:
- Executes
ecallwitha7 = 64(SYS_WRITE),a0 = 1(stdout),a1 = msg1(address of "hello\n"),a2 = 6(length). - Executes
ecallagain witha7 = 64,a1 = msg2, outputting "world\n". - Executes
ecallwitha7 = 93(SYS_EXIT),a0 = 0(exit code 0).
Write a linker script that places the kernel trap handler at 0x00000000 (M-mode code), the user program at 0x00001000, and the kernel stack at 0x00010000. The boot stub should set up mtvec to point to _trap_start, set mscratch to the kernel stack top, and use mret to jump to the user program with mstatus.MPP = U-mode.
Part C: Measurement and verification (5 pts)
C1: Waveform verification (3 pts)
Run the kernel + user program under Verilator with VCD waveform dump enabled. Open the waveform in GTKWave (or equivalent). Capture screenshots or annotate the waveform at the following points:
- The ECALL instruction in the user program reaches the execute stage.
- The
privregister transitions from U-mode (2'b00) to M-mode (2'b11). - PC jumps to
mtvec(the trap handler address). - The MRET at the end of the handler.
- The
privregister transitions back to U-mode.
Submit the annotated waveform screenshots.
C2: Cycle cost measurement (2 pts)
Add two csrr instructions (reading mcycle) to the user program: one immediately before the ECALL and one immediately after MRET returns. Compute the round-trip cycle count: ECALL to MRET-return.
Record this measurement. It is your trap-overhead baseline that the Module 11 scheduler will reference when measuring context-switch cost (each context switch incurs this overhead twice: once into the kernel, once back to user).
Expected range: 60-120 cycles for the save/restore of 32 registers plus kernel dispatch.
Grading
| Part | Criteria | Points |
|---|---|---|
| A1 | priv register + mstatus bits implemented; mode transitions correctly | 3 |
| A2 | All 6 trap CSRs read/write correctly; CSR instructions work | 4 |
| A3 | ECALL and MRET semantics correct; rv32mi-p-* PASS | 3 |
| B1 | Trap handler saves/restores all 32 regs; dispatches correctly; SYS_WRITE + SYS_EXIT work | 6 |
| B2 | User program boots from U-mode; calls SYS_WRITE twice + SYS_EXIT | 4 |
| C1 | Waveform shows mode transitions at correct instruction boundaries | 3 |
| C2 | Round-trip cycle count measured and recorded with explanation | 2 |
| Total | 25 |