On the Commodore 64 and the ZX Spectrum the picture on the television is a block of ordinary memory. The video chip reads it every frame, so changing a byte changes the picture. The two machines lay that memory out very differently. The C64’s text screen stores one character code for each of its 1000 positions, in reading order, and keeps their colours in a separate chip that cannot move. The Spectrum stores every pixel, 6,144 bytes of them, in an order Sinclair’s own manual calls “rather curiously laid out”, and puts one colour byte for each 8×8 square straight after them. In both cases the layout comes from the video hardware, and it decides what drawing costs.
Commodore 64
Type POKE 1024,1 on a Commodore 64 and a letter A should appear in the top-left corner of the screen. Often nothing seems to happen at all. Sheldon Leemon explained why in Mapping the Commodore 64: the newer version of the operating system sets the colour of every character to the background colour whenever it clears the screen, so the A is there, drawn in blue on blue. POKE 55296,1 turns it white.
That small puzzle shows how the C64’s text screen works. It is two blocks of memory, not one. Screen memory holds a code for which character to show at each of the 40×25 positions. Colour memory holds each position’s colour. The VIC-II video chip reads both and draws the picture, again and again, with no help from the processor. Change a byte and the picture changes on the next frame. The two blocks also behave differently: screen memory can be moved almost anywhere, and colour memory cannot be moved at all.
Fast facts
- Screen memory: 1000 bytes, normally
$0400–$07E7(1024–2023), one screen code per character position. - Colour memory: 1000 four-bit locations at
$D800–$DBE7(55296–56295). Fixed. - Layout: row by row, 40 bytes to a row, top-left first.
- Character shapes: 8 bytes per character, normally from the character ROM.
- Pointers:
$D018(53272) places screen memory and the character set within the VIC-II’s 16K bank; bits 0 and 1 of$DD00(56576) choose the bank.
Screen memory
The Programmer’s Reference Guide counts “1000 possible locations on the Commodore 64 screen”, starting at 1024 and running to 2023. Each holds one byte, a screen code from 0 to 255. The rows follow each other in memory:
| Row | Addresses |
|---|---|
| 0 (top) | 1024–1063 |
| 1 | 1064–1103 |
| 2 | 1104–1143 |
| … | … |
| 24 (bottom) | 1984–2023 |
So any position is one sum away:
10 R=10:C=5
20 S=1024+R*40+C:REM SCREEN MEMORY
30 K=55296+R*40+C:REM COLOUR MEMORYPosition (0,0) is 1024, (0,39) is 1063, (1,0) is 1064 and (24,39) is 2023.
Screen memory occupies a 1K block, of which the screen uses 1000 bytes. Of the 24 bytes left over, the last eight (2040–2047 with the screen at 1024) are the sprite pointers, which is why they move when the screen moves.
Colour memory
The Programmer’s Reference Guide describes “a group of 1000 locations called COLOR MEMORY or COLOR RAM”, from 55296 to 56295, each “4 bits wide”, enough for the 16 colours. It is a separate chip. Commodore’s service manual lists it as a 2114 static RAM, U6 on the board, “allocated for screen color data storage”, and suggests checking it first when “abnormal colors appear in the letters”.
Only the low four bits of each location exist. Leemon writes that “writing to the high bits will not affect them, and these four bits will usually return a random value when read”, so a program reading colour memory masks them off:
10 CR=PEEK(55296)AND15The colour values are the same as for the border and background registers: 0 black, 1 white, 2 red, 3 cyan, 4 purple, 5 green, 6 blue, 7 yellow, 8 orange, 9 brown, 10 light red, 11 dark grey, 12 grey, 13 light green, 14 light blue, 15 light grey.
How the VIC-II uses them
In the standard text mode each screen code selects a character from the character set, and its colour memory nybble sets the colour of that character’s pixels. Pixels that are off show the background colour from $D021.
The VIC-II does not read screen and colour memory on every line. On the first pixel line of each character row, a badline, it stops the processor and reads all 40 screen codes and 40 colours for the row in one burst. On every line it reads one byte of shape data for each character. The VIC-II entry describes the timing and what it costs the processor.
Screen codes are not PETSCII
The number stored in screen memory is a screen code, and it is not the same as the PETSCII code that PRINT and CHR$ use. The Programmer’s Reference Guide lists the two in separate appendices.
| Character | PETSCII (CHR$) |
Screen code (POKE) |
|---|---|---|
| @ | 64 | 0 |
| A | 65 | 1 |
| B | 66 | 2 |
| Space | 32 | 32 |
| 0 | 48 | 48 |
Screen codes 128–255 are, in the guide’s words, “reversed images of codes 0-127”: add 128 to any code to show it in reverse video. Screen code 160 is a reversed space, a solid block. See PETSCII Chart for the full tables.
Examples
A yellow H in the top-left corner (screen code 8, colour 7):
10 POKE 1024,8
20 POKE 55296,7A word at row 10, column 15, converting the letters A–Z from PETSCII to screen codes as it goes:
10 T$="HELLO"
20 P=1024+10*40+15
30 FOR I=1 TO LEN(T$)
40 A=ASC(MID$(T$,I,1))
50 IF A>=65 AND A<=90 THEN A=A-64
60 POKE P+I-1,A
70 POKE P+I-1+54272,1
80 NEXT I54272 is the distance from screen memory at 1024 to colour memory at 55296, so line 70 colours each letter white.
A screen of solid blue blocks:
10 FOR I=0 TO 999
20 POKE 1024+I,160
30 POKE 55296+I,6
40 NEXT IBASIC takes a few seconds over this. The same job in machine code, or a printed string, is far quicker, but the POKE loop shows exactly what is being stored.
The character set
A character is 8 bytes, one per row of 8 pixels, and a full set of 256 characters takes 2K. The shapes normally come from the character ROM. The processor sees the ROM at $D000–$DFFF only when the I/O chips are switched out of that address range. The VIC-II sees it somewhere else: the guide explains that the chip is made to “think that the ROM characters are at 4096-8191 ($1000-$1FFF)” in bank 0, and at $9000–$9FFF in bank 2, “even though the character ROM is actually at location 53248-57343”. Banks 1 and 3 have no ROM image, so a program using them must supply its own character set.
The letter A is character 1, so its shape is the second 8 bytes of the ROM:
$D008 00011000 $18
$D009 00111100 $3C
$D00A 01100110 $66
$D00B 01111110 $7E the crossbar
$D00C 01100110 $66
$D00D 01100110 $66
$D00E 01100110 $66
$D00F 00000000 $00Pointing the VIC-II at a character set in RAM, a copy of the ROM with some characters redrawn, is how most C64 games draw their scenery.
Moving the screen: $D018 and $DD00
Register $D018 (53272) holds two pointers, each an offset inside the VIC-II’s 16K bank:
Bits 7-4: screen memory, in 1K steps (value × $0400)
Bits 3-1: character set, in 2K steps
Bit 0: unusedAt power-up the upper four bits are 1, which puts screen memory at $0400, and bits 3–1 are 2, which puts the character set at $1000, the ROM image with upper case and graphics. Bit 0 reads back as 1, so PEEK(53272) gives 21. The guide’s two everyday values are POKE 53272,21 for upper case and graphics and POKE 53272,23 for lower and upper case, which moves the character set to $1800, the ROM’s second half.
To move screen memory without disturbing the character set, the guide gives:
POKE 53272,(PEEK(53272)AND15)OR Awhere A is 16 times the number of 1K steps: 16 for $0400, 32 for $0800, 48 for $0C00, up to 240 for $3C00. The guide adds that the KERNAL’s screen editor has to be told as well, with POKE 648 and the new address divided by 256, or PRINT carries on writing to the old screen.
The 16K bank comes from the lowest two bits of CIA 2’s port A at $DD00, and the bits count backwards: 3 (binary 11) selects bank 0, $0000–$3FFF, the default; 2 selects $4000–$7FFF; 1 selects $8000–$BFFF; and 0 selects $C000–$FFFF. Screen memory, character set and sprites all have to be in the chosen bank.
Two screens, one colour memory
Colour memory, the guide says, “can NOT move. It is always located at locations 55296 ($D800) through 56295 ($DBE7).” Screen memory can sit at any 1K boundary. That split decides how C64 programs switch between screens. A program can draw a second screen out of sight and then show it by changing $D018, but both screens share one set of colours.
This short program puts a second screen at $3C00 (15360), the last 1K of bank 0. Line 10 first lowers the top of BASIC’s memory to 15360, the method Leemon recommends for keeping BASIC’s variables out of an area a program wants for itself.
10 POKE 56,60:CLR
20 FOR I=15360 TO 16359:POKE I,160:NEXT
30 POKE 53272,(PEEK(53272)AND15)OR240
40 POKE 648,60
50 FOR T=1 TO 2000:NEXT
60 POKE 53272,21:POKE 648,4Line 20 fills the hidden screen with screen code 160, a solid block. Line 30 shows it, in whatever colours are already in colour memory, and line 40 sends PRINT there too. Line 60 puts everything back. Games use the same switch for double buffering, building the next frame of a scrolling screen while the player watches the current one. Colour memory cannot be double-buffered, so when the colours scroll too they have to be moved while the beam is somewhere else, which is one of the costs of scrolling on the C64.
Things to watch for
- Screen memory is 1000 bytes of a 1K block. Writing past the 1000th byte does not wrap to the top of the screen: it lands in the unused bytes and then the sprite pointers.
- Only the low four bits of colour memory exist, so mask
PEEKresults withAND 15. - Screen memory must start on a 1K boundary within the current bank.
- The character ROM cannot be written. To change a character, copy the set into RAM and point
$D018at the copy. - On machines with the newer operating system, clearing the screen sets colour memory to the background colour, so
POKEa colour as well as a character.
Beyond text: FLI
Demo coders used the same two pointers to break the colour rules. In the bitmap modes the colour of each 8×8 cell comes from screen memory, which the VIC-II reads only on badlines, once every eight lines. Christian Bauer’s description of the chip explains the trick known as FLI (flexible line interpretation): “if you make every raster line a Bad Line by appropriately modifying YSCROLL, the VIC will read from the video matrix in every line and so it will also read new color information for every line.” Because the chip does not move on through screen memory when a badline is forced like this, the program also switches $D018 to a different screen on each line, cycling through eight of them. Colour memory still cannot be switched, so its share of the colour stays fixed for each cell.
It costs almost the whole processor. A widely used display routine on Codebase64 runs a loop of exactly 23 cycles per line, which with the 40 cycles the VIC-II takes for its colour fetches fills the 63 cycles of a PAL line. It also leaves a mark: the forced fetch starts too late for its first three reads, which get the value $FF, and Bauer notes that this shows as “24 pixel wide stripes on the left side of the screen”, the FLI bug. The FLI entry has the full story.
ZX Spectrum
Sinclair’s manual did not encourage anyone to touch the Spectrum’s screen directly. The display file, Steven Vickers wrote, “is rather curiously laid out, so you probably won’t want to PEEK or POKE in it”, and he advised programmers used to poking a screen to “start using SCREEN$ and PRINT AT instead, or PLOT and POINT”. Machine-code programmers wrote to it anyway. The layout follows simple rules once it is written as an address, and it was arranged with machine code in mind.
Fast facts
- Display file: 6,144 bytes at 16384–22527 (
$4000–$57FF), one bit per pixel for 256×192 pixels. A set bit shows the square’s INK colour, a clear bit its PAPER colour. - Attribute file: 768 bytes at 22528–23295 (
$5800–$5AFF), one byte for each of the 32×24 character squares, in reading order. - Together: 6,912 bytes. The manual defines
LOAD "name" SCREEN$as the same asLOAD "name" CODE 16384,6912. - Fixed: the ULA always reads the picture from
$4000on the 48K machine. The Spectrum 128 and +2 add a second screen (below). - Border: not memory at all, but bits 0–2 of the byte written to port 254 (
$FE). - Shared: all of it sits in the lower 16K of RAM, which the ULA shares with the Z80.
The display file
Each row of 256 pixels takes 32 bytes, left to right, with the leftmost pixel of each byte in bit 7. What the manual calls “curious” is the order of the rows. It explains that “the corresponding rows in the 32 characters of a single line are stored together as a scan of 32 bytes”, and then: “First come the top scans of lines 0 to 7, then the next scans of lines 0 to 7, and so on to the bottom scans of lines 0 to 7; then the same for lines 8 to 15; and then the same for lines 16 to 23.” A “line” there is a line of characters, eight pixel rows deep.
So the screen is three blocks of 2,048 bytes, the top, middle and bottom thirds, each 64 pixel rows high. Within a third, the first 256 bytes hold the top pixel row of each of its eight character lines, the next 256 bytes the second pixel row of each, and so on.
That order looks strange as a list of rows. As an address it is tidy. Number the pixel rows y from 0 at the top to 191 at the bottom, and the pixel columns x from 0 to 255. Then the 16-bit address of the byte holding pixel (x, y) is made from the bits of those two numbers:
high byte (H) low byte (L)
bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 y7 y6 y2 y1 y0 y5 y4 y3 x7 x6 x5 x4 x3
y7 y6 which third (0, 1 or 2)
y2 y1 y0 pixel row inside the character square (0-7)
y5 y4 y3 character line inside the third (0-7)
x7 ... x3 byte column (0-31)Chris Smith, who reconstructed the ULA’s logic from the silicon, puts it in one sentence: the address of a display byte comes from “setting A4-0 to the column number, A12-5 to the line number and swapping A10-8 with A7-5”. Take a row-by-row address and swap two groups of three bits.
A worked example: the byte holding pixel (100, 100).
- y = 100 is
01 100 100in binary. The third (y7 y6) is 1, the middle third. The character line inside the third (y5 y4 y3) is 4, and the pixel row inside the square (y2 y1 y0) is 4. - x = 100 gives byte column 100 ÷ 8 = 12 (
01100), and 100 mod 8 = 4, so the pixel is the fifth from the left, bit 3. - H =
010 01 100=$4C. L =100 01100=$8C. - The address is
$4C8C, 19596: 16384 + 2048×1 + 256×4 + 32×4 + 12.
Sinclair BASIC has no bitwise operators, so a BASIC program builds the same sum with INT. This lights the eight pixels from x = 96 to 103 on row 100:
10 LET x=100: LET y=100
20 LET a=16384+2048*INT (y/64)+256*(y-8*INT (y/8))+32*(INT (y/8)-8*INT (y/64))+INT (x/8)
30 POKE a,255
40 PRINT AT 0,0;aThe ROM does the same job for PLOT and POINT in its PIXEL-ADD routine at 22AA. Ian Logan and Frank O’Hara’s Complete Spectrum ROM Disassembly annotates it building H as “64 + 8INT (B/64) + B (mod 8)” and L as “32INT (B(mod 64)/8) + INT(x/8)”, which are the same fields. One difference catches people out: B there is 175 minus the PLOT y co-ordinate, because PLOT counts upwards from the bottom, and the manual gives it only 22 of the 24 character lines: the bottom 16 pixel rows belong to the lower part of the screen, where commands are typed.
One row down: INC H
The layout makes the most common step in drawing very cheap. Moving down one pixel row inside a character square changes only y2 y1 y0, which sit at the bottom of the high byte, so it adds exactly 256 to the address. Smith explains that this was the reason for putting them there: a 256-byte offset “allows software to step an address from one line to the next simply by incrementing the address high byte, easily achieved with a single Z80 instruction”. That instruction is INC H, and a routine that draws an 8-pixel-high character or sprite into one square needs nothing else between rows.
Crossing into the next square down takes a correction. Smith: the next line “may be stepped to by incrementing the high address byte until A10-8 are 111 binary, at which point the next line address may be found by recalculating the whole address or by applying an address adjustment”. The usual adjustment, with the address in HL:
down: inc h ; next pixel row: add 256
ld a,h
and 7 ; did y2-y0 wrap from 7 to 0?
ret nz ; no: still inside the square, done
ld a,l
add a,32 ; yes: next character line (y5-y3 + 1)
ld l,a
ret c ; carry out of L: next third, and H is already right
ld a,h
sub 8 ; same third: undo the carry INC H made into y6
ld h,a
retWhen INC H takes y2–y0 from 7 back to 0, the carry goes into the third bits and moves the address down a whole third. Adding 32 to L moves to the next character line instead. If that addition overflows, the move really was into the next third and the carry already made H correct. If it does not, the routine takes the 8 back off H. Starting from $4700, pixel row 7, it returns $4020, row 8. Starting from $47E0, row 63, it returns $4800, row 64, the top of the middle third.
The attribute file
After the display file come the attributes, which the manual says “are stored line by line in the order you’d expect”: 32 bytes for each of the 24 character lines, so the square at line r, column c is at 22528 + 32×r + c. The manual documents the byte through the ATTR function, whose result is the sum of “128 if the character position is flashing”, “64 if the character position is bright”, “8* the code for the paper colour” and “the code for the ink colour”:
| Bit | Meaning |
|---|---|
| 7 | FLASH: 1 swaps INK and PAPER at the ULA’s flash rate |
| 6 | BRIGHT: 1 for the brighter version of both colours |
| 5–3 | PAPER colour, 0–7 |
| 2–0 | INK colour, 0–7 |
The colour codes are 0 black, 1 blue, 2 red, 3 magenta, 4 green, 5 cyan, 6 yellow and 7 white. The manual’s own example is a flashing square with yellow paper and blue ink: 128 + 0 + 8×6 + 1 = 177. POKE that value into the attribute file and ATTR reads it back:
10 LET r=10: LET c=5
20 PRINT AT r,c;"A"
30 POKE 22528+32*r+c,177
40 PRINT AT 0,0;ATTR (r,c)Line 20 prints the A in the current colours, line 30 recolours its square without touching its pixels, and line 40 prints 177. Two colours per square is the whole colour system, and the source of colour clash; attribute-aware design covers how games worked with it.
The low byte of a display address and of its attribute address are the same, y5 y4 y3 followed by the byte column. Only the high byte differs, and the ROM’s PO-ATTR routine at 0BDB converts one to the other in five instructions: the display high byte “is divided by eight and ANDed with +03 to determine which third of the screen is being addressed”, then ORed with $58. In the example above, $4C becomes 1, then $59, so the attribute for the byte at $4C8C is at $598C, 22924, which is 22528 + 32×12 + 12.
Why the layout is interleaved
The layout comes from the way the ULA reads memory. For each 8 pixels of the picture it needs a display byte and the attribute byte for the same square, and it fetches them as a pair using the dynamic RAM’s page mode: one row address, then two column addresses. Smith: “both the display byte and attribute byte addresses must share the same 7-bit row address.” In a plain row-by-row bitmap the low bits of a pixel byte’s address change with every pixel row, while its attribute address stays put, so the pairing is impossible. Moving y2 y1 y0 out of the low byte makes the low bits of the two addresses match. Smith sets out the places they could have gone, 128, 256 or 1,024 bytes apart, and the ULA uses bits 8–10, the 256-byte step, for the INC H reason above. In Smith’s words, the memory is “carefully arranged so that an attribute byte is in the same memory row as its display byte, leading to the peculiar screen byte order”.
The order is visible whenever a picture loads from tape. LOAD "" SCREEN$ writes the 6,912 bytes in address order, so the picture arrives as a pixel row in every eighth line of the top third, then the rows below them, then the same in the middle and bottom thirds. The colours come last, square by square, because the attribute file comes after the display file.
The border
The border has no memory of its own. The manual: “Port address 254 in output drives the loudspeaker (D4) and the MIC socket (D3), and also sets the border colour (D2, D1 and D0).” In machine code:
ld a,2 ; red, with the speaker and MIC bits clear
out ($fe),aBASIC’s BORDER command does the same OUT and then, Logan and O’Hara show, keeps the colour times eight in the system variable BORDCR, which the ROM uses to colour the lower part of the screen. The ULA entry explains how the ROM’s tape routines use the same bits to draw the loading stripes.
What drawing costs
The screen lives in the 16K of RAM that the ULA reads to draw the picture, and the ULA comes first. Smith describes what happens when the ULA “detects a CPU address between 16384 and 32767 while it is reading video bytes”: it “immediately holds the Z80 clock high” until its own fetch is done. Every write to the screen made while the picture is being drawn can be held up like this, while code and data in the upper 32K run at full speed. This is contended memory; the ULA entry gives the delay pattern.
Size is the other cost. Copying the whole display file with LDIR takes 21 T-states a byte, about 129,000 T-states, nearly two of the 69,888-T-state frames, before any contention. The attribute file is an eighth of the size, which is why recolouring the screen is so much cheaper than redrawing it. Period programmers worked around both. In Your Spectrum in August 1985, Mike Leaman’s “3D Turbo Charger” drew everything into a second 6K buffer, then “the entire 6K of memory is copied to the real screen, updating the whole screen at once”, and he called it “a trick that you’ll find on many commercial pieces of software though it has the disadvantage of taking up another 6K of memory”. Two months later, Chris Wood’s sprite routine in the same magazine could be told to work on 8, 16 or all 24 character lines, starting from the top, middle or bottom third by POKEing 64, 72 or 80, the high bytes $40, $48 and $50, because “the less that has to move, the faster the routine runs”. He pointed out that Matthew Smith “only used the top two thirds”. The Scrolling entry works through what moving the whole picture costs, and Double Buffering covers the second-screen approach.
The Spectrum 128 and +2 gave machine code a second screen. Amstrad’s +2 manual explains that bit 3 of port $7FFD “switches screens; screen 0 is held in RAM 5 (beginning at 4000h) and is the one that BASIC uses, screen 1 is held in RAM 7 (beginning at C000h) and can only be used by machine code programs”. Both screens have the same layout, and both are in RAM that the manual describes as contended, sharing “time with the video circuitry”.
Things to watch for
- Count y from the top when working out addresses.
PLOTcounts from the bottom and covers only the top 176 rows. - The leftmost pixel of a byte is bit 7.
INC Hmoves down a pixel row only inside a character square. Every eighth row needs the adjustment.- An attribute covers a whole 8×8 square. POKEing it recolours all 64 pixels.
- Writing the border through port 254 also writes the speaker and MIC bits, so keep bits 3 and 4 as the program needs them.
Sources
- Commodore, Commodore 64 Programmer’s Reference Guide (1983): screen and colour memory (p. 100); bank selection (pp. 101–102); moving screen memory and
POKE 648, and colour memory’s fixed address (p. 102); character memory and the ROM images in banks 0 and 2 (pp. 103–104); the character ROM’s contents and the shape of A (p. 107);POKE 53272,21andPOKE 53272,23, and the screen display codes (Appendix B, pp. 376–378); PETSCII codes (Appendix C, from p. 379). - Sheldon Leemon, Mapping the Commodore 64 (COMPUTE! Publications, 1984): lowering the top of BASIC with
POKE 56(p. 18); screen memory, the invisiblePOKE 1024,1and the sprite pointers (pp. 81–82);$D018and location 648 (pp. 145–146); colour memory’s four connected bits (p. 170). - Commodore, C64/C64C Service Manual: the 2114 colour RAM (U6) and the fault table.
- Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (1996), section 3.14.3, “FLI”.
- Codebase64, “FLI” and “FLI display routine” (web, secondary): the 23-cycle loop and the FLI bug.
- Steven Vickers, Sinclair ZX Spectrum BASIC Programming (Sinclair Research, 2nd edn 1983): the colour codes (ch. 16, p. 109);
ATTRand the value 177 (ch. 16, pp. 115–116); the 22 lines available toPLOT(ch. 17, p. 121); port 254 (ch. 23, p. 161); the memory map, the display file’s layout and the attributes’ order (ch. 24, p. 164); theATTRbit layout (Appendix C, p. 198);LOAD SCREEN$asLOAD CODE 16384,6912(Appendix C, p. 204). - Ian Logan and Frank O’Hara, The Complete Spectrum ROM Disassembly (Melbourne House, 1983): PO-ATTR at
0BDB(p. 39); CL-ADDR at0E9B(p. 48); BORDER at2294and PIXEL-ADD at22AA(pp. 115–116). - Chris Smith, The ZX Spectrum ULA: How to Design a Microcomputer (ZX Design and Media, 2010): page-mode fetches and the screen byte order (ch. 12, p. 112); the shared 7-bit row address (ch. 15, p. 137); the 256-byte step and the display address (ch. 15, pp. 141–142); the ULA holding the Z80 clock (ch. 18, p. 190); 69,888 T-states per frame (p. 225).
- Zilog, Z80 CPU User Manual (UM008011-0816):
LDIRat 21 T-states per byte. - Your Spectrum issue 17, August 1985, p. 36: Mike Leaman, “3D Turbo Charger”.
- Your Spectrum issue 19, October 1985, p. 27: Chris Wood’s sprite routine.
- Amstrad, Sinclair ZX Spectrum +2 manual (1986), p. 147: port
$7FFDand the second screen.
See also
- PETSCII Chart: character code tables
- PRINT vs POKE
- VIC-II: the display chip and its registers
- Scrolling
- Double Buffering
- FLI
- Commodore 64
- ZX Spectrum
- ULA: the Spectrum’s display chip, contention and the border
- Colour Clash
- Attribute-Aware Design