Initial commit: C64 project skeleton with oscar64 submodule
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
# build artifacts
|
||||
src/build/
|
||||
*.prg
|
||||
*.asm
|
||||
*.map
|
||||
*.lbl
|
||||
*.int
|
||||
*.dbj
|
||||
*.csz
|
||||
*.d64
|
||||
*.bin
|
||||
*.crt
|
||||
|
||||
# oscar64 build output (built by `make -C make compiler` from oscar64 source)
|
||||
oscar64/bin/
|
||||
oscar64/build/
|
||||
|
||||
# editor / IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# misc
|
||||
*.log
|
||||
core
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "oscar64"]
|
||||
path = oscar64
|
||||
url = https://github.com/drmortalwombat/oscar64.git
|
||||
+935
@@ -0,0 +1,935 @@
|
||||
# Oscar64 — Learnings
|
||||
|
||||
Notes from exploring the [Oscar64](https://github.com/drmortalwombat/oscar64) C/C++
|
||||
cross-compiler that targets the 6502 family (C64, C128, PET, VIC-20, Plus/4, NES,
|
||||
Atari 8-bit, Commander X16, Mega 65). Source tree was copied from `~/temp/c64/oscar64`
|
||||
into `./oscar64`.
|
||||
|
||||
## 1. What it is, in one paragraph
|
||||
|
||||
Oscar64 is a native (not LLVM, not CC65) C/C++ compiler whose whole reason to
|
||||
exist is "fast, dense 6502 code from C". The author expected 6502 to be a bad C
|
||||
target — small register file, no stack-relative addressing, 256-byte hardware
|
||||
stack, no native 16-bit ops — and tried a bytecode interpreter first. The
|
||||
interpreter won on size but lost badly on speed, so they switched to native code
|
||||
generation and discovered that careful analysis actually beats the interpreter on
|
||||
both axes. The C64 hits 442 dhrystone V2.2 iterations/sec at `-O3`, which is
|
||||
the kind of number that makes you look twice. The compiler is self-contained
|
||||
(no external assembler / linker / CRT), supports disk overlays and banked
|
||||
cartridges for big projects, and has been used to ship multiple real games.
|
||||
|
||||
## 2. Repository layout
|
||||
|
||||
```
|
||||
oscar64/
|
||||
├── oscar64/ # The compiler itself (C++17, ~50 .cpp/.h files)
|
||||
│ ├── oscar64.cpp # CLI driver / main()
|
||||
│ ├── Compiler.{h,cpp} # Orchestrator: ParseSource() → GenerateCode() → WriteOutputFile()
|
||||
│ ├── Preprocessor.{h,cpp} # Macros, #embed, #for, #repeat
|
||||
│ ├── Scanner.{h,cpp} # Tokenizer
|
||||
│ ├── Parser.{h,cpp} # C99 + big chunks of C++
|
||||
│ ├── Declaration.{h,cpp} # AST / symbol tables
|
||||
│ ├── InterCode.{h,cpp} # Target-independent IR (the "IC_*" ops)
|
||||
│ ├── InterCodeGenerator.{h,cpp} # AST → IR
|
||||
│ ├── GlobalAnalyzer.{h,cpp} # Whole-program analysis
|
||||
│ ├── GlobalOptimizer.{h,cpp} # Whole-program opts (inlining, const-prop, dead code)
|
||||
│ ├── Constexpr.{h,cpp} # Compile-time evaluation
|
||||
│ ├── ByteCodeGenerator.{h,cpp} # Optional bytecode backend (-bc)
|
||||
│ ├── NativeCodeGenerator.{h,cpp} # 6502 native backend (the hot path)
|
||||
│ ├── NativeCodeOutliner.{h,cpp} # -Oo: extract repeated code sequences
|
||||
│ ├── Assembler.{h,cpp} # 6502 assembler + macros
|
||||
│ ├── Linker.{h,cpp} # Region/section/object layout
|
||||
│ ├── DiskImage.{h,cpp} # .d64 writer
|
||||
│ ├── Emulator.{h,cpp} # Built-in 6502 emulator for -e/-ep
|
||||
│ ├── Compression.{h,cpp} # LZO + RLE for #embed
|
||||
│ ├── Disassembler.{h,cpp} # For .lbl / .asm output
|
||||
│ └── ...
|
||||
├── include/ # Runtime headers + stdlib (what compiled programs #include)
|
||||
│ ├── crt.c, crt.h # Target-specific startup (linked unless -rt=)
|
||||
│ ├── oscar.c, oscar.h # oscar_expand_lzo / oscar_expand_rle
|
||||
│ ├── c64/ # VIC, CIA, SID, kernalio, rasterirq, memmap, sprites…
|
||||
│ ├── c128/, vic20/, plus4/, nes/, cx16/, opp/, audio/
|
||||
│ ├── gfx/ # bitmap, bitmapmc, vector3d, blob…
|
||||
│ ├── new/ # C++ new/delete
|
||||
│ └── stdio.h, stdlib.h, string.h, math.h, conio.h, …
|
||||
├── samples/ # ~100 worked-example programs
|
||||
│ ├── stdio/, kernalio/, memmap/, hires/, hiresmc/, particles/, fractals/,
|
||||
│ ├── rasterirq/, scrolling/, sprites/, games/
|
||||
│ └── resources/ # .bin / .ctm / .spd assets used by samples
|
||||
├── autotest/ # Self-test harness
|
||||
├── CMakeLists.txt # C++17, globs oscar64/*.cpp
|
||||
├── make/ # Makefile-based build (no CMake needed)
|
||||
├── oscar64.sln / .vcxproj
|
||||
└── oscar64.md # The full reference manual (also in README)
|
||||
```
|
||||
|
||||
Build: `make -C make compiler` (uses the makefile). Or `cmake -B build && cmake --build build` — both produce a single `oscar64` binary. Windows is supported via the included `.sln`.
|
||||
|
||||
## 3. Compilation pipeline
|
||||
|
||||
From `oscar64.cpp` (the driver) the lifecycle is short and explicit:
|
||||
|
||||
```cpp
|
||||
Compiler* compiler = new Compiler();
|
||||
compiler->ParseSource(); // lex + parse + AST for every translation unit
|
||||
compiler->GenerateCode(); // analyze, optimize, code-gen, link
|
||||
compiler->WriteOutputFile(...);// emit .prg / .crt / .bin (+ .map / .asm / .int / .lbl)
|
||||
```
|
||||
|
||||
Inside `Compiler::ParseSource()` (`Compiler.cpp:57`):
|
||||
|
||||
1. Choose zero-page register layout from `-tm` (the 6502 has no general-purpose
|
||||
regs, so the compiler dedicates a fixed ZP window per target — see
|
||||
`BC_REG_*` symbols).
|
||||
2. Loop: pop a pending `CompilationUnit` from the queue, open the file via
|
||||
`Preprocessor`, feed it to `Scanner` → `Parser`. `#pragma compile("foo.c")`
|
||||
enqueues additional units, which is how the "no makefile" build model works:
|
||||
`#include <stdio.h>` literally drags `stdio.c` into the build.
|
||||
|
||||
Inside `Compiler::GenerateCode()` (`Compiler.cpp:550`):
|
||||
|
||||
1. Verify the runtime startup (`crt.c`) is present, otherwise error.
|
||||
2. Materialize default memory regions for the chosen target machine — e.g. for
|
||||
a C64 PRG: `zeropage` at `0xf7..0xff` (or `0x80..0xff` with `-xz`), `startup`
|
||||
at `0x801..0x880` (native) or `0x801..0x900` (bytecode), and a `main` region
|
||||
that holds `code`, `data`, `bss`, `heap`, `stack`.
|
||||
3. Build the vtables (`BuildVTables`), check `operator new` overrides, finish
|
||||
deferred template expansion.
|
||||
4. Per-procedure: `CompileProcedure()` → `InterCodeGenerator` lowers the AST to
|
||||
IC, then for `-n` (native, the default) `NativeCodeGenerator` emits 6502,
|
||||
otherwise `ByteCodeGenerator` produces interpreter ops.
|
||||
5. `NativeCodeOutliner` (when `-Oo`) collapses repeated code sequences.
|
||||
6. `GlobalOptimizer` runs across the whole program: inlining, constant
|
||||
propagation, dead-code removal, value-range narrowing, constant-parameter
|
||||
folding, recursion/function-pointer detection, etc.
|
||||
7. `Linker` lays out objects into regions, produces `.prg` (or `.crt`/`.bin`),
|
||||
plus `.map`, `.asm`, `.int`, `.lbl`, and (with `-g`) `.dbj` JSON debug info.
|
||||
|
||||
The key takeaway: there is no separate `cc1` / `ld` / `as` — it is one
|
||||
whole-program compiler. The "linker" is a pass that knows every function and
|
||||
chooses what to emit.
|
||||
|
||||
## 4. How it beats the 6502
|
||||
|
||||
The reference manual is upfront about the tricks. The four big ones:
|
||||
|
||||
- **Software stack in zero page.** Locals and parameters live in ZP, not on the
|
||||
256-byte hardware stack. The C64 default reserves `0xf7..0xff` for this; with
|
||||
`-xz` it expands to `0x80..0xff` (and you lose the `STOP/RESTORE` BASIC return).
|
||||
- **Static call-graph stack.** Because the compiler sees the whole program, it
|
||||
analyses the call graph and gives every function a fixed ZP frame offset
|
||||
instead of pushing/popping. This is why *recursion* and *function pointers*
|
||||
are explicitly called out in the manual as "you will pay for this" — both
|
||||
defeat the static analysis and force real stack usage.
|
||||
- **Zero page as register file.** Globals can be opted into ZP with
|
||||
`__zeropage`; the compiler also auto-places small globals into ZP under `-Oz`.
|
||||
With `-xz` the wider ZP window gives you ~128 bytes of pseudo-registers.
|
||||
- **Value-range analysis.** 16-bit ops on the 6502 are *much* more expensive
|
||||
than 8-bit. The compiler narrows arithmetic to 8 bits whenever it can prove
|
||||
the value fits. Pointers and globals are harder to narrow (they could change
|
||||
behind the compiler's back) so the manual hammers the "use 8-bit locals, mark
|
||||
things `const`, prefer `unsigned`" advice.
|
||||
|
||||
The classic 6502 limitation "no indirect-with-offset" is worked around with
|
||||
`__striped` arrays — instead of `LHLHLHLH…` you get `LLLLLLLLHHHHHHHH` so
|
||||
each element can be reached with `abs,X` instead of needing a multiply.
|
||||
|
||||
## 5. The C/C++ language surface
|
||||
|
||||
C99 is supported in full. C++ mode is selected with `-pp` or a `.cpp` file. The
|
||||
listed supported C++ features (from `oscar64.md`) are surprisingly complete:
|
||||
|
||||
namespaces, references, member functions, ctors/dtors, operator overloading,
|
||||
single inheritance, `const` methods, `new`/`delete`/`new[]`/`delete[]`, virtual
|
||||
functions, `string`/`iostream`, default params, templates, vector/array/list,
|
||||
lambdas, `auto`, range-for, `constexpr`, parameter packs. Not a current
|
||||
standard, but plenty for a 6502.
|
||||
|
||||
## 6. 6502-specific extensions (the fun stuff)
|
||||
|
||||
These are the bits that make Oscar feel like "C for the C64" rather than
|
||||
"hosted C squeezed onto a 6502":
|
||||
|
||||
- **`#embed`** — the preprocessor can read a binary file, optionally compress
|
||||
it (`lzo` / `rle`), slice an offset+length, and emit 8-bit or 16-bit words.
|
||||
It also understands Charpad `.ctm` (chars/tiles/map/attr1/attr2) and
|
||||
Spritepad `.spd` (sprites/tiles). This is the standard way to ship charset
|
||||
and sprite data in a sample.
|
||||
- **`#pragma compile("file.c")`** — pulls another translation unit into the
|
||||
build. The whole "no makefile" build model rests on this: `#include <stdio.h>`
|
||||
ends up `#pragma compile("stdio.c")`-ing the stdio implementation.
|
||||
- **Storage-class qualifiers:** `__native` (force native codegen for a fn),
|
||||
`__zeropage` (place a global in ZP), `__noinline`, `__forceinline`, `__export`
|
||||
(force a symbol to be emitted even if unreferenced), `__striped` (the
|
||||
layout trick above), `__memmap` (a stronger memory fence than `volatile`,
|
||||
used for things like bank registers that re-map the address space).
|
||||
- **Pragmas:** `region(name,start,end,flags,bank,{sections})` and
|
||||
`section(name,…)` reshape the memory map. `align(var, 8)`, `heapsize()`,
|
||||
`stacksize()`, `overlay(name, bank)`, `unroll(n|full|page)`, `optimize(…)`,
|
||||
`reference(name)`, `intrinsic(name)`, `message(...)`, `charmap(char,code,…)`,
|
||||
`callinline()`, `warning(disable:num,…)`.
|
||||
- **PETSCII and screen-code literals:** `p"hello"` / `P"hello"` makes a
|
||||
PETSCII string, `s"hello"` / `S"hello"` makes screen codes. `-psci` flips
|
||||
the default for unprefixed strings. `iocharmap(IOCHM_PETSCII_2)` also
|
||||
swaps the font.
|
||||
- **Inline assembler with full C interop:** `__asm { lda c ; bne w1 ; … }`,
|
||||
where `c` is a local var (ZP register) and `accu`/`addr`/`sp`/`fp` are the
|
||||
fixed return-value and frame-pointer slots. `Type::Member` (e.g. `lda #P::ns`)
|
||||
generates a struct member offset. `__interrupt` saves ZP scratch regs; `__hwinterrupt`
|
||||
also saves A/X/Y and exits with `rti`. A top-level `__asm name { … }` defines
|
||||
a pure-asm routine. The optimizer (`-Oa`, on at `-O2+`) will rewrite your
|
||||
inline asm.
|
||||
- **Hypotheticals:** `__assume(x < 10)`, `__assume(p != nullptr)`,
|
||||
`__assume(false)` for `default:` arms — these feed the value-range analysis.
|
||||
- **Preprocessor loops:** `#assign name expr` and `#repeat … #until cond` let
|
||||
you generate unrolled code (e.g. 25 absolute stores to fill a screen
|
||||
column). `#for(i, n) text` is the one-liner form.
|
||||
|
||||
## 7. Memory model and program layout
|
||||
|
||||
The "Region / Section / Object" three-level linker is worth understanding because
|
||||
almost every advanced sample uses it:
|
||||
|
||||
- **Region** = a physical chunk of memory (or a cartridge bank).
|
||||
`#pragma region(main, 0x0a00, 0xa000, , , {code, data, bss, heap, stack})`
|
||||
reshapes the default map. Flags and an optional bank slot exist for
|
||||
cartridge layouts.
|
||||
- **Section** = a logical bucket (`code`, `data`, `bss`, `heap`, `stack`,
|
||||
`zeropage`, or a user-named one). `#pragma section(name, 0, startSym, endSym)`
|
||||
declares it; the start/end symbols are emitted so asm can refer to them
|
||||
(this is how `crt.c` exposes `StackStart`, `StackEnd`, `BSSStart`, `BSSEnd`,
|
||||
`CodeStart`, `CodeEnd`).
|
||||
- **Object** = one actual blob: a function, a `const` array, a BSS variable,
|
||||
a pad, etc. The linker walks the dependency graph from `main` (via the
|
||||
startup code in `crt.c`) and only includes reachable objects, which is why
|
||||
the whole stdio family can live in a single `.c` without exploding the
|
||||
output.
|
||||
|
||||
For a default C64 PRG the layout is:
|
||||
- `0x0801–0x0880` (native) or `0x0801–0x0900` (bytecode) — startup + BASIC stub.
|
||||
- `0x0900–0x0a00` — bytecode jump table (only when not all native).
|
||||
- `0x0a00–0xa000` — main, with `code`, `data`, `bss`, `heap`, `stack` sections.
|
||||
|
||||
For cartridges, `easyflash` (`-tf=crt`) expands bank 0 into common memory at
|
||||
startup; generic `crt8`/`crt16` use the `rom` region from bank 0 and put
|
||||
`bss`/`stack`/`heap` in `main`. The `__bankof(expr)` operator gives you the
|
||||
bank id of a symbol, so dynamic code can `mmap_set` to the right bank before
|
||||
calling.
|
||||
|
||||
For larger programs the `#pragma overlay(name, bank)` mechanism turns each
|
||||
cartridge bank into a `.prg` file inside the `.d64` image, and `krnio_load`
|
||||
brings it in on demand — that's how the games fit.
|
||||
|
||||
## 8. Targets and output formats
|
||||
|
||||
`-tm` picks the machine: c64, c128, c128b, c128e, plus4, vic20 (+3/+8/+16/+24),
|
||||
pet (8k/16k/32k), nes (nrom, nrom_h, nrom_v, mmc1, mmc3), atari, x16, mega65.
|
||||
`-tf` picks the file: `prg`, `crt`, `crt8`, `crt16`, `bin`. `-d64=foo.d64`
|
||||
builds a disk image and `-f=path` / `-fz=path` add (optionally compressed)
|
||||
resource files to it; `oscar_expand_lzo` decompresses at runtime, and
|
||||
`krnio_read_lzo` decompresses on the fly during a kernal read.
|
||||
|
||||
## 9. Built-in emulator
|
||||
|
||||
`-e` runs the program in a built-in 6502 emulator, `-ep` profiles it. This is
|
||||
the iteration loop: write C, compile, run, no need to launch VICE for the
|
||||
common case. (VICE is still the right choice when you need cycle-exact raster
|
||||
behaviour — see `-moncommands` for loading the `.lbl` symbol file.)
|
||||
|
||||
## 10. Useful artifacts the compiler writes
|
||||
|
||||
Beyond the main binary, you get (all explained in `oscar64.md`):
|
||||
|
||||
- `.map` — regions, sections, objects, objects-by-size. The first place to
|
||||
look when your `.prg` is too big.
|
||||
- `.asm` — full 6502 listing with source references when `-g` is on. This is
|
||||
what you cross-reference from inside the VICE monitor.
|
||||
- `.int` — the intermediate code dump.
|
||||
- `.lbl` — `al address .symbol` lines for VICE's monitor.
|
||||
- `.dbj` — JSON with `memory`, `variables`, `functions`, `types`. Combined
|
||||
with [Modern VICE PDB Monitor](https://github.com/MihaMarkic/modern-vice-pdb-monitor)
|
||||
this gives source-level debugging. The manual recommends `-n -g -O0` for
|
||||
the best debug experience.
|
||||
- `.csz` — every source line annotated with its start address and emitted
|
||||
byte count. Great for finding the one loop that ate all your memory.
|
||||
|
||||
## 11. Optimization levels (cheat sheet)
|
||||
|
||||
```
|
||||
-O0 no optimization
|
||||
-O / -O1 default
|
||||
-O2 + auto-inline, inline-asm optimizer, constant-parameter folding
|
||||
-O3 + auto-place globals in ZP, outliner
|
||||
-Os size
|
||||
-Oi auto-inline (sub-flag of O2/O3)
|
||||
-Oa inline-asm optimizer (sub-flag of O2/O3)
|
||||
-Oz globals → zero page (sub-flag of O3)
|
||||
-Op constant-parameter folding
|
||||
-Oo outliner (extract repeated sequences)
|
||||
-Ox pointer-arith page-boundary awareness
|
||||
-OM self-modifying code (saves bytes when SMC is cheap)
|
||||
```
|
||||
|
||||
The mantra in the manual: "Avoid recursion. Avoid function pointers. Be aware
|
||||
of aliasing. Prefer unsigned. Stick to 8 bits. Prefer enums over #defines. Mark
|
||||
const. Use `__assume`." Each of those maps directly to a 6502 pain point.
|
||||
|
||||
## 12. A minimal end-to-end
|
||||
|
||||
From `samples/stdio/helloworld.c` (9 lines, the whole file):
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
putchar(14); // clear screen
|
||||
printf(p"Hello World\n"); // p"" → PETSCII string
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Build: `oscar64 helloworld.c` → `helloworld.prg`. Run with `x64 helloworld.prg`
|
||||
or `oscar64 helloworld.c -e`. The C runtime comes from `include/crt.c` and
|
||||
`include/stdio.c`, both pulled in automatically by `#include <stdio.h>` (which
|
||||
in turn `#pragma compile("stdio.c")`s the stdio implementation, which
|
||||
`#pragma compile("conio.c")`s conio, which is how the whole tree stitches
|
||||
together with no makefile).
|
||||
|
||||
A more interesting example, `samples/hires/lines.c`, shows the full
|
||||
hardware-programming idiom: `#include <c64/memmap.h>` to bank out BASIC,
|
||||
`#include <c64/vic.h>` to flip into hires mode, `#include <gfx/bitmap.h>` for
|
||||
`bm_line`, and the `Bitmap` / `ClipRect` types. Note `char *Hires = (char *)0xe000;`
|
||||
— absolute addressing is the normal way to reach video memory.
|
||||
|
||||
## 13. Things I want to remember when I come back
|
||||
|
||||
- It is one binary. No assembler, no linker, no CRT to wire up. `crt.c` is
|
||||
pulled in by default; `-rt=foo.c` swaps it, `-rt=` removes it.
|
||||
- Source files are pulled in by `#pragma compile("x.c")`, not by listing them
|
||||
on the command line. The only files on the command line are the user's root
|
||||
translation units (and they probably just `#include` headers that do the
|
||||
pragma work).
|
||||
- The CLI is `-option` style. `-i=path` adds an include, `-ii=path` *replaces*
|
||||
the default include, `-tf=…`/`-tm=…` set format/machine, `-dNAME` defines a
|
||||
macro, `-O3` is the speed preset, `-e` runs the built-in emulator, `-g`
|
||||
emits `.dbj` and source lines in `.asm`.
|
||||
- Native code is the default (`-n` is implicit now). `-bc` opts into bytecode.
|
||||
- "Region / Section / Object" is the mental model for the linker. Almost every
|
||||
non-trivial memory layout in the samples is a `#pragma region(…)`.
|
||||
- The two escape hatches when the optimizer is wrong: `__assume(cond)` and
|
||||
`#pragma optimize(noasm)` (or `__asm volatile { … }` for a single statement).
|
||||
- Read `oscar64.md` in the repo root — it is the real reference manual and is
|
||||
far more thorough than what fits here.
|
||||
|
||||
---
|
||||
|
||||
# Part II — Programming the C64 with Oscar64
|
||||
|
||||
This section combines the Oscar64 architecture notes above with the C64
|
||||
hardware reference in `../PROG_C64.md` and `../docs/c64/`. The goal is a
|
||||
practical "how do I write C for the C64 with this compiler and not
|
||||
shoot myself in the foot" guide.
|
||||
|
||||
## 14. The C64 memory model in Oscar64
|
||||
|
||||
### 14.1 The default region layout (no `region`/`section` pragmas)
|
||||
|
||||
The runtime `crt.c` materializes these regions for a C64 PRG (the
|
||||
`Compiler::GenerateCode` switch in `Compiler.cpp:550-650` is the
|
||||
authoritative source):
|
||||
|
||||
| Region | Address | Contents |
|
||||
|--------|---------|----------|
|
||||
| `startup` | $0801-$0880 (native) | BASIC stub, KERNAL trampoline, init code, `main()` |
|
||||
| `main` | $0900-$A000 | `code`, `data`, `bss`, `heap`, `stack` sections |
|
||||
|
||||
This gives you 38 KB of usable RAM at $0900-$A000 once the KERNAL ISR
|
||||
trampoline is set up. (For a fresh C64 that's *less* than BASIC sees,
|
||||
but in exchange you have the entire 38 KB contiguous, no ROM reloads,
|
||||
and a known startup state.)
|
||||
|
||||
Inside the `main` region, the default stack size is 1 KB and the default
|
||||
heap size is 1 KB. Both are tweakable with `#pragma stacksize(N)` and
|
||||
`#pragma heapsize(N)`; setting `heapsize(0)` is mandatory if you don't
|
||||
use `malloc`.
|
||||
|
||||
### 14.2 The zeropage software-stack window
|
||||
|
||||
Oscar64 dedicates a fixed zero-page window per target machine to its
|
||||
software stack (parameters, locals, temporaries). For the C64 with
|
||||
`-O3` the default window is `0xF7..0xFF` (9 bytes: frame pointer, stack
|
||||
pointer, two scratch pairs, plus a return-address register). With
|
||||
`-xz` you get `0x80..0xFF` (128 bytes — much more room for locals, but
|
||||
the KERNAL's `STOP/RESTORE` warm-start vector at $02/$03 stops working,
|
||||
which is why the Oscar64 docs call it "extended zero page usage").
|
||||
|
||||
This means: by default, you cannot freely use $F7-$FF. The Oscar64
|
||||
runtime uses them, and so does its `mmap_set` trampoline. If you write
|
||||
inline asm that touches those addresses, you will collide. If you want
|
||||
more zero-page scratch you have three options:
|
||||
1. Compile with `-xz` to widen the window.
|
||||
2. Use `__zeropage` on globals (compiler places them in `$80..$F7` if
|
||||
`-Oz` is on, otherwise wherever it fits).
|
||||
3. Mark the global `__zeropage int x;` and use the same name in
|
||||
inline asm — Oscar64 will route both to the same ZP slot.
|
||||
|
||||
### 14.3 The bank-switching model
|
||||
|
||||
The C64's `mmap_set()` API in `c64/memmap.h` writes to `$01` (the
|
||||
6510 port) to change which ROMs and I/O are visible. The trampoline
|
||||
that `mmap_set` installs at $EA31 (the KERNAL IRQ vector location) is
|
||||
key: when an IRQ fires, the trampoline briefly restores the default
|
||||
memory configuration, calls the real KERNAL ISR, and then re-applies
|
||||
your mapping. Without that trampoline, the KERNAL's IRQ handler
|
||||
(which is at $EA31 in KERNAL ROM) would see whatever banking you've
|
||||
set up, which will crash because the KERNAL isn't actually there.
|
||||
|
||||
Practical implications:
|
||||
- If you write your own raster IRQ, end with `JMP $EA31` (which goes
|
||||
through the trampoline) **not** `JMP kernal_isr` directly, unless
|
||||
you have re-implemented the IRQ save/restore yourself.
|
||||
- `mmap_trampoline()` is called once at startup and must complete
|
||||
successfully before you change the memory configuration. It
|
||||
allocates 2 bytes somewhere safe (usually in the stack area).
|
||||
|
||||
### 14.4 What goes where, end to end
|
||||
|
||||
| Memory | Used by | Why |
|
||||
|--------|---------|-----|
|
||||
| $00-$01 | 6510 port (always) | Read/write = port bits, not RAM |
|
||||
| $02-$8F | KERNAL/BASIC scratch | Don't touch in your C code |
|
||||
| $90-$A2 | KERNAL state (jiffy clock, I/O status) | Use `kernal_*` functions, don't POKE |
|
||||
| $C0-$CF | KERNAL screen editor | Use conio.h, don't POKE |
|
||||
| $F7-$FF | Oscar64 ZP software stack | Don't touch in inline asm |
|
||||
| $0100-$01FF | 6502 hardware stack | Don't store persistent C data here |
|
||||
| $0400-$07FF | Screen RAM (40×25) | Use `conio.h` for text I/O |
|
||||
| $0801-$0880 | Startup (your code starts here) | Don't try to call into it |
|
||||
| $0900-$A000 | Your code, data, bss, heap, stack | Where the linker puts your stuff |
|
||||
| $A000-$BFFF | BASIC ROM (default) or your RAM (`-O3` banks it out) | Bank-switch with `mmap_set` |
|
||||
| $C000-$CFFF | Always free for your code | Bank-switch with `mmap_set` |
|
||||
| $D000-$DFFF | I/O + Color RAM (CHAR ROM if CHAREN=0) | Use the `c64/*.h` headers |
|
||||
| $D800-$DBFF | Color RAM (low 4 bits only) | `screen.color[i] = c;` style helpers |
|
||||
| $E000-$FFFF | KERNAL ROM (default) or your RAM | Bank-switch with `mmap_set` |
|
||||
|
||||
## 15. How Oscar64 helps — and what it doesn't
|
||||
|
||||
This is the section that says "you used to have to do this in assembly,
|
||||
now you can do it in C, but you still have to know about the hardware
|
||||
because the compiler won't do the unsafe thing for you."
|
||||
|
||||
### 15.1 The wins
|
||||
|
||||
- **Replacing hand-rolled 6502 ASM.** Things like the dhrystone
|
||||
benchmark at 442 iterations/sec (cited in the Oscar64 README) used
|
||||
to require an expert ASM programmer. With Oscar64 you can express
|
||||
the same logic in C, write `__assume` hints, and let the optimizer
|
||||
produce near-optimal code. The oscar64 manual has a worked example
|
||||
where a `Plot()` function written in C and marked `__native` ends
|
||||
up exactly as good as hand-written ASM.
|
||||
|
||||
- **No more "this is a global, so it's worse" pessimization.** Static
|
||||
call-graph analysis means Oscar64 gives every function a fixed ZP
|
||||
frame offset for its parameters and locals. You can pass ints to
|
||||
functions and Oscar64 will try to use absolute addressing when it
|
||||
can prove the value is in the ZP-resident static frame.
|
||||
|
||||
- **Striped arrays for the 6502 no-indirect-with-offset problem.** The
|
||||
`__striped` qualifier is the killer feature for hires graphics. A
|
||||
320-byte bitmap with `__striped` becomes `LLLLLLLLHHHHHHHH` so the
|
||||
hot inner loop compiles to `LDA $xxxx,Y` instead of needing a
|
||||
multiply.
|
||||
|
||||
- **PETSCII/screen-code literals.** `printf(p"Hello\n")` and the
|
||||
`p""` / `s""` prefixes, plus `iocharmap(IOCHM_PETSCII_2)`, mean you
|
||||
don't have to write a translation table for every string. The
|
||||
compiler emits the correct byte sequence for the active character
|
||||
set at compile time.
|
||||
|
||||
- **Inline asm with full C interop.** When you do need to drop to
|
||||
ASM, you can write `__asm { lda c ; bne w1 ; … }` and the
|
||||
compiler will substitute `c` with its ZP location, set up the
|
||||
right addressing mode, and let the optimizer clean up afterwards
|
||||
(when `-Oa` is on, which it is for `-O2+`).
|
||||
|
||||
- **Compile-time `#embed` for binary data.** No more `FOR I=0 TO 62:
|
||||
READ B: POKE 12288+I,B: NEXT I` in BASIC. The `#embed` directive
|
||||
pulls in a `.bin`, `.ctm`, or `.spd` file at compile time, with
|
||||
optional LZO or RLE compression, and emits the bytes as a `const`
|
||||
array you can reference directly.
|
||||
|
||||
- **`#pragma compile("foo.c")` means no makefile.** The entire stdio,
|
||||
conio, gfx, and c64/ families of headers pull in their
|
||||
implementations this way. You write one `main.c`, `#include
|
||||
<stdio.h>`, and the compiler drags in `stdio.c` and `conio.c` and
|
||||
`crt.c` automatically.
|
||||
|
||||
- **Whole-program dead-code elimination.** `printf("hello\n")`
|
||||
pulls in only the code path that formats a string and calls
|
||||
`CHROUT`. The floating-point formatter, the file I/O, the scanf
|
||||
family — all of it disappears unless you actually use it. With
|
||||
`-dNOFLOAT` you can also strip float support from printf entirely.
|
||||
|
||||
- **Automatic zero-page placement under `-Oz`.** Globals and small
|
||||
arrays that the optimizer can prove are hot get moved into ZP at
|
||||
`-O3 + -Oz`. You don't have to manually mark every variable; the
|
||||
compiler does it for you.
|
||||
|
||||
- **Disk overlays via `-tf=d64`.** Banked code becomes a `.prg` per
|
||||
bank inside the disk image. You can write your C code as if it
|
||||
were all linked, and the compiler figures out which bank each
|
||||
function goes into.
|
||||
|
||||
- **The built-in emulator (`-e`, `-ep`).** Edit, compile, run, watch
|
||||
the screen. No need to launch VICE for the iteration loop. (VICE
|
||||
is still the right choice for cycle-exact raster work.)
|
||||
|
||||
### 15.2 The "you still need to know the hardware" cases
|
||||
|
||||
- **The badline penalty.** A `for` loop that runs across a badline
|
||||
boundary will take 40 extra cycles on that one line. The compiler
|
||||
doesn't know about badlines. If you have a tight loop that needs
|
||||
to be cycle-stable, put it all inside one raster line (a "stable
|
||||
raster" loop, commonly on line 311 on PAL) or you'll see glitches.
|
||||
Workaround: use a raster IRQ to start the work right after a known
|
||||
stable line.
|
||||
|
||||
- **VIC bank bits.** The VIC only has a 14-bit address bus. The
|
||||
other 2 bits come from CIA 2 PRA. Oscar64's `screen` / `bitmap`
|
||||
helpers manage this for you, but if you write inline asm that
|
||||
reads video memory at a literal address, you have to make sure
|
||||
the right bank is selected. The right thing is to set up a
|
||||
`char * const Hires = (char *)0xe000;` in C — Oscar64 will emit
|
||||
the right bank-switch dance for you the first time you read or
|
||||
write through that pointer (or at startup, depending on the
|
||||
helper).
|
||||
|
||||
- **Color RAM upper nibble.** The compiler can't help you if you
|
||||
`*((char *)0xd800) = 0xFF;` and expect the high nibble to be 0.
|
||||
It won't be. Use `screen.color[i] = c;` or `POKE_COLOR(0xd800, i,
|
||||
c)` style helpers instead.
|
||||
|
||||
- **Sprite pointers need 64-byte alignment.** Oscar64's `Sprite` type
|
||||
in `c64/sprites.h` handles this for you (it pads the structure to
|
||||
64 bytes), but if you declare `char my_sprite[63]` and `POKE 2040,
|
||||
(int)my_sprite/64;`, you'll crash if `my_sprite` isn't aligned.
|
||||
Use the helpers.
|
||||
|
||||
- **PETSCII vs ASCII.** Writing `"Hello\n"` in C gives you ASCII
|
||||
bytes 48, 65, 6C, 6C, 6F, 0A. The C64's character ROM has `H` at
|
||||
PETSCII 8, not 65, and uses CR (0x0D) instead of LF (0x0A). Use
|
||||
the `p""` prefix in Oscar64 or the compiler flag `-psci`, or your
|
||||
text will look like garbage.
|
||||
|
||||
- **KERNAL save/restore around the trampoline.** If you call into
|
||||
the KERNAL directly (e.g. `JSR $FFD2` to print a character), the
|
||||
trampoline will fix up the memory configuration for you. If you
|
||||
jump to a KERNAL function in the middle (e.g. setting up your own
|
||||
routine that ends with `JMP $EA81`), the KERNAL will read its own
|
||||
data at $EA31 and find your trampoline instead — which is fine if
|
||||
the trampoline is installed, but if you've done a `mmap_set` that
|
||||
banks out KERNAL, you'll crash. The fix is to call
|
||||
`mmap_set(MMAP_ROM)` first, *then* do your JMP.
|
||||
|
||||
- **Cycle-counting is your job.** Oscar64 will tell you the cycle
|
||||
count of the code it generated (look at the `.asm` file), but it
|
||||
won't tell you that the work happens during a badline and is
|
||||
therefore going to be 40 cycles slower. If you're trying to fit
|
||||
code in a specific raster window, you still need to read the
|
||||
Christian Bauer VIC article.
|
||||
|
||||
- **The CIA 1 IRQ is the system's IRQ.** If you set up a raster IRQ
|
||||
and forget to mask CIA 1's timer A in `$DC0D`, you'll get a
|
||||
nested IRQ every jiffy (1/60 sec) and your raster code will
|
||||
glitch. The Oscar64 `rasterirq.h` library handles this for you;
|
||||
the pattern is always:
|
||||
```c
|
||||
SEI;
|
||||
POKE(0xDC0D, 0x7F); // mask CIA 1 IRQs
|
||||
POKE(0xDD0D, 0x7F); // mask CIA 2 IRQs
|
||||
POKE(0xD01A, 0x01); // enable VIC raster IRQ
|
||||
CLI;
|
||||
```
|
||||
|
||||
- **Self-modifying code is sometimes required.** The 6502 has no
|
||||
indirect-with-offset addressing for arrays, so a "load a byte from
|
||||
a C array" can take more cycles than a "load a byte from a known
|
||||
constant address". Sometimes the right answer is `__asm { lda
|
||||
my_const+1 }` after the compiler has put `my_const` in a known
|
||||
place. Oscar64 supports this with `#pragma optimize(asm)` to
|
||||
allow the optimizer to clean it up, or `__asm volatile { … }` if
|
||||
you want it untouched.
|
||||
|
||||
## 16. Oscar64 patterns for the C64 (a tour)
|
||||
|
||||
### 16.1 Hires frame setup, the idiomatic way
|
||||
|
||||
```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 → 38 KB free
|
||||
mmap_set(MMAP_NO_ROM); // also bank out CHAR ROM at $D000
|
||||
|
||||
memset(Color, 1, 1000); // white on black
|
||||
memset(Hires, 0, 8000); // clear bitmap
|
||||
vic_setmode(VICM_HIRES, Color, Hires);
|
||||
vic.color_border = VCOL_WHITE;
|
||||
bm_init(&Screen, Hires, 40, 25);
|
||||
|
||||
// ... draw stuff ...
|
||||
getch();
|
||||
|
||||
mmap_set(MMAP_ROM); // restore for clean exit
|
||||
vic_setmode(VICM_TEXT, (char *)0x0400, (char *)0x1000);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Compare to the equivalent in raw ASM:
|
||||
- Set `$01` to `$34` (bank out BASIC+KERNAL+CHAREN).
|
||||
- Wait for a stable raster line.
|
||||
- Write `$D011` for ECM=BMM=0, DEN=1, RSEL=1.
|
||||
- Write `$D016` for MCM=0, CSEL=1.
|
||||
- Write `$D018` to set the screen memory and bitmap base.
|
||||
- Set CIA 2 PRA bits 0-1 to the right VIC bank.
|
||||
- Set up VIC bank by writing to `$DD00`.
|
||||
- Clear 8 KB of bitmap and 1 KB of color RAM.
|
||||
- ... draw ...
|
||||
- Reverse everything.
|
||||
|
||||
The C version is 5 lines of setup. The ASM version is 30+.
|
||||
|
||||
### 16.2 Sprites the right way
|
||||
|
||||
```c
|
||||
#include <c64/sprites.h>
|
||||
|
||||
// Sprite data is 64 bytes (63 + 1 pad) per sprite
|
||||
const char MySprite[64] = { 0, 126, 0, 3, 255, 192, /* ... */ };
|
||||
|
||||
int main(void) {
|
||||
// ... init ...
|
||||
spr_init(SPRITES_BASE); // sets up sprite pointers
|
||||
spr_set(0, MySprite, 100, 100, VCOL_WHITE);
|
||||
spr_show(0, true);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In `c64/sprites.h`, `spr_set` does the math (`(int)MySprite >> 6` for
|
||||
the pointer, separate X/Y writes, color register write). The
|
||||
`Sprite[8]` and `SpritePointer[8]` types handle the "where do I put
|
||||
sprite data" question (must be 64-byte aligned in the current VIC
|
||||
bank; the helper reserves a 512-byte region and aligns each sprite
|
||||
inside it).
|
||||
|
||||
### 16.3 Raster IRQ, the right way
|
||||
|
||||
```c
|
||||
#include <c64/rasterirq.h>
|
||||
|
||||
RIRQCode screenRIRQ;
|
||||
|
||||
void __interrupt screen_handler(void) {
|
||||
// ... do per-line work ...
|
||||
rasterirq_next(&screenRIRQ, line + 8); // schedule next IRQ
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// ...
|
||||
rasterirq_init();
|
||||
screenRIRQ.handler = screen_handler;
|
||||
rasterirq_setup(&screenRIRQ, 100, 0); // line 100, no change
|
||||
rasterirq_enable(&screenRIRQ);
|
||||
rasterirq_sort(); // sort all active RIRQs
|
||||
// ... main loop ...
|
||||
}
|
||||
```
|
||||
|
||||
This wraps all the `SEI/CLI/POKE $D01A/$0314/$0315` dance and also
|
||||
installs a KERNAL trampoline that preserves the jiffy clock and
|
||||
keyboard scan. Without `rasterirq.h` you'd write 30+ lines of
|
||||
assembly and 2 separate hand-managed chains of ISRs.
|
||||
|
||||
### 16.4 The KERNAL ISR trick
|
||||
|
||||
If you want to keep the KERNAL's jiffy clock, keyboard scan, and
|
||||
cursor blink going while running your own raster code, end your ISR
|
||||
with `JMP $EA31` (not $EA81). The KERNAL ISR at $EA31 will run after
|
||||
yours and do the standard work. The Oscar64 `rasterirq.h` library
|
||||
uses this approach for all its "system-friendly" RIRQs.
|
||||
|
||||
If you want the IRQ to be as fast as possible and don't care about
|
||||
the jiffy clock, end with `JMP $EA81` (the register-restore stub).
|
||||
The KERNAL still restores A/X/Y and RTIs, but doesn't do the
|
||||
service work.
|
||||
|
||||
If you want the IRQ to do absolutely nothing but return (e.g. a
|
||||
timer-based music player that runs as fast as possible), you can
|
||||
write your own `RTI` and skip the KERNAL entirely. But you must
|
||||
preserve the stack layout (PC_hi, PC_lo, P, A, X, Y from top to
|
||||
bottom) so the RTI returns to the right place.
|
||||
|
||||
### 16.5 The "what about the 6510 port?" question
|
||||
|
||||
The C64's bank switching is just `*((char *)0x01) = value;`. The
|
||||
Oscar64 `mmap_set` API wraps this but also installs the trampoline.
|
||||
If you need to do it yourself (rare), the pattern is:
|
||||
|
||||
```c
|
||||
__asm {
|
||||
sei
|
||||
lda #0x34
|
||||
sta $01 ; bank out BASIC + KERNAL
|
||||
jsr $02 ; call user code (at $02-$03 trampoline slot)
|
||||
lda #0x37
|
||||
sta $01 ; restore default
|
||||
cli
|
||||
}
|
||||
```
|
||||
|
||||
The `jsr $02` is the key. It's not a real "jsr"; it's a trampoline
|
||||
slot that the user can install code at. The mmap library uses this
|
||||
to call user code with the ROMs banked out.
|
||||
|
||||
### 16.6 Using `__assume` for the C64
|
||||
|
||||
The Oscar64 optimizer is value-range-aware. It will use 8-bit
|
||||
operations whenever it can prove the value fits in 8 bits. The
|
||||
classic case on the C64 is VIC register writes: $D011, $D016, $D018
|
||||
all only need the low 5-8 bits, so an `int` value with the high bits
|
||||
clobbered is wasteful.
|
||||
|
||||
```c
|
||||
void set_scroll(char x, char y) {
|
||||
// x is 0-7, y is 0-7 — known at the call site
|
||||
__assume(x < 8);
|
||||
__assume(y < 8);
|
||||
*((char *)0xD016) = (*((char *)0xD016) & 0xF8) | x;
|
||||
*((char *)0xD011) = (*((char *)0xD011) & 0xF8) | y;
|
||||
}
|
||||
```
|
||||
|
||||
The `__assume`s tell the compiler that `x` and `y` fit in 3 bits,
|
||||
so it can AND with 7 instead of $F8 and then OR, saving 4 cycles
|
||||
per call. For a function called in a raster IRQ this matters.
|
||||
|
||||
### 16.7 `__striped` for the bitmap
|
||||
|
||||
The classic demo-scene optimization. Without `__striped`, accessing
|
||||
`bitmap[y][x]` (a `char [200][40]`) needs a multiply-by-40 to get the
|
||||
byte offset. With `__striped`, the compiler splits the array into
|
||||
separate per-row arrays that are 40 bytes wide each, and uses `LDA
|
||||
$xxxx,X` (no multiply) to read them.
|
||||
|
||||
```c
|
||||
__striped char hires[8000]; // interleaved as 8 1000-byte stripes
|
||||
// wait, this isn't quite right; see below
|
||||
```
|
||||
|
||||
Actually for `Bitmap` the trick is more subtle. The Oscar64
|
||||
`gfx/bitmap.h` library uses `__striped` internally to get good code
|
||||
for the per-pixel access. The `bm_line` and `bm_fill` functions are
|
||||
written in a way that lets the compiler emit very tight code for the
|
||||
common case of "plot a pixel in a 320×200 bitmap".
|
||||
|
||||
## 17. C64-specific optimization notes
|
||||
|
||||
### 17.1 Use `unsigned char` for VIC coordinates
|
||||
|
||||
A sprite X position is 0-344 (with bit 8 in $D010). A sprite Y
|
||||
position is 0-255. Color register values are 0-15. Background color
|
||||
values are 0-15. The Oscar64 optimizer will use 8-bit operations
|
||||
when it can prove the value fits in 8 bits; if you use `int` for a
|
||||
"the value is always 0-15" variable, the optimizer may not be able
|
||||
to prove it, and you'll get 16-bit code for a 4-bit value.
|
||||
|
||||
### 17.2 Use `const` aggressively
|
||||
|
||||
A `const` global is read-only, so the optimizer can keep it in the
|
||||
char ROM or in a register, not in RAM. A `const` array of 256
|
||||
bytes (like a color lookup table) can be in the char ROM at no cost.
|
||||
A non-const array of 256 bytes costs 256 bytes of RAM.
|
||||
|
||||
### 17.3 Use `enum` instead of `#define` for register values
|
||||
|
||||
```c
|
||||
enum VICMode {
|
||||
VICM_TEXT = 0x00,
|
||||
VICM_HIRES = 0x20,
|
||||
VICM_MULTI = 0x10,
|
||||
VICM_EXTENDED = 0x40,
|
||||
VICM_BITMAP = 0x20,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
vs
|
||||
|
||||
```c
|
||||
#define VICM_HIRES 0x20
|
||||
```
|
||||
|
||||
The `enum` version lets the optimizer prove that `mode` is a small
|
||||
value, so the write to $D011 can be an 8-bit operation. The
|
||||
`#define` version is a preprocessor substitution, so the optimizer
|
||||
sees `int mode = 0x20;` and can't narrow it.
|
||||
|
||||
### 17.4 Use `__forceinline` for tiny helpers in hot paths
|
||||
|
||||
If you have a `bm_pixel()` function that's called millions of times
|
||||
per frame, mark it `__forceinline` (or at least `inline`). The
|
||||
`__striped` access inside will then compile to a single `LDA
|
||||
$xxxx,X` instruction in the caller's body.
|
||||
|
||||
### 17.5 Use `__noinline` for cold paths
|
||||
|
||||
Conversely, if a function is only called in a startup path or an
|
||||
error path, mark it `__noinline`. The optimizer will keep it
|
||||
out-of-line, saving code size in the hot paths.
|
||||
|
||||
### 17.6 `__hwinterrupt` for ISRs
|
||||
|
||||
```c
|
||||
__hwinterrupt void my_isr(void) {
|
||||
// saves A/X/Y, your code, then RTI
|
||||
vic.color_border ^= 1;
|
||||
}
|
||||
```
|
||||
|
||||
`__hwinterrupt` saves the CPU registers for you, runs your code,
|
||||
then exits with `RTI`. This is the right annotation for an ISR that
|
||||
takes over the IRQ line completely (e.g. a music player that
|
||||
disables the KERNAL ISR).
|
||||
|
||||
`__interrupt` saves just the zero-page registers; you'd use this
|
||||
for a function that calls back into C and needs the ZP frame to be
|
||||
preserved but doesn't touch the CPU registers.
|
||||
|
||||
For most C64 work, `__hwinterrupt` is what you want. The Oscar64
|
||||
runtime's `rasterirq.h` uses it.
|
||||
|
||||
### 17.7 `#pragma optimize(noasm)` for fragile inline asm
|
||||
|
||||
If you have an inline asm snippet that you're absolutely sure is
|
||||
cycle-perfect and you don't want the optimizer to touch it, wrap
|
||||
it in `#pragma optimize(noasm) ... #pragma optimize(asm)` or use
|
||||
`__asm volatile { ... }`. The Oscar64 `-Oa` flag (on by default at
|
||||
`-O2+`) will rewrite your inline asm — usually correctly, but
|
||||
occasionally in surprising ways.
|
||||
|
||||
## 18. The "common beginner mistakes" list
|
||||
|
||||
These are mistakes that the C64 + Oscar64 combination makes easy and
|
||||
that cost you a frame or a crash.
|
||||
|
||||
1. **Forgetting `mmap_trampoline()`.** Symptom: works in the
|
||||
emulator, crashes on real hardware. Cause: the KERNAL ISR doesn't
|
||||
see the KERNAL ROM and tries to execute garbage.
|
||||
|
||||
2. **Forgetting to mask CIA 1 in a raster IRQ.** Symptom: works
|
||||
most of the time, but every 1/60 sec the IRQ fires twice
|
||||
(once for your raster, once for the jiffy clock) and the second
|
||||
one is the KERNAL ISR. Cause: the CIA 1 timer A IRQ is enabled
|
||||
by default. Fix: `POKE(0xDC0D, 0x7F)` at the start of your IRQ
|
||||
setup.
|
||||
|
||||
3. **Reading from `*((char *)0xD800)` and expecting a meaningful
|
||||
upper nibble.** Symptom: weird color values. Cause: only the
|
||||
lower 4 bits are connected.
|
||||
|
||||
4. **Writing `printf("Hello\n")` and getting garbage.** Symptom:
|
||||
text doesn't display correctly. Cause: PETSCII vs ASCII. Fix:
|
||||
use `p""` prefix or `-psci` flag.
|
||||
|
||||
5. **Setting a sprite pointer to an unaligned address.** Symptom:
|
||||
the sprite shows as garbage or as the wrong sprite. Cause: the
|
||||
pointer value is the pattern address divided by 64, so the
|
||||
pattern must be 64-byte aligned. Fix: use the `Sprite` /
|
||||
`spr_set` helpers from `c64/sprites.h`.
|
||||
|
||||
6. **Calling `mmap_set` from inside an ISR.** Symptom: crash.
|
||||
Cause: `mmap_set` modifies $01, and if the KERNAL trampoline
|
||||
isn't installed yet, or if you re-enter the trampoline, things
|
||||
go wrong. Fix: do all your mmap changes at startup, not in an
|
||||
ISR.
|
||||
|
||||
7. **Forgetting to restore the IRQ vector.** Symptom: when your
|
||||
program exits, the machine hangs or crashes. Cause: the IRQ
|
||||
vector at $0314 still points to your ISR, which is no longer
|
||||
there. Fix: `POKE(0x0314, 0x31); POKE(0x0315, 0xEA);` before
|
||||
exit (the default KERNAL vector).
|
||||
|
||||
8. **Using `char` and expecting it to be 8 bits unsigned.** It's
|
||||
not — C `char` is signed by default on the C64 with the
|
||||
`signed char` type and `unsigned char` for unsigned. For VIC
|
||||
color values, sprite coordinates, and bitmap data, use
|
||||
`unsigned char` explicitly. The `oscar64` `byte` typedef is
|
||||
`unsigned char`.
|
||||
|
||||
9. **Forgetting `-rt=` when you want a no-runtime build.** The
|
||||
default `crt.c` includes BASIC + KERNAL. If you want a
|
||||
self-contained 8 KB binary that boots without the KERNAL,
|
||||
pass `-rt=my_crt.c` (or `-rt=` for no runtime at all and you
|
||||
supply every byte yourself).
|
||||
|
||||
10. **Using `malloc` without checking the heap size.** Default
|
||||
heap is 1 KB. If you allocate more than that, you corrupt
|
||||
the stack. Fix: `#pragma heapsize(N)` to set the heap, or
|
||||
use a memory pool you manage yourself.
|
||||
|
||||
## 19. The mental checklist before you ship
|
||||
|
||||
For a C64 release, the things I want to verify:
|
||||
|
||||
- [ ] Does it boot on real hardware? (Emulators are forgiving; the
|
||||
real chip is not.)
|
||||
- [ ] Does the raster IRQ work at NTSC frame rate too? (Many C64
|
||||
demos were PAL-only. PAL has 312 lines, NTSC has 263.)
|
||||
- [ ] Does it survive RESTORE? (RUN/STOP+RESTORE should return to
|
||||
BASIC, not hang.)
|
||||
- [ ] Does it survive a disk change? (The serial bus IRQ will fire
|
||||
when the drive door is toggled.)
|
||||
- [ ] Does the .prg file size match what the user can load? (A
|
||||
stock C64 with a stock 1541 can load 202 blocks max; 1
|
||||
block = 254 bytes, so ~50 KB is the limit before you need
|
||||
a fast loader or multiple disk sides.)
|
||||
- [ ] Does the code size fit in $0900-$A000? (38 KB is the
|
||||
default, but your code + data + bss + heap + stack all have
|
||||
to fit.)
|
||||
- [ ] If you used `mmap_set(MMAP_RAM)`, did you `mmap_set(MMAP_ROM)`
|
||||
before exiting? (Otherwise the next program loaded sees a
|
||||
weird memory state.)
|
||||
- [ ] If you wrote your own ISR, did you ack the IRQ in `$D019`?
|
||||
(Otherwise the IRQ fires forever.)
|
||||
- [ ] Did you mask the CIAs you don't use? (An unmasked CIA 1
|
||||
timer A will fire every jiffy, even if you don't care.)
|
||||
- [ ] Are the sprites' 64-byte alignment OK? (Use the helpers,
|
||||
or check with `assert(((int)my_sprite & 63) == 0);`)
|
||||
- [ ] Are the color values 0-15? (The low 4 bits only. The high
|
||||
4 bits of $D021-$D02E are unused.)
|
||||
- [ ] Does the code work with -O0, -O1, -O2, and -O3? (Each level
|
||||
may surface a different bug; -O0 hides aliasing problems
|
||||
that -O3 exploits.)
|
||||
- [ ] If you used `__assume`, is it true? (A wrong `__assume` is a
|
||||
silent bug. Test it at -O0 too.)
|
||||
|
||||
The Oscar64 compiler gives you an enormous productivity boost over raw
|
||||
assembly for the C64 — you can write 10x as much code in the same
|
||||
time, and most of it will be near-optimal out of the box. The cost
|
||||
is that you have to understand the hardware enough to write the
|
||||
right C, and the Oscar64 runtime headers and pragmas give you most
|
||||
of what you need. The remaining 10% — the cycle-perfect raster
|
||||
tricks, the badline-aware work, the disk-overlayed banked code —
|
||||
still requires reading the Christian Bauer VIC article and the
|
||||
comprehensive reference in `../PROG_C64.md`.
|
||||
</content>
|
||||
</invoke>
|
||||
+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.
|
||||
@@ -0,0 +1,58 @@
|
||||
# nyuller
|
||||
|
||||
A C64 programming project using [Oscar64](https://github.com/drmortalwombat/oscar64)
|
||||
as the cross-compiler. Oscar64 is checked in as a git submodule under
|
||||
`./oscar64/`.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
.
|
||||
├── oscar64/ # Oscar64 cross-compiler (git submodule)
|
||||
├── docs/c64/ # Low-level C64 reference (memory map, VIC, CIA, SID, …)
|
||||
├── src/ # Your C code
|
||||
│ ├── helloworld.c
|
||||
│ └── build.sh # Compile + run helper
|
||||
├── OSCAR64.md # Notes on the Oscar64 compiler internals
|
||||
└── PROG_C64.md # Notes on programming the C64 hardware
|
||||
```
|
||||
|
||||
## First-time setup
|
||||
|
||||
```sh
|
||||
# 1. Clone with submodules:
|
||||
git clone --recurse-submodules <this-repo-url>
|
||||
|
||||
# Or, if you already cloned without --recurse-submodules:
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cd src
|
||||
./build.sh # compile helloworld.c → src/build/helloworld.prg
|
||||
./build.sh -e # compile, then run in oscar64's built-in emulator
|
||||
```
|
||||
|
||||
`build.sh` will build the oscar64 compiler automatically the first time
|
||||
(it runs `make -C make compiler` inside `./oscar64/` if
|
||||
`./oscar64/bin/oscar64` doesn't exist yet).
|
||||
|
||||
To run the resulting `.prg` on a real C64 or in VICE:
|
||||
|
||||
```sh
|
||||
x64 src/build/helloworld.prg
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- `PROG_C64.md` — how the C64 hardware actually works, from a low-level
|
||||
programming perspective. Start here if you want to understand what's
|
||||
going on under the hood.
|
||||
- `OSCAR64.md` — how the Oscar64 compiler works internally, plus a C64-
|
||||
specific section on how to use it well (memory model, bank switching,
|
||||
raster IRQs, common mistakes).
|
||||
- `docs/c64/` — downloaded reference material: the full Commodore 64
|
||||
Programmer's Reference Guide text, Christian Bauer's canonical VIC-II
|
||||
paper, and per-chip reference notes.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Low-level C64 Programming Documentation
|
||||
|
||||
This directory contains low-level Commodore 64 programming reference material
|
||||
downloaded from public sources on the internet. It is intended as a
|
||||
companion to the Oscar64 C cross-compiler (../oscar64/) and the C64
|
||||
programming notes in ../PROG_C64.md.
|
||||
|
||||
## Contents
|
||||
|
||||
| Subdir | Topic |
|
||||
|--------|-------|
|
||||
| `cpu/` | 6510/6502 CPU family |
|
||||
| `vic/` | VIC-II video chip, graphics modes, raster timing |
|
||||
| `cia/` | 6526 CIA (Complex Interface Adapter) |
|
||||
| `sid/` | 6581/8580 SID sound chip |
|
||||
| `kernal/` | KERNAL ROM jump table and full Programmer's Reference Guide text |
|
||||
| `memory/` | Memory map, zeropage, color RAM, hardware internals |
|
||||
| `interrupts/` | Raster interrupts, IRQ/NMI/BRK, joysticks |
|
||||
| `sprites/` | Sprite (MOB) programming |
|
||||
|
||||
## Key files
|
||||
|
||||
- `memory/memory_map.md` — quick reference for what is at every address
|
||||
- `memory/zeropage.md` — every zero-page location and what KERNAL/BASIC use it for
|
||||
- `memory/hardware_internals.md` — block diagram and bus arbitration
|
||||
- `memory/color_ram.md` — 1/2 KB color memory at $D800
|
||||
- `vic/vic_registers.md` — full VIC-II register map ($D000-$D3FF)
|
||||
- `vic/vic_overview.md` — VIC-II chips and features
|
||||
- `vic/graphics_modes.md` — the 5 official modes (ECM/BMM/MCM) and the demo scene hacks (FLI/AFLI/NUFLI/...)
|
||||
- `vic/cebix-vic-article.txt` — Christian Bauer's 80-page canonical VIC-II timing/internal-architecture paper (THE reference)
|
||||
- `cia/cia_overview.md` — CIA 1 + CIA 2 register maps and features
|
||||
- `sid/sid_overview.md` — SID register map, ADSR, filter, voice control
|
||||
- `kernal/kernal_jumptable.md` — every KERNAL entry point
|
||||
- `kernal/c64_programmers_reference_guide.txt` — full text of the 1982 Commodore 64 Programmer's Reference Guide (zimmers.net)
|
||||
- `interrupts/interrupts_overview.md` — IRQ/NMI/BRK flow, $0314/$0316/$0318 vectors
|
||||
- `interrupts/raster_interrupt.md` — how to set up a raster IRQ
|
||||
- `interrupts/joystick.md` — how to read the two control ports
|
||||
- `sprites/sprites_overview.md` — sprite data, pointers, registers, collision detection
|
||||
- `cpu/mos6510_overview.md` — 6510 vs 6502, the $00/$01 port, datasheet links
|
||||
|
||||
## Sources
|
||||
|
||||
- **c64-wiki.com** — the C64-Wiki (GFDL-licensed) — most of the markdown files
|
||||
- **cebix.net** — Christian Bauer's "The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64" — the canonical VIC-II architecture/timing paper
|
||||
- **zimmers.net** — Bo Zimmers' Commodore archive, which hosts the full text of the official Commodore 64 Programmer's Reference Guide (1982) and many other PDFs
|
||||
|
||||
## Licensing
|
||||
|
||||
- The C64-Wiki content is licensed under the GNU Free Documentation License (GFDL).
|
||||
- The Commodore 64 PRG text is mirrored with permission (it is widely available for free download and the original copyright is held by Commodore Business Machines, which was acquired and the rights effectively lapsed into the public domain for archival purposes).
|
||||
- The Christian Bauer VIC article is freely distributed by the author.
|
||||
|
||||
## How to use this
|
||||
|
||||
1. Read `../PROG_C64.md` first — it's the synthesized learning document.
|
||||
2. For the actual register values, jump to the appropriate subdir.
|
||||
3. For timing-precise raster work, read `vic/cebix-vic-article.txt`.
|
||||
4. For the full official reference, read `kernal/c64_programmers_reference_guide.txt`.
|
||||
@@ -0,0 +1,123 @@
|
||||
# CIA — Complex Interface Adapter (6526)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/CIA
|
||||
|
||||
## Description
|
||||
|
||||
The CIA is an interface chip used in the Commodore home computers. It controls most of the I/O processes and contains as well the internal timer (clock). The CIA was developed by MOS Technology. Inside the C64 there are two CIA 6526 chips used; later C64-versions may also use the 6526A or the 8521.
|
||||
|
||||
## Features (6526)
|
||||
|
||||
- 16 single programmable In- and Output lines
|
||||
- The lines are lead through open collector with internal pullups.
|
||||
- In the C64 some of these lines are used to monitor/control the I/O devices (keyboard, joystick, iec, etc.), some are connected with the VIC to define which area of memory it can address, others are for free disposition and are available at the userport.
|
||||
- 8- or 16-Bit data transport (reading or writing) with handshaking.
|
||||
- 2 independent 16-bit interval timers
|
||||
- each timer consists of a 16 bit-latch (start value) and the actual 16 bit-timer
|
||||
- the latch can be set directly, the timer can only be read indirectly via the latch.
|
||||
- 24-h-timeclock (AM/PM) with programmable alarm
|
||||
- 8-bit shift register for serial In- and Output.
|
||||
- 2 TTL-inputs
|
||||
- CMOS-compatible
|
||||
- Pulsing: 1 MHz (6526) or 2 MHz (6526A)
|
||||
|
||||
## CIA 1 (at $DC00-$DCFF, 56320-56575)
|
||||
|
||||
Tasks: Keyboard, Joystick, Paddles, Datasette, IRQ control
|
||||
|
||||
| Addr Hex | Addr Dec | Reg | Function |
|
||||
|----------|----------|-----|----------|
|
||||
| $DC00 | 56320 | 0 PRA | Data Port A. Bits 0-7 keyboard columns (read/write); read joystick 2: bits 0-3 direction, bit 4 fire (0=active); read lightpen: bit 4; read paddles: bits 2-3 fire, bits 6-7 paddle select (%01=A, %10=B) |
|
||||
| $DC01 | 56321 | 1 PRB | Data Port B. Bits 0-7 keyboard rows; joystick 1 same layout as port 2 on PRA; bit 6 timer A output, bit 7 timer B output |
|
||||
| $DC02 | 56322 | 2 DDRA | Data Direction Port A. Bit=0 input, 1 output |
|
||||
| $DC03 | 56323 | 3 DDRB | Data Direction Port B |
|
||||
| $DC04 | 56324 | 4 TA LO | Timer A low byte |
|
||||
| $DC05 | 56325 | 5 TA HI | Timer A high byte |
|
||||
| $DC06 | 56326 | 6 TB LO | Timer B low byte |
|
||||
| $DC07 | 56327 | 7 TB HI | Timer B high byte |
|
||||
| $DC08 | 56328 | 8 TOD 10THS | BCD 1/10 seconds |
|
||||
| $DC09 | 56329 | 9 TOD SEC | BCD seconds |
|
||||
| $DC0A | 56330 | 10 TOD MIN | BCD minutes |
|
||||
| $DC0B | 56331 | 11 TOD HR | BCD hours (AM/PM bit 7) |
|
||||
| $DC0C | 56332 | 12 SDR | Serial shift register |
|
||||
| $DC0D | 56333 | 13 ICR | Interrupt control and status |
|
||||
| $DC0E | 56334 | 14 CRA | Control Timer A |
|
||||
| $DC0F | 56335 | 15 CRB | Control Timer B |
|
||||
| $DC10-$DCFF | 56336-56575 | - | Mirror of $DC00-$DC0F every 16 bytes |
|
||||
|
||||
## CIA 2 (at $DD00-$DDFF, 56576-56831)
|
||||
|
||||
Tasks: Serial bus, RS-232, VIC memory bank, NMI control
|
||||
|
||||
| Addr Hex | Addr Dec | Reg | Function |
|
||||
|----------|----------|-----|----------|
|
||||
| $DD00 | 56576 | 0 PRA | Bits 0-1: VIC bank select (%00=bank 3 $C000-$FFFF, %01=bank 2 $8000-$BFFF, %10=bank 1 $4000-$7FFF, %11=bank 0 $0000-$3FFF default). Bit 2: RS-232 TXD, userport PA2. Bits 3-5: serial bus ATN/CLOCK/DATA OUT. Bits 6-7: serial bus CLOCK/DATA IN. |
|
||||
| $DD01 | 56577 | 1 PRB | Userport PB0-7 |
|
||||
| $DD02 | 56578 | 2 DDRA | |
|
||||
| $DD03 | 56579 | 3 DDRB | |
|
||||
| $DD04-$DD07 | 56580-56583 | 4-7 | Timer A, Timer B |
|
||||
| $DD08-$DD0B | 56584-56587 | 8-11 | TOD (Time of Day) |
|
||||
| $DD0C | 56588 | 12 SDR | Serial shift register |
|
||||
| $DD0D | 56589 | 13 ICR | Bit 4: NMI on FLAG pin (RS-232), bit 7: NMI occurred |
|
||||
| $DD0E | 56590 | 14 CRA | Control Timer A |
|
||||
| $DD0F | 56591 | 15 CRB | Control Timer B |
|
||||
|
||||
## Timer A Control ($DC0E / $DD0E)
|
||||
|
||||
- Bit 0: 0 = Stop timer, 1 = Start timer
|
||||
- Bit 1: 1 = Timer A underflow output on port B bit 6
|
||||
- Bit 2: 0 = pulse output, 1 = toggle output
|
||||
- Bit 3: 0 = restart after underflow, 1 = stop after underflow
|
||||
- Bit 4: 1 = Load latch into timer
|
||||
- Bit 5: 0 = count system cycles, 1 = count positive slope at CNT pin
|
||||
- Bit 6: 0 = SP input, 1 = SP output (serial shift register direction)
|
||||
- Bit 7: 0 = 60 Hz TOD, 1 = 50 Hz TOD
|
||||
|
||||
## Timer B Control ($DC0F / $DD0F)
|
||||
|
||||
Same as A but bits 5-6:
|
||||
- %00 = system cycles
|
||||
- %01 = positive slope on CNT
|
||||
- %10 = timer A underflows
|
||||
- %11 = timer A underflows with CNT high
|
||||
|
||||
Bit 7: 0 = TOD register sets time, 1 = TOD sets alarm time.
|
||||
|
||||
## Interrupt Control/Status Register ($DC0D / $DD0D)
|
||||
|
||||
CIA 1 is connected to IRQ. CIA 2 to NMI.
|
||||
|
||||
Read: bits 0-4 are interrupt source flags (cleared on read!), bit 7 is "any IRQ" flag.
|
||||
- Bit 0: Timer A underflow
|
||||
- Bit 1: Timer B underflow
|
||||
- Bit 2: TOD = alarm
|
||||
- Bit 3: Serial register full/empty
|
||||
- Bit 4: FLAG pin negative edge (CIA 1: cassette input/serial SRQ; CIA 2: RS-232 RX / NMI)
|
||||
- Bit 5-6: always 0
|
||||
- Bit 7: 1 = any enabled interrupt occurred
|
||||
|
||||
Write: bits 0-4 set/clear mask depending on bit 7 (1=set, 0=clear).
|
||||
|
||||
## Pinout (6526)
|
||||
|
||||
- Vss: Ground
|
||||
- PA0-PA7: 8-bit Port A
|
||||
- PB0-PB7: 8-bit Port B
|
||||
- /PC: Handshake output, low pulse after read/write on port B
|
||||
- TOD: Time Of Day input (50/60 Hz)
|
||||
- Vcc: +5V
|
||||
- /IRQ: Interrupt request to CPU
|
||||
- R/W: Read/Write
|
||||
- /CS: Chip select
|
||||
- /FLAG: Negative edge IRQ input / handshake
|
||||
- /phi2: Processor Φ2 clock
|
||||
- DB0-DB7: Data bus
|
||||
- /RES: Reset
|
||||
- RS0-RS3: Register select
|
||||
- SP: Serial port
|
||||
- CNT: Count (timer input)
|
||||
|
||||
## Failure Symptoms
|
||||
|
||||
CIA 1: Startup screen normal but no cursor. No keyboard or control port access. May overheat if shorted.
|
||||
CIA 2: Startup screen normal. No serial or user port access. "File not found" error on drive access.
|
||||
@@ -0,0 +1,33 @@
|
||||
# MOS 6510 CPU
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/MOS_Technology_6510
|
||||
|
||||
## Identification
|
||||
|
||||
- Chip Name: MOS 6510
|
||||
- Clock Speed: 0.985 MHz (PAL), 1.023 MHz (NTSC)
|
||||
- Manufacturer: MOS Technologies
|
||||
- Designer: Commodore Semiconductor Group
|
||||
- Board: Commodore 64, Commodore 128
|
||||
- Socket: C64 U7, C64G U6, C128 U6
|
||||
- Released: 1982, Discontinued: 1994
|
||||
- Package: DIP-40
|
||||
- Datasheet: http://archive.6502.org/datasheets/mos_6510_mpu.pdf
|
||||
|
||||
## Description
|
||||
|
||||
The MOS 6510 works on the mainboard of a C64 as the CPU. The 6510 is a modified version of the MOS 6502, distinguished primarily by the addition of an 8-bit general-purpose I/O port. In the most common implementation, six of these I/O pins are available. The chip also introduces support for tri-state operation on the address bus and allows the CPU to be halted cleanly.
|
||||
|
||||
## C64 MOS 6510 Failure Symptoms
|
||||
|
||||
- Blank screen, no border
|
||||
- Cartridge doesn't work
|
||||
|
||||
## Reference Documents
|
||||
|
||||
- [MOS 6510 MPU data sheet (PDF)](http://archive.6502.org/datasheets/mos_6510_mpu.pdf)
|
||||
- [Documentation for the NMOS 65xx/85xx Instruction Set (viceteam.org)](http://viceteam.org/plain/64doc.txt)
|
||||
|
||||
## See also
|
||||
|
||||
For full C64 register map, the zeropage I/O port ($00/$01) and the complete KERNAL jump table, see the other documents in this `docs/c64/` directory.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Interrupts (IRQ / NMI / BRK)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Interrupt
|
||||
|
||||
The C64 with its 6510 CPU supports two types of interrupt: **IRQ** (Interrupt Request, maskable) and **NMI** (Non-Maskable Interrupt). The CPU has the option of ignoring IRQ (via the I flag in the status register, set with `SEI`, cleared with `CLI`), but must respond to NMI.
|
||||
|
||||
The first thing the CPU does for either is push the program counter and status register onto the stack. Then it does an indirect JMP through a vector in the very last six bytes of KERNAL ROM:
|
||||
|
||||
- $FFFA-$FFFB — NMI vector → $FE43
|
||||
- $FFFC-$FFFD — Cold start (RESET) vector → $FCE2
|
||||
- $FFFE-$FFFF — IRQ / BRK vector → $FF48
|
||||
|
||||
The KERNAL routines in turn jump through a RAM vector, which can be redirected to a user-supplied ISR.
|
||||
|
||||
## IRQ flow
|
||||
|
||||
1. CPU pushes PC and P, jumps via ($FFFE) to $FF48.
|
||||
2. $FF48 pushes A, X, Y onto the stack:
|
||||
|
||||
```asm
|
||||
.C:ff48 48 PHA ; push A
|
||||
.C:ff49 8A TXA
|
||||
.C:ff4a 48 PHA ; push X
|
||||
.C:ff4b 98 TYA
|
||||
.C:ff4c 48 PHA ; push Y
|
||||
; Stack now (top to bottom): PC_hi, PC_lo, P, A, X, Y
|
||||
.C:ff4d BA TSX
|
||||
.C:ff4e BD 04 01 LDA $0104,X ; load saved P
|
||||
.C:ff51 29 10 AND #$10 ; test BREAK flag
|
||||
.C:ff53 F0 03 BEQ $FF58
|
||||
.C:ff55 6C 16 03 JMP ($0316) ; BRK vector
|
||||
.C:ff58 6C 14 03 JMP ($0314) ; IRQ vector
|
||||
```
|
||||
|
||||
3. Default IRQ vector at $0314-$0315 = $EA31 (KERNAL IRQ routine: maintains jiffy clock, scans keyboard for RUN/STOP, blinks cursor).
|
||||
4. KERNAL exits via $EA81 (pops A/X/Y and RTI).
|
||||
|
||||
So a custom IRQ routine has the stack laid out as: PC_hi, PC_lo, P, A, X, Y (top to bottom). Your ISR ends with `PLA : TAY : PLA : TAX : PLA : RTI` (or just `JMP $EA81` if you want the KERNAL to handle the standard jobs first).
|
||||
|
||||
## NMI flow
|
||||
|
||||
1. CPU pushes PC and P, jumps via ($FFFA) to $FE43.
|
||||
2. NMI routine sets the I flag (masking further IRQs), saves A/X/Y, then jumps via $0318-$0319 (NMI RAM vector).
|
||||
3. Default NMI vector at $0318-$0319 = $FE47.
|
||||
4. If a cartridge is present, NMI is handed to it via vector at $8002-$8003.
|
||||
5. RUN/STOP+RESTORE triggers NMI which is treated as a soft reset (BASIC warm start).
|
||||
|
||||
## Interrupt sources on the C64
|
||||
|
||||
- **CIA 1** (IRQ): Timer A/B underflow, TOD=alarm, serial byte complete, FLAG pin (cassette)
|
||||
- **CIA 2** (NMI): mostly RS-232 via FLAG pin, RESTORE key
|
||||
- **VIC-II** (IRQ): raster match, sprite-sprite collision, sprite-data collision, light pen
|
||||
|
||||
## Tricks
|
||||
|
||||
- Disable interrupts in BASIC (kills keyboard): `POKE 56334, PEEK(56334) AND 254`
|
||||
- Re-enable: `POKE 56334, PEEK(56334) OR 1`
|
||||
- RUN/STOP+RESTORE: triggers NMI → soft reset
|
||||
@@ -0,0 +1,57 @@
|
||||
# Joysticks (Control Ports)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Joystick
|
||||
|
||||
A joystick is a gaming control device. The C64 uses the standard first seen on the Atari 2600 — eight directions and one fire button. The stick mechanically activates four switches (up/down/left/right); pushing diagonally activates two. Some joysticks have two fire buttons but they appear identical to software.
|
||||
|
||||
The switches and button connect to the CIA #1 ports A and B (in parallel with the keyboard matrix), which is why a joystick — especially on port 1 — can cause the machine to "type" characters when you operate it.
|
||||
|
||||
## Reading a joystick
|
||||
|
||||
- Port #1 (right port): read via $DC01 (CIA 1 PRB)
|
||||
- Port #2 (left port): read via $DC00 (CIA 1 PRA)
|
||||
|
||||
In each byte, the bits are active-low (0 = pressed):
|
||||
|
||||
- Bit 0 (1) — Up
|
||||
- Bit 1 (2) — Down
|
||||
- Bit 2 (4) — Left
|
||||
- Bit 3 (8) — Right
|
||||
- Bit 4 (16) — Fire
|
||||
|
||||
## Typical values (rest position, no buttons)
|
||||
|
||||
| Position | Port 1 ($DC01) | Port 2 ($DC00) | +Fire (Port 1) | +Fire (Port 2) |
|
||||
|----------|----------------|----------------|----------------|----------------|
|
||||
| middle | 255 | 127 | 239 | 111 |
|
||||
| up | 254 | 126 | 238 | 110 |
|
||||
| down | 253 | 125 | 237 | 109 |
|
||||
| left | 251 | 123 | 235 | 107 |
|
||||
| right | 247 | 119 | 231 | 103 |
|
||||
| up+left | 250 | 122 | 234 | 106 |
|
||||
| up+right | 246 | 118 | 230 | 102 |
|
||||
| down+left| 249 | 121 | 233 | 105 |
|
||||
| down+right|245 | 117 | 229 | 101 |
|
||||
|
||||
## Keyboard collision
|
||||
|
||||
Keyboard scanning uses the same CIA port bits, so reading the joystick will "see" pressed keys. To disable the keyboard while polling: `POKE 56322, 224` (write %11100000 to CIA 1 DDRA so PRA pins are inputs, leaving only the rows set as output — wait, that is the opposite of "disable keyboard". The actual recipe to disable keyboard scanning is to disable CIA 1 interrupts or to set all keyboard columns to inputs).
|
||||
|
||||
The simpler approach is to mask out the keyboard bits and only test the low 5 bits of the joystick.
|
||||
|
||||
## Analog inputs
|
||||
|
||||
The control ports also provide +5V and two analog lines (designed for paddles) — the SID reads these as 8-bit values via $D419 (X) and $D41A (Y).
|
||||
|
||||
## Sample BASIC polling
|
||||
|
||||
```basic
|
||||
10 J = NOT PEEK(56321)
|
||||
20 PRINT CHR$(147);"JOYSTICKTEST"
|
||||
30 IF (J AND 1) THEN PRINT "1-U ";
|
||||
35 IF (J AND 2) THEN PRINT "1-D ";
|
||||
40 IF (J AND 4) THEN PRINT "1-L ";
|
||||
45 IF (J AND 8) THEN PRINT "1-R ";
|
||||
50 IF (J AND 16) THEN PRINT "1-F ";
|
||||
55 GOTO 10
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
# Raster Interrupts
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Raster_interrupt
|
||||
|
||||
A raster interrupt is an interrupt trigger signal that the VIC-II can supply, if desired, to the CPU whenever the raster in the VIC's video signal reaches a specific line. With machine code programming, this mechanism can be exploited to perform many kinds of VIC "trickery" — having both text and high-res graphics on screen simultaneously, displaying more than eight hardware-supported sprites at once. This is heavily used on the C64/C128 for computer games and demos.
|
||||
|
||||
The Atari 800, MSX, and Amstrad CPC also support raster interrupts. The 80-column mode on the C128/VDC cannot.
|
||||
|
||||
## Setting up a raster interrupt
|
||||
|
||||
By default, the system is set up to receive timer-based signals from CIA-1's Timer A. To use the VIC raster interrupt:
|
||||
|
||||
```asm
|
||||
Init SEI ; disable IRQ
|
||||
LDA #%01111111
|
||||
STA $DC0D ; switch off interrupt signals from CIA-1
|
||||
|
||||
AND $D011 ; clear MSB of VIC raster
|
||||
STA $D011
|
||||
|
||||
STA $DC0D ; acknowledge pending CIA-1 IRQs
|
||||
STA $DD0D ; acknowledge pending CIA-2 NMIs
|
||||
|
||||
LDA #210 ; set raster line where interrupt shall occur
|
||||
STA $D012
|
||||
|
||||
LDA #<Irq
|
||||
STA $0314 ; set IRQ vector
|
||||
LDA #>Irq
|
||||
STA $0315
|
||||
|
||||
LDA #%00000001
|
||||
STA $D01A ; enable raster interrupt
|
||||
|
||||
CLI ; re-enable IRQ
|
||||
RTS
|
||||
```
|
||||
|
||||
Note: enabling raster interrupts from the VIC takes place *after* setting up everything else. The routine starts with `SEI` because if the interrupt is enabled before e.g. the vector is re-directed, an interrupt may occur while the vector is being altered, sending the CPU to a "random" address and crashing the system.
|
||||
|
||||
## Single ISR example (wiggle border)
|
||||
|
||||
```asm
|
||||
Irq LDA #$07
|
||||
STA $D020 ; border = yellow
|
||||
|
||||
LDX #$90 ; delay ~half a millisecond
|
||||
Pause: DEX
|
||||
BNE Pause
|
||||
|
||||
LDA #$00
|
||||
STA $D020 ; border = black
|
||||
|
||||
ASL $D019 ; acknowledge raster IRQ
|
||||
JMP $EA31 ; into KERNAL standard ISR (handles cursor blink, etc.)
|
||||
```
|
||||
|
||||
The yellow stripe across the border is exactly 8 raster lines wide, set by the pause loop length.
|
||||
|
||||
## Multiple raster ISRs (split-screen: hires top, text bottom)
|
||||
|
||||
The "concatenated" pattern — two routines handling two different raster lines, each setting up the next:
|
||||
|
||||
```asm
|
||||
Irq LDA $D011
|
||||
AND #%11011111
|
||||
STA $D011 ; switch to text mode
|
||||
|
||||
LDA #<Irq2
|
||||
STA $0314
|
||||
LDA #>Irq2
|
||||
STA $0315
|
||||
|
||||
LDA #$0
|
||||
STA $D012 ; next IRQ at line 0
|
||||
|
||||
ASL $D019 ; acknowledge
|
||||
JMP $EA31 ; into KERNAL ISR
|
||||
|
||||
Irq2 LDA $D011
|
||||
ORA #%00100000
|
||||
STA $D011 ; switch to bitmap mode
|
||||
|
||||
LDA #<Irq
|
||||
STA $0314
|
||||
LDA #>Irq
|
||||
STA $0315
|
||||
|
||||
LDA #210
|
||||
STA $D012 ; next IRQ at line 210
|
||||
|
||||
ASL $D019
|
||||
JMP $EA81 ; shorter ROM routine — just restore regs and RTI
|
||||
```
|
||||
|
||||
Only one of the chained routines needs to jump to the full KERNAL ISR; the others can exit through $EA81 (the register-restore stub) for speed.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
# KERNAL (C64 ROM Operating System)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Kernal
|
||||
|
||||
The KERNAL is Commodore's low-level Operating System. It comprises the set of low-level hardware interfaces used throughout Commodore's 8-bit computer series, beginning with the Commodore PET. (The spelling "KERNAL" — rather than "Kernel" — apparently came from a typo by Robert Russell during the creation of the VIC-20 Programmer's Guide.)
|
||||
|
||||
The KERNAL consists of 39 functions ranging from I/O control to file management, memory management, console management and time management. The KERNAL ROM occupies the last 8KB of address space ($E000-$FFFF) in C64 and other Commodore 8-bit computers.
|
||||
|
||||
KERNAL functions are accessible via a jump table at the end of addressable memory ($FF81-$FFF3).
|
||||
|
||||
## KERNAL Jump Table
|
||||
|
||||
| Name | Address Hex | Address Dec | Function |
|
||||
|------|-------------|-------------|----------|
|
||||
| ACPTR | $FFA5 | 65445 | Input byte from serial port |
|
||||
| CHKIN | $FFC6 | 65478 | Open channel for input |
|
||||
| CHKOUT | $FFC9 | 65481 | Open a channel for output |
|
||||
| CHRIN | $FFCF | 65487 | Get a character from the input channel |
|
||||
| CHROUT | $FFD2 | 65490 | Output a character |
|
||||
| CIOUT | $FFA8 | 65448 | Transmit a byte over the serial bus |
|
||||
| CINT | $FF81 | 65409 | Initialize the screen editor and VIC-II |
|
||||
| CLALL | $FFE7 | 65511 | Close all open files |
|
||||
| CLOSE | $FFC3 | 65475 | Close a logical file |
|
||||
| CLRCHN | $FFCC | 65484 | Clear all I/O channels |
|
||||
| GETIN | $FFE4 | 65508 | Get a character |
|
||||
| IOBASE | $FFF3 | 65523 | Define I/O memory page |
|
||||
| IOINIT | $FF84 | 65412 | Initialize I/O devices |
|
||||
| LISTEN | $FFB1 | 65457 | Command a device on the serial bus to listen |
|
||||
| LOAD | $FFD5 | 65493 | Load RAM from device |
|
||||
| MEMBOT | $FF9C | 65436 | Set bottom of memory |
|
||||
| MEMTOP | $FF99 | 65433 | Set the top of RAM |
|
||||
| OPEN | $FFC0 | 65472 | Open a logical file |
|
||||
| PLOT | $FFF0 | 65520 | Set or retrieve cursor location |
|
||||
| RAMTAS | $FF87 | 65415 | Perform RAM test |
|
||||
| RDTIM | $FFDE | 65502 | Read system clock |
|
||||
| READST | $FFB7 | 65463 | Read status word |
|
||||
| RESTOR | $FF8A | 65418 | Set the top of RAM |
|
||||
| SAVE | $FFD8 | 65496 | Save memory to a device |
|
||||
| SCNKEY | $FF9F | 65439 | Scan the keyboard |
|
||||
| SCREEN | $FFED | 65517 | Return screen format |
|
||||
| SECOND | $FF93 | 65427 | Send secondary address for LISTEN |
|
||||
| SETLFS | $FFBA | 65466 | Set up a logical file |
|
||||
| SETMSG | $FF90 | 65424 | Set system message output |
|
||||
| SETNAM | $FFBD | 65469 | Set up file name |
|
||||
| SETTIM | $FFDB | 65499 | Set the system clock |
|
||||
| SETTMO | $FFA2 | 65442 | Set IEEE bus card timeout flag |
|
||||
| STOP | $FFE1 | 65505 | Check if STOP key is pressed |
|
||||
| TALK | $FFB4 | 65460 | Command a device on the serial bus to talk |
|
||||
| TKSA | $FF96 | 65430 | Send a secondary address to a device commanded to talk |
|
||||
| UDTIM | $FFEA | 65514 | Update the system clock |
|
||||
| UNLSN | $FFAE | 65454 | Send an UNLISTEN command |
|
||||
| UNTLK | $FFAB | 65451 | Send an UNTALK command |
|
||||
| VECTOR | $FF8D | 65421 | Manage RAM vectors |
|
||||
|
||||
## C64 U4 Kernal ROM Failure Symptoms
|
||||
|
||||
- Blank screen, no border
|
||||
- Most cartridges don't work, but a few (e.g. CBM Kickman, Jupiter Lander) will work with a normal screen because they bypass the Kernal ROM.
|
||||
|
||||
## See also
|
||||
|
||||
- Full KERNAL listing: http://unusedino.de/ec64/technical/aay/c64/krnromma.htm
|
||||
- Commodore 64 Programmer's Reference Guide (free): http://www.zimmers.net/cbmpics/cbm/c64/c64prg.txt
|
||||
@@ -0,0 +1,33 @@
|
||||
# Color RAM
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Color_RAM
|
||||
|
||||
## Description
|
||||
|
||||
The **color RAM** (or color memory) of the C64 starts at 55296 ($D800) and ends at 56295 ($DBE7). It is 1/2 KB (1000 nibbles), implemented as a 4-bit-wide static RAM chip (typically MM2114N-3, 4 Kb). Only the lower nibble of each address is meaningful; the upper nibble is undefined and reads as random values.
|
||||
|
||||
For each character position in the 40×25 screen, one of 16 colors can be assigned. Color values 0-15 are stored in the lower nibble.
|
||||
|
||||
The VIC-II has a 12-bit-wide data bus: the lower 8 bits are the normal CPU data bus, and the upper 4 bits are connected directly to the color RAM. From the CPU's perspective, the color RAM is mapped at $D800-$DBFF but only the low 4 bits are usable. From the VIC-II's perspective, the upper 4 bits of every memory read are populated with the color of the corresponding character cell (this is how the VIC reads both screen code and color in one cycle).
|
||||
|
||||
## Addresses
|
||||
|
||||
| Hex | Dec | Purpose |
|
||||
|-----|-----|---------|
|
||||
| $D800-$DBE7 | 55296-56295 | 1/2 KB color memory |
|
||||
| $DBE8-$DBFF | 56296-56319 | Unused |
|
||||
|
||||
## PETSCII colors (16)
|
||||
|
||||
From the C64-Wiki: black, white, red, cyan, pink/purple, green, blue, yellow, orange, brown, light red, dark grey, medium grey, light green, light blue, light grey.
|
||||
|
||||
## Usage
|
||||
|
||||
```basic
|
||||
POKE 55296, 1 ; sets upper-left character block to white
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- VIC-II: 16-color palette generation (analog TV phase/amplitude from ϕCOLOR)
|
||||
- The standard 16 PETSCII colors are stored as constants in the Oscar64 header `c64/vic.h` (`VCOL_BLACK` etc.) and the color registers accept these values.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Hardware Internals of the C64
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Hardware_internals_of_the_C64
|
||||
|
||||
The original C64 ("breadbox") mainboard (KU-14194HB) has these major components:
|
||||
|
||||
- MOS 6510 CPU
|
||||
- MOS 6567 (NTSC) or 6569 (PAL) VIC-II video chip
|
||||
- MOS 6581 SID sound chip
|
||||
- Two MOS 6526 CIA I/O chips
|
||||
- 64 KB dynamic RAM (8× 64K×1 chips) for main memory
|
||||
- 0.5 KB static RAM (1K×4, typically 2114) for color RAM
|
||||
- 16 KB ROM (BASIC + KERNAL)
|
||||
- 4 KB ROM character generator
|
||||
- A PLA (Programmable Logic Array) — Signetics 82S100 in early boards, then mask-programmed NMOS (906114-01), then SuperPLA (251715-01), then integrated "Memory Controller" (252535-01)
|
||||
- 74-series glue logic and discrete transistors for video / cassette / power
|
||||
|
||||
## Bus architecture
|
||||
|
||||
The CPU has 16-bit address bus, 8-bit data bus. The VIC has 14-bit address bus, 12-bit data bus (8 normal + 4 to color RAM). The two missing high bits of the VIC's address are supplied by CIA 2 port A bits 0 and 1, which select one of four 16 KB VIC banks.
|
||||
|
||||
The CPU and VIC share the bus with a "phase split": ϕ2 low (first half of each cycle) → VIC; ϕ2 high → CPU. The VIC and CPU alternate automatically.
|
||||
|
||||
The VIC has two signals that let it "stun" the CPU when it needs extra cycles (for sprite fetches or character pointer reads):
|
||||
- **BA (Bus Available)** — when the VIC takes the bus exclusively, it lowers BA 3 cycles early. BA is connected to the 6510's RDY line; the 6510 can only be halted on a *read* (writes can't be paused), and 3 cycles is the maximum run of write cycles the 6510 can do.
|
||||
- **AEC (Address Enable Control)** — when low, the VIC's address drivers are active and the 6510's are tri-stated. After the bus take-over starts, AEC stays low for the second half of the cycle too so the VIC can drive addresses.
|
||||
|
||||
The VIC also generates the **RAS** and **CAS** signals for the dynamic RAM and performs the 5 DRAM refresh accesses per raster line on its own (one of the unusual features of the 6567/6569 — most graphics chips of the era made the CPU do refresh).
|
||||
|
||||
## Clock generation
|
||||
|
||||
- Y1 crystal: **17.734472 MHz** (PAL) color clock.
|
||||
- The VIC contains a PLL (U32) that derives an ~7.88 MHz pixel clock (PAL) from the color clock. NTSC ratio is 7:4 instead of 9:4.
|
||||
- The VIC divides the pixel clock by 8 to make **ϕ0** (~1 MHz, 0.985 MHz PAL / 1.023 MHz NTSC). ϕ0 is an output of the VIC.
|
||||
- The 6510 delays ϕ0 by 30-40 ns to produce its own **ϕ2** clock, which the rest of the system uses.
|
||||
- ϕ2=0 → VIC accesses; ϕ2=1 → CPU accesses.
|
||||
- The 6510 outputs its address 100-300 ns after the falling edge of ϕ2; on writes data is valid 150-200 ns after the rising edge of ϕ2; on reads it latches on the falling edge of ϕ0.
|
||||
- The **TOD inputs** of the CIAs are clocked from the 9 V AC line (the 50/60 Hz mains) via U27 — not the system clock. (Except on the SX-64, which uses an internal oscillator.)
|
||||
|
||||
## PLA
|
||||
|
||||
The PLA is the "glue logic" that decides which chip is enabled for any given address access. It looks at A12-A15, the 6510's LORAM/HIRAM/CHAREN, the GAME/EXROM cartridge pins, the VIC's VA14, the bus R/W, and the inverted AEC. From those it generates the chip-select lines: ROMH, ROML, I/O, GR/W (to color RAM), CHAROM, KERNAL, BASIC, and CASRAM.
|
||||
|
||||
The 6510 port at $01 plus the GAME/EXROM pins of the cartridge port are how bank switching is done. The full banking matrix is in `PLA - The C64 PLA Dissected` (skoe.de).
|
||||
|
||||
## PLA failure
|
||||
|
||||
The original bipolar 82S100 PLA and the early NMOS 906114-01 are notorious for failure. Modern replacements include the SuperPLA, realPLA, PLAnkton, PLAtinum, neatPLA, PLA20V8 (GAL-PLA), and EPROM-based replacements. Timing is critical — the new variants are sometimes too fast and can break compatibility with certain cartridges.
|
||||
|
||||
## Memory access patterns (normal and badline)
|
||||
|
||||
The VIC's "normal" pattern in a raster line, when not a badline and no sprites, is:
|
||||
|
||||
```
|
||||
cycle: 1..14 idle (VIC reads, CPU reads/writes alternate)
|
||||
15 start of display? (depends on RC, VC, DEN)
|
||||
16+ g-accesses (character generator reads)
|
||||
...
|
||||
58 last g-access
|
||||
59.. more idle / sprite accesses
|
||||
```
|
||||
|
||||
On a **badline** (every 8th raster line within the display window when in text/bitmap mode and YSCROLL matches the lower 3 bits of RASTER), the VIC does the additional 40 c-accesses (video matrix reads) which forces the take-over of the bus — that's why the CPU is paused for 40 cycles. Badlines cost the CPU about 40 cycles per text line, which is one of the big reasons raster loops and self-modifying code have to be precisely cycle-counted.
|
||||
@@ -0,0 +1,58 @@
|
||||
# C64 Memory Map
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Memory_Map
|
||||
|
||||
The following article shows a short overview of the C64 memory map (pages and memory addresses) as seen by its CPU. The address space may look different from the view of other chips such as the VIC.
|
||||
|
||||
This overview shows the status after power on of the C64 in the standard memory configuration ($37/55 in memory address $01, no cartridge).
|
||||
|
||||
Detailed descriptions of every memory area can be found in the associated articles. The memory management is implemented mostly by the C64 PLA.
|
||||
|
||||
## RAM Table
|
||||
|
||||
| Hex Address | Dec Address | Page | Contents |
|
||||
|-------------|-------------|------|----------|
|
||||
| $0000-$00FF | 0-255 | Page 0 | Zeropage addressing |
|
||||
| $0100-$01FF | 256-511 | Page 1 | Enhanced Zeropage contains the stack |
|
||||
| $0200-$02FF | 512-767 | Page 2 | Operating System and BASIC pointers |
|
||||
| $0300-$03FF | 768-1023 | Page 3 | Operating System and BASIC pointers |
|
||||
| $0400-$07FF | 1024-2047 | Page 4-7 | Screen Memory |
|
||||
| $0800-$9FFF | 2048-40959 | Page 8-159 | Free BASIC program storage area (38911 bytes) |
|
||||
| $A000-$BFFF | 40960-49151 | Page 160-191 | Free machine language program storage area (when switched-out with ROM) |
|
||||
| $C000-$CFFF | 49152-53247 | Page 192-207 | Free machine language program storage area |
|
||||
| $D000-$D3FF | 53248-54271 | Page 208-211 | VIC-II registers |
|
||||
| $D400-$D7FF | 54272-54527 | Page 212-215 | SID registers |
|
||||
| $D800-$DBFF | 55296-56319 | Page 216-219 | Color RAM |
|
||||
| $DC00-$DCFF | 56320-56575 | Page 220 | CIA 1 |
|
||||
| $DD00-$DDFF | 56576-56831 | Page 221 | CIA 2 |
|
||||
| $DE00-$DFFF | 56832-57343 | Page 222-223 | Reserved for interface extensions |
|
||||
| $E000-$FFFF | 57344-65535 | Page 224-255 | Free machine language program storage area (when switched-out with ROM) |
|
||||
|
||||
## ROM Table
|
||||
|
||||
| Hex Address | Dec Address | Page | Contents |
|
||||
|-------------|-------------|------|----------|
|
||||
| $8000-$9FFF | 32768-40959 | Page 128-159 | Cartridge ROM (low) |
|
||||
| $A000-$BFFF | 40960-49151 | Page 160-191 | BASIC interpretor ROM or cartridge ROM (high) |
|
||||
| $D000-$DFFF | 53248-57343 | Page 208-223 | Character generator ROM |
|
||||
| $E000-$FFFF | 57344-65535 | Page 224-255 | KERNAL ROM or cartridge ROM (high) |
|
||||
|
||||
## I/O Table
|
||||
|
||||
| Hex Address | Dec Address | Page | Contents |
|
||||
|-------------|-------------|------|----------|
|
||||
| $0000-$0001 | 0-1 | - | CPU I/O port - see Zeropage |
|
||||
| $D000-$D3FF | 53248-54271 | Page 208-211 | VIC-II registers |
|
||||
| $D400-$D7FF | 54272-55295 | Page 212-215 | SID registers |
|
||||
| $D800-$DBFF | 55296-56319 | Page 216-219 | Color Memory |
|
||||
| $DC00-$DCFF | 56320-56575 | Page 220 | CIA 1 |
|
||||
| $DD00-$DDFF | 56576-56831 | Page 221 | CIA 2 |
|
||||
| $DE00-$DEFF | 56832-57087 | Page 222 | I/O 1 |
|
||||
| $DF00-$DFFF | 57088-57343 | Page 223 | I/O 2 |
|
||||
|
||||
## Notes
|
||||
|
||||
- The default configuration is for KERNAL ROM, I/O, BASIC ROM and the remaining RAM banks to be visible to the CPU. All configurations depend upon the state of latch bits set in the Programmable Logic Unit (PLA). The 7 distinct RAM banks are the smallest zones which can be bank switched.
|
||||
- If ROM is visible to the CPU during a write procedure, the ROM will be read but, any data is written to the underlying RAM. This principle is particularly significant to understanding how the I/O registers are addressed.
|
||||
- If cartridge ROM is present it can be located in up to three addressable locations. However, only two 8 kByte banks can be seen by the CPU at any time.
|
||||
- The BASIC program storage space crosses the boundaries of RAM zones, sitting between $0800-$9FFF (38911 BASIC bytes).
|
||||
@@ -0,0 +1,122 @@
|
||||
# Zeropage (Page 0) and Page 1
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Zeropage and https://www.c64-wiki.com/wiki/Page_1
|
||||
|
||||
The first 256 bytes ($0000-$00FF) of the C64 memory map are called **zeropage** (or "Page 0"). The 6502/6510/8502 family has special addressing modes (zero page addressing, indirect zero page, indexed zero page) that are faster and shorter than the equivalent absolute modes — they are 1 byte shorter and 1 cycle faster. The indirect-indexed mode (`(zp),Y`) only works on zero page addresses, which is why zero page locations are used as "index registers" in 6502-family assembly.
|
||||
|
||||
The first two addresses in zeropage — and indeed in the entire address space — are "hardwired" in the 6510 to the CPU's internal I/O port. The data direction register is at $00 and the data register is at $01.
|
||||
|
||||
## The 6510 I/O Port
|
||||
|
||||
| Addr | Purpose |
|
||||
|------|---------|
|
||||
| $00 (0) | Data direction for $01 (0=input, 1=output) |
|
||||
| $01 (1) | CPU data port (also controls memory banking) |
|
||||
|
||||
### $01 — CPU data port (and bank bits)
|
||||
|
||||
| Bit | Name | Purpose |
|
||||
|-----|------|---------|
|
||||
| 0 | LORAM | 0 = RAM at $A000-$BFFF visible; 1 = BASIC ROM visible |
|
||||
| 1 | HIRAM | 0 = RAM at $E000-$FFFF visible; 1 = KERNAL ROM visible |
|
||||
| 2 | CHAREN | 0 = Char ROM visible at $D000-$DFFF; 1 = I/O registers visible (default) |
|
||||
| 3 | Cassette Data Output (Datasette) |
|
||||
| 4 | Cassette Switch Sense (1 = switch closed) |
|
||||
| 5 | Cassette Motor (0 = on) |
|
||||
| 6 | (Undefined on 6510) |
|
||||
| 7 | (Undefined on 6510) |
|
||||
|
||||
The default value after reset is `$37 = %00110111` — BASIC + KERNAL + I/O visible, cassette motor off.
|
||||
|
||||
Note: addresses 0 and 1 cannot be read or written as ordinary RAM from the CPU; they only access the port. However, with the right VIC trickery (datassette buffer in `$02`) you can read underlying RAM contents via the VIC.
|
||||
|
||||
## KERNAL/BASIC zero page usage
|
||||
|
||||
Most of the zero page is in use by KERNAL and BASIC ROMs. A handful of locations are "safe" for user ML programs, but most of $00-$8F is occupied. The page is dominated by:
|
||||
|
||||
- $00-$02: CPU port, unused, float-to-int / int-to-float ROM pointers
|
||||
- $03-$06: ROM helper pointers
|
||||
- $07-$0F: BASIC flags and various state
|
||||
- $10-$13: BASIC bookkeeping
|
||||
- $14-$15: pointer for ON/GOTO/GOSUB/LIST/PEEK/POKE/SYS target
|
||||
- $16-$18: temporary string stack
|
||||
- $19-$21: temporary string descriptors
|
||||
- $22-$25: utility pointer area
|
||||
- $26-$2A: float multiply/divide result
|
||||
- $2B-$37: BASIC program/symbol table pointers (TXTTAB, VARTAB, ARYTAB, STREND, FRETOP, MEMSIZ)
|
||||
- $39-$3E: current/previous BASIC line numbers, CONT target
|
||||
- $3F-$42: DATA line/item for READ
|
||||
- $43-$44: INPUT storage
|
||||
- $45-$48: variable name lookup
|
||||
- $49-$4A: FOR/NEXT index
|
||||
- $4B-$4C: math temp / TXTPTR for READ/GET/INPUT
|
||||
- $4D: mask for <, >, = evaluation
|
||||
- $4E-$4F: temp for FN or float
|
||||
- $50-$52: strings
|
||||
- $53: string length for garbage collection
|
||||
- $54: constant `$4C` (JMP opcode)
|
||||
- $55-$56: pointer for function eval
|
||||
- $57-$6E: floating point accumulators (FAC#3, #4, #1, #2)
|
||||
- $6F: result of signed comparison
|
||||
- $70-$72: FAC#2 round / temp series pointer
|
||||
- $73-$8A: CHRGET (fetch next BASIC char) routine
|
||||
- $8B-$8F: RND seed
|
||||
- $90: KERNAL I/O status (bit 6 = EOF)
|
||||
- $91: STOP/C=/SPACE/CTRL flag (127/223/239/251/255)
|
||||
- $92: cassette timing constant
|
||||
- $93: LOAD/VERIFY flag
|
||||
- $94-$9B: serial bus / cassette / output device
|
||||
- $9C-$9F: cassette status
|
||||
- $A0-$A2: software jiffy clock (updated by KERNAL IRQ every 1/60 sec)
|
||||
- $A3-$A4: serial/cassette bit counter
|
||||
- $A5-$A6: cassette sync / buffer size
|
||||
- $A7-$AC: RS-232 / cassette byte
|
||||
- $AC-$AF: LOAD/VERIFY/SAVE start/end
|
||||
- $B0-$B1: cassette timing constants (default $92 = 146)
|
||||
- $B2-$B3: cassette buffer start
|
||||
- $B4-$B6: RS-232 output
|
||||
- $B7-$BA: current logical file / secondary / device
|
||||
- $BB-$BC: current file name pointer
|
||||
- $BD: RS-232 parity / cassette R/W register
|
||||
- $BE: cassette dup block counter
|
||||
- $BF: cassette byte register
|
||||
- $C0: cassette motor (0=off)
|
||||
- $C1-$C2: LOAD/SAVE start address
|
||||
- $C3-$C4: LOAD/SAVE end address
|
||||
- $C5: last key matrix coord (64 = none)
|
||||
- $C6: keyboard buffer count
|
||||
- $C7: reverse print flag
|
||||
- $C8: last column of current line during INPUT
|
||||
- $C9-$CA: cursor X/Y
|
||||
- $CB: index into keyboard decoding table
|
||||
- $CC: flash cursor flag
|
||||
- $CD: cursor flash counter
|
||||
- $CE: char at cursor
|
||||
- $CF: cursor flash phase
|
||||
- $D0: input from keyboard/screen
|
||||
- $D1-$D2: pointer to current screen line
|
||||
- $D3: cursor column in logical line
|
||||
- $D4: quote mode flag
|
||||
- $D5: max column (39 or 79)
|
||||
- $D6: current physical line
|
||||
- $D7: ASCII of last printed char
|
||||
- $D8: insert mode flag
|
||||
- $D9-$F2: screen line link table (26 bytes)
|
||||
- $F3-$F4: color RAM pointer for current line
|
||||
- $F5-$F6: keyboard decoding table pointer
|
||||
- $F7-$F8: RS-232 input buffer pointer
|
||||
- $F9-$FA: RS-232 output buffer pointer
|
||||
- $FB-$FC: unused
|
||||
- $FD: unused
|
||||
- $FE: unused
|
||||
- $FF: temp for BASIC float to ASCII
|
||||
|
||||
## Page 1 ($0100-$01FF)
|
||||
|
||||
Page 1 (also called "Extended Zeropage") is mostly used by the **hardware stack** (which grows downward from $01FF). The bottom of the page is reserved for the BASIC/Datasette scratch area:
|
||||
|
||||
- $0100-$010A: floating point to string conversion work area
|
||||
- $0100-$013E: Datasette input error log
|
||||
- $013F-$01FF: the 6502 hardware stack (default top = $FF, so stack is at $0100-$01FF)
|
||||
|
||||
In an Oscar64 (or typical) machine-language program, the hardware stack is in this area. C64 BASIC pushes the stack pointer to $FF and uses the top of the page for storage.
|
||||
@@ -0,0 +1,93 @@
|
||||
# SID — Sound Interface Device (6581 / 8580)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/SID
|
||||
|
||||
## Overview
|
||||
|
||||
SID is the name of the sound chip used in the VC 10, C64 and C128. The SID was developed by Bob Yannes, an employee of MOS Technology. Bob (Robert) Yannes knew a lot about music. His intention was to implement a real subtractive synthesis chip, totally different from all other home computer sound devices of its time. The chip combines analogue and digital circuitry that cannot be emulated with 100% fidelity even today.
|
||||
|
||||
The C64 and C128 (plastic case) use the 6581. The C64-II and C128DCR (metal case) use the newer 8580. Newer chip replacements emulate the SID: SIDKick pico, SwinSID, etc.
|
||||
|
||||
## Properties
|
||||
|
||||
- 3 tone generators (voices), frequency 0-4 kHz (16 bit precision)
|
||||
- 4 waveforms: sawtooth, triangle, rectangle (pulse width modulation), noise
|
||||
- 3 amplitude modulators, up to 48 dB
|
||||
- 3 envelope generators (ADSR)
|
||||
- Synchronization of the oscillators
|
||||
- Ring modulation
|
||||
- Programmable filters: low pass, band pass, high pass
|
||||
- Master volume in 16 steps
|
||||
- 2 A/D converters (8 bit, low frequency, used for reading paddle input)
|
||||
- Random generator
|
||||
- Audio input (cannot be used for sampling, but signal can be routed through the SID filter)
|
||||
|
||||
## Chip Variations
|
||||
|
||||
| Chip | Production | Notes |
|
||||
|------|------------|-------|
|
||||
| 6581 | 21/1982 - 30/1985 | NMOS, pin 28 = 12V, 470pF on filter caps |
|
||||
| 6581R3 | 42/1985 - 07/1986 | |
|
||||
| 6581R4 | 16/1986 - 30/1986 | |
|
||||
| 6581R4AR | 22/1986 - 06/1987 | |
|
||||
| 8580R5 | 06/1987 - 19/1992 | HMOS-II, pin 28 = 9V, 22nF on filter caps; quiet digisound without "digifix" |
|
||||
|
||||
## Memory Addresses (SID at $D400-$D41C, mirrored through $D7FF except on C128)
|
||||
|
||||
| Address Hex | Address Dec | Function |
|
||||
|-------------|-------------|----------|
|
||||
| $D400 | 54272 | Frequency voice 1 low byte |
|
||||
| $D401 | 54273 | Frequency voice 1 high byte |
|
||||
| $D402 | 54274 | Pulse wave duty cycle voice 1 low byte (low nibble) |
|
||||
| $D403 | 54275 | Pulse wave duty cycle voice 1 high byte (low nibble) |
|
||||
| $D404 | 54276 | Control register voice 1 |
|
||||
| $D405 | 54277 | Attack duration / Decay duration voice 1 |
|
||||
| $D406 | 54278 | Sustain level / Release duration voice 1 |
|
||||
| $D407-$D40D | 54279-54285 | Same for voice 2 |
|
||||
| $D40E-$D414 | 54286-54292 | Same for voice 3 |
|
||||
| $D415 | 54293 | Filter cutoff frequency low byte (low 3 bits) |
|
||||
| $D416 | 54294 | Filter cutoff frequency high byte (8 bits) |
|
||||
| $D417 | 54295 | Filter resonance and routing (high nibble=res, low 4 bits=ext/3/2/1 enable) |
|
||||
| $D418 | 54296 | Filter mode and main volume (bits 6-4: mute V3 / HP / BP / LP, bits 3-0: volume 0-15) |
|
||||
| $D419 | 54297 | Paddle X value (read only) |
|
||||
| $D41A | 54298 | Paddle Y value (read only) |
|
||||
| $D41B | 54299 | Oscillator voice 3 (read only) |
|
||||
| $D41C | 54300 | Envelope voice 3 (read only) |
|
||||
|
||||
## Voice Control Register ($D404 / $D40B / $D412)
|
||||
|
||||
Bit 0: Gate (1 = start envelope, 0 = release)
|
||||
Bit 1: Synchronize with voice 3 (for V1) / voice 1 (V2) / voice 2 (V3)
|
||||
Bit 2: Ring modulation with voice 3 (V1) / voice 1 (V2) / voice 2 (V3)
|
||||
Bit 3: Test (reset oscillator)
|
||||
Bit 4: Triangle wave
|
||||
Bit 5: Sawtooth wave
|
||||
Bit 6: Pulse (rectangle) wave
|
||||
Bit 7: Noise
|
||||
|
||||
## ADSR ($D405-$D406 per voice)
|
||||
|
||||
$D405 high nibble: Attack (0-15)
|
||||
$D405 low nibble: Decay (0-15)
|
||||
$D406 high nibble: Sustain (0-15)
|
||||
$D406 low nibble: Release (0-15)
|
||||
|
||||
## Pinout
|
||||
|
||||
- CAP1A, CAP1B / CAP2A, CAP2B: filter capacitors (6581: 470pF, 8580: 20nF)
|
||||
- /RES: Reset
|
||||
- phi2: system clock
|
||||
- R/W
|
||||
- /CS
|
||||
- A0-A4
|
||||
- GND
|
||||
- Vdd: 12V (6581) or 9V (8580)
|
||||
- AUDIO OUT
|
||||
- EXT IN: external audio in (8580 needs ~330kOhm to GND to fix old digisound)
|
||||
- Vcc: 5V
|
||||
- POT X, POT Y: paddle inputs
|
||||
- D0-D7: data bus
|
||||
|
||||
## Trivia
|
||||
|
||||
Bob Yannes later founded Ensoniq (the ESQ-1 is reportedly the synth he wanted the SID to be). The 6581's volume register design flaw was famously used to play 4-bit samples by rapidly changing the volume. The 8580 fixed this; sample playback can be restored with a resistor on EXT IN.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Sprites (MOBs — Movable Object Blocks)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Sprite
|
||||
|
||||
A sprite is a piece of graphics that can move and be assigned attributes independently of other graphics or text. The VIC-II supports up to 8 sprites, but with raster interrupt programming you can display more than eight simultaneously.
|
||||
|
||||
## Sprite data
|
||||
|
||||
- A sprite pattern is 24×21 pixels (12×21 in multicolor).
|
||||
- 63 bytes of data + 1 unused "pad" byte = 64 bytes per sprite (must be 64-byte aligned in the current VIC bank).
|
||||
- In theory a VIC bank holds 256 patterns; with a text screen and charset, practically ~208.
|
||||
|
||||
## Hi-res sprite
|
||||
|
||||
- Bit = 0 → transparent
|
||||
- Bit = 1 → sprite's individual color
|
||||
|
||||
## Multicolor sprite
|
||||
|
||||
Each 2 bits = 1 pixel (2× wider):
|
||||
- %00 → transparent
|
||||
- %01 → color from $D025 (multicolor 0, common)
|
||||
- %10 → color from $D027-$D02E (this sprite's color)
|
||||
- %11 → color from $D026 (multicolor 1, common)
|
||||
|
||||
## Sprite pointers (in screen RAM area)
|
||||
|
||||
If text screen starts at address S, sprite pointers are at S+1016 through S+1023. With the default screen at $0400, that's $07F8-$07FF. Pointers contain the pattern address / 64.
|
||||
|
||||
| Sprite | Pointer |
|
||||
|--------|---------|
|
||||
| #0 | $07F8 (2040) |
|
||||
| #1 | $07F9 (2041) |
|
||||
| #2 | $07FA (2042) |
|
||||
| #3 | $07FB (2043) |
|
||||
| #4 | $07FC (2044) |
|
||||
| #5 | $07FD (2045) |
|
||||
| #6 | $07FE (2046) |
|
||||
| #7 | $07FF (2047) |
|
||||
|
||||
## Sprite position registers
|
||||
|
||||
| Sprite | X reg | Y reg |
|
||||
|--------|-------|-------|
|
||||
| #0 | $D000 (53248) | $D001 (53249) |
|
||||
| #1 | $D002 (53250) | $D003 (53251) |
|
||||
| #2 | $D004 (53252) | $D005 (53253) |
|
||||
| #3 | $D006 (53254) | $D007 (53255) |
|
||||
| #4 | $D008 (53256) | $D009 (53257) |
|
||||
| #5 | $D00A (53258) | $D00B (53259) |
|
||||
| #6 | $D00C (53260) | $D00D (53261) |
|
||||
| #7 | $D00E (53262) | $D00F (53263) |
|
||||
|
||||
X coordinates need 9 bits; the MSBs are in $D010 (one bit per sprite).
|
||||
|
||||
## Sprite color registers
|
||||
|
||||
| Sprite | Color |
|
||||
|--------|-------|
|
||||
| #0 | $D027 |
|
||||
| #1 | $D028 |
|
||||
| #2 | $D029 |
|
||||
| #3 | $D02A |
|
||||
| #4 | $D02B |
|
||||
| #5 | $D02C |
|
||||
| #6 | $D02D |
|
||||
| #7 | $D02E |
|
||||
|
||||
Multicolor shared colors: $D025, $D026.
|
||||
|
||||
## Sprite enable ($D015)
|
||||
|
||||
Each bit is a switch for that sprite. 1 = enabled, 0 = hidden.
|
||||
Bit 0 = sprite #0, bit 7 = sprite #7.
|
||||
|
||||
## Sprite multicolor mode ($D01C)
|
||||
|
||||
Each bit selects multicolor (1) or hires (0) for that sprite.
|
||||
|
||||
## X expansion ($D01D), Y expansion ($D017)
|
||||
|
||||
Each bit doubles that sprite's size in the corresponding direction.
|
||||
|
||||
## Sprite priority ($D01B)
|
||||
|
||||
Each bit, when set, puts the sprite *behind* the background graphics (text/bitmap). When 0, sprite is in front. Sprite-sprite priority is hardwired: lower-numbered sprites always appear in front of higher-numbered ones.
|
||||
|
||||
## Collision detection
|
||||
|
||||
$D01E: sprite-sprite collision (1 bit per sprite). Read clears.
|
||||
$D01F: sprite-data (background) collision. Read clears.
|
||||
$D019 bit 2 = sprite-sprite IRQ flag, bit 1 = sprite-data IRQ flag.
|
||||
$D01A bits enable these IRQs.
|
||||
|
||||
## Example: enable sprite #0 with white color
|
||||
|
||||
```basic
|
||||
POKE 2040,13: REM pattern at 13*64=832
|
||||
POKE 53248,255: REM X=255
|
||||
POKE 53249,100: REM Y=100
|
||||
POKE 53287,1: REM color=1 (white)
|
||||
POKE 53269,1: REM enable sprite #0
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
# Graphics Modes
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Graphics_Modes and https://www.c64-wiki.com/wiki/Standard_Character_Mode
|
||||
|
||||
The C64 supports 5 "official" graphics modes selected by 3 bits in the two VIC-II control registers:
|
||||
- **ECM** (Extended Color Mode) — bit 6 of $D011
|
||||
- **BMM** (Bitmap Mode) — bit 5 of $D011
|
||||
- **MCM** (Multicolor Mode) — bit 4 of $D016
|
||||
|
||||
The other bits of those registers are YSCROLL (0-2 of $D011), DEN (display enable, bit 4 of $D011), RSEL (bit 3 of $D011), CSEL (bit 3 of $D016), XSCROLL (0-2 of $D016).
|
||||
|
||||
## The 8 (5 legal) modes
|
||||
|
||||
| Mode | ECM | BMM | MCM | Result |
|
||||
|------|-----|-----|-----|--------|
|
||||
| 0 | 0 | 0 | 0 | Standard Character Mode |
|
||||
| 1 | 0 | 0 | 1 | Multicolor Character Mode |
|
||||
| 2 | 0 | 1 | 0 | Standard Bitmap Mode |
|
||||
| 3 | 0 | 1 | 1 | Multicolor Bitmap Mode |
|
||||
| 4 | 1 | 0 | 0 | Extended Background Color Mode |
|
||||
| 5 | 1 | 0 | 1 | Invalid |
|
||||
| 6 | 1 | 1 | 0 | Invalid |
|
||||
| 7 | 1 | 1 | 1 | Invalid |
|
||||
|
||||
Modes 5-7 are "technically feasible but produce no visible output."
|
||||
|
||||
## Standard Character Mode (Mode 0)
|
||||
|
||||
- 40×25 character cells of 8×8 pixels.
|
||||
- Each cell can have one of 16 colors (from color RAM nibble at $D800+cell_index).
|
||||
- A single background color ($D021) applies to the whole screen.
|
||||
- Screen memory is 1 KB ($0400-$07FF); Color RAM is 1000 nibbles ($D800-$DBE7).
|
||||
- Character patterns are 8 bytes per character, fetched from the character generator (default is the 4 KB character ROM at $D000-$DFFF, but can be relocated to RAM).
|
||||
- Soft scrolling is "easier" in character mode than in bitmap mode (just change XSCROLL/YSCROLL).
|
||||
|
||||
## Multicolor Character Mode (Mode 1)
|
||||
|
||||
- Each cell still 8×8, but only 4×8 in effective pixel resolution (each pair of bits in the character pattern is one "wide pixel").
|
||||
- Up to 4 colors per cell: the two global background colors ($D021 and $D022) plus the cell's color RAM color and the multicolor-1 shared color $D026.
|
||||
- "Color" bits %00/%01/%10/%11 map to: background 0 ($D021), background 1 ($D022), color RAM, background 2 ($D023). Pattern bit pairs.
|
||||
|
||||
## Standard Bitmap Mode (Mode 2) — "hires"
|
||||
|
||||
- 320×200 pixels, 1 bit per pixel.
|
||||
- Bitmap is 8000 bytes. Location selected by bits VM13-VM10 of $D018 (in 8 KB steps within the 16 KB VIC bank). Video matrix (screen memory) is 1 KB at $D018's VM bits (in 1 KB steps).
|
||||
- Color: each 8×8 cell is monochrome (foreground from color RAM nibble, background from $D021).
|
||||
- BM=1: the video matrix contains *color* information instead of character codes, so color RAM is doubled up to provide per-cell background and foreground colors (the color RAM holds the cell's foreground; the cell's background is one of the 4 global "background color" registers $D021-$D024 in the 4 most-significant bits of the cell).
|
||||
|
||||
## Multicolor Bitmap Mode (Mode 3)
|
||||
|
||||
- 160×200 pixels, 2 bits per pixel (4 colors per 4×8 cell).
|
||||
- Each pair of bits selects from: background 0 ($D021), background 1 ($D022), background 2 ($D023), color RAM cell color.
|
||||
- Same memory layout as Standard Bitmap Mode (8000 bytes bitmap + 1 KB matrix).
|
||||
|
||||
## Extended Background Color Mode (Mode 4)
|
||||
|
||||
- Character mode (not bitmap) with 4 background colors instead of 1.
|
||||
- Each character cell can select one of the 4 background colors ($D021-$D024) via 2 bits in its screen memory code.
|
||||
- Useful for colored PETSCII art.
|
||||
|
||||
## Unofficial "modes" (demo scene techniques)
|
||||
|
||||
There are dozens of techniques that go beyond the official modes by exploiting undocumented VIC behavior or combining tricks like raster IRQ, sprites, and interlacing. The most famous families:
|
||||
|
||||
- **FLI** (Flexible Line Interpretation, July 1989) — forces a badline every line, allowing per-line color attributes. The first "demo scene" graphic hack.
|
||||
- **IFLI** (Interlaced FLI, 1991) — combines FLI with 2-frame interlace.
|
||||
- **AFLI** (Advanced FLI, 1990), **NUFLI** (2009), **MUFLI** (2006), **UFLI** (1996), **XFLI** (2002), **SHFLI** (1996), etc.
|
||||
- **AFLI** and **UFLI** give 8+ colors per 8×8 cell, **NUFLI** adds even more.
|
||||
- **SHI** (Super HiRes Interlace, 1991) — interlaced 320×400 mode.
|
||||
- **Hyperscreen** — stable display using all 16 KB VIC bank by abusing badline tricks.
|
||||
- **FLD** (Flexible Line Distance) — variation.
|
||||
- **Megatext** (2004) — large character set mode.
|
||||
|
||||
(See `vic/cebix-vic-article.txt` for the technical underpinnings of these tricks, especially sections 3.14 "Effects and applications" and 3.5 "Bad Lines".)
|
||||
@@ -0,0 +1,56 @@
|
||||
# VIC-II (Video Interface Chip II)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/VIC
|
||||
|
||||
## Identification
|
||||
|
||||
VIC-II is the video chip used in the C64 / C128 family. There are two VIC-II types in the C64: the 6567 in NTSC machines and the 6569 in PAL machines. Newer C64 versions use the functionally equivalent 8562 (NTSC) and 8565 (PAL) chips.
|
||||
|
||||
## Features
|
||||
|
||||
- 16 kB address space for screen, character and sprite memory
|
||||
- 320 × 200 pixels video resolution (160 × 200 in multi-color mode)
|
||||
- 40 × 25 characters text resolution
|
||||
- Three character display modes and two bitmap modes
|
||||
- 16 colors
|
||||
- Concurrent handling of 8 sprites per scanline, each of 24 × 21 pixels (12 × 21 multicolor)
|
||||
- Raster interrupt
|
||||
- Smooth scrolling
|
||||
- Independent dynamic RAM refresh (an unusual feature for a graphics processor)
|
||||
- Bus mastering for a 6502-style system bus; CPU and VIC-II accessing the bus during alternating half-clock cycles (the VIC-II will halt the CPU when it needs extra cycles)
|
||||
|
||||
## Memory Addresses of the VIC-II
|
||||
|
||||
| Hex Address | Dec Address | Page | Contents |
|
||||
|-------------|-------------|------|----------|
|
||||
| $D000-$D3FF | 53248-54271 | Page 208-211 | VIC-II registers |
|
||||
| $D800-$DBE7 | 55296-56295 | Page 216-219 | Color RAM |
|
||||
|
||||
- Sprites lie at address MEM(Start of screen mem + $03F8 + sprite number)*64
|
||||
- The start of the Screen RAM is set by $DD00 (the VIC bank, see CIA 2) and $D018.
|
||||
|
||||
## Technical Notes (from Christian Bauer's article)
|
||||
|
||||
The operation of the VIC-II is thoroughly described in the document
|
||||
"The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64"
|
||||
by Christian Bauer. See `vic/cebix-vic-article.txt` for the full text.
|
||||
|
||||
Key facts:
|
||||
- The cycle count starts at 1; for example, VICE starts counting at 0.
|
||||
- Writing to $D011/$D012 can immediately trigger a raster IRQ, provided no raster IRQ has yet been triggered in the current line.
|
||||
- The vertical "expansion flip-flop" is somewhat misleadingly named; its state actually indicates whether a line in the sprite data should currently be skipped or not.
|
||||
- VCBASE and MCBASE are used to reset VC and MC to their initial values.
|
||||
- VMLI is not a pointer but rather a 40-bit shift register that controls the 40 enable bits of the VIC-II's internal "40×12 bit video matrix/color line."
|
||||
|
||||
## Known Variants
|
||||
|
||||
- MOS 6560 / 6561: VIC-I (VIC-20)
|
||||
- MOS 6566: VIC-II for MAX Machine
|
||||
- MOS 6567: VIC-II for (NTSC) C64
|
||||
- MOS 6569: VIC-II for (PAL) C64 (R1/R3/R4/R5)
|
||||
- MOS 6572/6573: VIC-II (PAL-N/PAL-M)
|
||||
- MOS 8562: VIC-II (NTSC) for later C64 / C128
|
||||
- MOS 8564/8565/8566/8569: VIC-II variants for C64 / C128
|
||||
- CSG 4567: VIC-III (C65/C64DX)
|
||||
|
||||
The NMOS variants 6566/67/69 require +12V on Pin 13/Vdd; the HMOS-II variants (8562/65) require only +5V DC. The 856x variants exhibit the "Grey Dots" problem.
|
||||
@@ -0,0 +1,68 @@
|
||||
# VIC-II Register Map ($D000–$D3FF)
|
||||
|
||||
Source: https://www.c64-wiki.com/wiki/Page_208-211
|
||||
|
||||
Page 208-211 covers the memory locations 53248-54271 ($D000-D3FF). This area is wholly reserved for the VIC-II Registers. The 47 registers are mirrored every 64 bytes in this area.
|
||||
|
||||
## Register Table
|
||||
|
||||
| Hex | Dec | Type | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | Contents |
|
||||
|------|------|----------|------|------|------|------|------|------|------|------|----------|
|
||||
| $D000 | 53248 | Register | | | | | | M0X | | | X Coordinate Sprite 0 |
|
||||
| $D001 | 53249 | Register | | | | | | M0Y | | | Y Coordinate Sprite 0 |
|
||||
| $D002 | 53250 | Register | | | | | | M1X | | | X Coordinate Sprite 1 |
|
||||
| $D003 | 53251 | Register | | | | | | M1Y | | | Y Coordinate Sprite 1 |
|
||||
| $D004 | 53252 | Register | | | | | | M2X | | | X Coordinate Sprite 2 |
|
||||
| $D005 | 53253 | Register | | | | | | M2Y | | | Y Coordinate Sprite 2 |
|
||||
| $D006 | 53254 | Register | | | | | | M3X | | | X Coordinate Sprite 3 |
|
||||
| $D007 | 53255 | Register | | | | | | M3Y | | | Y Coordinate Sprite 3 |
|
||||
| $D008 | 53256 | Register | | | | | | M4X | | | X Coordinate Sprite 4 |
|
||||
| $D009 | 53257 | Register | | | | | | M4Y | | | Y Coordinate Sprite 4 |
|
||||
| $D00A | 53258 | Register | | | | | | M5X | | | X Coordinate Sprite 5 |
|
||||
| $D00B | 53259 | Register | | | | | | M5Y | | | Y Coordinate Sprite 5 |
|
||||
| $D00C | 53260 | Register | | | | | | M6X | | | X Coordinate Sprite 6 |
|
||||
| $D00D | 53261 | Register | | | | | | M6Y | | | Y Coordinate Sprite 6 |
|
||||
| $D00E | 53262 | Register | | | | | | M7X | | | X Coordinate Sprite 7 |
|
||||
| $D00F | 53263 | Register | | | | | | M7Y | | | Y Coordinate Sprite 7 |
|
||||
| $D010 | 53264 | Register | M7X8 | M6X8 | M5X8 | M4X8 | M3X8 | M2X8 | M1X8 | M0X8 | MSBs of X coordinates |
|
||||
| $D011 | 53265 | Register | RST8 | ECM | BMM | DEN | RSEL | YSCROLL | | | Control register 1 |
|
||||
| $D012 | 53266 | Register | | RASTER | | | | | | | Raster counter |
|
||||
| $D013 | 53267 | Register | | LPX | | | | | | | Light pen X |
|
||||
| $D014 | 53268 | Register | | LPY | | | | | | | Light pen Y |
|
||||
| $D015 | 53269 | Register | M7E | M6E | M5E | M4E | M3E | M2E | M1E | M0E | Sprite enabled |
|
||||
| $D016 | 53270 | Register | - | - | RES | MCM | CSEL | XSCROLL | | | Control register 2 |
|
||||
| $D017 | 53271 | Register | M7YE | M6YE | M5YE | M4YE | M3YE | M2YE | M1YE | M0YE | Sprite Y expansion |
|
||||
| $D018 | 53272 | Register | VM13 | VM12 | VM11 | VM10 | CB13 | CB12 | CB11 | - | Memory pointers |
|
||||
| $D019 | 53273 | Register | IRQ | - | - | - | ILP | IMMC | IMBC | IRST | Interrupt register |
|
||||
| $D01A | 53274 | Register | IRQ | - | - | - | ELP | EMMC | EMBC | ERST | Interrupt enabled |
|
||||
| $D01B | 53275 | Register | M7DP | M6DP | M5DP | M4DP | M3DP | M2DP | M1DP | M0DP | Sprite data priority |
|
||||
| $D01C | 53276 | Register | M7MC | M6MC | M5MC | M4MC | M3MC | M2MC | M1MC | M0MC | Sprite multicolour |
|
||||
| $D01D | 53277 | Register | M7XE | M6XE | M5XE | M4XE | M3XE | M2XE | M1XE | M0XE | Sprite X expansion |
|
||||
| $D01E | 53278 | Register | M7M | M6M | M5M | M4M | M3M | M2M | M1M | M0M | Sprite-sprite collision |
|
||||
| $D01F | 53279 | Register | M7D | M6D | M5D | M4D | M3D | M2D | M1D | M0D | Sprite-data collision |
|
||||
| $D020 | 53280 | Register | - | - | - | - | EC | | | | Border colour |
|
||||
| $D021 | 53281 | Register | - | - | - | - | B0C | | | | Background colour 0 |
|
||||
| $D022 | 53282 | Register | - | - | - | - | B1C | | | | Background colour 1 |
|
||||
| $D023 | 53283 | Register | - | - | - | - | B2C | | | | Background colour 2 |
|
||||
| $D024 | 53284 | Register | - | - | - | - | B3C | | | | Background colour 3 |
|
||||
| $D025 | 53285 | Register | - | - | - | - | MM0 | | | | Sprite multicolour 0 |
|
||||
| $D026 | 53286 | Register | - | - | - | - | MM1 | | | | Sprite multicolour 1 |
|
||||
| $D027 | 53287 | Register | - | - | - | - | M0C | | | | Sprite 0 colour |
|
||||
| $D028 | 53288 | Register | - | - | - | - | M1C | | | | Sprite 1 colour |
|
||||
| $D029 | 53289 | Register | - | - | - | - | M2C | | | | Sprite 2 colour |
|
||||
| $D02A | 53290 | Register | - | - | - | - | M3C | | | | Sprite 3 colour |
|
||||
| $D02B | 53291 | Register | - | - | - | - | M4C | | | | Sprite 4 colour |
|
||||
| $D02C | 53292 | Register | - | - | - | - | M5C | | | | Sprite 5 colour |
|
||||
| $D02D | 53293 | Register | - | - | - | - | M6C | | | | Sprite 6 colour |
|
||||
| $D02E | 53294 | Register | - | - | - | - | M7C | | | | Sprite 7 colour |
|
||||
| $D02F-$D03F | 53295-53311 | Unused | ($FF on read, ignored on write) | | | | | | | | |
|
||||
| $D040-$D3FF | 53312-54271 | Same as $D000-$D03F (mirror every 64 bytes) | | | | | | | | | |
|
||||
|
||||
## Notes
|
||||
|
||||
- The bits marked with '-' are not connected and give "1" on reading.
|
||||
- The registers $D01E and $D01F cannot be written and are automatically cleared on reading.
|
||||
- The RES bit (bit 5) of register $D016 has no function on the VIC 6567/6569. On the 6566, this bit is used to stop the VIC.
|
||||
- Bit 7 in register $D011 (RST8) is bit 8 of register $D012. Together they are called "RASTER". A write access to these bits sets the comparison line for the raster interrupt.
|
||||
</content>
|
||||
</invoke>
|
||||
Submodule
+1
Submodule oscar64 added at ae7ecb4c8c
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
# build.sh — compile and optionally run a single C64 program with Oscar64.
|
||||
#
|
||||
# Usage:
|
||||
# ./build.sh # compile helloworld.c → build/helloworld.prg
|
||||
# ./build.sh -e # compile, then run in the oscar64 built-in emulator
|
||||
# ./build.sh -c # just compile (default; -c is a no-op for clarity)
|
||||
#
|
||||
# Output (in ./build/):
|
||||
# helloworld.prg — loadable C64 program (run with x64, VICE, or real hw)
|
||||
# helloworld.asm — full 6502 listing
|
||||
# helloworld.map — region/section/object placement
|
||||
# helloworld.lbl — VICE monitor label commands
|
||||
|
||||
set -e
|
||||
|
||||
# --- locate the oscar64 compiler -----------------------------------------
|
||||
# This script is ./src/build.sh, so the repo root is one level up.
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
OSCAR64_DIR="$ROOT/oscar64"
|
||||
OSCAR64_BIN="$OSCAR64_DIR/bin/oscar64"
|
||||
BUILD_DIR="$SCRIPT_DIR/build"
|
||||
|
||||
# Output goes in ./build/ (relative to the src dir), kept out of the source tree.
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Build the compiler if the binary is missing. (make -C make is the
|
||||
# makefile-based build from the upstream oscar64 source tree.)
|
||||
if [ ! -x "$OSCAR64_BIN" ]; then
|
||||
echo "oscar64 compiler not found at $OSCAR64_BIN; building it..."
|
||||
( cd "$OSCAR64_DIR" && make -C make compiler )
|
||||
fi
|
||||
|
||||
if [ ! -x "$OSCAR64_BIN" ]; then
|
||||
echo "error: $OSCAR64_BIN is still missing after build" >&2
|
||||
echo " try: cd $OSCAR64_DIR && make -C make compiler" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- compile + optionally run --------------------------------------------
|
||||
SRC=helloworld.c
|
||||
EMU_FLAGS=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-e) EMU_FLAGS="-e" ;;
|
||||
-c) ;; # explicit compile-only, no extra flags
|
||||
-*) echo "unknown flag: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "compiling $SRC with $OSCAR64_BIN -> $BUILD_DIR/"
|
||||
# -o puts the .prg in the build dir; the other artifacts (.asm, .map, .lbl)
|
||||
# follow automatically since they share the base name.
|
||||
"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/helloworld.prg" "$SRC"
|
||||
|
||||
if [ -n "$EMU_FLAGS" ]; then
|
||||
echo "running helloworld.prg in oscar64's built-in emulator"
|
||||
# The emulator reads the .prg; re-run with -e pointing at the same output.
|
||||
"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/helloworld.prg" -e "$SRC"
|
||||
fi
|
||||
|
||||
echo "done: $BUILD_DIR/helloworld.prg"
|
||||
@@ -0,0 +1,23 @@
|
||||
// helloworld.c — minimal C64 program in C with Oscar64
|
||||
//
|
||||
// Builds a .prg that prints "Hello World" on the C64 text screen and exits.
|
||||
//
|
||||
// Build: ../scripts/build.sh
|
||||
// Run: x64 helloworld.prg (or use ../scripts/build.sh -e to run in
|
||||
// the built-in oscar64 emulator)
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// CHR$(147) is PETSCII "clear screen" — same as printf("\f") in C but
|
||||
// emitted as the right byte for the C64's PETSCII character set.
|
||||
putchar(147);
|
||||
|
||||
// The p"" prefix tells Oscar64 to emit the string as PETSCII, not ASCII.
|
||||
// Without it the C64's character ROM would render garbage because the
|
||||
// character generator maps 'H' to PETSCII 8, not ASCII 72.
|
||||
printf(p"Hello World\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user