Tuesday, 13 March 2012

In Which We Polish The ISR


I got basic IRQ functionality working a while ago, just to get the skeleton up-and-running so that I knew it worked. The ISR didn't do very much apart from change the display border colour and an associated flag (to show how much processor time it was consuming) and update the still-currently-invisible countdown clock. Since there wasn't anything else going on in the system at the time, it didn't matter that it overwrote registers, or failed to differentiate between a standard IRQ signal from VIA #2 and the  BRK  instruction (which routes through the same mechanism as IRQ, but sets the 'B' flag in the Status Register first). However, as the ROM matures - very slowly, admittedly - we really should address these shortcomings.*

Firstly, the ISR should take care to safeguard the registers it's going to use so that when it finishes and returns to the 'main' program (i.e. the language runtime) it hasn't broken anything by changing values unexpectedly. This is dead easy to do - we just push the .A, .X and .Y registers to the Stack as the ISR starts, and pop them off in reverse order as we exit. The return address (to wherever the CPU was executing code before the IRQ happened) and condition of the Status Register are also pushed on to the Stack when the IRQ occurs, and popped off when the ISR ends, but this is taken care of by the processor itself and we don't need to worry about it.

The next thing we should do is determine whether this is a VIA-generated IRQ or the result of a  BRK instruction; by sniffing the Stack for the .SR value pushed as the IRQ occurred, we can then mask-off the 'B' flag and decide which type of IRQ this is. As a result, we'll probably want to do different things - the IRQ handler should be doing all the OS housekeeping we expect (and have already started in a small way with the clock update) whereas the  BRK handler will almost certainly do something different, like trigger a drop out of the runtime and into some sort of machine-code monitor for debugging purposes. I haven't written this monitor yet, but we should set things up so that when I do (later) it can just plug in here.

Now the other thing I want to do is blatantly copy an idea from the original Commodore ROM, in which IRQ and  BRK  handler routines are actually routed through a RAM vector - in this way, user code can hook into these core pieces of logic, either to augment them or replace them entirely. Accordingly, we now have two brand-new vector words in Page 2 which the ISR does an indirect jump through for the IRQ and  BRK  routines; during OS initialisation these are set just to point straight back into the ISR and have no appreciable effect (aside from incurring a few cycles for the JMP  instruction) but if those vectors are altered then there is complete flexibility as to what the system will do when each of these conditions arises.

As we ask the ISR to do more tasks, these will just plug-in to the routine and it will slowly grow - but in the meantime, here's the code as it stands today:

; CPU IRQ handler
cpuirq SUBROUTINE
PHA ; [3] push .A to stack
TXA ; [2] move .X to .A
PHA ; [3] push .A to stack
TYA ; [2] move .Y to .A
PHA ; [3] push .A to stack
BIT _V2T1LL ; [4] acknowledge VIA IRQ (read VIA #2 timer #1 countdown lo-byte latch)
TSX ; [2] move .SP to .X
LDA _STACK+4,X ; [4] get .SR from stack (IRQ pushes .PCH/.PCL/.SR)
AND #$10 ; [2] mask BRK flag
BNE .brkvec ; [2/3] flag set, do BRK processing
JMP (_IRQVEC) ; [5] jump through IRQ vector
.brkvec
JMP (_BRKVEC) ; [5] jump through BRK vector
_irqmain
IFCONST _DEBUG = "Y"
LDA #$0A ; [2]
STA _SCRNCOL ; [4] set red border (IRQ time)
STA _IRQTIME ; [4] set IRQ busy flag
ENDIF
JSR updtcdc ; [6] update countdown clock
PLA ; [3] pull .A from stack
TAY ; [2] move .A to .Y
PLA ; [3] pull .A from stack
TAX ; [2] move .A to .X
PLA ; [3] pull .A from stack
RTI ; [6]

One thing you might notice is that the border-colour-change code is now bracketed by a conditional assembly directive. This just lets me disable all my debugging code (including the border-colour CPU time monitor) by simply commenting-out an EQU definition elsewhere in the sourcecode - when it doesn't resolve to 'Y', the debug code is not assembled into the binary ROM image. I do a similar thing with a video-display flag too, which makes it easy to assemble a ROM for either PAL or NTSC standards.



*Yes, I know I haven't mentioned NMI here. But I haven't forgotten about it, I just haven't got to the point where I need it yet...

No comments:

Post a Comment