Classroom Glossary Public page

Lab 7.1: Sv32 Paged VM Running with Page-Fault Handler

890 words

Total points: 30
Estimated time: 5 hours
Prerequisites: Labs 1-2 complete; full RV32I + privilege modes working; Verilator sim environment


Overview

This lab integrates the Sv32 MMU from the reference implementation (mmu.v) into your CPU, sets up a working page table, and demonstrates a demand-paging page-fault handler. By the end, user-mode code runs in a paged virtual address space and faults into the OS page-fault handler on unmapped accesses.


Part A: Integrate mmu.v (8 pts)

A1: Add mmu.v to your project (3 pts)

Copy ../virtus-academy-support/vca-csa-201/hdl/core/mmu.v into your hdl/core/ directory. Wire it into cpu.v between the fetch/data virtual address outputs and the memory bus:

  • Fetch-side: fetch_vaddr (from PC) → mmu.v fetch_vaddr → mmu.v fetch_paddrinstr_mem.addr
  • Data-side: data_vaddr (from ALU) → mmu.v data_vaddr → mmu.v data_paddrdata_mem.addr

Connect satp from csrfile output to mmu.v input. Connect priv from the CPU privilege register to mmu.v input. Connect fetch_fault and data_fault from mmu.v outputs to the trap logic (they should raise mcause = 12/13/15 in the trap handler).

A2: Bare-mode regression (3 pts)

In bare mode (satp.MODE = 0), mmu.v passes addresses through unchanged (paddr = vaddr). Verify by running all prior test suites:

make rv32ui && make rv32um && make rv32mi

All tests must still pass with mmu.v integrated and satp.MODE=0.

A3: SFENCE.VMA (2 pts)

Add SFENCE.VMA to the decoder. It takes the form FENCE with funct3=1 in the privileged encoding. When decoded, assert mmu.tlb_flush for one cycle. Verify: after writing a new PTE and executing SFENCE.VMA, the TLB is flushed and the next access uses the new translation.

Unit test: (1) map VA 0x1000 → PA 0x80001000; (2) access VA 0x1000 (TLB fills); (3) remap VA 0x1000 → PA 0x80002000; (4) execute SFENCE.VMA; (5) access VA 0x1000 again -- confirm the new PA is used (write to PA 0x80002000, not 0x80001000).


Part B: Set up a page table (10 pts)

B1: Kernel page table (4 pts)

Write a minimal kernel page table in assembly that maps:

  • VA 0x00000000-0x00FFFFFF → PA 0x00000000-0x00FFFFFF (identity map; kernel text + data)
  • VA 0x00001000 → PA 0x80001000 (one user code page, for the test program in B2)
  • VA 0x00002000 → PA 0x80002000 (one user stack page)

The page table must be a valid Sv32 structure: one root page directory and two second-level page tables (one for VPN[1]=0 which covers all low addresses; you only need to populate the VPN[0]=1 and VPN[0]=2 entries).

Set satp in the boot stub: satp = (1 << 31) | (root_page_table_ppn). Switch to S-mode using MRET with mstatus.MPP=1 (if you have S-mode); otherwise, stay in M-mode with satp set.

B2: Mapped access succeeds (3 pts)

Write a user-mode test program at VA 0x00001000 (physically at 0x80001000) that:

  1. Reads a word from VA 0x00002000 (stack page, mapped).
  2. Writes a word to VA 0x00002004 (stack page, mapped).
  3. Calls ECALL to signal completion.

Run under Verilator. Verify: the fetch from 0x00001000 succeeds (PA 0x80001000 reaches instr_mem); the load from 0x00002000 succeeds (PA 0x80002000); the store to 0x00002004 succeeds.

B3: Unmapped access faults correctly (3 pts)

Add a second test: the user program accesses VA 0x00003000 (not in the page table). Verify:

  • For a load: mcause = 13 (load page fault)
  • For an instruction fetch from 0x00003000: mcause = 12 (instruction page fault)
  • For a store to 0x00003000: mcause = 15 (store/AMO page fault)

Verify all three fault codes fire on the correct access type.


Part C: Demand-paging page-fault handler (12 pts)

C1: Physical page allocator (3 pts)

Write a minimal physical page allocator in C. The allocator manages a 16-page free list (each page = 4 KiB). alloc_page() returns the physical base address of a free page; free_page(pa) returns a page to the free list. Initialize the free list to physical addresses 0x80003000 through 0x80013000 (16 pages × 4 KiB).

C2: Page-fault handler (6 pts)

In the M-mode trap handler (extending Module 2's trap_dispatch), add a case for mcause = 13 (load page fault):

  1. Read mtval (the faulting virtual address).
  2. Compute the page base: virt_page = faulting_va & ~0xFFF.
  3. Call alloc_page() to get a physical page.
  4. Walk the page table to find the L0 PTE for virt_page:
    • Use satp.PPN to find the L1 page directory.
    • Compute VPN[1] and VPN[0] from virt_page.
    • If the L1 PTE is invalid, allocate a new L0 page table, install the L1 PTE.
    • Write the L0 PTE: (new_pa >> 12) << 10 | PTE_V | PTE_R | PTE_W | PTE_U | PTE_A | PTE_D.
  5. Execute SFENCE.VMA (via inline assembly or the csrw path).
  6. Return via MRET to the faulting instruction.

The faulting instruction will re-execute after MRET. Now that the page is mapped, it will succeed.

C3: End-to-end test (3 pts)

Write a user-mode test program that:

  1. Deliberately accesses 4 different unmapped VA pages (VA 0x00003000, 0x00004000, 0x00005000, 0x00006000).
  2. Writes a known pattern to each page after the page-fault handler maps it.
  3. Reads the patterns back and verifies they are correct.
  4. Calls ECALL SYS_WRITE to report: "demand-paged 4 pages, all data intact."

Grading

Part Criteria Points
A1 mmu.v integrated; fetch and data paths both connected 3
A2 All prior test suites pass with mmu.v in bare mode 3
A3 SFENCE.VMA decoded; TLB flush verified with the remap test 2
B1 Valid Sv32 page table; satp configured 4
B2 Mapped load/store/fetch all succeed; traces confirm PA 3
B3 All three fault codes fire on correct access type 3
C1 Physical page allocator correct; free list manages 16 pages 3
C2 Page-fault handler maps page + SFENCE.VMA + MRET 6
C3 4-page demand-paging test passes; data integrity verified 3
Total 30