Classroom Glossary Public page

Lab 2.1: ECALL Trap -- First User-to-Supervisor Transition

829 words

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 counter
  • mcause (0x342): machine trap cause
  • mtval (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:

  1. Save PC to mepc
  2. Write cause code to mcause (value 8 for U-mode ECALL; value 11 for M-mode ECALL)
  3. Save current privilege level to mstatus.MPP
  4. Write 0 to mstatus.MIE
  5. Set priv to M-mode (2'b11)
  6. Jump PC to mtvec (direct mode: PC = mtvec & ~3)

Implement MRET:

  1. Set priv to mstatus.MPP
  2. Set mstatus.MIE to 1 (re-enable interrupts)
  3. 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:

  1. Save all 32 registers to a kernel stack frame. Use mscratch to hold the kernel stack pointer while saving the user sp.
  2. Read mcause (via csrr a0, mcause) and branch on the cause code.
  3. Handle ECALL (mcause = 8) by dispatching on a7 (the syscall number saved in the frame).
  4. Implement SYS_WRITE (a7 = 64): write the null-terminated string at the address in a1 to the simulator output port at address 0xFFFF0000.
  5. Implement SYS_EXIT (a7 = 93): halt the simulation by writing the exit code to 0xFFFF0008 and entering an infinite loop.
  6. Handle illegal instruction (mcause = 2): write a fault message and call SYS_EXIT(-1).
  7. Restore all 32 registers from the frame.
  8. Advance mepc by 4 (past the ECALL instruction).
  9. 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:

  1. Executes ecall with a7 = 64 (SYS_WRITE), a0 = 1 (stdout), a1 = msg1 (address of "hello\n"), a2 = 6 (length).
  2. Executes ecall again with a7 = 64, a1 = msg2, outputting "world\n".
  3. Executes ecall with a7 = 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:

  1. The ECALL instruction in the user program reaches the execute stage.
  2. The priv register transitions from U-mode (2'b00) to M-mode (2'b11).
  3. PC jumps to mtvec (the trap handler address).
  4. The MRET at the end of the handler.
  5. The priv register 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