Introduction

Conditional and jump instructions change the flow of execution in a program. Exceptions are events that occur during program execution and also change the flow of execution.

An exception is said to be synchronous if it is caused by an instruction in the current program. Arithmetic exceptions (division by zero, overflow, …), access to invalid memory addresses are examples of synchronous exceptions. An exception is said to be asynchronous if it is caused by an I/O device (keyboard, mouse, …). This type of exception is not related to program execution and is also called hardware interrupts.

When an exception occurs, the current program is suspended and control is transferred to an exception handler to process the exception that occurred. Once the exception is handled, control is returned back to the program which resumes its execution from where it has been suspended. In contrast to traditional function calls, the exception handler is a “suddenly called” procedure (without parameters or return value).

There are two modes of program execution in a MIPS processor. The user mode and the supervisor mode (aka kernel mode). Standard programs run in user mode. The processor switches to supervisor mode when an exception occurs. The exception handling mechanism in MIPS is implemented by an auxiliary coprocessor called: Coprocessor0. This device contains several registers which store information about the exception.

vaddr ($8) Contains the memory address whose access is invalid. Caused by a load, store, or fetch instruction.
status ($12) Interruption mask (see below).
cause ($13) Exception type and pending interrupts (see below).
epc ($14) Holds the instruction address (i.e. content of $pc) when the exception occurred.

In MARS, the values ​​of the registers: vaddr, status, cause and epc are accessible from the Coprocessor 0 tab.

Coprocessor0

Examples of code to generate software exceptions are shown below. The first example initializes the register $t0 with 0x7fffffff and $t1 with 1. The addu instruction performs an unsigned addition and therefore ignores the overflow. However, the add instruction performs a signed addition and since there is an overflow here, this instruction will cause an arithmetic overflow exception. In the second example, an attempt is made to store data at an illegal address in memory. In the third example, we are trying to read a word from a misaligned address in memory. And in the last example, the very effect of inserting a breakpoint (i.e.a break instruction) into a program (while debugging) will cause an exception.

  # Arithmetic overflow exception
  li     $t0, 0x7fffffff    # $t0 = MAX_INT 
  li     $t1, 1             # $t1 = 1
  addu   $t2, $t0, $t1      # Ignore Overflow 
  add    $t3, $t0, $t1      # Detect Overflow

Overflow

  # Invalid address exception
  # cannot save to address 4 in memory 
  li     $t0, 4
  li     $a0, 5
  sw     $a0, ($t0)

Overflow

  # Unaligned memory address exception
  .data
  arr: .word 12, 17
  . . .
  la     $t0, arr
  lw     $t0, 1($t0)

Overflow

  # Breakpoint exception
  # caused by a "break" instruction
  .text
  . . .
  break

Overflow

The screenshots on the right above show the states of Coprocessor0 registers when exceptions occur. The exception program register epc ($14) stores the address of the instruction when the exception occured. The value of epc in the first example is 0x00400010. This value is the address in memory of the add instruction that caused the arithmetic overflow. In the second example, the epc register contains the value 0x0040001c, which corresponds to the address of the sw instruction that attempted to write to an illegal address in memory. In the third example, epc contains the value 0x00400028, which is the address of the lw instruction that generated the misaligned address exception.

If a read, write, jump, or branch instruction creates an exception, then vaddr ($8) register will contain the invalid address that caused the exception. Continuing with the examples above, the illegal write address that caused the exception in the sw instruction example is vaddr = 0x00000004, and the misaligned address in the lw read instruction example is vaddr = 0x10010001.

The cause ($13) register provides information (i.e. exception code) about the reason of the exception and any pending interrupts, if any. The exception code is stored in bits 2 through 6 of the cause register.

Cause
cause register


The MIPS architecture defines numeric codes (values) for different types of exceptions. Some of these codes are listed below.

Code Name Description
0 INT Hardware Interrupt
4 ADDRL Address error exception caused by data loading (load) or fetching (fetch) instructions
5 ADDRS Address error exception caused by a data save instruction (store)
6 IBUS Bus error while fetcing an instruction
7 DBUS Bus error while loading or saving data
8 SYSCALL A system call exception caused by a syscall instruction
9 BKPT Breakpoint exception caused by a break instruction
10 RI Exception due to “reserved” instruction
12 OVF Overflow exception
13 TRAP Exception caused by a trap instruction
15 FPE Floating point arithmetic exception

The status ($12) register controls interrupt permissions. Bit 0 (IE - Interrupt Enable) enables or disables interrupts globally. Bit 1 is called the Exception Level. This bit is normally 0 unless an exception/interrupt occurs, in which case it is set to 1. The **interrupt mask is an 8-bit field. One bit for each of the six hardware and two software interrupt levels. When a mask bit is 1, interrupts at that level are autorised. When a mask bit is 0, the interrupts associated with that level are masked (i.e. disabled). When an interrupt occurs, the associated bit in the cause register (interrupt pending) is set even if the mask bit is cleared. When an interrupt is pending, it will be processed by the processor once its mask bit is set.

Cause
status register


Coprocessor0 instructions

When an exception occurs, the processor switches to supervisor mode. The Coprocessor0 registers are accessible only when the processor is in this execution mode.

The contents of the Coprocessor0 registers can be transferred to/from this device using the mfc0 and mtc0 instructions, respectively. It is also possible to transfer data between the Coprocessor 0 registers and memory using the additional lwc0 and swc0 instructions.

Instruction Description
mfc0 rd, c0src Copy register c0src from Coprocessor0 to destination register rd
mtc0 rs, c0dst Copy source register rs to Coprocessor0 register c0src
lwc0 c0dst, addr Reads a word from memory and copies it into the c0dst register
swc0 c0dst, addr Saves the content of registerc0src to memory
eret Set EL = 0 (switch to user mode) and return: $pc = $epc + 4

The MIPS architecture also defines special instructions that cause exceptions and switch the processor from “user mode” to “supervisor mode”. These are the trap, break, and syscall instructions:

Instruction Description
teq rs, rt Generates a trap exception if registers rs and rt are equal
tne rs, rt Generates a trap exception if rs is different from rt
tlt rs, rt Generates a trap exception if rs is less than rt
. . . other trap instructions exist
break Generates a Breakpoint exception
syscall Generates a “system call” exception.

Trap instructions (teq, tne, …) generate the TRAP exception (code 13). The break instruction generates the breakpoint exception (code 9), and the syscall instruction triggers the system call exception (code 8).

Exception Handler

The memory layout in a MIPS architecture is shown below. The operating system appears in the top half of the address space, accessible only when the processor is running in supervisor mode. The supervisor code segment starts at address 0x80000000 and the supervisor data segment starts at address 0x90000000. The last segment of the address space is mapped (see paragraph below) to the I/O devices starting at address 0xffff0000.

MIPS_MEMORY

In MIPS, when a function is called using the jal instruction, control is transferred to the address provided by the instruction and the return address is stored in $ra register. In the case of an exception/interrupt, there is no explicit call… when an exception occurs, control is transferred to the fixed address 0x80000180. The exception handler must be implemented at this address. The return address from the exception cannot be stored in $ra because it will modify a return address that was placed in that register before the exception occurred. It is the epc register in Coprocessor0 that plays the role of the $ra register when an exception occurs (i.e. the epc register stores the address of the instruction that was executing when the exception occurred).

An exception handler must perform five steps:

  1. Save the standard processor registers before modifying them in the exception handler.
  2. Identify the exception that occurred by examining the contents of the Coprocessor0 registers.
  3. Execute the exception-specific routine, usually via a branch table.
  4. Restore all possibly modified standard registers in the exception handler.
  5. Continue execution of the program in “user mode” or terminate the program if it cannot be restarted

The exception handler must preserve the value of any registers it might modify so that execution of the interrupted program can resume at a later time. The MIPS architecture reserves registers $26 and $27 (i.e. $k0 and $k1) for use by the exception handler. The handler can modify these two registers without having to save them first. Other processor registers must be saved in memory before being modified in the exception handler.

For example, the exception handler shown below saves registers $at, $a0, and $v0 in the supervisor data segment (at address _regs) before modifying them, and restores their contents at the end of the routine before ereturning to the suspended program. In fact, since the epc register contains the address of the instruction at the time the exception occurred, the return address must be incremented by 4 to avoid executing the same instruction again.

Here is the complete routine. The program displays the address of the instruction that caused the exception and its code.

# Exception Handler starts here
.ktext 0x80000180
move $k0, $at                     # $k0 = $at
la $k1, _regs                     # $k1 = address of _regs
sw $k0, 0($k1)                    # save $at
sw $v0, 4($k1)                    # save $v0
sw $a0, 8($k1)                    # save $a0
 
la $a0, _msg1                     # $a0 = address of _msg1
li $v0, 4                         # $v0 = service 4
syscall                           # Print _msg1
mfc0 $a0, $14                     # $a0 = EPC
li $v0, 34                        # $v0 = service 34
syscall                           # print EPC in hexadecimal

la $a0, _msg2                     # $a0 = address of _msg2
li $v0, 4                         # $v0 = service 4
syscall                           # Print _msg2
mfc0 $a0, $13                     # $a0 = cause
srl $a0, $a0, 2                   # shift right by 2 bits
andi $a0, $a0, 31                 # $a0 = exception code
li $v0, 1                         # $v0 = service 1
syscall                           # Print exception code

la $a0, _msg3                     # $a0 = address of _msg3
li $v0, 4                         # $v0 = service 4
syscall                           # Print _msg3

la $k1, _regs                     # $k1 = address of _regs
lw $at, 0($k1)                    # restore $at
lw $v0, 4($k1)                    # restore $v0
lw $a0, 8($k1)                    # restore $a0

mtc0 $zero, $8                    # clear vaddr
mfc0 $k0, $14                     # $k0 = EPC
addiu $k0, $k0, 4                 # Increment $k0 by 4
mtc0 $k0, $14                     # EPC = point to next instruction

eret                              # exception return: PC = EPC

# kernel data is stored here
.kdata

_msg1:   .asciiz    "\nException caused by instruction at address: "
_msg2:   .asciiz    "\nException Code = "
_msg3:   .asciiz    "\nIgnore and continue program ...\n"
_regs:   .word 0:3               # Space for saved registers

Memory mapped inputs/outputs

In any computer, the input and output devices are located outside the processor chip. A MIPS processor communicates with the I/O devices using a technique called memory-mapped I/O.

With memory-mapped I/O, there is no need to add additional instructions to the MIPS instruction set. Any lw or sw instruction with an effective address equal to or greater than 0xffff0000 will not access main memory but an address reserved for accessing the I/O device registers. The I/O device controllers are connected to the system’s I/O bus as shown below.

MAPPEDIO

When a key is pressed on the keyboard, the Data register stores the associated character and the Ready bit in the Control register is set. The following MIPS code provides an example of memory-mapped access to the keyboard controller registers using the polling method:

li     $t0, 0xffff0000      # Address of keyboard control register
li     $t1, 0               # Initialize wait_counter = 0

wait_keyboard:

lw     $t2, ($t0)           # Read the keyboard control register
andi   $t2, $t2, 1          # Extract ready bit
addiu  $t1, $t1, 1          # wait_counter++ (counts iterations)
beqz   $t2, waitkeyboard    # loop back while not ready
lw     $a0, 4($t0)          # Get character from keyboard

The frequency of typing characters on the keyboard is very slow compared to the speed at which a processor can execute instructions. Typically, millions of instructions are executed in the period of time a key is pressed in succession. In our example, the $t1 register keeps track of the number of iterations performed in the wait_keyboard loop before a key is pressed. The lw instruction at the end of the loop fetches the character from the keyboard’s Data register, which also clears the ready bit. A MIPS program can only read the contents of the keyboard’s data register. Writing to this register has no effect.

Communication with the graphics card can be done in a similar way via polling. The display registers are mapped to addresses 0xffff0008 and 0xffff000c. Indeed, as seen in the code below, as long as the ready bit of the Control register is clear, the routine will continue to loop through that register. We should not write characters to the Data register until the display is ready to receive them. The display controller clears the ready bit when a character is written to the Data register and sets it to 1 again once the character is displayed.

li    $t0, 0xffff0008      # Address of display control register 

wait_display:

lw    $t2, ($t0)           # Read the display control register
andi  $t2, $t2, 1          # Extract ready bit
beqz  $t2, wait_display    # loop back while not ready
sw    $a0, 4($t0)          # Send character to display

The main disadvantage of the polling method is that it keeps the processor busy, wasting millions of cycles before the I/O device (such as the keyboard) is ready. An alternative method is to use interrupts. These can be enabled for the keyboard by setting the Interrupt Enable bit in the keyboard’s Control register as follows:

li  $t0, 0xffff0000   # Address of keyboard control register
li  $t1, 2             
sw  $t1, ($t0)        # Enable keyboard interrupt

When a key is pressed, the keyboard sends an interrupt signal to the MIPS processor and initialises the cause register to the value 0x00000100. Bit 8 in the cause register is set to indicate that the keyboard has interrupted the processor. In order for the character entered on the keyboard to be used by the running program, an interrupt handler must be written to read the character from the keyboard Data register and return it to the program.

Recent I/O device controllers (such as hard drives and network cards) use DMA (Direct Memory Access) to transfer blocks of data directly between the device and memory. As controllers become more complex, more than two registers are associated with each device controller. Typically, the I/O device manufacturer provides programmers with a manual (English Data sheet) on how to properly communicate with that device’s controller.