Tuesday, 21 May 2013

In Which We Consider How To Establish Stability


Let's consider a hypothetical, simple, video generator chip which produces a display of 100 raster lines (0-99) each of which equates to 50 cycles to draw. The entire screen is therefore 100 * 50 (5000) cycles long. The chip has no hardware raster interrupt, but does have a counter tracking which line it is currently drawing. We'll call this counter RASLINE. There is also a timer in the system which has a simple countdown mode that runs at the CPU clock frequency and triggers an IRQ to the CPU when it hits zero. So if we load the timer with 5000 and set it running, it'll trigger an IRQ after 5000 cycles, or once per video frame.

Imagine we wanted something to happen on line 21 of the screen on every frame; we could run a little loop watching for RASLINE to hit 21, then start the timer with a value of 5000. On every frame afterwards we'd get an IRQ on line 21 where we could run a little routine to do something exciting like a colour switch over the line. Pretty simple, and quite cool.

Now in this example we'd expect to see a nice thin bar of some other colour on line 21 (we'd have the code switch back to the previous colour at the beginning of the next line) but in fact there's a problem - for some reason the bar seems to start some distance in from the edge of the screen, and consequently then spills-over into line 22. Not by much, and not always by the same amount, but it's producing an annoying gap on the left and a jittery overrun on to the next line. Why is this?

Well, the gap on the left of line 21 is due to the fact that in our initialisation code we wait with a loop that watches RASLINE, and as soon as it hits 21 we start the timer with a value of 5000 so that we'll get an IRQ at this same place on every frame. But in fact it takes a few cycles for the wait-loop to spot that RASLINE has changed after it has actually changed - and if we run the program a few times, we might see that the point on line 21 where our timer starts is always somewhere between 2 and 9 cycles after the actual start of the line. It varies depending on where in the loop the CPU was at the point RASLINE ticked-over, and there's no magic number we can add or subtract to counter it.

The jittery overrun to line 22 is because we draw 50-cycles worth of alternate colour, which should exactly fill line 21 - but as there's a variable offset to the start of the line our coloured line runs on to line 22. It jitters because the CPU IRQ mechanism itself is not instantaneous, but needs 7 cycles to take effect - although this is a fixed amount and we can compensate for it. But what's worse is that - depending on what instruction is executing at the point the IRQ is triggered - there's an additional 7 cycle variability before our actual IRQ colour-changing code starts whilst the CPU finishes the current instruction.

So we have an initialisation variance of up to 7 cycles for the loop to discover that we're on the right line, and then on every IRQ afterwards we have up to another 7 cycles where the CPU has to finish what it's doing before the IRQ gets processed. Assuming we compensate for the fixed 7-cycle IRQ delay, then on any given frame our actual colour might start anywhere between 2 and 16 cycles after the video generator begins drawing line 21. How do we counter these two issues and get a stable raster interrupt that always starts exactly where we want it to, and always occurs in the same place thereafter?

Let's think about the initialisation start-point problem first, and consider the IRQ instruction-delay issue later.

We could try waiting for line 20 in the initialiser code and then having a small delay there to guess when RASLINE will hit 21 and start the timer at exactly the beginning of the line, but the problem is essentially the same because we will start line 20 at a variable point and then have no way of knowing how long to delay before the video generator is drawing line 21. Or we could use a second timer and some clever but complicated code to track an offset from the main IRQ timer, using that as a differentiator to compensate for the variability of the RASLINE wait-test loop - but then we've committed that second timer to the display, and can't use it for anything else. Plus, the code is gnarly, dude.

Or we could do something else:

First, establish execution on line zero; so wait for RASLINE to be zero, and we then know we're definitely running somewhere 2-9 cycles after the video generator begins drawing the very first line. What we don't know yet is where exactly on the line we are - so start a loop, arranging things such that the net cycle-length of the loop is 4999 cycles (one cycle less than the total needed for a whole frame); then check RASLINE. If it's still zero, we're tracking-back along that line; rerun the loop and then check RASLINE again. Eventually it will not be zero, because we'll have tracked-back to a point where the video generator is still drawing line 99. At this point we know exactly where we are - one cycle before the beginning of line zero.

In and amongst this we have to have some compensation applied to the loop length to counter the 7-cycle IRQ delay, and whatever cycles we consume actually doing the work of testing RASLINE - but that's trivial to calculate, because they're both fixed values.

Now the hard part - compensating for the 7-cycle variability in the IRQ where the CPU has to finish the current instruction. This might be REALLY gnarly, dude.

No comments:

Post a Comment