# CLINT Design Document

![modified MiRiV architecture](../diagrams/miriv_new.png)

To keep implementation simple and avoid handling of relatively complex edge cases, interrupts and exceptions are designed to function in a way similar to normal instructions.
Normal instructions in the MiRiV pipeline get a logical treatment: after being loaded in the fetch stage, they flow through all the other stages of the pipeline, being handled by functional units in each stage in progressive fashion accordingly to fulfill their function.
This treatment has the benefit of simplifying design by avoiding complex inter-dependencies, as each stage simply pushes their data forward to the next.
Some orchestration by a control unit is necessary, but the responsibilities of the control unit are kept small.

This leads to what seems to be a natural implementation of interrupts and exceptions.
Software interrupts are caused by an instruction, so they already flow through the pipeline.
External interrupts can be seen as events that are signalled to the processor from some outside unit.
Even though this is the case, for our implementation they can still be analogous to software interrupts, by having the fetch stage receive these interrupt signals and passing them on like instruction data.
Exceptions on the other hand are entirely internal events that appear only mid-execution.
In the case of our implementation this refers to the decode stage, where an illegal instruction can make decoding impossible, and the memory stage, where a misaligned load and store can occur.
In the case of the two special software exceptions, `ecall` and `ebreak`, they are detected in the decode stage and flow through the pipeline like any other instruction.

Each of these events requires treatment via a jump to an interrupt or exception handler, a so called trap.
Specifically for the MiRiV pipeline, this irregular control flow is not unlike the process of correcting a wrongly predicted branch result.
Only in later stages of the pipeline can it be ascertained that the branch was correct, which means that the instruction is pushed all the way to the memory stage until the processor can intervene to correct a possible misprediction.
This is accomplished by having the control unit flush respective pipeline stages, while the fetch stage assumes the corrected program counter signalled back to the fetch stage from the memory stage.

This already pre-existing mechanism to update the program counter in the case of a (perhaps not so) irregular event can be expanded to handle interrupts and exceptions as well.
Interrupts and exceptions can be passed down in the pipeline from stage to stage, until they reach the memory stage, where the irregular control flow event is then signalled to the control unit and the fetch stage, making interrupts and exceptions appear like any other misprediction.

Another reason that makes this implementation natural is the critical role of the CSR unit.
The CSR unit holds and controls all CSRs, which are essential parts of interrupt and exception handling.
We will describe all relevant CSRs and their role in this sort of irregular control flow before long.
The CSR unit does not only keep track of processor state, but it exposes processor state in the form of memory-mapped registers that can be interacted with using special CSR instructions.
This means it's necessary in a pipelined implementation to consider forward data dependencies for this software exposed side of the CSR unit.
It is not sufficient to track and manage processor state in a unit that is decoupled from individual pipeline stages.

Arguably, to uphold this principle of having only forward data dependencies, it is a good idea to place functional units as early in the pipeline as possible, but not earlier.
For example, if a functional unit required decoding the instruction first, then if the functional unit were placed in the stage that follows the decode stage, it can directly make use of the results of the decode stage.
In this example, the functional unit could still be placed earlier in the pipeline, but then it might require duplication of decoding hardware.
This is one reason to place the CSR unit in the execute stage of the MiRiV pipeline, which can then neatly make use of data prepared in the decode stage.
Upon handling a CSR instruction, none of the other functional units of the execute stage are busy and if any data is read via a CSR instruction, the output of the CSR unit is only a single 32-bit vector, just like with the ALU, or the muldiv unit.

If interrupts and exceptions are handled only in the memory stage, like previously discussed, this placement of the CSR unit in the execute stage has another elegant property:
data that is required to decide whether to trap and where to trap to can be passed on forward by the CSR unit in the execute stage to the memory stage.
Now we will get into a description of CSRs that are necessary and how they are used.

## Control and Status Registers (CSR Unit)

#### `mstatus`
This CSR serves to store general processor state that is particularly relevant to correct handling of nested traps and correct execution in the presence of multiple privilege modes.
There is only one bit that is currently relevant to the MiRiV processor, the `mstatus.MIE` bit, which denotes whether or not interrupts are enabled globally (targeting only interrupts, not any of the exceptions).
This bit can be interacted with programmatically, but the pipeline also sets and clears it when certain events take place.
Notably, to disable nested interrupts (interrupts inside traps), this bit is set during a trap and cleared by an `MRET` instruction.
This instruction signifies that the trap handler has finished and normal execution can be resumed.

#There is one notable implementation detail that might diverge from the RISC-V privileged specification: whether this bit also disables exception handling or not.
#Officially, this would not be the case, for it can be necessary to handle exceptions even inside interrupt routines.
#One could imagine, as an example, that unimplemented instructions which are still present in the code are handled inside a handler that emulates the behavior of those instructions, in order to support execution of those instructions.
#It is not unreasonable to wish for such behavior inside an interrupt handler, or even an exception handler.

#For our implementation, nested traps are currently not supported, which is why this bit toggles both interrupts and exceptions.

#### `mie`
This CSR serves to store whether interrupts are enabled, for each individual interrupt.
Do note that, like the related CSR `mip`, this CSR is only concerned with interrupts, not any exceptions.
In general this CSR can toggle different interrupt types for different privilege modes, but in our case only M-mode interrupts are relevant.
There are only three bits relevant to our implementation, `mie.MEIE` (external interrupts enabled), `mie.MTIE` (timer interrupts enabled) and `mie.MSIE` (software interrupts enabled).

#### `mip`
This CSR is functionally equivalent to CSR `mie`, except for one difference: instead of storing whether an individual interrupt is enabled, it stores whether an individual interrupt is pending.
Pending interrupts are those that occur and can not immediately be handled.
One such instance would be an interrupt that is currently disabled.
Information about pending interrupts is always stored, even when they are disabled.

When execution of interrupts becomes possible again, when no conditions prevent an interrupt from taking place, this CSR will cause an interrupt similar to how an interrupt would normally be caused and clear the pending bit.
In the case of multiple pending interrupts, we could either use a simple prioritization scheme that always chooses certain interrupts over others, or we could use some kind of arbitration.
Interesting arbitration schemes that could serve to prevent starvation would be a round-robin scheme, or a scheme that keeps a simple counter for each pending interrupt type and chooses those that were chosen least.
In the case of a prioritization scheme, the priorities could also be made configurable.
For now, a simple unconfigurable prioritization scheme has been chosen for the MiRiV processor.

Like with the CSR `mie`, only the following bits are relevant: `mip.MEIP` (external interrupt pending), `mip.MTIP` (timer interrupt pending) and `mip.MSIP` (software interrupt pending).

#### `mtvec`
This CSR stores either the direct address of the trap handler, or the base address of the trap vector.
The lowest two bits (which for an address would be unavailable anyway to ensure correct alignment) denote how this CSR is set up.
Direct mode ($\text{MODE} = 0$) sets all interrupts and exceptions to the stored address.
Vectored mode ($\text{MODE} = 1$) sets all interrupts and exceptions to the base address with some offset denoted by the CSR `mcause` ($\text{BASE} + 4 \times \text{cause}$).

The CSR can contain a read-only value, but it can also be writable.
In general it is exposed to software at least for reads.
In a real context this CSR can be expected not to change too often, but because it can change if it is writable, its value has to be loaded and passed on.
Because the CSR unit is located in the execute stage, its value can be loaded and passed on to the memory stage, where information about the correct jump target is necessary.

#### `mepc`
This CSR is used to keep track of where execution was stopped before trapping.
One advantage of the design choice to have all exceptions and interrupts flow to the memory stage, is that the value of this CSR can easily be determined.
Because exceptions and interrupts, when they do take place and cause a change in control-flow and flush of the pipeline, accompany an instruction up until the memory stage, these exceptions and interrupts can be said to hijack the execution of the instruction they accompany.
The program counter that needs to be stored in the CSR `mepc` is always the instruction that is currently in the memory stage.
It will be necessary however to make sure that the correct program counter is retained and usable in the case of a flushed instruction.
This could be easily circumvented by delaying traps, such that a flush can never have an adverse effect on which program counter is chosen.
Upon encountering an `MRET` instruction, the value stored in this CSR is used to jump back to where normal execution was interrupted.

#### `mcause`
This CSR has an elementary purpose, to keep track of what has caused a change in control-flow.
When CSR `mtvec` is in direct mode, the cause can be used by software to figure out the appropriate course of action.
In vectored mode, it is also used to directly compute the correct jump target.
When multiple interrupts or exceptions occur, it is set to the interrupt or exception of the highest priority.
Exceptions are prioritized over interrupts.
For multiple simultaneous exceptions, the RISC-V privileged specification specifies how they should be prioritized.
In the case of MiRiV, because exceptions have to be carried all the way to the memory stage by the originating instruction, as long as there are no instructions that can cause exceptions in multiple pipeline stages, there are no simultaneously occuring exceptions.

Do note that CSR `misa` is also implemented but not relevant to interrupt and exception handling.
Also note that exceptions that happen for instructions which are executed as part of a mispredicted branch get flushed as well when the branch is corrected.
When a trap is executed and earlier stages are flushed, earlier exceptions are also flushed.
Interrupts on the other hand do not get flushed, but in the case that a trap is taken, the pending bit is set to delay handling of the interrupt until appropriate.

## Trap Generation (Trap Unit)

Like previously mentioned, the memory stage determines whether a branch prediction was correct and if it wasn't, the misprediction is signalled to the control unit and the corrected jump target to the fetch stage, causing a pipeline flush and change in control flow.
This mechanism can be re-used for traps, by making the memory stage change the control flow in the additional case of a trap (or a return from a trap).
For this purpose, a new functional unit can be added that has the purpose of deciding whether to trap, calculating the correct jump target and writing back to the CSRs.

The trap unit is kept stateless.
This has the advantage of simplifying implementation and verification of the unit.
Any state that is necessary to ensure correctness is instead added to the memory stage.
The only relevant information from the perspective of the trap unit is whether or not it would be acceptable to cause a trap.
In the implementation of the MiRiV processor, there are two cases where trap generation needs to be delayed:
traps during pipeline flushes and during execution of other traps.

The first case is problematic because it complicates calculation of the correct program counter for the CSR `mepc` in the case of exceptions and interrupts.
As for the second case, raising any trap during execution of any other trap is explicitly forbidden, due to possible state inconsistencies.
Except for the case of exceptions raised during the execution of an interrupt handler, nested traps are not as useful.
Interrupts are disabled during other interrupts regardless, because all interrupts are part of the same privilege mode.
During traps caused by exceptions, it is acceptable to simply delay interrupts.
In all other cases, exceptions during other exceptions and exceptions during interrupts, the exception would prove fatal if not handled specifically by the application, because the correct control flow information, such as the value of the CSR `mepc`, are lost.
Because the target application does not handle these special cases, nested traps are unsupported.

Delaying the generation of traps is simply done by adding appropriate state information in the memory stage and using this state information to enable or disable the trap unit appropriately.
The trap unit is disabled for three clock cycles after a pipeline flush and while in a trapped state.
The processor is in a trapped state after a trap has been raised and until an `mret` instruction has reached the memory stage.

The trap unit takes as inputs an enable signal, the program counter of the memory stage and a trap operation data structure `trapop`, which is an instruction packet containing all information necessary to determine whether or not to raise a trap, calculate the correct jump target and new values for the CSRs.
Inside a `trapop` are current values for CSRs `mstatus`, `mie`, `mip` and `mtvec` supplied by the CSR unit in the execute stage, a signal for `mret` and two signal vectors for accumulated exceptions and interrupts that have passed through the pipeline.
Out of this, the trap unit determines the correct course of action and generates the aforementioned information.
The memory stage uses this information to update the control flow if necessary and passes on new values for the CSRs to the write-back stage, where the values are written back to the CSR unit, similar to how writes to the register file are handled.
Just like writes to the register file, if necessary the updated CSR values can be forwarded.

## Core Local Interruptor (CLINT Unit)

The final component of this interrupt and exception scheme is the interruptor itself.
The interruptor is a unit that collects information from outside the processor and causes interrupt service requests.
Software interrupts are caused by writes to the CSR `mip` and as such, software interrupt requests are already contained within the MiRiV processor with the addition of the CSR and trap units.
But the core itself does not generate any timer interrupt requests and external interrupt requests.
