Initial commit: C64 project skeleton with oscar64 submodule
This commit is contained in:
+645
@@ -0,0 +1,645 @@
|
||||
# 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 #<raster_line ; raster line low
|
||||
STA $D012
|
||||
LDA #<Irq
|
||||
STA $0314 ; IRQ vector
|
||||
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 <c64/memmap.h>
|
||||
#include <c64/vic.h>
|
||||
#include <gfx/bitmap.h>
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user