12

After sporadically reading about the C64 and tinkering here and there for over 32 years I finally decided to get to know my C64 memory layout properly. In doing so I have a question about the VIC-II chip:

Why is $D000 - $D03F "copied" to each of $D040 - $D07F, $D080 - $D0BF, ..., $D3C0 - $D3FF ?

According to this C64 Wiki page each one of those 64 byte address areas is the "... same as $D000-$D03F." For years I've only understood the VIC2 registers in terms of $D000 - $D03F.

For a moment I thought I might be able to tell the VIC-II which of those 64 byte memory areas to work with (*), but after doing some POKE-ing and PEEK-ing I found out that the Wiki article is meant to be taken literally. For some reason the C64 duplicates $D000 - $D03F across all those extra 64 byte address areas. Why? I'm guessing some kind of simple hardware hack for cost/design reasons?

Just imagine if (*) were true - the potential for use with raster interrupts and sprites could have been awesome because 128 lots of sprite position/other data could have been stored in those areas and 16 raster line interrupts could have quickly switched between them !

6
  • 9
    If you don't decode all address lines, then there will be aliases for any address in the not-entirely-decoded space. In your case, it looks like the address bits with value 0080 and 0040 are ignored when the top two digits are D0. This presumably saved some gates. (I know nothing about Commodore machines, but this is pretty general) Nov 2, 2020 at 15:00
  • Incomplete address decoding ... not all address lines are evaluated to find out if the VIC registers need to be addressed. The "don't care" address lines produce the "duplicates".
    – dirkt
    Nov 2, 2020 at 15:01
  • You are kind of mixing up the VIC-II ability to switch among the 16KiB bank of RAM it is using with an imaginary ability for the C64 to switch among many instances of VIC-II registers. Those extra register sets can't exist without multiple VIC-II chips existing.
    – Brian H
    Nov 2, 2020 at 15:22
  • 1
    The VIC-II chip doesn't store sprite positions in "RAM" as such; instead, it has for each register a set of latches that will latch a value from the CPU if accessed when R/W is low, and circuitry to feed the contents of the latches to the CPU if accessed when R/W is high. The eight bits of each latch also feeds into a position-compare circuit.which is used to trigger the sprite. To have 16 different sprite positions at $D000, D040, etc. would have required making 16 duplicates of much of the circuitry required for such purposes.
    – supercat
    Nov 2, 2020 at 15:47
  • 1
    @Pixel: The sprite 0 X position register is a set of latches that captures data from the CPU any time a memory cycle is performed, the VIC-II receives a signal saying "this address is for you", the lower 6 address bits are all clear, and the read/write signal from the CPU is low. The VIC-II receives the aforementioned "this address is for you" signal for all addresses in the range $D000-$D3FF.
    – supercat
    Nov 2, 2020 at 20:27

2 Answers 2

28

It's called incomplete decoding.

Peripheral registers in the C-64 (and generally in the 6800/6502 world) are memory mapped, meaning that RAM, ROM, and all the peripheral chips (VIC, SID, and the two CIAs) share one big 64K / 16 bit address space, and a register access on one of these looks just like a memory access to software.* Some kind of circuitry is needed to look at the address bus and figure out which IC should be selected when a given address is accessed. This circuitry is called the address decoder, and the more fine-grained it divides memory, the more complicated and thus expensive it becomes.

To uniquely decode one 64 byte area out of a 64 KiB address range, the address decoder must evaluate the first 10 bits of the address, which, using the technology of 1982, would have cost more than Commodore was willing to pay. As the entire memory range from 0xd000 to 0xdfff had been designated as I/O anyway, a compromise was made to evaluate just the first 6 bits of the address in that region, thus assigning

  • 0xd000 to 0xd3ff to the VIC,
  • 0xd400 to 0xd7ff to the SID and
  • 0xd800 to 0xdbff to the colour RAM, while
  • subdividing 0xdc00 to 0xdfff further into 4 pages with an additional decoder

The VIC, however, only has 47 registers, so it needs to look only at the bottom 6 bits of the address bus, and has only 6 address input pins. That means that no-one looks at the 7th to 10th bit of the address -- they are now undecoded. The result is that the VIC appears 16 times in memory, once for each combination of these four bits.

Incomplete decoding was common practice when your address space is larger than your physical memory. When you have only 16K of RAM and 8K of ROM, who cares if you're giving a whole K of the address space to a handful of registers. It's a bit weird to see it in the C-64's cramped address space, but then again, you need bank switching anyway to get to the full 64K.

Also, it's important to keep in mind that the VIC really only has 47 registers, and that these registers are on the chip, not in memory. So even if you could choose where the VIC appears (your (*)), there'd still be only one set of registers.


* as opposed to using a separate I/O address space, as the 8080 and its descendants do

8
  • If I'm reading the schematic properly, the VIC-II has twelve address pins, though I'm not sure what distinguishes between character ROM fetches which use A8-A11 as the page address, and RAM accesses which use A0-A5 for page address during the first part of a cycle and then the LSB of the address during the second part. The design seems somewhat curious, since VIC-II address pins A10-A11 could be eliminated if the kernel copied character data to RAM on startup and the VIC-II chip always used a RAM character set.
    – supercat
    Nov 2, 2020 at 20:46
  • @supercat You're right, of course. I should have been more precise: six address pins that can operate as inputs. A0-A5 are bidirectional; A6-A11 are output only (with a high impedance state). See p. 16 of the datasheet here: archive.6502.org/datasheets/mos_6567_vic_ii_preliminary.pdf Nov 3, 2020 at 0:35
  • 1
    @MichaelGraf I now also understand the different chips have different "views" of memory, e.g. the PLA switches out $1000-$1FFF for the VIC2 so it only ever sees Character ROM in those places (so no custom data can reside there from the VIC perspective). But we can still use this area for other things (maybe machine code) because the CPU view of $1000-$1FFF is mapped to RAM not ROM. Same for $9000-$9FFF. Thanks for your thorough answer, learned a lot.
    – user16295
    Nov 3, 2020 at 9:07
  • I suppose the upper bound for colour RAM is 0xDBFF, not 0xDCFF, and the lower bound of the next 4 ages 0xDC00, not 0xDD00?
    – jcaron
    Nov 3, 2020 at 17:37
  • I find it somewhat interesting to compare the design of the C64's memory mapping with that of the 64K-expanded Apple II+ which never tries to make all 64K of RAM expandable at once, but has a 4K chunk of address space $D000-$DFFF that can be switched between two regions, meaning I/O can always remain banked at $C000-$CFFF. The Apple concentrates all of its motherboard I/O in an almost-fully-decoded $C000-$C07F, leaving $C080-$CFFF available for expansion cards, but the C64 only leaves $CE00-$CFFF for catridge-port I/O, thus eliminating the need to be efficient with address space.
    – supercat
    Nov 3, 2020 at 17:55
7

Only top 6 bits are used for address decoding the chip select line for this chip, most likely for simplicity. This covers the range from 0xD000 to 0xD3FF, as it will have the bit pattern 1101:00xx:xxxx:xxxx. Any access in this area selects the same chip that is being talked to.

But as the chip itself uses bottom 6 bits of address from 0x00 to 0x3F to select a certain register, the bit pattern is 1101:00xx:xxrr:rrrr so the x bits are don't care and thus the 0x40 chip registers have many aliases in the 0x400 address space reserved for it.

1
  • If the C64 didn't use a giant PLA to accomplish many of its tasks, the most logical way to handle its memory map would be to use some logic (possibly a smaller PLA) to identify I/O addresses in the 0xD000-0xDFFF range, use half of a 74LS139 to split that range into four chunks of 1024 bytes each, and then use the other half to split the top chunk into four smaller chunks of 256 bytes each. Given the choice to use the giant PLA, finer decoding for everything but the 1K of color RAM would have cost nothing, but once initial development used the coarser memory map, there was no reason to change.
    – supercat
    Nov 3, 2020 at 20:10

You must log in to answer this question.