When an instruction cannot be carried out

ecall is a deliberate trap: your program runs it to ask the Playground to do something. This lesson handles a different kind of event, a synchronous fault: an instruction cannot be carried out, so the processor transfers control to a handler.

For example, lw needs a word-aligned address. An address ending in binary ...01 is not a multiple of four, so a word load from it faults.

What happens on a fault

Before the handler's first instruction, the hardware does these things in order:

  1. It abandons the instruction that cannot run.
  2. It writes that instruction's address to uepc.
  3. It writes a cause code to ucause, and the related address or value to utval.
  4. It jumps to the address in utvec, if user exceptions are enabled in ustatus.

The handler finishes with uret. uret resumes execution at the address currently in uepc. That is why a handler can either retry a faulting instruction or arrange to skip it.

utvec, ustatus, uepc, ucause, and utval are control and status registers (CSRs), separate from the 32 general-purpose registers. These are the CSR instruction forms used here:

instructionmeaning
csrr rd, csrread a CSR into general-purpose register rd
csrw rs, csrwrite general-purpose register rs into a CSR
csrsi csr, maskOR the immediate mask into a CSR

So csrsi ustatus, 1 ORs in the mask 1 (...0001 in binary), setting bit 0. Without enabling bit 0, writing a handler address to utvec alone has no effect. When user exceptions are disabled, this Playground reports the fault and ends the run.

Installing a handler

This program deliberately places w at a misaligned address. The byte odd occupies one byte; .align 0 prevents the usual padding before the word, so w's address ends in ...01. A normal word alignment directive would insert padding before w.

The handler uses t0 and t1, so it saves and restores both. It makes two word-sized stack slots, saves those registers, does its work, restores them, then restores sp. The Playground provides a valid stack for this program, and every stack access below is word-aligned.

Here j end is ordinary control flow. end names the address after the last assembled instruction, so reaching it completes this Playground run.

After the run, s0 is 5. last_cause holds 4, the code for a misaligned load, and last_value holds the misaligned address of w.

Choosing where to return

uepc contains the address of the lw that faulted, not the address after it. If the handler executes uret without changing uepc, the processor retries that same lw; it faults again and the program repeats the handler forever.

This handler chooses not to perform the load, so it adds 4 to uepc. Four is correct for the 32-bit, four-byte instructions this Playground assembles. A handler that repaired the problem instead would leave uepc unchanged and let the instruction run again.

For these memory faults, ucause and utval help identify the problem:

codewhat happenedutval
4load address misalignedthe load address
5load access faultthe inaccessible address
6store address misalignedthe store address
7store access faultthe inaccessible address

Arithmetic instructions do not raise these faults. That includes div: as in the earlier arithmetic work, this Playground produces -1 for division by zero.

Polling still handles input

An interrupt is a device asking for attention between instructions; it is different from the instruction-caused faults above. This Playground does not deliver device interrupts. Its keyboard model remains polling: read the ready status, then read a character when it is ready.

Your turn

The lw below faults. Fill the handler so it advances uepc past that lw and returns with uret. Preserve t0 and restore sp before returning.

Automated state checks cover reaching post-fault main, preserved t0, unchanged t2, and restored sp; stepping verifies the uepc update and uret control path.

Show solution