# Programming the Commodore 64 — A Low-Level Overview Notes from studying the downloaded C64 reference docs in `./docs/c64/` (C64-Wiki, Christian Bauer's VIC-II article, the Commodore 64 Programmer's Reference Guide, and the Oscar64 runtime headers). This is the practical "how do I actually program the thing" companion to `OSCAR64.md`. ## 0. The mental model in one paragraph The C64 is a 6510 (6502 plus an 8-bit I/O port) running at ~1 MHz, sharing the bus with a 6567/6569 VIC-II graphics chip and a 6581 SID sound chip. The VIC and CPU alternate bus cycles automatically; the VIC can "stun" the CPU when it needs extra cycles. Two 6526 CIAs handle I/O and timers. The whole 64 KB address space is overlaid with RAM, ROM, I/O, and Color RAM in a swizzled fashion controlled by the 6510's $01 port plus the PLA. The KERNAL ROM at $E000-$FFFF is always present at reset and provides 39 service routines via a jump table at $FF81-$FFF3. There is no memory protection. To make the chip set do interesting things, you write to its registers — for the VIC that's $D000-$D3FF, for the SID $D400-$D7FF, for the CIAs $DC00-$DCFF and $DD00-$DDFF. Everything visible on screen is driven by writes to these registers plus writes to the 1 KB of "screen memory" at $0400-$07FF and the 1/2 KB of "color RAM" at $D800-$DBFF. ## 1. The hardware ``` +-------+ +-------+ +-------+ +--------+ | BASIC | |KERNAL | | CHAR | | 8x8K | | ROM | | ROM | | ROM | | RAM | +--++---+ +--++---+ +--++---+ +--++---+ || || || || +------+||+++++++++||+++++++++||++++++++++++||+++++ | PLA |+>------------>------------>----------- --->| (chip selects) +--+---+ | | | +------+------+ +-----+----+ | 6510 | | VIC-II | | CPU |<--data/addr----+--------------+ 6569 | +-----+------+ | | +-----+----+ | | | | | | | +--------+ | | | +--+ 6581 | | | | | | SID | | | | | +--------+ | | | | | +-----+-----+ +-----+--+----+ +-----+-----+ | 6526 CIA1| | 6526 CIA2 | | 4-bit | | (IRQ) | | (NMI) | | Color RAM| +-----+-----+ +-----+-------+ +-----------+ | | +----------+----------+ | | keyboard, joystick | +-----+----+ | paddles, datasette | | IEC bus | +---------------------+ | RS-232 | | userport| +----------+ ``` The 6510's port at `$01` plus the PLA determine which of BASIC, KERNAL, CHAR ROM, and I/O is visible at $A000-$BFFF, $E000-$FFFF, and $D000-$DFFF respectively. CIA 2 port A bits 0-1 pick one of four 16 KB "VIC banks" by extending the VIC's 14-bit address bus to a full 16 bits. ## 2. The bus and the "stun" mechanism This is the bit that explains every weird timing thing on the C64. The system clock is one period of ϕ2 (phi2). PAL: ~985 kHz, NTSC: ~1.023 MHz. The VIC generates the pixel clock (8 pixels per ϕ2 cycle) and divides by 8 to make ϕ0, which the 6510 delays by ~30-40 ns to make ϕ2. Within each ϕ2 cycle, the first half (ϕ2 low) is the VIC's turn and the second half (ϕ2 high) is the CPU's turn. So they normally share the bus cleanly with no contention. The VIC needs extra cycles for two things: 1. **C-accesses** — 40 character-pointer reads at the start of each text row. These happen on "badlines" (every 8th raster line when the display is enabled and `(RASTER & 7) == YSCROLL` for raster in the range $30-$F7). 2. **S-accesses** — 2 sprite data reads per sprite per scanline, when the sprite's Y position matches the current raster line. For these, the VIC pulls BA (Bus Available) low 3 cycles early. BA is wired to the 6510's RDY line. The 6510 can only be paused on a *read* (writes can't be paused — that's why the VIC waits up to 3 cycles: 3 is the maximum run of consecutive writes the 6510 can do). When AEC is then held low for the second half of a cycle too, the VIC owns the bus exclusively. The 6510's actual data/address timing from the datasheet, relative to ϕ2: - Address valid: 100-300 ns after falling edge of ϕ2. - Write data valid: 150-200 ns after rising edge of ϕ2. - Read data latched on falling edge of ϕ0. In practice the C64 is a bit slower than this because the VIC holds AEC late, so address is valid about 60-75 ns after ϕ2 rising edge and data another ~120 ns later. **Why this matters:** any code that depends on cycle-accurate timing (raster IRQ tricks, floppy timing, raster bars) has to count cycles *including* badline stalls. The "raster stable" raster line (line 311 on PAL) is the line right after vertical blank where no badline penalties have happened recently, so cycle counts on that line are exact. The VIC also performs 5 DRAM refresh accesses per raster line — that means you don't have to refresh the 64 KB of main RAM yourself; the VIC does it. (Pretty unusual for a graphics chip.) ## 3. The 6510 - 6502 plus a 6-/8-bit on-chip I/O port. - 16-bit address bus, 8-bit data bus. - Two external interrupts: **IRQ** (maskable via the I flag in P; SEI/CLI) and **NMI** (non-maskable; fires on RESTORE key or CIA 2 /FLAG). - Three vectors in the top six bytes of the address space (KERNAL ROM always present at reset): - `$FFFA-$FFFB` NMI → `$FE43` - `$FFFC-$FFFD` RESET → `$FCE2` - `$FFFE-$FFFF` IRQ / BRK → `$FF48` The 6510 is *object-code-compatible* with the 6502. Same instruction set, same addressing modes, same cycle counts. The only thing the 6510 adds is the port. The port lives at `$00` (data direction, 1=output) and `$01` (data). `$01` is also the CPU's bank-switch latch. The bit meanings: | Bit | Name | Effect | |-----|------|--------| | 0 | LORAM | 0=RAM at $A000-$BFFF, 1=BASIC ROM | | 1 | HIRAM | 0=RAM at $E000-$FFFF, 1=KERNAL ROM | | 2 | CHAREN | 0=CHAR ROM at $D000-$DFFF, 1=I/O (default) | | 3 | Cassette Data Out | | 4 | Cassette Switch Sense | | 5 | Cassette Motor (0=on) | | 6-7 | Unused | Default after reset is `$37` (%00110111) — everything visible, cassette motor off. To run with no ROMs (for a self-contained .prg that owns all memory), set `$01` to `$34` (or thereabouts depending on whether you need CHAR ROM). The Oscar64 runtime does exactly this kind of bank switching in its crt.c when `-rt=` is given, and the oscar64 `mmap_set` API exposes it. There is no "address 0/1 in RAM". The 6510 port occupies those two addresses. To read the underlying RAM, you have to use a VIC trick (corrupting the datassette buffer at $02 and using the VIC's read prefetch), but you almost never need to. ## 4. The memory map (after reset, no cartridge) | Address | Contents | |---------|----------| | $0000-$00FF | Zeropage (see §5) | | $0100-$01FF | Hardware stack (and a few KERNAL scratch bytes at the bottom) | | $0200-$02FF | BASIC / KERNAL pointers | | $0300-$03FF | More KERNAL/BASIC pointers; **$0314-$0315 = IRQ vector, $0316-$0317 = BRK vector, $0318-$0319 = NMI vector** | | $0400-$07FF | Screen RAM (1000 bytes for the 40×25 text screen) | | $0800-$9FFF | Free BASIC program storage (38911 bytes) | | $A000-$BFFF | BASIC ROM (8 KB, visible iff LORAM=1) | | $C000-$CFFF | Free for ML programs | | $D000-$D3FF | VIC-II registers (47 of them, mirrored every 64 bytes) | | $D400-$D7FF | SID registers (mirrored every 32 bytes; not on C128) | | $D800-$DBFF | Color RAM (1000 nibbles, 4-bit wide) | | $DC00-$DCFF | CIA 1 | | $DD00-$DDFF | CIA 2 | | $DE00-$DFFF | I/O expansion / open bus | | $E000-$FFFF | KERNAL ROM (8 KB, visible iff HIRAM=1) | Two important properties: - **Write-through to RAM under ROM.** If a write targets an address where ROM is currently visible, the read still goes to the ROM but the data is written to the underlying RAM. This is how a .prg loaded at $0801 can be "installed" — the kernal loads it at the standard BASIC start address $0801, writes a 0 to $01 to hide ROMs, then JMPs to the entry point. The RAM at $A000-$BFFF and $E000-$FFFF is still 8 KB of usable space once you bank it in. (But you have to write a custom ISR vector because the KERNAL ISR is in $EA31, which is in KERNAL ROM.) - **Color RAM upper nibble is open bus.** Color RAM is a 4-bit-wide 2114 SRAM, so reads of $D800-$DBFF return the low nibble as a valid color, but the high nibble is "random" (often it reflects the last byte the VIC read, due to bus capacitance). For full detail see `docs/c64/memory/memory_map.md`. ## 5. Zero page Every 6502 has 256 bytes of "page 0" with a special addressing mode that is 1 byte shorter and 1 cycle faster than the equivalent absolute mode. The C64 puts the 6510 port at $00/$01, the floating-point accumulators at $61-$6E, the BASIC interpreter state at $2B-$8F, the keyboard buffer state at $C5-$D7, and a ton of other shared state through the rest of the page. The hardware stack (which the 6502 grows downward) lives in $0100-$01FF, so $013F-$01FF is the actual usable stack. For ML work the "free" scratch locations are very few. A common pattern: a few variables at $FB-$FE (which the KERNAL doesn't touch except for $FF in the float-to-ASCII routine), and the area $C0-$C4 is free when no cassette or serial I/O is in progress. For the full table of every zero-page address, see `docs/c64/memory/zeropage.md`. The Oscar64 runtime uses its own scheme (it dedicates a zero-page window to its software stack, e.g. $F7-$FF on a C64, and shifts it around for other targets — see `Compiler.cpp` lines 57-105 for the ZP register allocation logic). ## 6. The VIC-II: how the screen actually gets drawn The VIC has: - A 14-bit address bus (16 KB of address space). The high 2 bits come from CIA 2 port A, picking one of 4 banks. - A 12-bit data bus (8 normal + 4 directly to color RAM). - A 320×200 pixel display generated 8 pixels at a time (1 character cell). - 16 colors, fixed palette (see below). - 8 sprites (MOBs), 24×21 each (12×21 in multicolor). - 5 DRAM refresh accesses per line. - A raster counter you can read at $D012 (low 8 bits; the 9th bit is bit 7 of $D011). ### 6.1 The display window and border The VIC paints within a fixed "display window" surrounded by a border in the color stored in $D020. The window dimensions are selected by RSEL (bit 3 of $D011) and CSEL (bit 3 of $D016): | RSEL | Window height | First line | Last line | |------|---------------|------------|-----------| | 0 | 24 rows (192 px) | 55 ($37) | 246 ($F6) | | 1 | 25 rows (200 px) | 51 ($33) | 250 ($FA) | | CSEL | Window width | First X | Last X | |------|--------------|---------|--------| | 0 | 38 cols (304 px) | 31 ($1F) | 334 ($14E) | | 1 | 40 cols (320 px) | 24 ($18) | 343 ($157) | The YSCROLL (bits 0-2 of $D011) and XSCROLL (bits 0-2 of $D016) shift the *content* of the window in 1-pixel increments, which is how smooth scrolling works. (Set RSEL=1, CSEL=1, and XSCROLL=YSCROLL=0 to get the canonical 40×25 layout, or XSCROLL=YSCROLL=7 for 38×24, etc.) DEN (bit 4 of $D011) is the "display enable" master switch. If DEN=0 the VIC still runs but doesn't actually display anything. ### 6.2 Badlines and bus stalls A "badline" is any line on which the VIC has to do the 40-character pointer read. By default that happens on the first raster line of each text row, i.e. every 8th line. The exact definition (from Christian Bauer's article): > A Bad Line Condition is given at any arbitrary clock cycle if, at the > negative edge of ϕ0 at the beginning of the cycle, RASTER >= $30 and > RASTER <= $f7 and the lower three bits of RASTER are equal to YSCROLL, > and if the DEN bit was set during an arbitrary cycle of raster line $30. That means you can trigger a badline manually by changing YSCROLL mid-line — this is the basis of FLI/AFLI/NUFLI, the demo scene hacks that get more colors per cell. When a badline fires, the VIC "stuns" the CPU for 40 cycles (plus the 3-cycle setup), then does 40 c-accesses to read the video matrix. That's the 40-cycle-per-text-row overhead that the CPU pays for graphics. ### 6.3 The 16 colors The 16-color palette is hard-wired in the VIC (the VIC generates color from the phase and amplitude of a signal derived from a 14.3/17.7 MHz color clock): | Code | Color | Code | Color | |------|----------|------|-------------| | 0 | Black | 8 | Orange | | 1 | White | 9 | Brown | | 2 | Red | 10 | Light red | | 3 | Cyan | 11 | Dark gray | | 4 | Pink | 12 | Medium gray | | 5 | Green | 13 | Light green | | 6 | Blue | 14 | Light blue | | 7 | Yellow | 15 | Light gray | ### 6.4 The 5 legal graphics modes The VIC is set into one of 5 legal "modes" by three bits: - **ECM** (Extended Color Mode) — bit 6 of $D011 - **BMM** (Bitmap Mode) — bit 5 of $D011 - **MCM** (Multicolor Mode) — bit 4 of $D016 | Mode | ECM BMM MCM | Result | |------|-------------|--------| | 0 | 0 0 0 | Standard Character Mode (40×25 text) | | 1 | 0 0 1 | Multicolor Character Mode | | 2 | 0 1 0 | Standard Bitmap Mode (hires, 320×200) | | 3 | 0 1 1 | Multicolor Bitmap Mode (160×200) | | 4 | 1 0 0 | Extended Background Color Mode | Modes 5-7 are technically possible but produce no visible output. In Standard Character Mode: - Screen memory at $0400-$07FF (40×25 = 1000 bytes) holds character codes (PETSCII values 0-255, but the character generator only has 256 chars so effectively 0-255). - Color RAM at $D800-$DBE7 (1000 nibbles, low 4 bits) holds the color of each character cell, drawn from the 16-color palette. - One global background color in $D021. - Character patterns in the char generator (default 4 KB at $D000-$DFFF in banks 0/2; can be relocated by changing $D018 to point to RAM at $1000-aligned addresses). In Bitmap Mode: - 8000 bytes of bitmap at the address pointed to by bits VM13-VM10 of $D018 (in 8 KB steps). Each bit is one pixel. - 1000 bytes of "screen memory" at the address pointed to by bits VM13-VM10 in 1 KB steps. The screen memory holds color information: the low 4 bits are the cell's foreground color, the high 4 bits are the cell's background color *index* (0-3, picking one of $D021-$D024). When MCM=1, the 2 bits per pixel pick from $D021/$D022/$D023/color RAM. ### 6.5 Sprites 8 sprites (MOBs), each: - 24×21 pixels (12×21 in multicolor). - Data: 63 bytes + 1 unused pad = 64 bytes, must be 64-byte aligned in the current VIC bank. - Position: X (9 bits — low 8 in $D000/$D002/.../$D00E, bit 8 in $D010 one bit per sprite) and Y (8 bits in $D001/$D003/.../$D00F). - Color: $D027-$D02E (one per sprite); shared multicolor colors at $D025 (MC0), $D026 (MC1). - Pointer: at $07F8-$07FF (one byte per sprite; contains the pattern address divided by 64). - Enabled: $D015 (one bit per sprite). - Mode (hires or multicolor): $D01C (one bit per sprite). - X/Y expansion: $D01D (X) and $D017 (Y), one bit per sprite. - Priority vs background: $D01B (one bit per sprite, 1 = behind background). - Sprite-sprite priority is hardwired: lower-numbered sprite is in front. - Collision detection: $D01E (sprite-sprite, read clears) and $D01F (sprite-data, read clears). Bits 1 and 2 of $D019 / $D01A are the collision IRQ flag and enable. The "trick" of showing more than 8 sprites is to use raster IRQs to re-load the sprite pointers and Y positions mid-frame, presenting a different set of up to 8 sprites on each part of the screen. With careful timing you can show 24+ sprites on a single line. ### 6.6 Raster interrupt The raster IRQ is the heart of all the C64 graphics tricks. The hardware fires IRQ whenever the 9-bit raster counter (RASTER in $D012 plus bit 7 of $D011) equals the 9-bit value last written to those same bits. The standard setup is: ```asm Init SEI LDA #%01111111 STA $DC0D ; mask CIA 1 IRQs AND $D011 ; clear bit 7 of $D011 (RST8) STA $D011 STA $DC0D ; ack CIA 1 STA $DD0D ; ack CIA 2 LDA #Irq STA $0315 LDA #%00000001 STA $D01A ; enable raster IRQ in VIC CLI RTS Irq ; do stuff ASL $D019 ; ack: clear bit 0 of $D019 JMP $EA31 ; chain into KERNAL ISR (or $EA81 to skip) ``` The 8-bit "raster line 311" trick (writing a specific YSCROLL value during certain ranges of the raster counter to force extra badlines) is the basis of FLI/AFLI/NUFLI. Stable raster: raster line 311 on PAL is the one that gives the cleanest, most predictable cycle counts because no recent badline penalty is in effect. Writing to $D012 on this line will trigger the IRQ exactly 63 cycles later. For the rest of the line the timing is stable too; Christian Bauer's article has the full per-cycle timing table. ## 7. The SID The 6581 (early C64s) or 8580 (C64C) is a 3-voice analog synthesizer chip designed by Bob Yannes. The 8580 is "cleaner" but lacks the bugs of the 6581 that people used for tricks like 4-bit sample playback. Each voice has: - 16-bit frequency ($D400/$D401, $D407/$D408, $D40E/$D410). - 12-bit pulse width ($D402/$D403, $D409/$D40A, $D410/$D411). - Control register $D404 / $D40B / $D412: gate, sync, ring mod, test, triangle, saw, pulse, noise. - ADSR envelope in $D405/$D406 etc. (4-bit attack, 4-bit decay, 4-bit sustain, 4-bit release). Shared: - Filter cutoff at $D415/$D416 (low 3 bits + 8 bits). - Filter routing/resonance at $D417 (high 4 bits = resonance, low 4 bits = enable ext/v3/v2/v1). - Filter mode and master volume at $D418 (bit 6 = mute V3, bit 5 = HP, bit 4 = BP, bit 3 = LP, bits 0-3 = volume 0-15). - Read-only: $D419 paddle X, $D41A paddle Y, $D41B oscillator V3, $D41C envelope V3. Classic 4-bit sample playback on the 6581: rapidly write the high nibble of $D418 to output 4-bit sample values. The 8580 fixed this "feature" so samples are very quiet; restoring it requires a 470kΩ-1MΩ resistor on EXT IN to GND. ## 8. The CIAs Two 6526 CIAs. CIA 1 drives the keyboard, joysticks, paddles, and datasette. CIA 2 drives the serial (IEC) bus, RS-232, the userport, and provides the VIC bank bits (lower 2 of port A). Each CIA has: - Two 16-bit timers (A and B). Timer A is normally used by the KERNAL IRQ for the jiffy clock. - 24-hour TOD clock with alarm (B.C.D format). - 8-bit serial shift register. - 16 GPIO lines (PA0-PA7, PB0-PB7), with separate DDR. - Interrupt control register (ICR) with sources from timers, TOD, serial, FLAG pin. The CIA is at $DC00 (CIA 1) and $DD00 (CIA 2); each is 16 bytes, mirrored every 16 bytes in its 256-byte page. ### 8.1 Joystick reading The joystick switches are wired in parallel with the keyboard matrix. Reading $DC00 (PRA, port 2 / left) and $DC01 (PRB, port 1 / right) gives a byte where the low 5 bits are active-low direction + fire. The high 3 bits are keyboard column bits. Rest position, no buttons, is $7F (127) on port 2 and $FF (255) on port 1. Important: in the C64, the CIA 1 IRQ line is the IRQ for the *whole CPU*. If you don't want CIA 1 timer A to fire IRQ (which is the default and triggers the jiffy-clock-and-keyboard-scan KERNAL handler), mask it via `$DC0D` (the ICR) at the start of your raster-IRQ setup. ## 9. The KERNAL 39 service routines, jump table at $FF81-$FFF3. The most useful ones for low-level work: | Use | Call | |-----|------| | Print a character | `JSR $FFD2` (CHROUT, A = char) | | Read a character | `JSR $FFCF` (CHRIN) — A = char on return | | Test for STOP key | `JSR $FFE1` (STOP) — carry set if STOP pressed | | Open a file | `JSR $FFC0` (OPEN) — A=LA, X=FA, Y=SA | | Close a file | `JSR $FFC3` (CLOSE) — A=LA | | Set filename | `JSR $FFBD` (SETNAM) — A=length, X/Y=ptr | | Set logical file | `JSR $FFBA` (SETLFS) — A=LA, X=FA, Y=SA | | Load | `JSR $FFD5` (LOAD) — .A=0 load, .A=1 verify | | Save | `JSR $FFD8` (SAVE) — A=zpage pointer, X/Y=end ptr | | Read clock | `JSR $FFDE` (RDTIM) — A/X/Y = jiffy/seconds/min | | Set clock | `JSR $FFDB` (SETTIM) — A/X/Y = same | | Plot (get/set cursor) | `JSR $FFF0` (PLOT) — carry=set, X=row, Y=col | Oscar64 wraps these in its runtime. `printf("hello")` ultimately calls CHROUT in a loop; `getch()` calls CHRIN; `oscar_kernalio.h` provides the serial-bus file routines. ## 10. The interrupt model ### 10.1 Hardware vectors In the top 6 bytes of KERNAL ROM (always mapped at reset, in the top of the 64 KB address space): - `$FFFA-$FFFB` NMI - `$FFFC-$FFFD` RESET - `$FFFE-$FFFF` IRQ / BRK The KERNAL entry points then read indirect RAM vectors: - `$0314-$0315` IRQ (default → $EA31, the KERNAL standard IRQ) - `$0316-$0317` BRK (used for the BASIC `BRK` instruction in monitor-style use) - `$0318-$0319` NMI (default → $FE47) ### 10.2 The KERNAL IRQ handler When an IRQ fires, the 6510 hardware pushes P and PC and jumps via ($FFFE) to $FF48. $FF48 pushes A, X, Y onto the stack and then indirect- jumps via $0314 (for IRQ) or $0316 (for BRK). The default $0314 → $EA31 runs the standard KERNAL ISR which: 1. Reads CIA 1 ICR to find the source. 2. If timer A: increments the jiffy clock at $A0-$A2. 3. Scans the keyboard. 4. Handles the cursor blink. 5. Checks for RUN/STOP. 6. Exits via $EA81 which pulls A/X/Y and `RTI`. The stack at the start of a user IRQ handler is (top to bottom): PC_hi, PC_lo, P, A, X, Y. The fastest way to do an IRQ that does work but still gets the KERNAL service: end with `JMP $EA31`. The fastest way to do an IRQ that needs no KERNAL service at all (e.g. a music player): end with `JMP $EA81`. ### 10.3 NMI NMI is wired to CIA 2's FLAG pin, which is the RESTORE key. The KERNAL's NMI handler at $FE47 is also the "soft reset" entry: if RUN/STOP is held when RESTORE is pressed, the system returns to BASIC without losing the program in memory. To disable the soft-reset, mask CIA 2 interrupts ($DD0D). ## 11. The character set and PETSCII PETSCII is the C64's character encoding — not ASCII. Printable range is $20-$7E and $A0-$FE (with some overlap differences). CR ($0D) is the line terminator, not LF. There's an upper/lowercase font switch: $00-$1F are graphics characters in uppercase mode; $00-$1F are uppercase letters in lowercase mode (the "shifted" font). The two font variants: - "Uppercase" / graphics mode: PETSCII $00-$1F → graphics chars - "Lowercase" mode: PETSCII $00-$1F → uppercase letters Switch with `iocharmap(IOCHM_PETSCII_2)` in Oscar64. In assembly: KERNAL call `CHROUT` with $0E to set uppercase+graphics, $8E for lowercase. In Oscar64 you can mark string literals with `p""` for PETSCII or `s""` for screen code (the byte values used directly in screen memory, which are the same as the character set ROM offsets). ## 12. Putting it all together: a tiny "hires clear" example In C, with Oscar64: ```c #include #include #include char Color[1000]; char Hires[8000]; Bitmap Screen; int main(void) { mmap_trampoline(); mmap_set(MMAP_RAM); // bank out BASIC+KERNAL memset(Color, 0x01, 1000); // white-on-black memset(Hires, 0x00, 8000); // clear bitmap mmap_set(MMAP_NO_ROM); // also bank out the I/O CHAR ROM // (so hires memory is visible) vic_setmode(VICM_HIRES, Color, Hires); vic.color_border = VCOL_WHITE; bm_init(&Screen, Hires, 40, 25); getch(); mmap_set(MMAP_ROM); // put ROMs back vic_setmode(VICM_TEXT, (char*)0x0400, (char*)0x1000); return 0; } ``` What this exercises: - Bank switching: `mmap_set` writes to $01 (and triggers the trampoline for IRQs). - Absolute addressing: `char *Hires = (char*)0xe000` is the straightforward way to reach video memory, made possible by `mmap_set(MMAP_RAM)` which gives us $E000-$FFFF as 8 KB of RAM (with the CHAR ROM not at $D000 anymore, because CHAREN=0 maps CHAR ROM there). - VIC control: `vic_setmode` writes ECM/BMM/MCM, $D018 (video matrix and bitmap pointers), $D016 (CSEL=1, XSCROLL=0), and the screen memory pointer (set via CIA 2 PRA low 2 bits, plus $DD00 writes). ## 13. Things to remember - The VIC and CPU share the bus. Badlines cost 40 cycles every 8 lines in text mode. Cycle-counting code on other lines gives you one cycle per line, but the badline penalty can blow your timing. - Writes to ROM addresses go to underlying RAM. This is how .prg files install themselves and how you can put code at $A000 without banking out BASIC (just write to the right places, but BASIC will still run if you don't bank it out). - The 6510 I/O port at $00/$01 is the master bank switch. Three bits (LORAM, HIRAM, CHAREN) plus the PLA. The Oscar64 `mmap_set` API exposes this as `MMAP_NO_BASIC`, `MMAP_RAM`, `MMAP_NO_ROM`, etc. - The VIC raster counter is 9 bits. Always write to $D011 bit 7 (RST8) and $D012 together to set the IRQ trigger line. Reading them also gives you 9 bits of current raster. - The KERNAL ISR at $EA31 does useful work (jiffy clock, keyboard scan, cursor blink) but you can call $EA81 to skip it. For a music player running in IRQ, the trick is to keep $EA31 alive by patching $0314 to your routine, ending with `JMP $EA31`. - PETSCII != ASCII. Use `p""` or `s""` prefixes in Oscar64, or `-psci` on the command line. Use the KERNAL `CHROUT` ($FFD2) and `CHRIN` ($FFCF) for character I/O so the translation happens. - The Color RAM upper nibble is meaningless. Only the low 4 bits are connected. - Sprites need to be 64-byte aligned in the current VIC bank. The pointers at $07F8-$07FF are byte values, pattern address / 64. - The SID's 6581 has bugs that the 8580 doesn't. If you want 4-bit sample playback, you need a 6581 (or simulate the bug). - The CIAs' FLAG pins are NMI/IRQ sources. The RESTORE key triggers an NMI through CIA 2. If you want RUN/STOP+RESTORE to be a no-op, mask CIA 2 NMI in $DD0D. ## 14. Where to look for more - `docs/c64/memory/memory_map.md` — every address - `docs/c64/memory/zeropage.md` — every zero-page address - `docs/c64/memory/hardware_internals.md` — block diagram and bus - `docs/c64/vic/vic_registers.md` — every VIC register - `docs/c64/vic/graphics_modes.md` — the 5 official modes - `docs/c64/vic/cebix-vic-article.txt` — Christian Bauer's VIC paper (the canonical timing reference, 80 pages, dense) - `docs/c64/sprites/sprites_overview.md` — sprite programming - `docs/c64/interrupts/raster_interrupt.md` — how to set up a raster IRQ - `docs/c64/interrupts/interrupts_overview.md` — IRQ/NMI/BRK flow - `docs/c64/cia/cia_overview.md` — CIA 1 + CIA 2 register maps - `docs/c64/sid/sid_overview.md` — SID register map, ADSR, filter - `docs/c64/kernal/kernal_jumptable.md` — every KERNAL entry point - `docs/c64/kernal/c64_programmers_reference_guide.txt` — the full 500-page official Commodore 64 Programmer's Reference Guide (1982) The PRG is dense and a bit old-fashioned (it's 1982) but it's the definitive reference and includes the full instruction set with cycle counts. Christian Bauer's VIC article is the modern, exhaustive treatment of the VIC-II — section 3.14 ("Effects and applications") is where the FLI/AFLI/NUFLI/etc. tricks are explained.