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