Friday, 2 March 2012

In Which We Contemplate Interruptions


Having completed the memory test and established what we've got installed and where it is, the remainder of the KERNAL startup process is really only concerned with initialising the VIC and VIA chips which control audio/visual output and peripheral I/O, respectively. After that, we'll hand-over to the language run-time, which is the code that manages whatever screen-editing facilities we want to provide, and looks after the organisation and execution of user programs. But that's still some distance away.

We'll initialise the VIAs first (there are two of them) because Commodore did something quite clever with them in wiring them up - they are connected to the 6502 NMI and IRQ lines, so in addition to all the other things each chip handles, VIA #1 (with its' 16 registers mapped at $9110) is hooked to the NMI line, and VIA #2 (found at $9120) is tied to IRQ. We'll concentrate on VIA #2 to begin with, because it is the one that generates the IRQ signal every 1/60th of a second and is therefore the 'heartbeat' of the OS (unlike the VIC chip, which generates the core timing signal for the 6502 and is arguably the heartbeat of the hardware).

The 6522 VIA is a complicated little beastie, as its' name implies - Versatile Interface Adapter. You can read the datasheet on it here, which is fairly tough going if you're not into stuff like timing signal sequences, but does contain a whole bunch of really useful information once you filter-out the chaff aimed at electronics engineers and just concentrate on the descriptions of the registers and how they affect the chip behaviour. I confess to having read this datasheet a dozen times and still missed something significant, as you'll see below. It's fascinating, and deeply satisfying when you're banging your head against a problem and then suddenly see the light. Persevere!

So what is IRQ? It stands for Interrupt ReQuest, and the 6502 is designed in such a way that when the IRQ line is asserted, it stops doing whatever it was doing, stashes the SR and PC registers on the Stack, and jumps to the code referenced by the IRQ Vector at $FFFE. It then executes that code until it hits an RTI instruction (ReTurn from Interrupt) when it retrieves PC and SR from the Stack and carries-on with the task it was performing before it got interrupted. It's a kind of primitive pre-emptive multitasking, allowing the CPU to be performing some 'main' task whilst periodically dashing off to do something else before coming back and continuing as if nothing had happened. More or less.

In the original Commodore OS, the IRQ feature is used to control a bunch of stuff that has to happen in order for the machine to behave the way it does - blinking the cursor, scanning the keyboard, and updating the clock, amongst other things. Look at $EABF in the original ROM if you want to get an idea of the kinds of things IRQ takes care of - there's quite a lot that goes on in the background during each IRQ cycle, but because it happens very fast, 60 times a second, you don't notice it. You can also hook into the IRQ cycle by altering the vector at $0314, because the hardware vector at $FFFE points to a short bit of code at $FF72, which in turn jumps through $0314 which is initialised to point to $EABF - essentially allowing you to alter what the IRQ routine will do by pointing $0314 at your own code. Of course, if you want all the usual stuff to happen as well, you have to remember to jump to $EABF at the end of your routine, so that the normal IRQ jobs get done too. I'm going to replicate that mechanism in the VIC++ ROM, because it's both useful and clever (though the IRQ vector will be somewhere other than $0314) but first we have to get the IRQ signal generated by VIA #2.

To begin with, it's important to understand that each VIA has two timers, and they can be configured in several different ways - the datasheet is your friend if you want to see how they can be set up to do different things. In order to generate the IRQ signal, VIA #2 has it's Timer #1 register configured for 'free run' mode, which means it takes a counter value you give it and decrements it at an orderly pace until it hits zero, at which point it triggers the IRQ signal, resets the counter to the original start value, and begins counting down again. The interesting part is the rate at which it counts down, and in free run mode this is dictated by the System Phase 2 Clock - the same clock that the VIC generates to drive the CPU. In other words, the VIA will be decrementing the Timer #1 counter at a 1:1 ratio with CPU cycles - which makes for an interesting possibility for doing cycle-count-specific stuff, but for now we don't really care about that.

What we care about is setting the Timer #1 counter to some number which, when decremented at 1MHz (roughly) will hit zero 60 times per second. To calculate that value, take the VIC Phase 1 (Dot) Clock Frequency and divide it by a specific fixed value to get the Phase 2 (Bus) Clock - this is the CPU cycle frequency which the VIC outputs to drive the rest of the system, and that specific divisor value depends on whether the VIC is a 6560 (NTSC) or 6561 (PAL) model.

Then take the bus cycle frequency and divide by 60 to get the number of cycles per Jiffy (a Jiffy is 1/60th of a second; I'm not making this up). Now do a standard 8-bit Modulo 256 calculation on this Jiffy Cycles number to turn it into a two-byte Hi and Lo value to be loaded into the VIA #2 Timer #1 counter.

This is interesting because, as you see from the table, if we use the frequency values from the MOS 656x datasheet we get values of $42/$95 (17045 cycles) for an NTSC 6560 and $48/$29 (18473 cycles) for a PAL 6561. But rather fascinatingly, the Commodore ROM uses values of $42/$89 (17033 cycles) for NTSC and $48/$26 (18470 cycles) for PAL. I can't find any information on why this should be the case - did the Commodore engineers experiment with slightly different timing values until they found one that was 'just right' even though it was, in both cases, lower than we'd expect? Or did they miscalculate the values, coming-up with timing counts that were pretty close to correct, but just off? Or is there some other effect going-on in the hardware that skews the frequencies generated by the VIC just enough that the bus clock frequency is a bit lower than advertised? If anyone has a compelling explanation, let me know!

I'm going to use my calculated values to begin with, and keep the Commodore settings in reserve in case I later discover that 'unknown something' that requires the lower values. As an interesting side note, look at the bus frequencies in the table - the PAL VIC-20 runs 8% faster than the NTSC variant. This is the reason why programs like games and demos, which use cycle-perfect timing tricks to achieve neat display effects, don't work properly when run on the model other than that which they were written for; aside from the fact that the displays have different line counts, the CPU itself is running at a slightly slower rate on the NTSC variant which would throw-off anything requiring a specific number of cycles to have elapsed between IRQs.

Now for some code. I tuck the VIA timer values in a little table called .viadata (which makes it easier to switch between PAL / NTSC settings without changing code) so the initialisation routine looks - at this early stage - like this:

.initvias
LDA #%01111111 ; [2] disable all interrupts (#$7F)
STA .V1IER ; [4] set VIA #1 interrupt enable register
STA .V2IER ; [4] set VIA #2 interrupt enable register

LDA #%01000000 ; [2] timer #1 free run, timer #2 clock Ø2, sr disabled, a/b latches disabled (#$40)
STA .V2ACR ; [4] set VIA #2 auxiliary control register

LDA #%11000000 ; [2] enable interrupts for timer #1 only (#$C0)
STA .V2IER ; [4] set VIA #2 interrupt enable register

LDA .viadata ; [4] get timer #1 frequency lo-byte (NTSC or PAL)
STA .V2T1LL ; [4] set VIA #2 timer #1 lo-byte latch
LDA .viadata+1 ; [4] get timer #1 frequency hi-byte (NTSC or PAL)
STA .V2T1LH ; [4] set VIA #2 timer #1 hi-byte latch
RTS; [6]
; 29 bytes 44 cycles

VIA registers sometimes appear to behave a little peculiarly when you first start playing with them, but that's because they encapsulate a surprising amount of functionality in a tiny space - if you look at that first little section which disables all the VIA interrupts (there are several, for a variety of purposes) it looks a bit odd until you realise how the Interrupt Enable Register (IER) works. Cleverly, bit 7 tells us whether we are enabling ('1') or disabling ('0') any of the 7 interrupts the chip supports; the following bits then apply that setting to the appropriate interrupt, so here we're saying 'disable' (because bit 7 is zero) all interrupts (because bits 0-6 are set, applying the action to each one). We turn off all interrupts in both VIAs, although we're not actually doing anything with VIA #1 just yet.

The Auxiliary Control Register (ACR) tells the VIA how we want to use the facilities it offers, so for now we just set the Timer #1 configuration (bits 6 and 7) to enable 'free run' mode as discussed above. We then go back to the VIA #1 IER and turn on the interrupt for Timer #1 (remember, the '1' in bit 7 says 'enable interrupts', and the '1' in bit 6 says 'just this one, which is the Timer #1 flag'). And then we end by loading our carefully-calculated (and possibly wrong!) timer start value into the Timer #1 lo-byte and hi-byte counter latches. Another neat trick happens here, because there's no separate action needed to start the timer counting down - the act of setting the hi-byte latch tells the VIA to get started.

As of that very machine-cycle, the VIA starts counting down, one unit per cycle, until it hits zero. When it does, it'll raise the interrupt, and the VIC-20 design has that interrupt linked to the 6502 IRQ line, which will cause the CPU to initiate the IRQ process and jump to the address at $FFFE. Or at least it will when we clear the 'I' flag in the SR, because as long as that is set the 6502 ignores IRQs. So we now need to write some code which the IRQ vector can point to, and perform a CLI to clear the 'I' flag and let interruptions commence!

No comments:

Post a Comment