I think I need to write down a bit about the way I want to see interrupts to work out for the DWMC-16. Partially to be able to remember it for later, partially to answer a question I got after the last post.
Interrupt Vectors
I have already set down the Interrupt vectors for the DWMC-16 in the memory layout, with the first 16 words of the memory taken up by those Interrupt vectors. Yes, I do see the Reset Vector as an Interrupt Vector, though only on a technicality, as the reset signal interrupts the normal program flow to reset the computer.
0x000000 – 0x000001 | Reset Vector |
0x000002 – 0x000003 | Interrupt Vector 1 |
0x000004 – 0x000005 | Interrupt Vector 2 |
0x000006 – 0x000007 | Interrupt Vector 3 |
0x000008 – 0x000009 | Interrupt Vector 4 |
0x00000A – 0x00000B | Interrupt Vector 5 |
0x00000C – 0x00000D | Interrupt Vector 6 |
0x00000E – 0x00000F | Interrupt Vector 7 |
With seven Interrupt vectors, the DWMC-16 can handle several forms of interrupts in a targeted manner. Depending on the design, I will assign some interrupts to IO operations, like mass storage operation, serial IO, timers and some other hardware. I might assign one of them as a general Interrupt Vector for any other hardware to share it.
The way I intend to use them is that I intend to have these Interrupt vectors in a ‘dual state’, since I simply have no idea how else to call it. When the CPU is reset, those Interrupt vectors are initially set by the Monitor/OS EPROM, but, with a set of registers build into the CPU, all, except the Reset Vector, can be overridden, by writing to those memory addresses, which will set an invisible flag for the interrupt handling logic to use the register instead of the EPROM version.
This would allow a program to intercept those interrupts with their own methods. Setting the Vectors to 0, with reset the flag and the CPU will access the fixed Interrupt vector from the Monitor/OS again.
Additionally, I think those Interrupt Vectors actually only save 24 bits for an address in memory, since it’s pretty clear the interrupt handler itself resides elsewhere in memory. So I think it would be unnecessary to include a direct ‘jump’ operation and its opcode.
Interrupt Flags
The interrupts themselves are going to be handled by the interrupt flags.
One is IE, the Interrupt Enable Flag, which is used to enable and disable the interrupts. This is needed, as you do not want an interrupt interrupting the interrupt you are already handling.
This in turn brings me to the I0 to I2 flags, a set of three flags meant to signal to the control logic that there is another interrupt signal coming in while it is already handling an interrupt, but only when the Interrupt Enagle Flag is set to 0. If it is set to 1, all flags will be set to 0 if an interrupt comes in.
At the moment, I am thinking whether or not to include a one or two stage FIFO memory into those Interrupt Flags, just in case several interrupts come in while another interrupt is handled.
Interrupt Handling
Handling of the interrupts might be a tad complicated and a multi stage process.
If an interrupt comes in, the control logic completes the operation it is currently executing, before going into the interrupt handling routine, which is as follows:
- Reset Interrupt Enable Flag
- Push registers R0 to R7 into the Stack
- Push the Flag Register into the Stack
- Push the Program Counter into the Stack
- Push the Z Address Register into the Stack
- Reset the Flag Register
- Set the Program Counter to the Interrupt Vector
- Execute Interrupt Routine
This means the control logic has to push 13 registers into the stack, before the interrupt can be handled. I initially thought I would handle this with an assembler macro, but perhaps it is better to do this in microcode, so the programmer does not have to deal with saving the registers in question.
Saving the Flag Register saves the state of whatever flags were set or reset before the Interrupt came in, so that, e.g. IO or branch operations continue to work normally after the interrupt handler is done.
Of course, once the interrupt was handled, the Interrupt Handler has to return to the CPU to normal operation. Here, I missed an unconditional jump operation that would be necessary for this in my initial Instruction Set design.
Return from Interrupt | reti |
Here, however, we have a bit more complication, as the DWMC-16 CPU has those I0 to I2 flags, which may or may not have been set by an interrupt coming in while the Interrupt Handler was worked on. So how reti
would be handled is different in both cases.
I0 to I2 are 0
- Pop the Z Address Register from the Stack
- Pop the Program Counter from the Stack
- Pop the Flag Register from the Stack
- Pop registers R0 to R7 from Stack in reverse order
- Set the Interrupt Enable Flag
- Execute the normal program
Any of I0 to I2 are set
- Reset I0 to I3
- Set the Program Counter to the new Interrupt Vector
- Execute Interrupt Routine
I initially thought I should return from the first interrupt back to the main program, before executing the next interrupt handler, but that would mean the CPU would loose quite a few cycles for handling the Interrupt as it first would pop the registers from the stack, only to directly push them back onto the stack.
Which is unnecessary in this case, as they are on the stack anyway and going nowhere. Unless of course that some idiot programmer messes up the stack with an orphan push
or pop
in the Interrupt Handler.
Conclusion
This concludes my initial thoughts on how I want to deal with interrupts in the DWMC-16. I’m not entirely sure if this will be the last time I look at the Interrupts, but I think it’s a good start and certainly a good thing to write down and have other comments on in some way.
3 Comments