Introduction
Interrupts are useful to handle a run-time error condition that is not detectable by the logic of a running task handler, but is detected by other means, such as a watchdog timer. However, after an interrupt is handled, control returns to the interrupted code – which would be the code that cause the error in the first place. Unless we can change this return to the faulty code, error handling and recovery is confined to crude measures, such as restarting the system. If we only wanted to reset or terminate the offending task handler, we’re out of luck. This is where the “non-returning interrupt” comes into play.
RTI
An interrupt handler is terminated with a specific instruction, called RTI
in RISC5.v
. Executing this instruction, in lieu of the normal branch based on the link register, signals to the CPU to retrieve the information stored in the SPC
FPGA-register,1 which contains the PC of the next instruction of the interrupted code, as well as the condition flags.
If we don’t want to return to the code as referred to by SPC
, and stored there upon entering an interrupt, we need to change the contents of SPC
before RTI
is executed. This requires minor changes to RISC5.v
.
To compare, a Cortex-M3 MCU, for example, stores the return address on the stack, where it is easy to change, also before executing the corresponding return-from-interrupt instruction.
Abort Handler
As the “non-returning interrupt” is intended exclusively for handling error conditions, we don’t need to be able to change SPC
to any arbitrary value, it suffices to invoke the existing abort handler, and deal with the error condition there. For this, the abort handler must be able to tell the different conditions apart when it is called. FOr this, the interrupt handler stores the error condition in the System Control Register. A bit in the SCR is also used to signal the CPU to load the address of the abort handler, ie. address zero, into the SPC from within the interrupt handler.
Example: Watchdog
The watchdog interrupt handler:
PROCEDURE watchdog[0];
BEGIN
SysCtrl.AbortInt(SysCtrl.WatchdogAbort)
END watchdog;
The abort handler (basic structure):
PROCEDURE abort;
VAR c: INTEGER;
BEGIN
(* ... *)
SysCtrl.GetAbortCause(c);
IF c = SysCtrl.WatchdogAbort THEN
(* ... *)
ELSIF c = SysCtrl.StackOverflow THEN
(* ... *)
ELSE
(* ... *)
END;
(* ... *)
END abort;
Note that calling SysCtrl.AbortInt
in the interrupt handler, or calling any other procedure in general, would not be possible without explicitly saving and restoring the link register. However, as we’ll never return to the interrupted task handler, this is not necessary in this case.
Other Usages
The above mechanic is currently also used for the handling of stack overflow errors, as well as a button that kills the current task handler without resetting the processor.
-
A register
reg
in the Verilog sense, not a CPU register. ↩︎