Wednesday, 7 March 2012

In Which Time Goes All Wibbly-Wobbly


So getting something other than a blank screen to look at requires us to start telling the VIC what to do, and that means making some decisions about things like the dimensions of the display area, numbers of columns and rows (in pixels), and a few other parameters - and interestingly, making those decisions sent me off into a little temporal anomaly in which I found myself reconsidering the IRQ timing settings I'd calculated for VIA initialisation. Why was this, you ask? Well, because I need a way to visualise how much CPU time the IRQ logic is consuming (practically none at the moment, as it happens) and the simplest way to do that is to alter the border colour during IRQ-time; the wider the band of alternate colour, the more time the CPU is investing in IRQ logic.

To get that to work accurately, the IRQ routine has to be synchronised with the screen refresh rate so that its' colour change shows as a proportionate, static band of colour - we don't want the band moving around, so we have to make sure the ISR is called synchronously with the screen refresh so that it appears on the same raster line frame after frame. And how do we ensure that this is the case? Simply by making-sure that the VIA #2 Timer #1 latch-values correspond to a cycle count which matches the frame-refresh frequency. So that's all right then!

Now on many other machines (e.g. the C64, successor to the VIC-20) there's something called a Raster Interrupt - a rather handy mechanism in which you tell the system to trigger an IRQ when the Raster Counter hits a certain value, or line number. On the C64 this interrupt is provided by the VIC-II chip, but the original VIC doesn't have this feature, only a counter which keeps track of which raster line is being drawn - so we can't establish frame synchronisation by simply waiting for the Raster Interrupt to tell us, because there isn't one. Instead we have to get 'in-sync' by setting the VIA timer to match the frame refresh frequency, waiting for a desired value to appear in the Raster Counter, and then setting the VIA timer running to invoke the IRQ signal at that same line on each frame.

This means that my carefully-calculated VIA timer values don't fit any more, or at least not for a PAL 6561 VIC, because I've set those values to produce a steady 60Hz IRQ signal but PAL refreshes at 50Hz - clearly, if my IRQ routine is being called at 10Hz faster than the screen refresh, this Can't Possibly Work. Arguably it's fine for NTSC, because screen refresh happens at 60Hz there, but since I'm going to have to rework things for PAL compatibility I might as well make sure NTSC is dead-on at the same time.

I won't bore you with the details of how I got the exact timer values, other than to say I started with the values I'd already calculated (for NTSC) or just recalculated for 50Hz (for PAL) and then fine-tuned the values manually by running them through VICE repeatedly until I got stable rasters in both NTSC and PAL. As well as differentiating between PAL and NTSC values for the VIA timer, I now also need to explicitly describe where the raster line synchronisation point should be, and what the screen refresh rate is - you'll see why. We end up with this:
  • NTSC: VIA Timer - $4243 (16963 cycles, 60.29Hz); raster line - $04 (8); screen refresh - $3C (60Hz)
  • PAL: VIA Timer - $5686 (22150 cycles, 50.04Hz; raster line - $0E (14); screen refresh - $32 (50Hz)
Yes, those VIA timer values do look a bit odd, but they're precisely what is needed to achieve a stable raster 'lock' in each case. The raster line values look peculiar too, but this is because the VIC raster counter is actually 9 bits long, with the 'high' 8 bits in $9004 and the 'low' bit in $9003 bit 7; this means $9004 actually counts every other line, so the value we need to track is actually half the real line number. And the reason for needing to record the 'target' refresh rate? Aha, read on...
 
Here's what the new code looks like to initialise the VIA:

    SEI                ; [2]   disable CPU interrupts
LDX #%01111111 ; [2] disable VIA interrupts (#$7F)
STX _V1IER ; [4] set VIA #1 interrupt enable register
STX _V2IER ; [4] set VIA #2 interrupt enable register
LDX #%01000000 ; [2] timer #1 free run, timer #2 clock Ø2, sr disabled, a/b latches disabled (#$40)
STX _V2ACR ; [4] set VIA #2 auxiliary control register
LDX #%11000000 ; [2] enable interrupts for timer #1 only (#$C0)
STX _V2IER ; [4] set VIA #2 interrupt enable register
LDX _VIADATA ; [4] get timer #1 frequency lo-byte
STX _V2T1LL ; [4] set VIA #2 timer #1 lo-byte latch
LDX _VIADATA+1 ; [4] get timer #1 frequency hi-byte
LDA _NTSCPAL+1 ; [2] get raster line sync
.raswait
CMP _RASTER ; [3] wait for line
BNE .raswait ; [3/2] loop until we hit the right line
STX _V2T1LH ; [4] set VIA #2 timer #1 hi-byte latch (synced to raster)
CLI ; [2] enable CPU interrupts
RTS ; [6]

Most of it is the same, except for the fact that we now get the VIA timer values from a little table at _VIADATA (so we can swap values for PAL and NTSC easily - I've put the differing values in a conditional assembly directive so it builds as PAL unless I uncomment the NTSC flag) and there's now a tiny wait-loop where we synchronise setting the timer hi-byte (and starting it running) with the VIC raster counter and allow IRQs to start. Speaking of which, here's what the IRQ routine looks like now:

    BIT _V2T1LL        ; [4]   read VIA #2 timer #1 countdown lo-byte latch
LDA #$02 ; [2]
STA _SCRNCOL ; [3] set red border (IRQ time)
STA _IRQTIME ; [3] set IRQ busy flag
JSR updtcdc ; [6] update countdown clock
RTI ; [6]

No substantial difference here except that the ISR now sets the border to red when it starts, and also sets a flag to indicate it is doing its' thing - this flag (_IRQTIME) is checked by the 'main' routine, which is just the end of the ROM startup code after all the initialisation is done - it replaces that JMP *  that used to tie things off:

.main
LDA #$00 ; [2]
STA _SCRNCOL ; [3] set black border (idle time)
.busywait
LDA _IRQTIME ; [3] get processor load flag
BEQ .busywait ; [3/2] wait for IRQ to finish
LDA #$00 ; [2]
STA _IRQTIME ; [3] clear IRQ flag
LDA #$05 ; [2]
STA _SCRNCOL ; [3] set green border (processor load)

. . .

JMP .main ; [6]

So here, after initialising memory, the VIC and VIAs, this little bit of code executes; it's really just a placeholder stub for the entire rest of the OS and Language runtime, so gradually the space just prior to JMP .main will fill up with meaningful, beautiful code. We hope. Anyway, all this does is sets the border to black and then waits for the ISR to finish - we know it's finished when its' RTI executes and brings us back to the loop at .busywait, where we spinloop until the _IRQTIME flag is set. We then clear that flag, switch to a green border showing that we're processing some user code with the language runtime, and then restart the main loop, waiting for the next IRQ (which is why it's commented as 'idle time').
 
What this gives us is a three-colour border, where the red bar at the top shows how much CPU time is being spent in the IRQ logic, a green band showing language runtime load, and a black section showing 'idle', or 'free' time. This image isn't what the ROM currently shows, because the IRQ and language runtimes are consuming virtually no CPU - I mocked this up with a simple 'time-waster' loop in those two routines just to demonstrate how it looks.

The green band doesn't extend across the whole screen like the red one does because the middle of the screen is now defined, and is black - so only the border around it shows as green.

Oh, and why did I need that explicit data item to hold the 'target' refresh rate? Because now the IRQ routine can be fired at either 60Hz (NTSC) or 50Hz (PAL) and that means the countdown clock can't simply count down from 60 for its' jiffy interval any more. So we now set the jiffy countdown start value to 50 or 60 depending on which screen frequency (and therefore IRQ frequency) we're set for. See?

Next time: thinking about VIC settings. Really.

No comments:

Post a Comment