NEWS, EDITORIALS, REFERENCE
VIC-II and FLI Timing (2/3)
This post is part 2 of a 3 part series. If you haven't read Part 1: Memory Access yet, I'd recommend starting there first.
I like to start with a few updates on the progress of C64 OS. If you're not interested in the updates, you can skip to the the main topic.
Update on C64 OS
TKInput Class, File Lock Library, and File Info Utility
I've recently finished the TKInput class, which is a single line text input field. It supports most of what a modern text field supports. Selections, cut/copy and paste to and from the clipboard. Content length constraints, blur, focus and character insert events, ability to filter or substitute typed or pasted characters. Good keyboard controls. I've also recently finished the File Info Utility, which makes use of the TKInput class for editing a file's name.
C64 OS applications use a standard pointer to a file reference for the currently open or selected file. The system's status bar has a mode that automatically renders this file reference. Now, the File Info Utility also taps into this file reference and allows you to rename the file, back it up, or see its metadata such as size on disk, its last modified date/time, and its file type.
Any application that uses the standard open file reference automatically integrates with and benefits from the features provided by the File Info Utility.
File LockThe directory library in C64 OS now has the ability to optionally read in the date-time stamps on files from CMD devs, SD2IEC and IDE64. And the date/time is now shown in the File Info Utility. #c64 pic.twitter.com/E7hADKBhAr
— Gregorio Naçu (@gregnacu) October 20, 2021
The File Info Utility also lets you lock or unlock the file, by making use of a new File Lock library (flock.lib). The File Lock library toggles the lock bit on any file specified by a C64 OS file reference. The library supports the 1541, 1571, 1581, IDE64, CMD HD, FD and RamLink. The only devices not supported are SD2IEC, (because they don't support file locking.)
The File Info Utility can be used in any application. For example, in C64 OS's App Launcher, the selected alias can be edited with the File Info Utility. Locking an alias prevents the alias from being deleted from one of App Launcher's desktops. Very cool.
File RenameThe File Info Utility's main purpose is to rename files. It uses the TKInput class which gives full support for selections and the clipboard, so you can copy and paste to edit filenames.
If the new filename is taken, a disk error is generated. The error gets rendered in the status bar and the original filename is restored. An alert is generated (which blinks the border) to notify you of the error. You can also use C=+Z to revert the filename field to the current name of the file, undoing any edits you have made.
File CopyIn addition to the rename button, there is also a "Copy" button.
This supports a common usage pattern that I have when developing. You open a file and start working on it. Now you have a copy in memory that is different from the file on disk. But then you realize that you'd like to have a backup of the original file before you save over it with your new changes.
What I often do, in Turbo Macro Pro, is use the disk command feature to issue the copy command:
c:myfile.bak=myfile
Boom. Now you have a backup copy of the file on disk. You can now safely save your changes, knowing that you can easily revert to the backup. This is not quite the same as the traditional File → Save As..., in a modern OS. "Save As..." saves the current file in memory to a different filename. The File Info's copy button makes a copy of the file on disk, before you overwrite the original with what's in memory.
Multiple Documents, Multi-SelectionsIf the application supports more than one open file, such as two text files in two different tabs or two sides of a split view, each open file will have a file reference that indicates where that file lives in storage. You can simply point the standard system pointer at the appropriate file as focus shifts between them. This keeps the status bar showing the path to the current file, and allows the File Info Utility to operate on that file too.
In some applications, principally File Manager (but App Launcher too), the File Info Utility is the official way to rename a selected file. There is one file reference that tracks the location of the current directory (device, partition and path), and this is rendered by the status bar. If one file is selected, its filename is copied into that file reference, which allows the File Info Utility to work on it. If zero files, or more than one file is selected, the filename is cleared in the file reference, and the File Info Utility clears and disables its UI.
Another cool thing, of course, File Info isn’t limited to use within the File Manager. Here, it’s being used in the App Launcher and shows the date/time and other details about a desktop alias. #c64 pic.twitter.com/DhIDiq10Y6
— Gregorio Naçu (@gregnacu) October 20, 2021
C64 OS Beta 0.6
Much like how the Colors Utility is a standard system component which can be called up overtop of any application and provides functionality that the app doesn't have to reimplement for itself, the File Info Utility also behaves like a floating palette, that contextually responds to what file is open or selected in the underlying Application.
The File Info Utility, the Scratch, Copy and Move Utilities, their supporting libraries (fcopy.lib and flock.lib) and many other under-the-hood bug fixes, changes and new features, have been released to the beta testers as Beta 0.6.
Those are all the updates on C64 OS for now!
Part 2: Data Fetching
Time to move on to: VIC-II and FLI Timing, Part 2: Data Fetching
The limits imposed by memory bandwidth
To summarize very briefly from what was discussed in Part 1: Memory Access, the memory bandwidth of a 1MHz computer that is generating a 320x200 pixel image is incredibly limited. In the abstract, at 30 frames per second, it works out to approximately 0.26 bytes per pixel. In other words, in the length of time the video chip has to display one pixel, it has only enough time to retrieve 0.26 bytes from memory. This is an average, of course, real pixels come in clusters on the screen. There are spans of consecutive cycles when the VIC-II cannot profitably pull graphics data, which means in practice it has even less time per pixel.
In this post we will get into exactly how much time it has, for everything.
What it means is very simple. In theory, if you could read from memory only 2 bits per pixel, you can only specify four distinct values per pixel, which could be mapped to 4 colors. To specify 16 colors per pixel, you'd need to be able to pull at least 4 bits per pixel. But if you cannot pull that much data, how else can you utilize 16 colors across the context of the whole screen? The C64's answer is to divide the screen into 1000 subregions, called cells. It uses time that is available when the video chip is not busy pulling pixel data, to retrieve and cache color data for a row of cells. And then later, uses the scanty pixel data as an index into the pre-cached color data for the cell that's currently being drawn in.
This is more or less what the C64 does. And now we know why—because there isn't enough time to do something more direct.
Memory Accesses
There are 4 sources of data that the VIC-II reads and collates in order to decide what color to generate in the video signal at any given moment. Not including sprites. We'll talk about sprites later, but this list does not include sourcing sprite data.
- Color Memory (static RAM chip)
- Bitmap Memory (main dynamic RAM chips)
- Screen Matrix (cached internally, from main dynamic RAM chips)
- Registers (internal, auto-incrementing or set explicitly by the CPU)
How exactly these data are interpreted depends on the current video mode, and the video mode is defined by the combination of a few bits in the VIC-II's registers, which are set by the CPU. What's interesting is that the memory accesses the VIC-II makes are identical regardless of video mode. The addresses for where the data comes from are composed on-the-fly by the VIC-II and those address compositions vary based upon the video mode, however, the phases of data retrieval that it goes through are identical in all modes. The only difference between the modes is how the data get interpreted. This is part of the beauty of its simplicity. It has a hardwired data retrieval pattern, that is shared by all the video modes.
Screen matrix memory, for example, is sometimes used for color data and sometimes used as textual character indexes or for custom bitmap tile indexes. The screen matrix is the 1000 cells, arranged in 25 rows of 40 columns each. The VIC-II pre-caches one row of screen matrix data at a time, and uses that internal cache until the start of the next screen matrix row. At which point, it reads in and caches the next row of screen matrix data.
Perhaps the simplest video mode of the VIC-II is HiRes bitmap mode. Let's see how each of the 4 sources of data are employed for this mode:
- Registers define the start of bitmap memory and the start of screen matrix memory.
- Bitmap memory is accessed in realtime to fetch the pixels (8 pixels at a time, 1-bit per pixel.)
- Screen matrix is accessed during one line out of every 8 lines, cached internally, and used as color data.
- Color memory is not used at all.
Color data from the static RAM chip is not used at all in HiRes Bitmap mode. But, if I were to guess, I'll bet that color ram chip is still technically being read, even though its data is not being integrated into the video output in this mode. Just as the CPU makes those ghost accesses to main RAM on every cycle, because it would take more work and coordination effort between the VIC-II and CPU to prevent those accesses than to just let them happen. My guess is it's the same with the color static ram. Even if the VIC-II doesn't need the data in a given mode, there's no harm and no time wasted making the access, and it would take more work and logic circuitry to prevent the accesses.
The Memory Map and the VIC-II
Now the rubber must truly meet the road. How does the VIC-II know where to get the data from in main memory, at any given moment?
First, let's start by looking at a memory map.
Hand-drawn memory map of the C64 and VIC-II
There may be cleaner drawings of this information somewhere out there, but I quite enjoy drawing these for myself by hand. In fact, if you want to improve your memory of something they say that it helps to write something out by hand. So I actually recommend that you make one of these diagrams for yourself to help you get a feel for the proportions of its areas.
The VIC-II has only 14 address lines, which is not enough to access 64KB directly. 214 = 16,384 or 16KB. So the VIC-II can only directly address a 16KB block of memory. When the VIC-II is active and making its requests by setting the address bus, it is only controlling the low 14 bits, but the high 2 bits can't just be left floating or who knows which of the 4 possible 16KB banks the VIC-II would actually be accessing. Instead, CIA 2 has two of its port lines (Port A, bits 0 and 1) drive the two high bits of the address bus during VIC-II accesses.
The CPU can program CIA 2 by writing to its registers at $DDxx. Thus a program running on the CPU can change CIA 2's configuration to change which one of four banks the VIC-II accesses. The VIC-II itself has absolutely no idea which bank it is looking at. In fact the VIC-II has no concept of banks at all. As far as the VIC-II is concerned there is 16KB of main memory, plus it has the additional private 4-line data bus to its color memory chip.
Let's talk about this memory map. On the grid paper there are rows and columns. 16 rows and 16 columns, labeled from $0 to $F in hexadecimal. 16 x 16 is 256, and 256 x 256 is 65,536. Each square of grid paper is therefore one 256-byte unit of memory, which is referred to as a page. So there are 256 pages of memory, 256 bytes big each. There are 16 pages in a row, 16 x 256 is 4096, so each row represents 4KB of memory. Two consecutive rows together make an 8KB block, and two consecutive 8KB blocks together make a 16KB bank, and that's how much the VIC-II can see at once. Thus the total memory is divided into 4 banks, each bank consisting of 4 rows of 4KB each.
The only odd thing about the banks is that they are numbered in reverse. We'll need a chunk of the schematic again to see what this means and why it's like this.
Detail has been removed for clarity of focus.
The video address lines 14 and 15 (labeled VA14 and VA15, the two highest bits), are, as mentioned, supplied by CIA 2. In the schematic above, the lines highlighted in purple show VA14 and VA15. It doesn't show here that they come from CIA 2, but they do. It shows that they feed into a 74LS258. A quick web search tells us that the 74LS258 is a Tri-state Quad-2-Input Inverting Multiplexer. Phew!! That's a mouthful, eh? We're not going to get into exactly how it works, other than to say that it assists the VIC-II in addressing, by combining the inputs from CIA 2.1
That mouthful of a description is not too bad if we break it down. Tri-state means that its outputs can be disabled and have no influence on the bus. The VIC-II's AEC line is used to signal to the CPU to take its address lines off the bus. That same AEC line is connected to the 74LS258's output enable pin, so when the CPU goes off the bus, this chip comes onto the bus, which in part is how the VIC-II and CPU share the buses.
It's a 2-input multiplexer because it has a switch that selects an output line (Y) to pass through one of two input lines (A or B). But its quad because the switch controls 4 of those multiplexers in parallel; A0 or B0 pass through to Y0, A1 or B1 pass through to Y1, etc. BUT, it's also inverting. So if A0 comes in high and passes through, Y0 will output low. You can see that the Ax and Bx inputs do not have a bar over them, and yet the corresponding Yx outputs do have a bar over them. This is shown highlighted in green above.
The upshot of this inversion is that the binary value of the 2 bits provided by CIA 2 are inverted. Thus, if you set %00 on the CIA lines, they get inverted to %11 on the address bus, which points the VIC-II at the highest 16KB bank in memory ($C000 to $FFFF). But if the CIA lines are %11, they get inverted to %00 and the VIC-II sees the lowest 16KB bank ($0000 to $3FFF). The banks are typically named by the binary value you set on the CIA: Bank 0, 1, 2 or 3. But because those bits get inverted the banks in memory are reversed in order, to form this table:
Bank # | CIA 2 bits | Address Bus bits | Memory Range |
---|---|---|---|
0 | %00 | %11 | $c000 – $ffff |
1 | %01 | %10 | $8000 – $bfff |
2 | %10 | %01 | $4000 – $7fff |
3 | %11 | %00 | $0000 – $3fff |
And this is how we see the Bank #'s labeled in the memory map drawing above.
Next we must consider the size of a bitmap. We're talking about HiRes bitmap mode first, because it is in some sense the simplest mode and the easiest to understand. One bit controls the appearence of one pixel. There are 320 x 200 pixels, or 64,000 pixels, which is 64,000 bits divided by 8 bits per byte, is 8000 bytes, or just shy of 8KB. Thus a single fullscreen bitmap must occupy (the vast majority of) an 8KB block of memory. A VIC-II bank of memory is only 16KB big to start with, so a fullscreen bitmap essentially occupies half of an entire video bank. The equivalent of 1/8th or 12.5% of the C64's total memory capacity.
No wonder the the VIC-20 doesn't have a HiRes bitmap mode.2 It has 22 columns by 23 rows, for 506 cells. Each cell is 8x8 for 64 pixels times 506 or 32,384. At 8 pixels per byte, that's 32,384 / 8 or around 4KB for a fullscreen bitmap. But the machine only has 5KB of RAM total. The takeaway point here is that by the memory capacity standards of the early 1980s, bitmap graphics are extraordinarily data-heavy, even if you devote the bare minimum of just one bit per pixel.
Memory Alignment
The next thing we must observe is how these 4K, 8K and 16K blocks are aligned within memory. As a programmer the memory feels like one big unified space, with mere numbers attached to the addresses. When we have 8 kilobytes of data in a file on disk, it is just as easy to load that 8K to one contiguous range of addresses as it is to load it to any other. You want to load it starting at $7020? No problem! As each byte is loaded in the address for the next byte is incremented and the full 8000 bytes ($1F40 in hex) are loaded in, ending at $8F5F. We just need to point the VIC-II at $7020 and boom, it'll show us the 8K bitmap that starts there, right?
Unfortunately, although it feels all the same to the programmer, the hardware doesn't work like this. The hardware cannot access a bitmap from just any arbitrary memory offset. I first covered this idea in the post Versa64Cart and C64 ROMs, under the heading 16K Banking.
Memory Alignment Thought ExperimentTo keep this simple, we can use an example with very small numbers. Imagine you have exactly 16 bits of memory, and you have a mechanism for displaying the contents of that memory to 16 LEDs. This is a purely fictional device, a high school electronics project, say. The 16 LEDs are arranged in a 4x4 matrix and the 16 bits are individually addressable by a 4 bit address bus. We need a circuit that will read 1 bit from our 16 bits of memory, then use that bit to supply (if the bit is 1) or withhold (if the bit is 0) a pulse of electricity to the corresponding LED. After processing the first LED, our circuit must read the next bit value, and use it to energize (or not) the second LED. It repeats this for all 16 LEDs, and then it loops back around to the first one again and continues the process indefinitely. As long as it processes the full bank of LEDs quickly enough, the lit ones will appear lit together and the unlit ones will remain unlit together. Bingo-bango we've got a simple video matrix that is actively displaying the contents of our 16 bits of memory.
How shall we cycle through the 16 addresses? That's not too hard, we could use a standard 4-bit counter, like a 74161. The output of the counter is connected to the address bus: 4 counter bit lines, 4 address bus lines, 16 possible addresses, 0 to 15. The image presented by our 4x4 LED matrix is stable, and showing the contents of memory.
Now let's suppose that we want to step our electronics project up a notch. Rather than 2 bytes of memory (16 individually addressable bits) and a single video frame, we want to double the memory to 4 bytes, for 2 frames of video. How do we double the memory? For starters, we have to add one more bit to the address bus. In addition to the existing 4 bit address bus, we add another addressing line, and now have 5 bits. Plus we have to add another 2 byte memory chip.
Both memory chips have their 4-bit address connected to the original 4-bit address bus. The state of the extra address bit will now toggle between activating the first and the second memory chip.
The video matrix now can read out of two different chips, but what determines which chip is active? The 5th address bit. But what controls the state of the 5th address bit? Whatever it is, it has to be in addition to the existing 4-bit counter, because the 4-bit counter is 4-bit; it knows nothing about the 5th bit. Rather, we could wire the 5th bit to a push button. Depress the button, and memory chip one deactivates and memory chip 2 activates. The video matrix is always doing same thing, regardless of what's going on with the push button. But when the button is depressed the video matrix shows frame 2. Release the button and the video matrix shows frame 1.
Now, from a programming/data perspective, there are 32 bits of memory, with addresses from 0 to 31. A video frame occupies 16 bits. So, can't a video frame occupy any 16 bits? Like, say, from address 5 to address 20? Nope. It should be clear why this is impossible. In our fictional project, the 4-bit counter is still hardwired to the lowest 4-bits of the address bus.
Here's what the address range looks like from 0 to 15:
a4 | a3 | a2 | a1 | a0 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 |
0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 0 | 1 | 1 | 0 |
0 | 0 | 1 | 1 | 1 |
0 | 1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | 0 |
0 | 1 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
0 | 1 | 1 | 1 | 1 |
Now shifting the data up in memory by just 5 bytes, here's what the address range looks like from 5 to 20:
a4 | a3 | a2 | a1 | a0 |
---|---|---|---|---|
0 | 0 | 1 | 0 | 1 |
0 | 0 | 1 | 1 | 0 |
0 | 0 | 1 | 1 | 1 |
0 | 1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | 0 |
0 | 1 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
0 | 1 | 1 | 1 | 1 |
1 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
1 | 0 | 1 | 0 | 0 |
The 4-bit counter only has 4 bits, but the range from 5 to 20 spans 5 bits. It's not just a matter of the 4-bit counter being hardcoded to the wrong set of bits, but unaligned ranges require more bits in total to even be expressed. It is obvious why it is impossible for a single frame to start on an arbitrary memory offset. And although our simple video matrix is just a toy model, a thought experiment really, the VIC-II is fundamentally in the same situation. It has counters that roll through a fixed range of bits, and in addition, it has statically set registers that function like the push button on the 5th address bit in our fictional project.
Rule: Even Increments
Let's return to our memory map drawing now to see how the various memory blocks are aligned for the VIC-II.
Here it is again so you don't have to scroll all the way back up.
Hand-drawn memory map of the C64 and VIC-II
Because the addressing lines of the VIC-II are hardwired to the lowest (and corresponding) lines of the main address bus,3 the rule of thumb is that any given block of memory may only begin at an even multiple of its own size. So let's think about that for a moment: The VIC-II has access to 16KB banks. A bank, according to our rule, may only begin on even increments of 16KB, and so it is in the diagram. Within a bank, an 8KB bitmap may only begin on an even increment of 8KB. In other words, there are exactly two non-overlapping areas in a bank into which an 8KB bitmap can be fitted.
How about for character sets? A character set is exactly 2KB. According to the rule, a character set can only begin on an even increment of 2KB. Each row is 4KB, therefore a character set can appear either fully in the first half or fully in the second half of any row. Lastly, we have screen matrix memory, 1KB (plus some space at the end for sprite pointers.) A block of screen matrix memory, thus, must appear aligned to 1KB increments within a bank. Think about where screen memory on a C64 is by default: $0400 (1024 in decimal). Exactly on an even increment of 1KB. You can move screen memory to $0800 (2048 in decimal), or to $0c00, etc., but you can't move it to, say, $0460. And now we know why.
Fitting a Bitmap Past the Character ROM
We now know where the possible places are that an 8KB bitmap can fit. There are two slots per bank, an upper slot or a lower slot, and there are 4 banks. Therefore, there are 8 possible slots in memory where an 8KB bitmap can be squeezed, right? Well, not so fast. The C64 has two 2KB character sets in a 4KB ROM. The ROM is sometimes called the "Character Generator" but, to be frank, I don't like the word "generator" because it sounds active. It sounds as though the characters are being algorithmically generated. In fact the Character ROM is just a ROM (a memory chip) like any other. Except instead of having BASIC code or KERNAL code, or plug-in game code, it contains the bitmap patterns of the stock PETSCII character sets.
When the VIC-II needs to read the bitmap pattern data, which we'll talk more about later, it uses its internal registers to provide the upper bits for the offset within the bank of where the bitmap patterns are to be found. It uses rolling, auto-incrementing registers—these describe where on the screen it is drawing—to compose the lower address bits to specify the right spot in memory to fetch the right bitmap byte for the immediate video output it needs to produce. Somehow though, it needs to know whether to fetch from RAM or from the Character ROM. The VIC-II actually does not make this decision. It is responsible for setting the address whence to fetch the byte, but it is blind to where the byte ultimately comes from.
For this job we can thank our old friend the PLA, the so-called traffic cop of the C64's memory map. Again in the post, Versa64Cart and C64 ROMs, under the heading The C64's Memory Map and the PLA, I give a more in-depth description of how the PLA controls what is mapped where. But in summary here, the PLA gets inputs from the high 4 address bits, which allows it to know when any of the 16 different 4KB regions of memory is being accessed. These 4KB regions correspond to the 16 rows in the memory map diagram above. For any given 4KB region being accessed, the PLA can select what will be found in that region by selectively disabling all but one thing. It decides between alternatives for a given 4KB region based on other inputs it is receiving.
One of the PLA's 16 inputs is the /AEC line from the VIC-II. If the /AEC line is asserted, the PLA knows that it's the VIC-II making the request, and if the request is being made to anywhere within the 4KB region from $1000 to $1FFF (%0001 on the high 4 address lines that the PLA sees) or $9000 to $9FFF (%1001 on the high 4 address lines), then the PLA disables RAM and enables the Character ROM. These two special regions are shown with a grey background in the memory map drawing above.
Note that when the CPU is making accesses, this remapping does not come into play, because, remember, the state of the AEC line differentiates between CPU and VIC-II memory accesses. Note also, that when the VIC-II is making the request, the PLA is hardcoded to perform this remapping; it cannot be turned off. What that means is that when the VIC-II is pointed at Bank 1 or Bank 3, (bank 3 is the start up default,) the VIC-II is unable to see RAM in 4 of the 16 kilobytes of that bank. Instead, it can only see the Character ROM.
And so, in fact, we do not have 8 possible slots into which a bitmap can be squeezed, because in two of those slots, only half the slot sees RAM and the other half the slot sees the pre-defined character set bitmap data. Now, I promise you that we will eventually get to talking about FLIs and FLI timing, but we can foreshadow that by saying, the FLI format requires access to not only a bitmap but also 8 screen matrix blocks, all in the same 16K bank. Which is to say, an FLI requires the FULL 16KB of a bank. This means that there are only 2 possible places where FLI data can be situated: Bank 0 or Bank 2. And within each bank the bitmap must be in either the lower or upper half, with the 8 screen matrices in the other half.
Fetching Data for a HiRes Bitmap
Before we move to explaining FLI though, let's continue on with a discussion of the much simpler HiRes bitmap mode. To display a HiRes bitmap, we will need the 8KB bitmap data, plus we need one 1KB screen matrix to hold 1000 cells of color data.
A HiRes bitmap can be displayed from any bank. But if stored in bank 1 or 3, the bitmap data must be in the upper half of the bank, because the lower half is intruded upon by the Character ROM. In whichever half-bank the bitmap data is found, the screen matrix must be found in one of the 8 possible 1KB blocks in the other half of that same bank. In bank 1 and 3, the Character ROM only occupies 4KB leaving 4 possible 1KB slots available for the screen matrix.
For example. Let's put a HiRes bitmap in Bank 3 ($0000 to $3FFF). The bitmap must go in the upper half of the bank, because the Character ROM appears in half of the lower half of that bank. The 1000 screen codes must fit in the lower half of that bank, but can't appear where the Character ROM is either. That leaves 4 possible slots. But $0000 to $03FF is not acceptable because this memory is used for the CPU's hardware stack ($0100 to $01FF), and zero page is used heavily by the C64's operating system ($0000 to $00FF). It could go from $0400 to $7FFF, but this overlaps with where the typical Screen Matrix is. If we overwrite that area, then after coming back from graphics mode, our text screen would be full of crap. How about from $0800 to $0BFF then? Well, that's where BASIC programs usually start, so it might work, but if you use BASIC to implement the image viewer, as you load in the screen matrix data it would be overwriting the program! That leaves just one slot left: $0C00 to $0FFF. This would leave 1KB starting at $0800 in which you could implement the viewer program, and nothing collides. That sounds like a winning combination. Here's how it would look:
Memory map with highlighted regions for HiRes Bitmap
I find this aesthetically beautiful. It's balanced and symetrical. I like it.
As mentioned earlier, the data available in the static color RAM chip is simply not used in HiRes bitmap mode. A byte from screen matrix memory (labeled color data above), for a given 8x8 pixel cell, defines the foreground and background colors (the on and off colors) of the cell in its upper and lower nybbles, respectively. The bitmap data defines one bit per pixel, if the bit is 1 the pixel is drawn using the foreground color (the upper nybble of the screen matrix byte) for that cell. And if the bit is 0, the pixel is drawn using the background color (the lower nybble) for that cell.
Screen Matrix Byte | Color Nybbles | Bitmap Byte | Pixel Colors on Screen |
---|---|---|---|
$10 (16 in decimal) | White,Black | %00101001 | Black,Black,White,Black,White,Black,Black,White |
$36 (54 in decimal) | Cyan,Blue | %11001010 | Cyan,Cyan,Blue,Blue,Cyan,Blue,Cyan,Blue |
The task of the VIC-II is to compose the correct addresses to make requests for the right data, at the right moment, to integrate them together and generate the video signal in realtime. Not forgetting that the VIC-II must also be responsible for the sprite data fetches and the memory refreshes, as described in Part 1: Memory Access.
A typical program does not need to concern itself with the exact behavior of the VIC-II when it is in a native video mode, like HiRes bitmap mode. You simply put the data in the right places in memory and you adjust the registers to put it in the right mode and specify the locations for where to find the data, and the VIC-II handles everything else. However, to generate a non-standard video mode, like FLI, the CPU must intervene at critical moments, tweaking the VIC-II registers while it's in the middle of rendering a frame. In order to make those adjustments at the right moments, it is necessary to understand precisely what the VIC-II is doing on every single clock cycle.
The data fetch pattern, as mentioned earlier, is the same regardless of what mode the VIC-II is in. The mode merely defines how the data gets interpreted to generate the output signal. Therefore we can start by seeing what the VIC-II does to generate HiRes bitmap mode, even though we don't need the CPU to intervene to make a native mode display properly.
Again, to make sense of what the VIC-II is doing, we will turn to that most excellent document from 1996, by Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64. The following diagram is taken from section 3.6.3. Timing of a raster line. The original document is a plaintext file, but I have added the vertical strokes on the signal lines, because I feel it makes it easy to visualize.
6569 (PAL), Bad Line, no sprites.
There are different versions of the VIC-II chip. Above is from a 6569 (PAL) VIC-II. There are also two versions of NTSC, an older less common version and a newer more common version. Their precise timing varies, which will be extremely important for FLI timing. For now, we'll just look at the PAL version. This is complicated enough and we don't want to add unnecessary detail until we have a basic understanding of how it works.
Although the VIC-II has a simple hard-wired fetch pattern, it has more than one fetch pattern depending upon where on the screen it is drawing. The screen is built up by drawing successive rasterlines. At the top of the screen it's drawing the top border and at the bottom of the screen it's drawing the bottom border. It doesn't need to fetch bitmap or color data from RAM during these rasterlines because the border color is stored in a VIC-II register.
While drawing the bitmapped area though, it needs to fetch a byte of bitmap data on every clock cycle, which it then uses immediately. A byte of bitmap is fetched, and its bits are output as pixels that very cycle. At the start of the next cycle it must fetch the next bitmap byte, which it immediately outputs, etc.
It only needs to do this while drawing the bitmapped area of the screen, 200 rasterlines and only while drawing between the left and right borders. It does not cache bitmap data; it reads a byte of bitmap data and output those 8 bits as pixels before it fetches the next byte of bitmap data.
It also needs to combine the pixel data with the color data that comes from the screen matrix. The same color data is used for 8 consecutive rasterlines, one typical text row, or all 8 rows in an 8x8 cell. So, it grabs one screen matrix byte immediately before it grabs one bitmap byte. It then combines and outputs them together, and repeats this across the entire bitmapped rasterline. However, it only needs to grab the screen matrix bytes from RAM on the first (the zeroeth) rasterline of a row of cells, because its the same screen matrix color bytes for all 8 rows. It grabs the screen matrix bytes, interleaved with the bitmap bytes, on the zeroth rasterline of a row of 8 cells, and caches them in 40 internal hidden registers. Then on the following 7 rasterlines it can skip fetching the screen matrix bytes from RAM because it's got them cached and ready to go.
The problem—everyone who knows something about the C64 has already heard this 1000 times—is that on that zeroth row, when it needs to grab a screen matrix byte then a bitmap byte, then a screen matrix byte then a bitmap byte, and so on across the screen, it needs to grab the pair of bytes within a single clock cycle. Typically the VIC-II and the CPU share a clock cycle. The VIC-II can access memory when the clock is low, the CPU can access memory when the clock is high. But during this zeroth raster of a new row of cells, the VIC-II must fetch 2 bytes per clock cycle. How does it do that? It steals those cycles from the CPU. And that's why that rasterline is called a badline.
But now we shall look at precisely how it accomplishes this trick that we've all heard about. We'll look at the timing diagram again, but now, using this key to the meaning of the symbols, we'll see exactly what everything is doing on every cycle of a badline.
Symbol | Definition | Notes |
---|---|---|
ø0 | Phi 0 | The main system clock signal |
IRQ | Interrupt Request | The VIC-II drives the IRQ line as shown when raster interrupts are configured appropriately. |
BA | Bus Access | Connects to the CPU's RDY line, which when asserted puts the CPU in a wait state. |
AEC | Address Enable Control | Connected to the CPU's AEC line, which when asserted puts the CPU's address bus, data bus and R/W lines in a high impedance state. |
VIC | VIC-II | Access types being made by the VIC-II |
6510 | 6510 CPU | Access types being made by the 6510 CPU |
c | Color | Access to memory for screen matrix data (and simultaneously to static color RAM) |
g | Graphics | Access to memory for graphical bitmap data |
s | Sprite | Access to memory for sprite graphic data |
r | Refresh | Dynamic RAM refresh cycle |
i | Idle | No work performed, idle time is passed |
0-7 | Sprite Pointer | Access to memory for the sprite pointer corresponding to the number |
x | CPU Read/Write | CPU performs any standard operation |
X | CPU Write-only | CPU performs any outstanding write operations, but waits on the first read operation. |
Here's the timing diagram again:
6569 (PAL), Bad Line, no sprites.
The top row shows us the Cycle # within the rasterline. In this diagram it starts at 63, the final cycle of the previous raster line. Then it shows the full 1 through 63 of this rasterline, then shows 1 again, the start of the next rasterline. This padding is just to show a bit of context for this rasterline relative to the ones before and after it.
The next row shows the ø0 clock signal. This is the most consistent signal, keeping steady time regardless of what else is happening. Note though, that the cycle number is above the low phase of ø0. A clock cycle, by number, begins in the low phase. The low phase is typically when the VIC-II takes its turn, then the CPU takes its turn on the second phase, the high phase.
The IRQ line that goes to the CPU's IRQ line can be driven by many different sources. But in this diagram we're seeing when precisely the VIC-II pulls the line low for a raster interrupt, when this raster counter matches the raster interrupt register. There isn't much to see here, except that the IRQ line is pulled low exactly in sync with the ø0 going low on cycle 1 of the triggering rasterline. This will become more important when we look at the FLI timing routine.
We'll skip the BA line for a moment, and look instead at the AEC line and the corresponding VIC and 6510 accesses during the start of the rasterline. The AEC line is being driven up and down, by the VIC-II, in phase with ø0. When AEC is low, the VIC-II makes an access. When AEC is high, the 6510 makes an access.
Sprite Pointer and Data Accesses
In the very first cycle of the raster, the VIC-II starts with an access to Sprite 3's pointer. That might seem a bit weird at first, but there isn't enough time to grab all the sprite data during the drawing of the left border only. Therefore, sprites are accessed, in order, but 0, 1 and 2 are fetched in the right border at the end of the previous rasterline, then 3, 4, 5, 6 and 7 are fetched during the left border at the start of the current rasterline. Now, in this particular example, sprites are disabled in the VIC-II's registers. So although the VIC-II grabs the sprite pointers during the low clock phase, it doesn't need to fetch the sprite's data. It would be a different story if the sprite were turned on; it would use the following three half-cycles (stealing cycles from the CPU again) to grab this sprite's data. This is why sprites are only 3 bytes wide, by the way. In addition to the bitmap data, the time available in the border is enough to fetch up to 8 sprites, at 3 bytes per sprite per rasterline. But we'll leave sprites disabled to make things simpler.
Thus, AEC is going up and down, VIC-II grabs the pointer for sprite 3, then the CPU makes a standard access, then the VIC-II goes idle for a cycle. That time has to be reserved for if the sprite were on. But it's not on, so the VIC-II simply goes idle for that cycle, then the CPU makes its standard access. This pattern repeats for sprites 3, 4, 5, 6 and 7.
Dynamic RAM Refreshes
The VIC-II spends its next 5 half-cycles making Dynamic RAM refresh calls. We learned about how the refreshes are performed in Part 1 of this post. There are 200 bitmapped lines and the VIC-II makes 5 refresh calls per line. We're not looking at the timing for the rasterlines that occur in the top and bottom borders, but my educated guess is that the VIC-II makes refresh calls on those lines too.
NTSC
262 lines per frame, times 5 refreshes per line, times 60 frames per second.
262 x 5 x 60 = 78,600 DRAM refreshes per second.
312 lines per frame, times 5 refreshes per line, times 50 frames per second.
312 x 5 x 50 = 78,000 DRAM refreshes per second.
Video Standard | Lines | Refreshes/Line | Refreshes/Frame | Frames/Second | Refreshes/Second |
---|---|---|---|---|---|
NTSC | 262 | 5 | 1,310 | 60 | 78,600 |
PAL | 312 | 5 | 1,560 | 50 | 78,000 |
According to the spec, the RAM can be fully refreshed in only 128 refresh cycles, but a memory matrix row cannot be left unrefreshed for longer than 2ms, or it will lose its data. That means every row must be refreshed at least 500 times per second. And there are 128 rows to refresh, so the RAM must receive a minimum of 500 x 128, or 64,000 refreshes per second. The VIC-II in both NTSC and PAL is doing in the range of 78,000 refreshes per second. So, breathe a deep sign of relief, folks, the math works out! Our C64's Dynamic RAM will not lose its information.
By the way, if you review how a RAM refresh cycle works, and then you consider that this process must be undergone 64,000 times per second, just to maintain the status quo, to prevent the RAM from draining and losing its contents… It's enough to boggle the mind, thinking about the rate and degree of precision work being performed even when the machine is just sitting there idle. All the more when you consider that the C64 is an inexpensive home computer from 40 years ago.
Badline in Action
Something else happens midway through the 5 refresh cycles. Concurrently with the 2nd refresh cycle, the VIC-II pulls the BA line low. The BA line is the VIC-II's Bus Access line, which is connected to the 6510's RDY (Ready) line. There are actually two conditions that will pull the 6510's RDY line low: The DMA line on the expansion port gets pulled low, or the VIC-II's BA line goes low. The DMA line is used to put the CPU in a wait state. The DMA line also forces the CPU's AEC line low so while it's waiting its R/W, address bus and data bus lines are put in HighZ, so they don't have any influence over the buses. This allows a device on the expansion port, such as an REU, to perform direct memory accesses (DMA) to the main RAM.
Well, the VIC-II's BA line does essentially the same thing. It forces the CPU into a wait state so the VIC-II can make, in essence, DMA accesses to main memory. Very cool.
As was discussed in Part 1, the VIC-II has no idea what is going on inside the CPU. It doesn't know what operation it's executing, nor how long that operation will take to complete. The VIC-II pulls BA low, which pulls the CPU's RDY line low, which puts the CPU into an unknown state for up to 3 cycles. When the RDY line goes low, the CPU will carry out any pending write cycles from the current operation. It will wait before it begins its very next read cycle, but the VIC-II cannot know what the CPU is doing. Therefore, it pulls the BA line low and waits for 3 full cycles. At that point it knows for certain that the CPU is no longer executing.
Now, if we look at it in the timing diagram, (here it is again so you don't have to scroll up), by pulling BA low at the start of the 2nd refresh cycle, that gives the CPU one cycle after the 2nd refresh, one cycle after the 3rd refresh, one cycle after the 4th refresh. The CPU is now guaranteed to be waiting. The VIC-II makes its 5th refresh cycle, on its turn, but on the CPU's turn following the 5th DRAM refresh cycle, BOOM, the VIC-II grabs the very first screen matrix byte.
6569 (PAL), Bad Line, no sprites.
Not only has the BA line been low for 3 full CPU cycles, but on that first cycle that the VIC-II steals from the CPU it is also the first cycle in which it doesn't raise the AEC line. It holds the AEC line (and the BA line) low for the next 40 cycles. During those 40 cycles it does the following: fetch a Screen Matrix byte (cache it), fetch a Bitmap Graphics byte, render them both, and repeat.
As soon as it's done stealing the last cycle, grabbing the last screen matrix byte (a "c" access in the diagram), it raises BA. Then it grabs one final bitmap graphics byte (a "g" access in the diagram), during the standard low phase of the clock, before the CPU continues its normal operations during the high phase of that cycle.
So, cycle 54 is the last one stolen and cycle 55 is totally normal. Cycle 15 is the first cycle guaranteed to be stolen, so 15 to 54 (inclusive) is exactly 40 stolen cycles. But, it's not quite that clean, because cycles 12, 13 and 14 can only be used by the CPU if the CPU just happens to be finishing up some write cycles. Which means, on any given badline, the CPU is guaranteed to lose 40 cycles, but it might lose 41, or maybe 42, or maybe even 43 cycles. Under typical circumstances, ones which are not timing critical, it's not such a big deal precisely how many cycles are lost. For FLI timing though, as we will see, it is critically important to know exactly what the VIC-II is doing when a given operation of our program running on the CPU is executing. But we'll come to that. There is still more to cover first.
Rendering the Data as HiRes Bitmap
What happens when it's not a badline? Remember, 8 consecutive rasterlines share the same screen matrix byte for a given cell. So when the raster counter is on a rasterline that is the zeroth of those 8 consecutive lines, it's a badline. It pauses the CPU and fetches the 40 screen matrix bytes. But it also caches them, so that the following 7 rasterlines don't need to be badlines.
Assuming that sprites remain disabled, then for 7 consecutive rasterlines there is no badline. The BA line doesn't get pulled low. The VIC-II accesses the bitmap graphics bytes from memory on its own turn, (when the clock phase is low) and uses the cached screen matrix bytes for color.
This is what forces a HiRes 8x8 pixel cell to share the same 2 colors. It grabs a bitmap byte, then it renders the bits, from highest bit on the left to lowest bit on the right. If a bit is 1 it takes the color of the high nybble from the screen matrix byte, and if the bit is 0 it takes the color from the low nybble of the screen matrix byte.
Let's prove all this to ourselves! I love using BASIC to demonstrate to myself something that I've read. Because, you can read something and it could be mistaken. But the test against the real hardware (or a good emulator) is the proof in the pudding.
On the left we have a simple BASIC program. The first line turns on HiRes bitmap mode, but it leaves Bank 3 as the default VIC-II bank, and it leaves screen matrix memory at $0400 (1024), and the bitmap is being displayed out of the lower half of the bank. The top of the screen thus is rendering the contents of zero page, and stack, and the OS's workspace memory, and screen memory, etc. The lower half the screen should be rendering the 4K character ROM... and so it appears to be, when you look at the output in the image above on the right.
Next, the program writes a 16 (that's $10 in hexadecimal) into screen memory. This will be used as a the color byte, defining the high nybble as white, and the low nybble as black.
Lastly, the program overwrites the last byte of zero page ($ff or 255) with successive values: 0, 1, 2, 4, 8, 16, 32, 64 and 128. These even binary numbers move the "1" bit in that bitmap byte through the positions from the lowest bit up to the highest bit. It delays for a brief period after each bit position so you can actually see it moving at a reasonable pace.
In the image above on the right, I've highlighted the area to pay attention to with a yellow outline. You can see the the background of that entire cell is black. And you can see a single white pixel. That pixel gradually marches from the right to the left, as the value we poke into address 255 gets bigger. This proves that it is indeed the high nybble of the screen matrix byte that defines the color of the on-bit, and bit 7 of the bitmap byte renders leftmost on the screen, and bit 0 of the bitmap byte renders rightmost on screen.
Don't believe me? Type in the program for yourself, and try it out. Play around some with the colors by changing the number being written to screen memory (at 1024+31).
Here's another cool thing, because the ordinary screen matrix, which had text on it, is suddenly reinterpreted as color data, you can see the shape of the text from the left image in the shape of the color blocks in the right image. What about all the rest of the screen that's red and black? When Screen Matrix was showing us text, everything else was filled up with the PETSCII space character. That's decimal 32, or hexadecimal $20. That's $2 for the foreground color (red) and $0 for the background color (black.) Bingo.
Changing Modes: Multi-Color Bitmap
Here's what's great. Everything about the timing and the data accesses are exactly the same for the other native video modes as they are for HiRes Bitmap.
The next most simple video mode, in my personal estimation, is Multi-Color Bitmap mode. There is one bit that controls whether the VIC-II is in bitmap mode or not: Bit 5 of Register 17 ($11). To flip this bit on, for example, we can do the following:
LDA $D011 ORA #%00100000 STA $D011
Bitmap mode determines how the addresses are composed to fetch the bitmap pattern data. Also while in bitmap mode, Screen Matrix data is still fetched and cached, but is used as color data. In addition to the bitmap mode bit, another bit determines how to interpret the bitmap data and apply the color data. This is the Multi-Color mode bit: Bit 4 of Register 22 ($16). To flip this bit on, and switch from HiRes to Multi-Color mode, we can do the following:
LDA $D016 ORA #%00010000 STA $D016
The Multi-Color mode bit changes how the VIC-II interprets the data, but in bitmap mode (whether HiRes or Multi-Color) the data fetches not only follow the same pattern (as they do for all modes) but even the addresses whence the data is retrieved is identical.
In Multi-Color mode, two bits in a row are combined to select a color source that is used for both pixels, creating a double-wide pixel with a wider range of available color options.
Bit Pair | Value | Color Source | Note |
---|---|---|---|
00 | 0 | Common Background Color | VIC-II Register 33: $D021 |
01 | 1 | Screen Matrix Cell's Upper Nybble | HiRes Mode's Foreground Color |
10 | 2 | Screen Matrix Cell's Lower Nybble | HiRes Mode's Background Color |
11 | 3 | Static Color RAM's Lower Nybble | It's a 4-bit RAM, so only the Lower Nybble is available. |
In the timing diagram there are the "c" requests. The "c" requests are when the VIC-II is stealing a cycle from the CPU to request the screen matrix byte from main memory. That byte contains two 4-bit color values. During the "c" request the VIC-II is simultaneously reading from color matrix memory. Color matrix memory is from the static color RAM chip, which contains only a single 4-bit color value, and is read over the private 4-bit color bus that connects it directly to the VIC-II.
In HiRes bitmap mode the value from the color matrix memory is simply not integrated into the output video signal, whereas in Multi-Color bitmap mode, it is.
Changing Modes: HiRes Character Mode
To continue the illustration of how the VIC-II's data fetches follow precisely the same pattern, we'll switch out of bitmap mode and into character mode. Two do that, we simply unset the bitmap mode bit: Bit 5 of Register 17 ($11). To flip this bit off, we can do the following:
LDA $D011 AND #%11011111 STA $D011
And, of course, Character mode has a Multi-Color and a HiRes mode too, controlled by the same Multi-Color Mode bit as for Bitmap mode: Bit 4 of Register 22 ($16). To flip this bit off, and switch to the standard HiRes character mode, we can do the following:
LDA $D016 AND #%11101111 STA $D016
Now what happens? The timing diagram is exactly the same:
6569 (PAL), Bad Line, no sprites.
However, the address composition for the bitmap pattern data is different. Before each "g" graphics request is a "c" (Color, aka Screen Matrix) request. But in character mode, the screen matrix byte—if this is a badline, it's the byte that was just fetched, but if it this is a non-badline then it's retrieved from cache—is used as an index into the character set bitmap.
How exactly it is used to index the character set is out of scope for this post, but in short, in composing the address whence to retrieve the byte of bitmap data, the VIC-II includes the bits of the screen matrix byte as part of the address.
The result is that after the "g" fetch, it has a pattern of bitmap bits to render, except this time, the screen matrix byte was used for addressing the bitmap byte. Therefore, it doesn't contain any color data. We're in HiRes character mode, each bit must render one pixel, so where do the colors come from? An on-bit takes the 4-bit color retrieved from color matrix memory, but what about the off-bit? The only option is to pull it from one of the VIC-II's background color registers. It displays Background #0, VIC-II register 33 ($D021).
It may seem unfortunately limited that in Character mode you can only have a single background color for the whole screen. But, as you can see in the timing diagram, the VIC-II is already robbing the CPU to find the time needed to fetch the screen matrix bytes, but the screen matrix bytes are used for composing the bitmap data fetch address. There is simply no time available for the VIC-II to source additional color information.
Changing Modes: Multi-Color Character Mode
Just as in bitmap mode, the change from HiRes to Multi-Color involves flipping on the Multi-Color mode bit, the change to Multi-Color mode in Character mode involves flipping on that same Multi-Color mode bit. Bit 4 of Register 22 ($16). To flip this bit on, we can do the following:
LDA $D016 ORA #%00010000 STA $D016
Once again, the pattern of data accesses and timing is precisely the same. And even the address composition for where to fetch bitmap bytes is the same for all Character modes. The only difference is in how the data is interpreted. The "c" request gets the Screen Matrix byte, the Screen Matrix byte is immediately used to compose the address to get the bitmap data in the "g" request. But now, just as in Multi-Color bitmap mode, two bits in a row combine to select the source of color. We saw in HiRes Character Mode though, the sources for color are limited. In this case, 3 different "background" colors are selectable, three register values that are global to the whole screen.
For Multi-Color Character mode, we thus have the following table:
Bit Pair | Value | Color Source | Note |
---|---|---|---|
00 | 0 | Common Background Color #0 | VIC-II Register 33: $D021 |
01 | 1 | Common Background Color #1 | VIC-II Register 34: $D022 |
10 | 2 | Common Background Color #2 | VIC-II Register 35: $D023 |
11 | 3 | Static Color RAM's Low "3" bits | The high bit of the color matrix nybble is special. |
Just when we thought everything was about to make sense, the VIC-II throws a monkey wrench into the cogs. The selection of the 3 background colors is as you would expect. However, the nybble pulled from Color matrix memory has one extra twist to its interpretation.
If the high bit (bit 3) of the Color matrix nybble is low, the bitmap data for this whole cell gets interpreted as HiRes, one bit per pixel. In which case, the off (0) pixels get their color from Background #0 (Register 33, $D021), and the on (1) pixels get their color from only the 3 least significant bits of Color matrix memory. Thus, the character appears in high resolution, but it can only have one of 8 possible foreground colors. The advantage of this extra interpretational hiccup is that you can mix Multi-Color (low-resolution, double-wide pixels) with high resolution text characters in the same native video mode.
I'm not a games programmer, but I assume this is useful for games. You can define your graphical tiles in a custom character set stored in main RAM, which you can design with mostly double-wide pixels but greater color flexibility, and you can design some of the characters (perhaps just uppercase letters and numbers, and a few essential symbols) in high resolution to be used in various contexts, like speech bubbles or status bars.
Final Thoughts. Ready for Part 3.
Extended color mode also exists, but I decided not to talk about it, for brevity.
This post is now already 13,000 words long! So I am going to wrap this up here, but we are now primed and ready to attack FLI mode, with all the background information we need.
I have repeatedly used the word native to describe various VIC-II video modes. But what do I really mean when I call a mode native? It means, you set the registers in the VIC-II to configure the appropriate mode, and then the VIC-II goes off and does everything else necessary to produce the image on the screen. Usually the CPU, running the code for the game or other program, manipulates the data. In other words, the CPU changes what values are stored in main memory and color matrix memory, and the VIC-II simply does what its hardware is coded to do, and reads and renders the data 50 or 60 times a second.4
The VIC-II's rendering is, for the most part, on-the-fly. What would happen if we used the CPU to change the value of a register, mid-frame? Or between frames? The answer is that the VIC-II would simply fetch the data, and render the data, according to how its registers are configured in that instant. The result, if it's carefully planned, is that, by manipulating the VIC-II with realtime interventions from the CPU, what gets displayed on the screen can be more than what the VIC-II natively supports.
Let's take a very simple example. We saw that in HiRes Character mode, each character (each 8x8 cell) gets one color for the all of the on-bits from the nybble pulled from color matrix memory. But all of the off-bits across every cell, they have to share a single color that is common to the whole screen: the background #0 color, register 33 ($21 found at $D021). Thus, the native video mode supports just one background color for the whole screen.
However, if the CPU knew precisely when the VIC-II had finished drawing the top half the screen, it could change the value stored in the background #0 color register. The VIC-II would then proceed, completely unawares, and draw the remainder of the bottom half of the screen using the new background #0 color. The CPU then needs to know when the VIC-II is finished the full frame, and it sets the background #0 color register back to the value for the top half of the screen. The CPU must intervene twice per frame to make this effect appear stable. What the user sees on screen is a unique, non-native video mode. In other words, he or she sees something that the VIC-II cannot, according to the specification, actually do!
Games programmers and demo coders, of course, make use of non-native video modes all the time. It was always the intention of the designers of the VIC-II that programs running on the CPU could and would change the VIC-II's registers in realtime to produce novel video effects. One very common example of this is sprite multi-plexing. By carefully timed intervention from the CPU, the VIC-II can be made to display, on screen at the same time, more than the 8 sprites natively supported by the hardware.
FLI mode, as we will see in detail in Part 3, is a non-native video mode, which requires an extraordinary amount of CPU time and also a high degree of CPU timing precision. But, the results are worth it.
- It's a bit complicated because the VIC-II needs to be able to perform the RAM refresh cycles on all the memory, regardless of which BANK is selected by CIA 2.
-
Bitmapping can be done on a VIC-20 by creating a custom character set in memory. The
C64 can do this too, but the C64 also has a "true" hires bitmap mode which the VIC-20 does
not. You can read more about VIC-20 bitmapping here:
https://www.atarimagazines.com - By the way, there is no other sensible way for it to be connected, so this wasn't just some engineer's arbitrary decision.
- A minor exception to this rule, the CPU usually changes the positions of sprites in realtime by writing to the VIC-II's registers.
Do you like what you see?
You've just read one of my high-quality, long-form, weblog posts, for free! First, thank you for your interest, it makes producing this content feel worthwhile. I love to hear your input and feedback in the forums below. And I do my best to answer every question.
I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider purchasing one of the items I am currently offering or making a small donation, to help me continue to bring you updates, in-depth technical discussions and programming reference. Your generous support is greatly appreciated.
Greg Naçu — C64OS.com