Lab 6 - Interrupts

The purpose of this lab is to familiarize you with the use of interrupts, and to compare their use to that of polling. We will do so by re-implementing your lab5 game using interrupts (you can of course re-use parts of your code).

The disadvantage to polling is that a program must constantly check and recheck to determine when a peripheral is ready to be used. As more peripherals are added to a system, the time spent to check these increases accordingly, making it more and more difficult to perform other tasks at the same time.

An alternative to constantly polling is interrupts. Rather than having the processor constantly check the device status, an interrupt will signal the processor when it is ready for use. This allows a program to work on other tasks, and only deal with the devices when they are available. Obviously this makes a program much more efficient, and often simpler as well, since events are taken care of in a single interrupt service routine, allowing the rest of the program to ignore it.

Catch The Numbers - Interrupt version

In this lab, you will need to modify the program you designed in lab 5 (the scrolling numbers game) in two ways.

  1. First, edit the polling version so that a counter is included. This counter should be incremented every time the program finishes its main loop (polling the UART and the Timer). Then, when the program finishes, the value of this counter should be divided by 100, and then written to the 7-segment display. To write to the 7-segment display, simply execute a store of your counter value to the base address of the 7-segment display (0xff1100). You can find the full documentation of the 7-segment display here.

  2. Second, write a version of the program that uses interrupts instead of polling for both the JTAG UART and the Timer, but make sure you keep the counter in the main loop.

How to Enable Interrupts

You should consult your class notes and the appropriate device documentation for how to initialize interrupts. But in general, you have to perform the following steps:

  1. Enable the interrupt on the device. For example, you need to set the 0th bit of the Timer's Control Register to high to tell the Timer to interrupt the processor.
  2. On the processor, enable interrupts for the device's IRQ line using the processor's control register ctl3 (aka ienable). For example if you're using Timer0, enable IRQ line 3 by setting bit 3 high (remember the first bit is bit 0).
  3. Enable interrupts globally for the processor by setting bit 0 high in the processor's ctl0 (aka status) register.

Where to Place your Interrupt Service Routine (ISR)

After enabling interrupts on both the processor and the device, device interrupts will cause the processor to jump to location 0x1000020. It is crucial that you make sure your interrupt service routine is located here, which you can do by following these steps:

  1. Make sure that if you are compiling multiple files, that the file with your interrupt service routine is listed first in the SRCS list so that it will be first in the .text section.
  2. Use the .org directive to set you're location to 0x20. Note that all of your code in the first file in the SRCS list will automatically be placed starting at location 0x1000000, the ".org 0x20" statment will move forward any number of bytes until it reaches the location 0x1000020 (.org can not move backwards - hence the 3rd step).
  3. To be safe, don't put any code, or variable declarations (unless you put them in .data) before the .org statement. You can safely put any number of assembly directives such as .global, .equ, but beware of using .include which may insert code. You can also safely use the first 32 bytes in any way you wish, but rather than counting, it is safer to put nothing. See the examples below.
    Valid
    Valid
    Error
    /***** Start of Code *****/
      .org 0x20
    myISR:    
    
    /***** Start of Code *****/
      nop
      nop
      nop
      nop
    
      nop
      nop
      nop
      nop
      .org 0x20
    myISR:    
    
    /***** Start of Code *****/
      nop
      nop
      nop
      nop
    
      nop
      nop
      nop
      nop
    
      nop
      .org 0x20
    myISR: 
    

How to Resolve Interrupts

When an interrupt is triggered and your interrupt service routine is invoked, you need to first find out what device, and then what event has caused the interrupt. Finding which device interrupted can be done by examining the ctrl4 (aka ipending) register which tells you which IRQ line(s) have triggered an interrupt. Some devices, such as the timer, have only one event which causes an interrupt, so you know if bit 3 of ienable is high that Timer0 has timed out. But devices such as the JTAG UART have multiple events which cause interrupts (for either reads or writes, provided you enabled both). In that case you need to check the device's appropriate control register(s) to see what event actually caused the interrupt.

How to Return from an Interrupt

When a device interrupt occurs the address of the next instruction is saved in register ea. However the current instruction which was interrupted never gets completed. Therefore you must adjust the saved address in ea so that it re-executes the interrupted instruction. To do this, simply subtract 4 from ea (remember each instruction is 4 bytes). Then execute the "eret" (exception return) instruction to return to normal program execution.

Preparation

Read the documentation on the 7-segment display. Then create two versions of your lab 5 as described above - one with a counter, the other with both a counter and interrupts. Bring both programs, to the lab. Don't forget to comment your program!

In the Lab

Demonstrate both programs to the TAs and comment on the speed of the counter for both programs.