This paper will discuss design practices and guidelines that will maximize the efficiency of interrupts and interrupt handling in an embedded system IC. These practices can result in a smaller code size, lower interrupt latency, and less confusion on the part of firmware developers. The final effect of intelligent interrupt design is a cheaper development effort and faster time to market for customers intending to use the IC in a product.
In any embedded system design, there are multiple channels of communication between the hardware and the firmware. One of the most obvious methods of communication is the use of registers. Firmware can read registers to determine the state of the hardware, and they can also write registers to affect the operation and activity of the hardware.
One major issue that must be considered when designing the hardware of an embedded system is the difference in time domains of the hardware and f irmware. Delays in hardware logic circuits are measured in fractions of nanoseconds, while delays in firmware are measured in CPU cycles. The difference between the two time domains can be discussed in terms of orders of magnitude rather than an arithmetic ratio.
When firmware requires an immediate action from the hardware, it can write to registers to tell the hardware what to do. Even if the hardware is "busy" and takes some time to accomplish the new operation, it will usually seem to be immediate from the point of view of the firmware.
When hardware requires an immediate action and the firmware registers are not sufficient, this requires the use of interrupts. Interrupts, from the perspective of the firmware, are asynchronous signals. Firmware engineers will always prioritize interrupt handling to facilitate the quickest possible reaction to a given interrupt. However, due to the different reactive speeds of hardware and firmware, firmware can never react, from a hardware perspective, immed iately to an interrupt.
Basic interrupt handling
When an interrupt occurs, the processor continues until it reaches a point where the interrupt can be handled, usually by finishing its current instruction. The processor then saves its current state and performs a software branch to an Interrupt Service Routine. This is a function that is identified to the compiler to handle the interrupt signal. This routine will perform the following actions:
The delay in handling an interrupt, called the interrupt latency, is generally measured from the occurrence of the interrupt until the firmware begins to handle the interrupt. From a hardware point of view, this is the time it takes for the processor to branch to the ISR. From a firmware point of view, a more useful measurement is the time it takes to reach the third step in the list above. This is the meaning of the term as used in this paper. While the interrupt latency can be very quick from a firmware perspective, from the perspective of the hardware this delay is always an eternity.
- Mask the interrupt if necessary.
- Read and clear the interrupt status register that generated the interrupt signal.
- Perform any immediate actions required by the interrupt.
- Unmask the interrupt.
- Return to the point in the program from which the interrupt routine was called.
The scheme used by hardware designers to implement the interrupts in an embedded system can, however, have a big effect on the interrupt latency. A good interrupt design scheme can also reduce the number of problems that will occur when the interrupt is handled. On a larger scale, good interrupt design can result in a cheaper development effort, and faster time to market for those implementing the embedded IC in a product.
Hardware design of interrupts
The cardinal rule for all embedded hardware design should be:
"Always use interrupts to get the attention of the firmware."
As stated previously, there are only two ways for the firmware to receive information from the hardware -- reading a register or receiving an interrupt. Note that a register must be read; this takes active participation from the firmware.
Any hardware status that must be checked independently of an interrupt requires extra code and lots of extra CPU cycles just to stop and check the status. This use of "polled status registers" is inefficient for both code size and operation speed, and is very limiting to the firmware. A polled status register in a hardware design can break a firmware program because all other operations must stop to check the polled register whenever it may have occurred.
The opposite corollary also applies; interrupts should only be triggered by events or status changes that require action from the firmware. Firmware only cares about the hardware when it needs to respond to something the hardware has done. Interrupts at times when the firmware is not expected to do anything will simply be masked and ignored. Unfortunately the firmware engine ers lose development time verifying the lack of need for a given interrupt before it can be comfortably ignored.
On the other hand, all significant events should have an interrupt. If the hardware designer is unsure of the need for an interrupt, he or she should work through the operation of the IC at that point to verify that firmware intervention is needed. When in doubt, an unneeded interrupt is better than a required interrupt that wasn't supplied by the hardware design.
Remember that when an interrupt occurs, the firmware is going to read related status registers to determine what to do. This happens an "eternity" after the interrupt has occurred (from the point of view of hardware) so special steps must be taken in order to guarantee the firmware can still find the status information it needs. There are other differences between interrupts and "normal" register bits that should also be considered.
Latch interrupt related status
If there is any way that the hardware could chan ge any of the status related to an interrupt, that status must be latched and held separately for use by firmware.
Furthermore, if multiple occurrences of the same interrupt can occur without firmware intervention, then the status for each successive interrupt must be saved. This will most likely require a FIFO or storage bank that can be accessed by the firmware to read each successive status as it is handled. It is usually unnecessary to save the actual interrupt flag for each occurrence unless the firmware cannot simply handle all occurrences on a single function call.
Interrupt register bits should be cleared by writing a high value back to the register bit location. This allows the firmware to read the interrupt register and clear it by immediately writing back the new value. This saves code space and processor time during the critical early part of the Interrupt Service Routine.
Interrupt bits are information for the firmware. Clearing an interrupt bit should cause no other effects in the hardware logic. When the firmware is handling an interrupt, the state of the system is already slightly chaotic (remember, asynchronous). When a simple operation, such as clearing a status bit, does more than expected, the effort to control the system is greatly increased.
A typical ISR will possess a small, efficient code sequence to mask and clear the interrupt before addressing the cause (or to clear and unmask before returning). If the interrupt does something special when cleared, this code becomes complicated, more so for every interrupt with special characteristics. This affects interrupt latency and system timing in an area where it can be least afforded. This also increases the size of the code.
The ironic part about multi-function interrupt bits is that the reason sited for their implementation is to provide for more efficient firmware.
Interrupt bits should always be kept in their own register with no other writeable register bits. Normal registe r bit fields are modified by reading the register, changing the appropriate bit field, and writing back the full register. When interrupt Write-1-to-Clear bits share a register with normal register bits, an extra step is required by the firmware to avoid writing a '1' to the Write-1-to-Clear bit locations when setting other bits in the register. This leads to extra code if the firmware engineer realizes the problem, or buggy code if he or she does not.
While interrupt bits should never share a register with other writeable bits, they may share a register with read-only register bits. These additional status bits should, however, be related to the interrupt; this may allow the firmware to read the entire interrupt related status from a single register while in the ISR.
Sharing status bits is only useful if the total number of interrupt and status bits required to handle the interrupt is less than the bus width of the system (the purpose of combining the bits is to cut the number of register reads r equired in the ISR). If this is done the interrupt bits should be grouped together at one end of the register so that the time and code required to differentiate the bits will be minimized.
Make all interrupts maskable
Always assume that, for either normal operation or debug purposes, the firmware may want to mask any specific interrupt at some time. An interrupt source that is "masked" will not trigger an external interrupt signal to the processor when the source bit is set. Masking can be done by setting an interrupt mask bit high.
Note that mask bits are different from an enable bits. Enable bits will either inhibit the function of a logic circuit that generates and interrupt source bit, or inhibit the setting of the source bit by the controlling logic circuit. The status of a masked interrupt bit can still be checked by firmware if desired while a disabled interrupt bit is not set, and so is invisible to firmware.
Interrupt mask bits require certain distinct characteristics in order to be efficiently used by firmware. Mask bits should never share a register with the corresponding interrupt bits. The reason for this is to allow the mask bits to occupy the same bit position, in the mask register, that the interrupt occupies in the interrupt register. It is important to keep these bit positions the same; the firmware may wish to qualify the contents of the interrupt register with the contents of the mask register. Occupying the same bit positions also means the firmware engineer has a much easier time keeping the specification of the registers clear in his or her mind.
Interrupt mask bits should come out of power-up and hardware reset in the active state. In other words, all interrupts should be masked after power-up and reset. This avoids the possibility of an erroneous interrupt caused by poor signal integrity at power-up occurring.
In today's embedded systems, the size of the logic handled by a single IC is so large that a single chip- level interrupt register is often insufficient to handle the multiplicity of interrupt signals generated. This requires the creation of an interrupt hierarchy with lower level interrupt source registers feeding into a top-level register. In order to determine the source of an interrupt, the firmware first reads the top level interrupt register and then reads the lower level registers which were indicated by the top level register value. In order to keep the interrupt latency as short as possible, the software interrupt handlers will check the interrupts in hierarchical order. In other words, the firmware will check the top-level register for active interrupt sources first. The firmware will check only the lower level sources for which an active top-level interrupt exists. This is faster than a single, flat, design that requires checking a large number of inactive interrupt registers before finding those that are actually set. However, too many levels of the hierarchy will also have a negative effect the interrupt latency, and more than two levels of interrupts should not be allowed.