6502-PRG
A 6502 assembly language program template for the AC6502
- Active · last change 11-5-2026
- ★ 5 on GitHub
README
6502-PRG
A 6502 assembly language program template for the AC6502 family of computer systems.
📖 Guide: AC6502 Documentation — the user's and programmer's guide for the whole family. This template is walked through end to end in Starting from a template.
Overview
Programs for this system are loaded into RAM at $0800 and executed from BASIC. Unlike cartridges (which replace ROM), programs run entirely in RAM alongside the BIOS — the Kernal, the BASIC interpreter, Wozmon and, on BIOS 1.x, the Monitor — all of which remain available.
The template builds two ways from the same source: make for any ACE on BIOS 1.x with a TMS9918A, and make VDP=1 for an ACE converted to a 6502-PICOVDP on BIOS 2.x. See Building for the 6502-PICOVDP.
When the program works, 6502-ASM is where a small one goes: demos, samples, scratchpads and tests, each in a directory of its own. 6502-BAS and 6502-C are the same collection for BASIC and C. Anything bigger than a directory — a game, an application, anything with releases of its own — gets a repository of its own instead.
How It Works
- The program is loaded into RAM at
$0800— normally with BASIC'sLOAD, from CompactFlash or over serial - BASIC's
RUNcommand executes the tokenized stub at the start of the file - The stub decodes to
10 SYS 2060, which calls the machine code entry point at$080C - Your program runs with full access to the Kernal jump table
- Return to BASIC with
RTS
Memory Layout
| Range | Contents |
|---|---|
$0000–$0039 |
Zero page — system pointers, BASIC, and Kernal scratch (see 6502.inc for details) |
$003A–$00FF |
Zero page — free for user programs (198 bytes) |
$0100–$01FF |
CPU stack |
$0200–$02FF |
Input ring buffer (managed by Kernal) |
$0300–$03FF |
Kernal variables (vectors, cursor, HW flags, etc.) |
$0400–$05FF |
BASIC's input line and tokenizing buffers |
$0600–$07FF |
CompactFlash sector buffer — clobbered by any filesystem call |
$0800–$080B |
BASIC startup stub (10 SYS 2060) |
$080C–$7FFF |
Your program code and data (~30 KB available) |
$8000–$9FFF |
I/O hardware registers |
$A000–$A0FF |
Kernal jump table (stable API) |
BASIC Startup Stub
The first 12 bytes of every program file contain a tokenized BASIC line that serves as a loader:
| Address | Byte | Meaning |
|---|---|---|
$0800 |
$0A |
Next-line pointer lo → $080A |
$0801 |
$08 |
Next-line pointer hi |
$0802 |
$0A |
Line number 10 (lo) |
$0803 |
$00 |
Line number 10 (hi) |
$0804 |
$A5 |
TOK_SYS |
$0805 |
$32 |
'2' |
$0806 |
$30 |
'0' |
$0807 |
$36 |
'6' |
$0808 |
$30 |
'0' |
$0809 |
$00 |
Line terminator |
$080A |
$00 |
End-of-program sentinel (lo) |
$080B |
$00 |
End-of-program sentinel (hi) |
$080C |
… | Machine code entry point (2060 decimal = $080C) |
Kernal Services
The system is already fully initialized when your program runs. Key entry points:
| Address | Routine | Description |
|---|---|---|
$A000 |
Chrout |
Output character (routed by IO_MODE) |
$A003 |
Chrin |
Read character from input buffer (non-blocking — C=1 with the character in A, C=0 if none; loop for blocking behaviour) |
$A00C |
BufferSize |
Number of unread bytes in input buffer |
$A018 |
VideoClear |
Clear screen and reset cursor |
$A01B |
VideoPutChar |
Write character at cursor position |
$A01E |
VideoSetCursor |
Set cursor position (X=col, Y=row) |
$A027 |
VideoSetColor |
Set text color (A = fg<<4 | bg); on BIOS 2.x the pen for text printed from then on, and the border |
$A033 |
SidPlayNote |
Play note (A=voice, X=freqLo, Y=freqHi) |
$A075 |
SysDelay |
Delay A=lo, X=hi centiseconds |
$A048 |
ReadJoystick1 |
Read joystick 1 bitmask |
See 6502.inc for the complete jump table with calling conventions. On BIOS 2.x the jump table grew by thirteen PICOVDP entries (VdpInfo through VdpStatus, $A0B1–$A0D5): see Section 4 of 6502-VDP.inc.
Hardware Detection
Check HW_PRESENT ($030D) before using optional hardware:
lda HW_PRESENT
and #HW_SID ; Is SID present?
beq @NoSound ; Skip sound code if not
jsr SidPlayNote
@NoSound:Building for the 6502-PICOVDP
make VDP=1 builds Program-VDP.prg with 6502-VDP.inc instead of 6502.inc. Program.asm picks the include with the VDP symbol, which the Makefile passes to ca65:
.ifdef VDP
.include "6502-VDP.inc"
.else
.include "6502.inc"
.endifStart from 6502-VDP.inc when the program needs anything BIOS 2.x adds: the PICOVDP's modes, layers, palette and sprites (VC_* register names), or the Kernal's VDP entries. A program built that way needs an ACE converted to a 6502-PICOVDP, running BIOS 2.0 or later, and does not run on a TMS9918A. Its build checks KernalVersion at Start and, on BIOS 1.x, prints NEEDS BIOS 2 AND A 6502-PICOVDP and returns to BASIC.
A program built with 6502.inc that only calls the jump table needs no VDP build: it runs on 2.x as it is.
What is different on BIOS 2.x:
- There is no Monitor. Load with
LOAD(see Loading & Running);SYSpasses registers, and aBRKprints the registers and returns to BASIC. - The font is in the card. A program that overwrote the pattern table or changed modes gets the text console back with
InitVideo, which returns after the next vertical blank. BASIC also restores it when a program stops, if the mode was changed through the Kernal's VDP entries (which keepVID_MODE); a program that writes the registers itself callsInitVideobefore it returns. VideoSetColorsets the pen, the colour of text printed from then on, and the border follows the background.
Building
Prerequisites
CC65 Compiler
On macOS, install via Homebrew:
brew install cc65For other platforms, see the cc65 project.
bin2woz (optional — for Wozmon loading)
npm install -g bin2wozConverts the binary to a format loadable via the Wozmon serial monitor. See the bin2woz project.
cffs (optional — for CompactFlash images)
npm install -g cffs-image-toolCreates CompactFlash disk images with the program file. See the cffs project.
6502 CLI (optional — for make run)
Installed via the 6502-EMULATOR app's Settings → Command Line → Install.
Build Commands
| Command | Description |
|---|---|
make |
Build all targets (.prg, .woz, and CF image) |
make VDP=1 |
Build all targets for the 6502-PICOVDP / BIOS 2.x (Program-VDP.*) |
make build |
Assemble only (Program.prg) |
make view |
Display hexdump of the built program |
make woz |
Create Wozmon-compatible file (Program.woz) |
make cf |
Create CompactFlash disk image with the program |
make run |
Launch the emulator app with the built program loaded (make VDP=1 run selects the PICOVDP card; add ROM=path/to/BIOS.bin to boot a local BIOS image) |
make clean |
Remove build artifacts |
Build Output
makeProduces:
Program.prg— Raw binary, load address$0800Program.woz— Wozmon-compatible format for serial uploadProgram.lst— Assembly listing file for debuggingProgram.img— CompactFlash disk image with the program
make VDP=1 writes the same four files named Program-VDP.*. The file inside Program-VDP.img is still PROGRAM.PRG.
Loading & Running
Use BASIC's LOAD. Requires BIOS v1.3 or later; the VDP build requires BIOS v2.0 or later.
From CompactFlash — the usual case:
LOAD "PROGRAM.PRG"
RUNOver serial, with no CF card:
LOADThen send Program.prg from the host with any terminal that speaks XMODEM
(128-byte blocks, checksum mode). The transfer is padded up to a block boundary,
which is harmless.
Either way, RUN executes the stub and SYS 2060 enters your code at $080C.
From the Monitor (BIOS 1.x only; 2.x has no Monitor), if you are already there:
L "PROGRAM.PRG"
XL loads to $0800 by default and X returns to BASIC, where RUN works as
usual.
Why the loader matters
Your machine code lives past the end-of-program marker at $080A, where BASIC
would otherwise put its variables. Keeping the two apart depends on the loader
telling BASIC how many bytes it wrote, so BASIC can place VARTAB past the whole
image rather than at the end of the tokenized line chain.
LOAD and the 1.x Monitor's L both do this as of BIOS v1.3, and LOAD
still does on 2.x. Wozmon does not —
it writes bytes one at a time with no notion of a length, so BASIC falls back to
walking the line chain, VARTAB lands at $080C on top of your code, and the
first variable assignment destroys it. Use Wozmon for code you will enter from
Wozmon itself (or, on 1.x, from the Monitor), not for programs you intend to
RUN.
On BIOS versions before v1.3 the byte count was discarded on every path, so all three loaders had this problem.
Template Structure
| File | Purpose |
|---|---|
Program.asm |
Main source — BASIC stub, entry point, example code |
6502.inc |
System include file for BIOS 1.x and the TMS9918A — Kernal jump table, hardware registers, constants |
6502-VDP.inc |
System include file for BIOS 2.x and the 6502-PICOVDP (identical to 6502-ASM's) |
6502.cfg |
Linker configuration — memory layout for RAM programs |
Makefile |
Build system |
Customizing
- Edit
Program.asm— replace the example code after theStart:label with your program - Do not modify the
BasicStartupbytes — they must remain at$0800for BASICRUNto work - Return to BASIC with
RTSwhen your program finishes - Add additional
.asmfiles and.includethem as needed - You have ~30 KB of RAM (
$080C–$7FFF) for code and data
Related
- 6502-ACE — the hardware, and the index of the whole family
- 6502-BIOS — the firmware behind the Kernal jump table:
6502.incis its 1.6 API,6502-VDP.incits 2.x API - 6502-PICOVDP — the video card
6502-VDP.incis for - 6502-EMULATOR — run a program without hardware (
make run) - 6502-CRT — the same idea for cartridge ROMs and banked Flash Carts
- 6502-BIN — the same idea for a raw binary with no BASIC stub
- 6502-ASM — where small assembly programs live: demos, samples and tests, with worked examples of this template
- 6502-BAS / 6502-C — the same collection for BASIC listings and for C
- 6502-DOCS — the documentation site: the cross-development and assembly guides, and the printable reference cards
- cffs / bin2woz — the tools behind
make cfandmake woz
License
MIT License — see LICENSE.

