Bitplanes are now mostly working, although we are still tracking down some remaining bugs with V400 mode, and some bitplanes being shifted by 16 pixels.
The next challenge is to implement the VIC-III Display Address Translator (DAT). This is a little piece of logic in the VIC-III that makes it easier to modify pixels in the bitplanes. Basically you provide it an X and Y coordinate, and it maps the corresponding bytes of the bitplanes to $D040-$D047, so that they can be manipulated, without having to do any crazy arithmetic to find them. This makes the bitplanes on the C65 less cumbersome than they would have otherwise been. This is how the C65 specifications document describes the DAT:
Display Address Translator (DAT)
The C4567R6 contains a special piece of hardware, known as the
Display Address Translator, or DAT, which allows the programmer to
access the bitplanes directly. In the old VIC configuration, the
bitmap was organized as 25 rows of 40 stacks of 8 sequential bytes.
This is great for displaying 8 x 8 characters, but difficult for
The DAT overcomes the original burden by allowing the programmer
to specify the (X,Y) location of the byte of bitplane memory to be
read, modified, or written. This is done by writing the (X,Y)
coordinates to the BPX and BPY register, respectively. The user can
then read, modify, or write the specified location by reading,
modifying, or writing one of the eight Bitplane registers. There is
one bitplane register for each bitplane.
The DAT automatically determines whether to use 320 or 640 pixel
mode, and whether to use 200 or 400 line mode. It will also use the
areas specified for the bitplanes, using the Bitplane Address
Anyway, that is the purpose of the DAT, how it is implemented is rather interesting. The C65 specifications document tells us, somewhat cryptically:
DAT -- Display Address Translation
Display Address Translation, or DAT fetches, are not actually
DMA-type accesses, but rather CPU address redirections to RAM. In this
case, the unmultiplexed address bus is totally separated from the
multiplexed address bus.
That is, it suggests that the VIC-III somehow causes the target address to be re-written, when the CPU accesses $D040-$D047. To better understand what is going on here, one has to understand how the VIC-III is the gate-keeper of all RAM accesses on the C65. While the 4502 is an 8-bit processor, the VIC-III is a partly 16-bit graphics chip. More specifically, it has two 8-bit wide data buses, D and E, each of which is normally attached to 64KB of RAM. In order to have enough bandwidth for the bitplane modes, it can simultaneously fetch from both the D and E buses. Because it uses DRAM, and also looks after the CAS/RAS selection, the whole process is somewhat complicated.
However, as the DAT fetch description above describes, it is possible for the VIC-III to completely substitute the address that the CPU provides, for one that it has provided internally. This indeed works very nicely to allow the DAT to be a very simple piece of hardware in the VIC-III.
So, now the question is how to implement the DAT on the MEGA65. We don't have this bus arrangement on the MEGA65: The CPU has direct access to memory. Thus we can't implement the DAT solely in the VIC-III. The CPU itself does have memory address re-writing capability, however, so we could implement it there.
The question then becomes whether to calculate the bitplane addresses and offsets in the VIC-III, and export them to the CPU, or to calculate them in the CPU, which means the CPU has to sniff VIC-III/IV register writes, in order to know what mode we are in, and where the bitplanes are located in memory.
It is probably simpler and safer to export the bitplane start addresses and video mode flags to the CPU, and have it do the address computation on those. This requires 8 bitplanes x 3 address bits per bitplane = 24 bits of address information, plus the H640 and V400 flags. In reality, we need 2x24 bits of address, because the 400 pixel high modes are interlaced, and pull data from two separate sources in that mode. In both cases, the CPU then needs to calculate INT(Y/8)*(320/8*8)+ (Y and 7)+INT(X/8)*8 + bitplane start address for 320x200 bitplanes. For the V400 modes with interlace, we need to use Y/2 instead of Y, and to use the bottom bit of Y to pick the odd or even bitplane address. For the H640 modes, we simply change the 320/8*8 to 640/8*8.
I could have written those as simply 320 or 640 instead, however, I wanted to make transparent that the addresses are based on C64-style bitmap addressing, where each 8x8 character-sized cell is formed from 8 bytes. Thus the pixel at (0,0) (measured from the top-left of screen) will be in byte 0, while the pixel at (8,0) will be in byte 8, because after every 8 pixels in the X direction, you have to skip 8 bytes, not one, because you are moving to the next character cell. If that sounds confusing, search for a tutorial on the C64 bitmap mode.
So, taking all off that into account, this means that the CPU needs to look at the DAT X and Y positions, H640 and V400, and from those, it can calculate the offset into a bitplane, and whether it is the odd or even bitplane as the source. We really want to avoid multiplication by big numbers in the CPU, so we can instead calculate the bitplane offset as:
offset = INT(Y/8)*256 + INT(Y/8)*64+ (Y and 7) + INT(X/8)*8
While we still have some divides and multiplies here, they are all powers of two, so can be done by simply shifting bits left and right as required.
As is often the case, by explaining something, it is possible to find improvements. Whereas I had previously figured I would need to sniff the DAT X and Y positions, I can instead calculate those in the VIC-IV, and export only the bitplane offset and odd/even flag to the CPU, together with the bitplane addresses. Then the CPU will know all that it needs to, in order to redirect memory accesses to $D040-$D047 to the appropriate place in the bitplanes.
Now that I have thought out how I can implement the DAT, I have added it to my list of tasks.