Skip to main content

exceptions

Introduction

Synchronous and asynchronous exceptions are fundamental concepts in computer architecture, distinguishing between exceptions caused directly by the execution of instructions and those resulting from external events or conditions. Here's a breakdown of examples for both types:

Synchronous Exceptions

Synchronous exceptions, also known as traps, are directly caused by the execution of the current instruction sequence. They include:

  1. Instruction Address Misaligned: Occurs when an instruction is fetched from a non-aligned address that does not conform to the architecture's alignment requirements.
  2. Load Address Misaligned: Similar to the instruction address misalignment, but occurs during a load operation from a non-aligned memory address.
  3. Store/AMO Address Misaligned: Occurs during a store or atomic memory operation to a non-aligned memory address.
  4. Page Fault: Occurs when accessing a page that is not currently mapped in the memory management unit (MMU), requiring a page table walk or indicating a protection violation.
  5. Floating Point Exception: Arises when a floating-point operation results in an undefined or unrepresentable result, such as division by zero, overflow, or underflow.
  6. System Call: A deliberate exception triggered by software to request a service from the operating system's kernel.

Asynchronous Exceptions (Interrupts)

Asynchronous exceptions, or interrupts, occur independently of the instruction stream currently being executed. They are typically triggered by external events. Examples include:

  1. External Interrupts: Generated by devices outside the CPU, such as network cards, keyboards, or other peripherals, signaling that they require attention.
  2. Timer Interrupts: Generated by a timer within the processor, useful for operating system schedulers to implement multitasking by periodically interrupting the current process.
  3. Inter-Processor Interrupts (IPIs): Used in multi-processor systems where one processor needs to interrupt or signal another processor, often for task scheduling or synchronization purposes. Both synchronous and asynchronous exceptions are integral to the operation of modern computing systems, enabling efficient and controlled management of resources, error handling, and interaction between hardware and software components.

Exceptions In our RISC-V Core

Illegal Instruction Exception (trap)

Illegal instruction is a synchronous exception because its origin comes from the software flow.

Causes of illegal instructions

  • Un familiar instruction that belongs to a specific RISCV extension that not supported bt the core.
  • Hw errors while fetching the instruction from the memory.
  • Custom instructions that not supported by RISCV spec (In most of the cases the compiler will not compile that).
  • Compiler errors (very rare but possible).

Cases in our core

For more details, please refer to /source/big_core/illegal_instruction.vh

  • Some of the Funct7 fields in R-type instructions do not zero
  • Funct3 do not match the instruction. For example we try to execute S-type instruction and Funct3 = 111.
  • Un recognized OpCode that not supported by the core or not allowed by the spec.

Illegal Instruction Generation

  • We use the test /verif/big_core/alive_illegal.c.
  • We try to create an instruction with illegal FUCT7, we generate slli with funct7 = 0x7f instead of 0x0
   // This instruction is trying to generate slli instruction with illegal FUNCT7.
asm(".word 0xfff79793" : /* outputs / : / inputs / : / clobbers */);
  • This is a code snippet from the elf.txt file
  1660: fd010113            addi    sp,sp,-48
.
.
.
1674: 00200793 li a5,2
1678: fef42423 sw a5,-24(s0)
167c: fff79793 0xfff79793
1680: fec42703 lw a4,-20(s0)
.
.
.
16b0: 00008067 ret

Illegal Instruction Mechanism

  1. Detection if illegal instruction inside the controller : assign IllegalInstructionQ101H = (PreIllegalInstructionQ101H) && ! (flushQ102H || flushQ103H);
  • In case of illegal instruction and flush, we do not start the the interrupt routine because the instruction will be flushed anyway.
  • When the illegal instruction is a part of the instruction flow than we erase that instruction by inserting NOP and jumps to the interrupt routine.
  1. Csr update Once we decide to take the exception we start to update and read csr's. T
  • We update the cause of the exception by modifying the csr_mcause csr by assign the 32'h00000002.
  • Update csr_mepc with the return value PC of the illegal instruction. We will use it as return address from the interrupt routine.
  • Update csr_mtval with the illegal instruction machine code. In our case it will be fff79793
  • Set the CSR_MSTATUS[MIE] to the current value of CSR_MSTATUS[MIE] to store the previous machine interrupt enable mode.
  • Disable CSR_MSTATUS[MIE] when taking an exception to avoid nested interrupts.
  1. Jumps to Interrupt routine
  • Store the values of the registers
  • Perform the routine Jump to csr_mtvec value that keeps the address pf the routine.
  1. Return from interrupt routine
  • Restore the registers
  • update CSR_MSTATUS[MIE] with CSR_MSTATUS[MIE].

crt0.s_boot_trap.s file

csr_init:
li t0, 0x100 # Load the immediate value 0x100 of trap handler address
csrw mtvec, t0 # Write the value in t0 to the mtvec CSR
  • The address of the interrupt routine is 0x100
  • Inside that file we store and restore the registers before jumping to the routine inside interrupt_handler.h. Please see /app/crt0/crt0_boot_trap.S and /app/defines/interrupt_handler.h

Interrupt_handler.h

 if ((mcause & 0xFFF) == ILLEGAL_INSTRUCTION_EXCEPTION) { 
csr_mepc = read_mepc();
csr_mtval = read_mtval();
rvc_printf("ILGL INST\n");
rvc_printf("MEPC:");
rvc_print_unsigned_int_hex(csr_mepc);
rvc_printf("\n");
rvc_printf("MTVAL:");
rvc_print_unsigned_int_hex(csr_mtval);
rvc_printf("\n");
}

Timer interrupt exception

  • We use internal timer to generate the interrupt. The timer interrupt mechanism compared between csr_custom_mtime to csr_custom_mtimecmp (will be explained soon)
  • One of the tests we use is `bubblesort_with_timer.c' to test the timer interrupt

Timer interrupt flow

  1. By default we disable timer interrupts. The reason for that is to avoid from any exceptions while booting the system like we do in crt0.s file
csr_init:
li t0, 0x100 # Load the immediate value 0x100 of trap handler address
csrw mtvec, t0 # Write the value in t0 to the mtvec CSR

# Enable software and external interrupts. Set meie and msie to 1 and mtie to 0 as default values
li t0, 0x808
csrw mie, t0

# Enable interrupts. Set MIE and MPIE bit to 1 and 0 respectively in mstatus register.
li t0, 0x8
csrw mstatus, t0

# update custom csr at address 0xBC0 that serves as mtimecmp register
li t0, 0x00000500
csrw 0xBC0, t0

The above code is a part of csr initialization in the crt0.s file. We disable timer interrupts by clear MTIP bit. We allow other interrupts to be taken by setting mie csr bits and mstatus appropriate bits. We set csr_custom_mtimecmp to 0x500. 2. Inside the csr module once csr_custom_mtime >= csr_custom_mtimecmp, we put the timer interrupt interrupt in pending mode

next_csr.csr_mip[CSR_MIP_MTIP]  = (csr.csr_custom_mtime >= csr.csr_custom_mtimecmp);
  1. From naive perspective, the timer interrupt can ocurred only when the following conditions are met:
assign TimerInterruptEnable =  csr.csr_mip[CSR_MIP_MTIP]          && // 1) mtime >= mtimecmp : MTIP - Machine Timer Interrupt Pending
csr.csr_mstatus[CSR_MSTATUS_MIE] && // 2) in mstatus register MIE bit is set
csr.csr_mie[CSR_MIE_MTIE]; // 3) in mie register MTIE bit is set
  1. When TimerInterruptEnable = 1 than the we let the controller to decide whether to take the interrupt or not. We do that cause we do not want to take that exception while the controller has an "Invalid Instruction". The condition that effects if the exception will be taken or not are inside the controller.
assign TimerInterruptTakenQ101H = (TimerInterruptEnable) & (PreValidInstQ101H) & !(JumpOrBranch) & !(IllegalInstructionQ101H);
  1. Once the Timer interrupt begins we do the following:
  • update mcause
  • update mepc with the return address
  • set the CSR_MSTATUS[MPIE] to the current value of CSR_MSTATUS[MIE]
  • disable CSR_MSTATUS[MIE] when taking an exception to avoid nested interrupts
  1. We store the registers and jumps to interrupt handler. At the end we update csr_custom_mtimecmp with new value
    if (mcause == MACHINE_TIMER_INTERRUPT) {

//Run the mtime interrupt handler routine
mtime_routine_handler();

unsigned int csr_custom_mtime = read_custom_mtime();
unsigned int csr_custom_mtimecmp = read_custom_mtimecmp();

csr_custom_mtimecmp = csr_custom_mtime + TIMER_INTERRUPT_INTERVAL;
write_custom_mtimecmp(csr_custom_mtimecmp);


}
  1. Restore the registers and return to the original program
  2. next_csr.csr_mstatus[CSR_MSTATUS_MIE] = csr.csr_mstatus[CSR_MSTATUS_MPIE];