Flickering scanlines: The VIC-II and “Bad Lines”

After spending some time getting up close and personal with raster image generation with the Atari 2600, I was feeling confident enough to go back to the Commodore 64 and try to learn more of the VIC-II’s internals. I started with Pasi Ojala’s Demo Corner articles for C=Hacking and was eventually pointed to Christian Bauer’s 1996 comprehensive article. The latter was really what I was looking for, since it was at the level of detail of the TIA HW Notes article for the Atari 2600.

I won’t say I’ve mastered the material therein, but I’ve learned a lot, I’ve solved a mystery that puzzled me the last time I explored this space, and I have seen some recent demos with new eyes and greater understanding.

Let’s talk mysteries.

The Anomaly

In my article on Cycle-Exact delays for the 6502, I ended up having a sort of strange result; firing a background color change a few cycles too early produced a “tearing” effect that moved a few pixels left or right with each change in delay, but firing it a few cycles too late would cause the entire next line to flicker to one color or the other.

When I first encountered this, my reaction was “Aha! I have found a place where VICE is cutting corners in its VIC-II emulation!” However, tests on real hardware quickly showed that this behavior was matched by the VIC-II chip itself. I had no ready explanation at the time, but theorized that perhaps the background color was being cached by the chip if it had not just serviced a raster interrupt.

The truth turns out to be a bit more interesting.

The Explanation

As the graphics chip, the VIC-II is responsible for pulling character, sprite, and bitmap data out of RAM and assembling scanlines out of it. It is, in a very real sense, a combination of the Atari 2600’s TIA with a flexible display kernel burned into an ASIC. Another advantage of being a separate chip like this is that it can do its memory accesses when the CPU is not actually talking to RAM. That gives us about a 2x speedup, and it also frees the CPU to be doing real work during frame display.

The trick is, the VIC-II doesn’t quite have enough time to slip in all the graphics loads that it needs to do the display—even when it’s just in text mode. At the start of each new line of text the VIC-II sets some signals not unlike WSYNC that block the CPU from operating until it gives the all-clear. It then spends a few dozen cycles loading in the next line of text so that it can properly index the character generator ROM for the next eight scanlines. These scanlines, in which the VIC-II pauses the 6510 CPU so that it can grab more data, are traditionally referred to as “bad lines” because they mess up your CPU’s timing code.

In my color-clock test, I set the background color to change between two lines of text, so my raster interrupt was on the final scanline of one text row. Bad lines hit at the beginning of any row of text, which means that the scanline right after the one I had stopped on would be a bad line. From this we see where the bad line hits and what it means when it does—we get much of the HBLANK to ourselves, but then when the VIC-II takes over it takes over for the entire visible scanline. We are already deep into the next HBLANK by the time we get around to changing the color.

In fact, this also explains why we had such disastrous results when we shifted scanlines in the beginning of Colorchart Madness. There we were trying to fire while busy-waiting and got stung right at the start of a badline, instead of stumbling into it as part of standard interrupt processing.

(From an actual hardware implementation standpoint, my explanation is actually kind of backwards—it’s not that badlines happen when we need a new row of text, it’s that badlines are triggered when the raster value lines up with the vertical scroll value and triggering a badline is what makes a line of text happen. This is the basis of a sizable number of demoeffects because carefully timed adjustment of the vertical scroll value will give you control over text rendering that the Programmer’s Reference Guide never contemplated.)

The Test

To really get a handle on what bad lines do to our raster processing, I extended the color clock experiment from a few months back. I kept the ability to select various delay values, and extended it with an ability to change which raster we trigger on, within a range that keeps the logic working.

Because of how I had set it up to test the logic, before, I can’t trigger on anything before 128. However, I only really need to trigger on stuff within an 8-raster range to try everything out. I expand it a bit beyond that just so there can be multiple data points.

Changing the raster is done by actually modifying a constant in a load-immediate instruction with a POKE from BASIC, which is a bad thing to do and I am a bad person for doing it, but it’s safe as long as the routine doesn’t otherwise change. (I’m kind of terrifyingly casual about self-modifying code when I have a symbolic assembler to work with, but even so I’m pretty careful to make sure that the modification part will always execute before the modified code does. I like my code to be runnable more than once.)

I also renumbered the source to clean it up afterwards, which a patched edition normally would not do. However, petcat makes it just as easy to enter this way, so here’s the final source:

10 rem color-clock experiment
20 print "loading, please wait..."
30 d=36:rem offset into delay code (0-49)
40 tr=146:rem target raster
50 for i=49152 to 49234:read v:poke i,v:next
60 if peek(49220)<>tr then print "{clr}error in data statements":end
70 poke 56333,127:poke 788,d:poke 789,192:poke 53265,27:poke 53266,1
80 poke 53281,0:poke 53274,1
90 print "{clr}{blk}{down}{5 right}raster interrupt cycle counter"
100 print "{7 down}{wht}{12 right}target raster is"
110 print "{4 down}{11 right}between these rows"
120 print "{4 down}{6 right}use +/- keys to change delay"
130 print "{7 right}use f1/f3 to change raster"
140 print "{12 right}press f7 to quit"
150 rem main loop
160 if d=49 then t=6:goto 180
170 t=56-d
180 print "{home}{blk}{4 down}{8 right}current delay:";t;"{left} cycles "
190 print "{8 right}current raster:";tr;"{left} ";(tr and 7);"{3 left}({right})"
200 poke 788,d:poke 49220,tr
210 get a$
220 if a$="-" and d<49 then d=d+1:goto 160
230 if a$="+" and d>0 then d=d-1:goto 160
240 if a$="{f1}" and tr>136 then tr=tr-1:goto 160
250 if a$="{f3}" and tr<156 then tr=tr+1:goto 160
260 if a$<>"{f7}" then 210
270 rem return to status quo
280 poke 53274,0:poke 788,49:poke 789,234:poke 56333,129
290 poke 53281,6:print "{lblu}{clr}";
300 end
310 data 201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201
320 data 201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201
330 data 201,201,201,201,201,201,201,201,201,201,201,201,201,197,234,238,33
340 data 208,169,1,141,25,208,174,18,208,48,7,169,15,141,33,208,169,146,141,18
350 data 208,173,13,220,240,3,76,49,234,76,188,254

The Results

So far I have only tested this in emulation, but the results bear out the theory nicely. We’re still using the default vertical scroll here, so badlines occur when the raster value mod 8 is 3. If we shift the raster interrupt down to the badline, then we find ourselves in the middle of the next line instead, even with a minimum delay. Adjusting the interrupt to occur after the badline produces results identical to beforehand, but if we then increase the delay, we find the frontier between the two background colors gradually creeps out from the left side of the screen, just as it had gradually crept off the right side on the previous scanline.

By altering the POKE to 53265 in line 70, we can also adjust the vertical scroll to different values and see that the badline follows our lead.

The Lesson Learned

When you’re playing around with split-screen effects, you are fighting with the VIC-II for control of the memory bus. If you’re aiming for the first scanline in a row of text, do it late on the previous line. If you’re aiming for the second scanline in a row of text, you’re kind of stuck.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s