The RISC-V microcontroller profile provides a nested vectored interrupt controller as part of the common specifications.
Each hart may be able to process its own set of interrupts, independent from the other harts. Only hart 0 is required to implement a HIC; additional interrupt controllers in all other harts are optional and implementation specific.
Hard real-time devices may dedicate separate harts to process fast interrupts. It is possible to wire all interrupts to all harts, and decide in software which interrupts are processed by each hart.
The HIC supports the following features:
-
HIC interrupts can be enabled and disabled by writing to their corresponding
status.enabled
orstatus.clearenabled
bit fields, using a write-1-to-enable and write-1-to-clear policy.When an interrupt is disabled, interrupt assertion causes the interrupt to become pending, but the interrupt cannot become active. If an interrupt is active when it is disabled, it remains in the active state until this is cleared by a reset or an exception return. Clearing the enable bit prevents any new activation of the associated interrupt.
An implementation can hard-wire interrupt enable bits to zero if the associated interrupt line does not exist, or hard-wired them to one if the associated interrupt line cannot be disabled.
-
the pending state of HIC interrupts can set or removed by software using the
status.pending
andstatus.clearpending
bit fields. The registers use a write-1-to-enable and write-1-to-clear policy. Writing 1 to a bit in thestatus.clearpending
bit field has no effect on the execution status of an active interrupt.It is implementation specific for each interrupt line supported, whether an interrupt supports either or both setting and clearing of the associated pending state under software control.
-
status bits are provided to allow software to determine whether an interrupt is active, pending, or enabled.
-
HIC interrupts are prioritized by updating a priority field. Priorities are maintained according to the RISC-V prioritization scheme.
-
HIC supports a maximum of 1024 interrupts.
Offset | Name | Width | Type | Reset | Description |
---|---|---|---|---|---|
0x0000 | interrupts[] |
32b * 2 * N | rw | 0x00000000 | Array of interrupt control registers. |
The number of interrupts (N) is implementation specific, but no higher than 1024, including the system interrupts.
Total size: 0x2000.
Each interrupt has a small per-hart set of status and configuration attributes:
enabled
: interrupts can either be disabled (default) or enabledpending
: interrupts can either be pending (a request is waiting to be served) or not pendingactive
: interrupts can either be in an active (being served) or inactive stateprio
: interrupt priority
To store and control these attributes, each interrupt has two 32-bit registers:
Offset | Name | Width | Type | Reset | Description |
---|---|---|---|---|---|
0x0000 | prio |
32b | rw | 0x00000000 | The interrupt priority register. |
0x0004 | status |
32b | rw | 0x00000000 | The interrupt status and control register. |
The prio
register has the the following content:
Bits | Name | Type | Reset | Description |
---|---|---|---|---|
[N:0] | prio |
rw | 0 | The interrupt priority. |
[(xlen-1):(N+1)] | Reserved. |
N is the number of bits required to store the maximum priority level, and is implementation
specific. It must match the number of bits used by the iprioth
CSR.
The status
register has the following content:
Bits | Name | Type | Reset | Description |
---|---|---|---|---|
[0] | enabled |
rw1s | 0 | Enabled status bit; 1 if the interrupt is enabled. When 1 is written, the enabled bit is set. |
[1] | pending |
rw1s | 0 | Pending status bit; 1 if the interrupt is pending. When 1 is written, the pending bit is set. |
[2] | active |
r | 0 | Active status bit; 1 if the interrupt is active. |
[3] | Reserved | |||
[4] | clearenabled |
w1c | When 1 is written, the enabled status bit is cleared. |
|
[5] | clearpending |
w1c | When 1 is written, the pending status bit is cleared. |
|
[31:6] | Reserved |
The alternative to packing all status and control bits related to an interrupt in two words would be to have separate multi-word fields with status, enable, disable, set pending, clear pending, active bits. It was considered that the packed solution is easier to use in software.
[JB] Why use separate bits for enabling and disabling interrupts? Why not use the same write-1-to-enable and write-0-to-clear? ... it is not the way assignment works anywhere else in C. [ilg] C programmers are very much used to different semantics when accessing peripheral registers, actually most real peripherals in modern devices use write-1-to-set/clear bits, so this not surprise anybody. As for keeping the language semantics, in C++ all operators can be redefined, so they can implement any semantics is required.
Individual interrupts are enabled by setting the status.enabled
bit and are disabled by writing 1 in the status.clearenabled
bit. To be effective, interrupts must also have non-zero priorities.
hic.interrupts[7].prio = 7;
hic.interrupts[7].status = INTERRUPTS_SET_ENABLED;
hcb.interrupts[7].status = INTERRUPTS_CLEAR_ENABLED;
Interrupts can be programmatically set to be pending by writing 1 in the status.pending
field; the pending status can be cleared by writing 1 to the status.clearpending
bit.
hcb.interrupts[7].status = INTERRUPTS_SET_PENDING;
hcb.interrupts[7].status = INTERRUPTS_CLEAR_PENDING;
To check the status bits:
if (hcb.interrupts[7].status & INTERRUPTS_STATUS_PENDING) {
// ...
}
[JB] Each hart has a fixed set of interrupt vectors. For each interrupt source, a register exists that defines which vector receives interrupts from that source. Effectively, the hart has some number of IRQ lines and interrupt sources are assigned manually to IRQ lines. The IRQ lines have a fixed priority, based on the interrupt number.
If multiple peripherals are assigned to the same vector, then the ISR for that vector must poll each of the peripherals assigned to that vector to determine the cause of the interrupt.
This also limits interrupt nesting, since only a higher-priority interrupt (or an exception) can interrupt an ISR, there can be at most 2*PRIORITY_LEVELS nested interrupts, if every ISR is interrupted in an exception handler and exception handlers do not themselves raise exceptions.
[ilg] The only advantage to be noted is that it limits nesting. The disadvantages are: increased software complexity, increased latency, more complicated to maintain (changing the priority in the first case requires only a write to the priority register, while in the second case it is also necessary to move the test and the call from one intermediate handler to the other), possible out-of-sync cases, when the test is not in the right handler.