Interrupt handlers for the 9x8 micro controller


Copyright 2012, Sinclair R.F., Inc.

This document describes how to implement interrupt handlers for the 9x8 micro controller.

There is a single interrupt in the controller, although this interrupt can be triggered by more than one signal. Implementing interrupts consists of two actions: adding a single interrupt peripheral to the processor architecture and adding a ".interrupt" body to the assembly source.

The interrupt test bench illustrates how to add an interrupt for a single external event and the design in example/interrupt illustrates how to add an interrupt for two events, one external to the processor and one internal to the processor.

Theory of Operation

The interrupt peripheral creates two signals, s_interrupt and s_interrupted, for the interrupt event and to disable normal processor operation until the interrupt handler is running.

Specifically, s_interrupt is a non-registered signal that is high when (1) interrupts are enabled, (2) an interrupt edge has occurred (and not be precluded by the interrupt mask, if any), and (3) the processor is not in the middle of executing a jump, call, or return.

When s_interrupt goes high, the processor pushes the PC address for the current instruction onto the return stack, sets the next PC address to be the interrupt handler start address (so that the interrupt handler will start executing in 2 instruction cycles), and otherwise performs a "nop." Because of the piplined PC/opcode architecture, a delay register is required for the current opcode PC address to be available.

The instruction cycle after s_interrupt is high must perform a "nop." This is done by using s_interrupted as a registered, delayed, version of s_interrupt. When s_interrupted is high, the processor core is coerced to perform a "nop" and the instruction pipeline architecture starts fetching the second instruction in the interrupt handler.

When the "return" opcode is performed by the interrupt handler, execution will resume at the instruction that would have been performed when s_interrupt was high. This instruction cannot be one immediately after a jump, call, or return or one after a jumpc or callc if the conditional was true, otherwise the processor will not perform the desired jump, call, or return and will simply start executing the code following the instruction after the jump, call, or return.

On return from the interrupt handler, the interrupts are enabled in a way that precludes the interrupt handler from being interrupted again. This is done with the three instruction sequence "O_INTERRUPT_ENA return outport." The outport, as the instruction performed immediately after the return, enables interrupts on the following instruction cycle, which will be the first instruction cycle resuming the previous execution sequence.

The interrupt peripheral needs to generate the s_interrupt and s_interrupted signals and the O_INTERRUPT_DIS and O_INTERRUPT_ENA outport strobes; create signals for any interrupt signals external to the processor; and instantiate the HDL for the interrupt. Using the base class SSBCCinterruptPeripheral from ssbccPeripheral ensures the s_interrupt and s_interrupted signals are declared, although the code to generate their values is not created, and it ensures the two outport strobes are created.

Example Implementation

The interrupt peripheral provided with the core provides an interface for one to eight edge triggered interrupts. These interrupt sources can be external to the processor or they can be signals from other peripherals. They are normally rising edge triggered, but they can also be falling edge triggered. The peripheral also provides an optional mask for the interrupt sources, allowing it to be set, read, and initialized to a particular value. Constants for bit maps for the interrupt signals can be defined as part of selecting the signal for each of the one to eight interrupt signal sources.

The test bench for this interrupt peripheral illustrates a single, external, rising-edge interrupt signal. The timing of the external interrupt was varied to validate correct generation of the s_interrupt signal and return from the interrupt handler (this was done by manually verifying the displayed instruction sequences).

An example interrupt controller for two interrupt signals, one external and one internal, and one rising edge and one falled edge, along with a mask for the interrupt signals, is also provided in example/interrupt.

Construction of Interrupt Peripherals

This discussion is based on the interrupt peripheral provided with the 9x8 processor core. It describes the HDL required to implement the interrupt hardware.

The processor core sets s_bus_pc to C_BUS_PC_JUMP when a jump or call is performed and it sets it to C_BUS_PC_RETURN when a return is being performed. When s_bus_pc is either one of these values at the end of a processor clock cycle, then the instruction pipeline will be in the middle of performing a jump, call, or return during the following interval. During this subsequent interval, interrupts must be disabled. This is done by capturing the status of s_bus_pc in the register s_in_jump and prohibiting interrupts if s_in_jump is high.

The status of candidate interrupt signals is captured in s_interrupt_raw. I.e., signal inversion is performed as required by the peripheral architecture statement and masking is performed where the mask is high if the signal is to be included as a candidate interrupt. The "raw" interrupt triggers are then generated by looking for rising edges in this signal as compared to the value(s) for the previous clock cycle.

Two signals are then used to capture the trigger. The first, s_interrupt_trigger records which enabled signals had a rising edge. In order to reduced the depth of subsequent logic for the interrupt signal itself, the single-bit signal s_interrupt_trigger_any records whether or not any enabled signal had a rising edge. The history of both of these signals is cleared if the processor reads the input port for s_interrupt_trigger.

The non-registered interrupt signal is then generated if (1) interrupts are enabled, (2) a rising edge has occured, and (3) interrupts are not disabled because the instruction pipeline is in the middle of a jump, call, or return.

A delayed version of s_interrupt is registed as s_interrupted for generation of the interrupt-induced "nop" instruction that must follow the interrupt.

Finally, the interrupt enable signal is generated. Interrupts are initially disabled (so that the processor can perform its initialization without spurious stated induced by premature interrupts). Interrupts are then disabled when an interrupt occurs or when the O_INTERRUPT_DIS strobe is received. Interrupts are only enabled when the O_INTERRUPT_ENA strobe is received.

Construction of Interrupt Handlers

If there is only one signal that can produce an interrupt (as set in the peripheral architecture statement), then the interrupt handler simply processed the interrupt and exits using the .returni macro. For example, the following code simply counts the number of interrupts received (provided that they don't occur so fast that the interrupt handler isn't called as fast as the interrupts occur):

  .interrupt
    .fetchvalue(interruptCount) 1+ .storevalue(interruptCount)
    .returni

If there is more than one signal that can produce an interrupt, then the construction of the interrupt handler is slightly more complicated. Suppose the interrupt peripheral architecture statement is:

  PERIPHERAL interrupt insignal0=i_int0,C_INT0 \
                       insignal1=i_int1,C_INT1 \
                       inport=I_INTERRUPT      \
                       ...

The interrupt handler then reads the interrupt trigger, conditionally calls subroutines for the appropriate interrupt, clears the trigger from the data stack, and returns as follows:

  .interrupt
    .inport(I_INTERRUPT)
    dup C_INT0 & .callc(int0)
    dup C_INT1 & .callc(int1)
    drop
    .returni