The initialisation of VIA #2 is done - we're using none of its' facilities bar Timer #1 at this point, because the objective is just to get IRQ working; there are all sorts of other things VIA #2 can do that we're simply not interested in just yet, and of course VIA #1 is sitting there doing absolutely nothing right now and we've turned-off all of its' interrupt indicators. We'll return to it shortly though, because it handles NMI (Non Maskable Interrupts) which we will want to play with.
The next thing to do is write a simple IRQ handler, typically known as an Interrupt Service Routine (ISR). This is just the bit of code that gets called when the 6502 receives the IRQ signal from VIA #2, and it will, eventually, be quite sophisticated - but right now we just need something really simple to prove that VIA #2 is doing what we want (i.e. the initialisation, as far as it goes, is correct) and that we can respond to those IRQ signals in a sensible way. So as a basic test of the configuration, I'm just going to have the ISR count down from 60, one 'tick' every IRQ, and then change the screen colour - if IRQs are coming-in every 1/60th of a second as expected, then counting down from 60 will give us an elapsed time of 1 second, and changing the screen colour is an obvious way of seeing that things are working as planned. Here's the code for the ISR:
.cpuirq
DEC .IRQTEST ; [5] ZP decrement 1-second countdown
BNE .irqexit ; [3/2] exit if not zero yet
LDA #$3C ; [2] reset counter to 60
STA .IRQTEST ; [4] ZP save it
INC .SCRNCOL ; [5] increment screen colour value
.irqexit
RTI ; [6]
; 12 bytes 14 cycles (no colour change) or 24 (colour change)
I've assigned a temporary Zero Page variable called .IRQTEST which gets initialised to 60 after the memory test is complete, so when the IRQ triggers, we just decrement it once per iteration until it hits zero - then reset it and change the screen colour. The code to set this up just follows-on after the RAM test - it's the current end of the ROM, because after this we'll be refining initialisation in order to get something interesting on the screen and handing-over to the language runtime:
JSR .initvias ; [6] initialize VIAs
;JSR .initvic ; [6] initialize VIC
LDA #$3C ; [2] IRQ counter to 60
STA .IRQTEST ; [4] ZP initialise IRQ counter
CLI ; [2] enable interrupts
JMP * ; [3/3] KERNAL initialisation complete
; 11 bytes 17 cycles
So we call the .initvias subroutine, initialise the IRQ counter for the first time, clear the CPU 'I' flag to tell the 6502 to start responding to IRQ triggers, and then drop into an endless loop by jumping to the address of the JMP itself. There are two things to note here; firstly, the call to .initvic is still commented-out (and not tallied in the trailing comment) because we haven't written anything for it yet. Secondly, that JMP at the end would, normally, mark the death of executing code, because nothing ever happens after that - we loop forever - which means that if anything happens at all, it must be the IRQ mechanism making it happen.
We also set the hardware IRQ vector at $FFFE to point to .cpuirq, so that the CPU knows what to call when it gets the IRQ signal from VIA #2. That's done in the sourcecode so that the ROM is assembled with that address 'baked in', and we don't set it with explicit instructions. Let's fire it up, and see what we get.
Ah.
Hmm.
Er...
Not the desired effect. Not at all. What on earth is going on here? Evidently, given the appearance of that rolling line pattern on the screen (you're only seeing a static point-in-time screenshot) the ISR must be executing and incrementing the screen colour - but it should be once per second, and that image shows that the colour is changing multiple times per frame - it's hard to count, but my estimate is somewhere in the region of 1000 times per second, not just once! Clearly, something is broken.
I could regale you with a commentary of the long and sorry tale of the hours I spent trying to figure this out, but I won't. Instead, here's a summary of the things I did to try to figure-out why IRQ seemed to be happening far more frequently than I expected:
- Checked that VIA #2 was having the Timer #1 interrupts initialised correctly
- Checked that VIA #2 was having the Timer #1 counter latches loaded properly
- Checked that VIA #2 was having the right values from .viadata loaded correctly
- Checked that the VIA #2 register EQU values were correct
- Checked that XVIC showed all the right values in the VIA #2 registers*
- Checked that XVIC showed the VIA #2 Timer #1 counter counting-down
- Checked that the CPU hardware vector for IRQ pointed at .cpuirq
- Checked that there was no loopback into .cpuirq from somewhere else
- Checked that .cpuirq wasn't accidentally being called from anywhere else
- Checked that we weren't 'falling' into .cpuirq past that JMP * somehow
- Checked that the VIA #2 counter latch values were calculated correctly
- Checked that the VIA #2 counter latch values had no effect on the display behaviour
Finally, having re-read the 6522 datasheet half-a-dozen times, I spotted it. The VIA does indeed flag the IRQ when the timer reaches zero, and then leaves the IRQ line low until it is acknowledged by the CPU. In other words, it keeps telling the CPU that an IRQ has happened until the CPU tells it to shut up about it - so after my ISR routine ran, exactly as planned, the CPU immediately looked at the IRQ line coming from VIA #2, saw that it was (still) low, and triggered IRQ processing again. And again. Over and over again, because it wasn't ever telling the VIA that it had handled that first IRQ signal.
There are a couple of ways to tell the VIA that the IRQ signal has been dealt with, but the simplest is to read the Timer #1 counter lo-byte latch in the ISR, which is enough to clear the VIA IRQ flag. Here's what the ISR looks like now:
.cpuirq
BIT .V2T1LL ; [4] read VIA #2 timer #1 countdown lo-byte latch
DEC .IRQTEST ; [5] ZP decrement 1-second countdown
BNE .irqexit ; [3/2] exit if not zero yet
LDA #$3C ; [2] reset counter to 60
STA .IRQTEST ; [4] ZP save it
INC .SCRNCOL ; [5] increment screen colour value
.irqexit
RTI ; [6]
; 15 bytes 18 cycles (no colour change) or 28 (colour change)
That's all it needed - by reading that register, it tells the VIA that the IRQ has been handled and the signal is turned-off. When the ISR hits the RTI, the endless JMP * loop carries-on running until VIA #2 Timer #1 reaches zero again, at which point the IRQ signal is sent once more and the ISR executes. At 60Hz, counting down from 60, that means the screen changes colour once per second.
IRQ works.
Now I can start thinking about initialising the VIC...
* It did, but I noticed that the XVIC monitor 'IO' command showed the registers with the VIA #1 and VIA #2 names reversed. One for the VICE team to fix. :)

No comments:
Post a Comment