From character mode to Bitmap

Original picture by Uka

Original picture by Uka


The graphic chip (VIC II) on the Commodore 64 can work in two modes, either ‘characters’ or ‘bitmap’.

Most of games and a vast majority of basic programs run in the first mode. It is preferred when performance is required with a lot of graphisms that must be updated on each frame refresh. Unfortunately, this mode is based on 8×8 tiles and not adapted to complex pictures (e.g cover pictures, photos, …). Moreover, it becomes tricky to draw a single pixel at a given location or simply draw a line…

When booting the C64, the basic editor is displayed in this mode. You can read and type characters in a 40×25 matrix where each character is subdivided in a 8×8 pixels grid. The VIC II need 3 memory areas to draw a screen in this mode :

  1. the screen ram (default $0400-$07FF) that stores the characters (or ’tiles’) codes
  2. the color ram ($D800-$DBFF) that stores the foreground color of each character
  3. the character rom ($D000-$DFFF) that defines characters pixels (kind of Font)

All we need to know for Maze Master is how the character data are stored in ROM.

Figure 1. Character ‘A’

Screen RAM

The ‘A’ character above is located at column 6 (starting from 0) and row 3.

We know that the screen ram is used by VIC II to know which character to draw at each 40×25 location. For instance, memory address $0400 stores the char code of character at column 0, row 0. Second char at column 1, row 0 is located at address $0401 (1025 in decimal). Then, the ‘A’ character above is referenced in screen ram at address $0400+6+3*40=$047E.


If you try to modify theses addresses in basic (in VICE emulator or real C64), you can display new characters at any 40×25 location :


Figure 2. Poking in Screen RAM

You notice that this short program display characters with codes 0..39 in the first row of display. So char ‘A’ is the character with identifier 1 (starting from 0) but also the second character defined in C64 ROM ‘FONT’.

Character ROM

This ‘FONT’ or ‘character generator’ is an area of the C64 memory used by VIC II to draw pixels of an individual character. It is stored in a chip on the motherboard :


Mos 901225-01 Character ROM Chip

If you dear old C64 is starting to burn out, this chip can return bad values to VIC II and mess up your display…


But let’s get back on track…

All we need to know for our Maze Master case study is how characters are defined in this ROM.

If you look back at figure 1, you can see that a character is defined in an 8×8 dots matrix. Each row of this matrix is encoded in 1 byte. Thus one character takes up 8 bytes and an entire charset requires 2048 bytes (256 chars * 8 bytes).

C64 Character rom is located at addresses $D000-$DFFF, thus defining two charsets of 256 chars each :

  • $D000-$D7FF stores uppercase & symbols charset


  • $D800-$DFFF stores uppercase & lowercase charset


So, if you’d just like reading pixels values for character ‘A’, you would read memory locations $D008-$D00F. But it is not as easy as it looks ! Let’s try out…


Figure 3. Reading character ROM

Ouch ! We expected to get $18, $3c, … and got null values…

Actually, C64 is using what is called a memory overlay :

What is read at address $D000 by the CPU (I/O area) is not necessarily what is read by VIC II (char ROM). Moreover, $D000-$DFFF is also mapped to RAM !! It’s getting a bit confusing but this complexity is also mandatory to make C64 so powerfull with only 64Kbytes of addressable memory…

The block of RAM or ROM visible at a given address is controlled by the register mapped to memory address $1.

If you look at this address content in basic, you get the default memory overlay :

print peek(1)


Here is the meaning of the first 3 bits of this register :

Bits #0-#2: Configuration for memory areas $A000-$BFFF, $D000-$DFFF and $E000-$FFFF.
%x00: RAM visible in all three areas.
%x01: RAM visible at $A000-$BFFF and $E000-$FFFF.
%x10: RAM visible at $A000-$BFFF; KERNAL ROM visible at $E000-$FFFF.
%x11: BASIC ROM visible at $A000-$BFFF; KERNAL ROM visible at $E000-$FFFF.
%0xx: Character ROM visible at $D000-$DFFF. (Except for the value %000, see above.)
%1xx: I/O area visible at $D000-$DFFF. (Except for the value %100, see above.)

So, in this default memory configuration, our basic program displayed the 8 first bytes of I/O area. This deals with horizontal and vertical positions of sprites 0..3 !

To display properly the character rom content, we must modify this overlay by clearing the third bit of this register. Let’s try to write value 51 to address $1:

Unhide charset memory

Figure 4. Unhide charset memory

Well done, we finally got the ROM values for character ‘A’ ($18=27, $3C=60, …).

But wait… what are those lines 10 and 50 needed for ?

Mmmm… here we tell C64 to temporary deactivate CIA1 timer to avoid using I/O area while we read from ROM.


The CIA (Complex Interface Adapter)

The CIA (which is an other custom CHIP of C64) can generate interrupts used by BASIC for various tasks (make cursor blink, scan keyboard, …) and need to read some I/O registers.

As the CPU cannot read both I/O and char rom in range $D000-$DFFF at the same time, C64 would freeze when such an interrupt occurs and I/O area is hidden by characters rom.

Line 10 deactivates CIA1 timer and line 50 reactivates this timer. We will write the same code in 6510 assembly when creating our custom charset in Maze Master !

I won’t discuss further more about charset set. There is a lot more to learn about it :

  • color character mode
  • color RAM
  • X/Y scrolling registers
  • VIC bank and custom charset in RAM…

But we have seen more than what is required to code the Maze Master display routines…

Bitmap RAM

When using Bitmap mode, VIC II is getting each pixel state of screen directly from a specific memory area. Thus, in order to display an entire screen, 320*200/8=8000 bytes of memory must be reserved for display.

As only 16Kbytes of memory can be addressed by VIC II at once, this means that only half of ‘graphic’ memory is now available for other graphic ressources (sprites).

This is not a severe issue for Maze Master since only 1280 bytes of sprites data are stored in the 16Kbyte cartridge.

But let’s enter Bitmap mode directly from BASIC :

POKE 53265,PEEK(53265)OR32



Figure 5. Entering BITMAP mode


Display mode is controlled by register at address $D011 :

Screen control register #1. Bits:
Bits #0-#2: Vertical raster scroll.
Bit #3: Screen height; 0 = 24 rows; 1 = 25 rows.
Bit #4: 0 = Screen off, complete screen is covered by border; 1 = Screen on, normal screen contents are visible.
Bit #5: 0 = Text mode; 1 = Bitmap mode.
Bit #6: 1 = Extended background mode on.
Bit #7: Read: Current raster line (bit #8).
Write: Raster line to generate interrupt at (bit #8).
Default: $1B, %00011011.

Thus, we entered bitmap mode by writing to bit #5 of the control register.

Oups… the screen is completly broken now, and we can see some pixels blinking in the first rows of display.

Actually, what we are watching is the C64 memory content in range $0000-$1FFF.

When using bank 3 for its 16Kbyte ‘graphic’ memory, VIC II can read the character ROM at addresses $1000-$1FFF. As you can see in figure 5, the character ROM is displayed in the lower half part of the screen. This should give you an hint about the way pixels data are stored in a bitmap…

Bitmap data are divided in 8×8 pixels blocks, where data is encoded the same way as in character ROM.

This means that the pixel at location (0,0) is mapped by the most signifiant bit of byte at address $0000. Equally, pixel at location (7,7) is mapped by the least signifiant bit at address $0007 and pixel at (15,7) is mapped by the least signifiant bit at address $000F !

How to clear bitmap

With defaults settings (i.e after a C64 reset), VIC II is using addresses $0000-$1FFF for its bitmap. As a lot of data in the first memory page ($0000,$00FF) are needed by the operating system and basic interpreter, we can’t simply erase bitmap memory to clear the screen.

Moreover, as the VIC II ‘sees’ character rom at $1000-$1FFF, we can’t erase this area of the screen with the CPU.

In sum, we’d better modify the bitmap location if we want to clear screen in our game. And its definitely one role of the VIC Memory Control Register at address $D018 :

Memory setup register. Bits:
Bits #1-#3:
In bitmap mode, pointer to bitmap memory (bit #13), relative to VIC bank, 
memory address $DD00. Values:
%0xx, 0: $0000-$1FFF, 0-8191.
%1xx, 4: $2000-$3FFF, 8192-16383.
Bits #4-#7: Pointer to screen memory (bits #10-#13), relative to VIC bank, memory address $DD00. Values:
%0000, 0: $0000-$03FF, 0-1023.
%0001, 1: $0400-$07FF, 1024-2047.
%0010, 2: $0800-$0BFF, 2048-3071.
%0011, 3: $0C00-$0FFF, 3072-4095.
%0100, 4: $1000-$13FF, 4096-5119.
%0101, 5: $1400-$17FF, 5120-6143.
%0110, 6: $1800-$1BFF, 6144-7167.
%0111, 7: $1C00-$1FFF, 7168-8191.
%1000, 8: $2000-$23FF, 8192-9215.
%1001, 9: $2400-$27FF, 9216-10239.
%1010, 10: $2800-$2BFF, 10240-11263.
%1011, 11: $2C00-$2FFF, 11264-12287.
%1100, 12: $3000-$33FF, 12288-13311.
%1101, 13: $3400-$37FF, 13312-14335.
%1110, 14: $3800-$3BFF, 14336-15359.
%1111, 15: $3C00-$3FFF, 15360-16383.


If you read the default control register value after a reset, you will get value ’21’ = %10101. According to register description, this means :

  • bitmap memory at $0000-$1FFF
  • screen RAM at $0400-$07FF (1024-2047)

Let’s modify this value to display bitmap at $2000-$3FFF :

POKE 53272,PEEK(53272)OR8 : POKE 53265,PEEK(53265)OR32
Figure 6. Bitmap at $2000-$3FFFF

Figure 6. Bitmap at $2000-$3FFFF

VIC II now displays an area of RAM reserved for BASIC programms. But as nothing has been written yet in these locations, default values of $00 or $FF are displayed on screen. It results in horizontal alterning bands of empty or full 8×8 blocks.

But wait, I didn’t tell you anything about this display mode colors…

At first sight, one could believe that HiRes bitmap display is limited to 2 colors (one for background, one for foreground) and only the bitmap memory determines the pixel state on screen. Actually, the background color (unlit pixels) and foreground color (lit pixels) are defined by the lower nibble (4 least signifiant bits) and upper nibble (4 most signifiant bits) of … screen RAM !

Indeed, on one hand the memory area at $0400-$07FF (default) is used to store characters code in character display mode and, on the other hand, the same locations are used in bitmap mode to define colors of each 8×8 pixels blocks.

This explains why, when turning on bitmap mode from BASIC, some blocks have distinctive colors.

In figure 6, if we consider line 9, we should read the basic prompt ‘READY.’ in character mode. This means that screen ram is filled with values $12,$05,$01,$04,$19,$2E.

If we keep only the upper nibble of each values, we get : $1,$0,$0,$0,$1,$2.

And if we get the corresponding colors from the table below, we obtain WHITE, BLACK, BLACK, BLACK, WHITE, RED

Black 0
White 1
Red 2
Cyan 3
Violet 4
Green 5
Blue 6
Yellow 7
Orange 8
Brown 9
Lightred 10
Grey 1 11
Grey 2 12
Lightgreen 13
Lightblue 14
Grey 3 15

Knowing that bitmap values for pixels located in the 8×8 blocks of the ‘READY.’ characters are set to $FF, VIC II displays foreground color in each of theses 6 blocks.

If you are still a bit confused on how VIC II display colors pixels in bitmap mode, let’s try to fill a bitmap with a 4×4 checker pattern with light grey foreground on dark grey background :

  1. turn bitmap mode ON
  2. for each 8×8 pixel block draw on matrix with a sub-4×4 checker pattern
  3. for each block location in screen RAM, set nibbles according to colors (15 & 11)
10 POKE 53272,PEEK(53272)OR8:POKE 53265,PEEK(53265)OR32
20 FORI=8192TO16191 STEP 8
30 POKEI,240:POKEI+1,240:POKEI+2,240:POKEI+3,240
40 POKEI+4,15:POKEI+5,15:POKEI+6,15:POKEI+7,15
60 FORI=1024TO2034:POKEI,251:NEXT
70 GOTO 70

Then you shoud get the following screen :


Figure 7. HiRes checkers

We can observe the obvious slowness of BASIC when dealing with bitmap graphics ! There are too many memory addresses to change if you have the modify the entire screen on each frame refresh…

This will end our session on C64 display modes.

Next time, we will rewrite the last program in 6510 assembly to compare execution speed !


One comment

  1. Greg Nacu says:

    This was super helpful. I’m curious to know how the VIC registers at d018 and d011 are different on the c128. I was surprised to find that this code does not work on the 128.

    I’m hoping I can find the article you say you’re going to write that shows an assembly version of this code.

    I particularly found this article useful in understanding how the VIC uses the bits a d018 to select ram areas “relative” to its selected bank. At first I didn’t get it, but now I do. Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *


Please enter the CAPTCHA text

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>