SDK¶
Software Development Kit
Introduction¶
The distribution format of a Picocomputer program is the ROM: one file
that ends in .rp6502, such as game.rp6502. A ROM describes the
state of the machine as it comes out of reset: the program and data are
in memory, and the reset vector points to where the program begins. One
ROM can hold all the code, video, and sound of a program.
A ROM is similar by design to the cartridge ROM of a game console and to the BASIC ROM of an 8-bit home computer.
The SDK builds a ROM, runs it with the RP6502-EMU, an RP6502-PICO, or the RP6502-WEB, and debugs it in the RP6502-EMU while it runs. A project starts as a copy of the RP6502 project template, which builds with either of two 6502 compilers, cc65 or llvm-mos. The template includes the same “Hello, world!” program four times: in C, in the assembly syntax of cc65, in the assembly syntax of llvm-mos, and in BASIC. The C program builds with either compiler.
Installing the Prerequisites¶
Building Picocomputer programs requires VS Code, CMake 3.28 or later, Python 3, git, Make or Ninja, and one or both of the 6502 compilers, cc65 and llvm-mos. Only the compilers must be recent. New Picocomputer features often go into cc65 and llvm-mos before their next release. The versions in package managers such as apt and Homebrew are too old.
The commands in this section are a tested recommendation. You may install the prerequisites in another way. To install a compiler in another way, first check the Picocomputer GitHub page. It shows whether to use the upstream project or the Picocomputer fork of it, and why.
The command for each compiler puts the compiler in the .rp6502
directory of the home directory and adds its bin directory to PATH.
Run the command again to update the compiler.
Windows¶
Open PowerShell. Install the prerequisites with winget:
winget install -e --id Git.Git
winget install -e --id Kitware.CMake
winget install -e --id Ninja-build.Ninja
winget install -e --id Microsoft.VisualStudioCode
winget install -e --id Python.PythonInstallManager
Close PowerShell. Then open it again, so that the new PATH takes effect. Run these commands, which install Python, make Ninja the build tool, and install the compilers:
pymanager install default
setx CMAKE_GENERATOR Ninja
irm https://raw.githubusercontent.com/picocomputer/.github/main/install/cc65.ps1 | iex
irm https://raw.githubusercontent.com/picocomputer/.github/main/install/llvm-mos.ps1 | iex
On Windows, CMake defaults to a Visual Studio or NMake generator, which
does not work with cc65 or llvm-mos. CMAKE_GENERATOR selects Ninja
instead. For a project configured before this step, run “CMake: Delete
Cache and Reconfigure” from the VS Code Command Palette.
Close every PowerShell and VS Code window before the next step. If a command is still not found, sign out of Windows and sign in again.
In WSL, follow the Linux steps instead.
macOS¶
Open Terminal. Install Apple’s command line tools, which include git, Make, and Python 3:
xcode-select --install
Download the universal disk image of CMake for macOS. Drag CMake to Applications. Then add its command-line tools:
sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install
Download VS Code. Drag it to Applications. Then install the compilers:
curl -fsSL https://raw.githubusercontent.com/picocomputer/.github/main/install/cc65.sh | sh
curl -fsSL https://raw.githubusercontent.com/picocomputer/.github/main/install/llvm-mos.sh | sh
VS Code reads PATH only when it starts. Quit VS Code with Cmd+Q. Then open VS Code again.
Linux¶
On Ubuntu and Debian, install the prerequisites with apt. Ubuntu 24.04, Debian 13, and their later versions have CMake 3.28 or later.
sudo apt install git cmake build-essential python3 curl
Download the .deb of VS Code. Install it from the
directory that holds it:
sudo apt install ./code_*.deb
In WSL, you do not need to install this .deb if you use VS Code for
Windows.
Run these commands, which give the user account access to the USB serial port of an RP6502-PICO and install the compilers:
sudo usermod -a -G dialout $USER
curl -fsSL https://raw.githubusercontent.com/picocomputer/.github/main/install/cc65.sh | sh
curl -fsSL https://raw.githubusercontent.com/picocomputer/.github/main/install/llvm-mos.sh | sh
Restart the login session, so that the new group and PATH take effect.
Getting Started¶
Install the prerequisites first. The steps are in Installing the Prerequisites.
1. Make a project. On the GitHub page of the template, select “Use this template”. Then select “Create a new repository”. GitHub creates a new repository with a copy of the template. These commands clone the repository and open its directory in VS Code:
git clone https://github.com/<user>/my-project.git
cd my-project
code .
2. Install the recommended extensions when VS Code prompts for them:
the C/C++ Extension Pack, which includes CMake Tools, to build;
LLDB DAP, to debug in the emulator;
Python Debugger, to run
tools/rp6502.py.
3. Choose a configure preset. The first time the project opens,
CMake Tools lists five presets. Four are for C or assembly:
cc65/Debug, cc65/Release, llvm-mos/Debug, and
llvm-mos/Release. Choose a Debug preset, because breakpoints and
stepping require debug information. A Release preset builds an optimized
ROM for distribution. The fifth preset, basic, is for BASIC. To
change the preset later, use the Configure row of the CMake view of the
Side Bar.
A C program goes in src/main.c, and a BASIC program goes in
src/main.bas. To write in assembly, replace src/main.c in
CMakeLists.txt with src/main-cc65.s for cc65 or
src/main-llvm-mos.s for llvm-mos.
The first configure downloads the tools into tools/.
4. Press F5 to debug. VS Code builds hello.rp6502 and runs it in
the emulator. “Hello, world!” appears in the emulator window and in the
Debug Console of VS Code. The session stays open after the ROM exits, so
the emulator window keeps its output. Stop the session with Shift+F5.
The emulator window can open behind VS Code.
The project now looks like this:
my-project/
├── CMakeLists.txt the ROMs this project builds
├── CMakePresets.json the five configure presets
├── README.md web player link, requirements, updating
├── .github/ GitHub Pages workflows
├── .vscode/ F5 configurations, tasks, extensions
├── src/
│ ├── main.c Hello, world! in C
│ ├── main-cc65.s the same in cc65 assembly
│ ├── main-llvm-mos.s the same in llvm-mos assembly
│ ├── main.bas the same in BASIC
│ ├── xram.h the XRAM layout
│ └── help.txt the help asset
├── tools/ the tools that build and test
│ ├── rp6502.cmake
│ ├── rp6502.py
│ ├── cc65-toolchain.cmake
│ ├── cc65-config.cmake
│ ├── rp6502-0.40-linux-x86_64.tar.gz
│ └── rp6502-0.40-web.zip
├── build/ build output, ignored by git
└── .rp6502 the settings file, ignored by git
Your new project supports every compiler and language. You may keep all of it, or change it in any way. When you choose one compiler and language, these changes remove the parts you do not use:
In
CMakeLists.txt,if(RP6502_BASIC)has a branch for BASIC and a branch for C. Keep the branch you use, and delete the other branch.Delete the presets and the source files that the project does not use.
Commit tools/ with the project. Git ignores some of its files on
purpose. To learn which ones and why, read Updating the Tools.
Running and Debugging¶
“Start Debugging” (F5) runs one of three launch configurations. Choose one in the Run and Debug view of the Side Bar.
The RP6502-EMU launch configuration is the default. It builds the project and runs the ROM in the RP6502-EMU with source-level debugging.
The RP6502-PICO launch configuration builds the project and runs the
ROM on an RP6502-PICO. It uploads the ROM to the RP6502-PICO, then runs
it from there. The upload replaces any file with the same name. The
console opens in the VS Code Terminal panel. The RP6502-PICO has no
breakpoints or stepping, so a common debugging technique is printf()
to the console.
An RP6502-PICO may connect through the USB port of the VGA module or
over telnet, so the launch configuration
requires a setting for the connection. Its first run fills in a hint for
device in the settings file. Unless your RP6502-PICO
happens to be on that port, the run fails with an error until you set
device.
The RP6502-WEB launch configuration builds the project and opens a
page in a browser. The page has a link to every web player that rp6502_web() makes.
Building a ROM¶
CMakeLists.txt describes the ROMs a project builds. This listing is
the C branch of the CMakeLists.txt of a new project, without the
if(). A BASIC project calls rp6502_basic() instead of
rp6502_executable(). Learn about it in BASIC Programs, the next
section.
cmake_minimum_required(VERSION 3.28)
include(${CMAKE_CURRENT_LIST_DIR}/tools/rp6502.cmake)
project(MY-RP6502-PROJECT C CXX ASM)
add_executable(hello)
rp6502_align(hello "XRAM_.*")
rp6502_asset(hello help src/help.txt)
rp6502_executable(hello DATA default RESET default)
rp6502_web(hello)
target_sources(hello PRIVATE
src/main.c
)
include()loads the RP6502 CMake functions fromtools/.project()names the project and the languages it compiles. ChangeMY-RP6502-PROJECTto any name.add_executable(hello)creates the program, a CMake target namedhello.rp6502_align()checks that the addresses ofhelloin extended RAM (XRAM) are even. See Symbols in CMake.rp6502_asset()adds an asset to the ROM. See Adding Assets.rp6502_executable()packages the program and its assets intohello.rp6502, named after the target. See Load Addresses and Linker Scripts.rp6502_web()packages the ROM into a web player. See Web Players.target_sources()lists the program’s source files.
add_executable() must come first, because every other call refers to
its target. rp6502_align() and rp6502_asset() must come before
rp6502_executable(), and rp6502_web() must come after it.
Each preset builds into its own directory. With the cc65/Debug
preset, the ROM is build/cc65/debug/hello/hello.rp6502. With the
basic preset, it is build/basic/hello/hello.rp6502.
BASIC Programs¶
rp6502_basic() packages the BASIC interpreter and the BASIC programs
into one ROM. A BASIC program can be an ordinary file on a drive, but
with the SDK it is usually an asset of the ROM. The
program can then be distributed as a single ROM instead of several
files.
cmake_minimum_required(VERSION 3.28)
include(${CMAKE_CURRENT_LIST_DIR}/tools/rp6502.cmake)
project(trek BASIC)
add_executable(trek)
rp6502_asset(trek instructions.bas src/instructions.bas)
rp6502_asset(trek game.bas src/game.bas)
rp6502_basic(trek instructions.bas)
At start, BASIC runs the asset autorun.bas if the ROM has one.
Otherwise BASIC starts at the OK prompt. When rp6502_basic() has
a second argument, instructions.bas above, it adds an autorun.bas
that runs that program.
A project may add its own autorun.bas asset instead, and call
rp6502_basic() without a second argument. autorun.bas is a good
place to adjust the CAPS setting if needed, and to print a message
that says how long a large program takes to load:
10 CAPS 0
20 PRINT "Loading, about 10 seconds..."
30 RUN "ROM:GAME.BAS"
rp6502_asset(quest autorun.bas src/autorun.bas)
rp6502_asset(quest game.bas src/game.bas)
rp6502_basic(quest)
F5 runs your BASIC ROM like any other ROM.
Adding Assets¶
rp6502_asset() adds graphics, level data, help text, and any other
file that the program uses to the .rp6502 file. BASIC programs may also be assets.
set(TABLE_ADDR 0x8000)
rp6502_asset(hello RAM(${TABLE_ADDR}) bin/table.bin)
rp6502_asset(hello RAM(0x00F0) bin/f0.bin)
rp6502_asset(hello XRAM(0x1000) img/tiles.bin)
rp6502_asset(hello XRAM(XRAM_LOGO) img/logo.bin)
rp6502_asset(hello help src/help.txt)
An asset with an address in RAM() or XRAM() is a memory chunk.
Before the 6502 starts, the RIA (the RP6502 Interface Adapter) loads the
file to that address in 6502 RAM or in XRAM. XRAM
is the 64 KB of memory outside the address space of the 6502. Inside the
parentheses, the address is a number, a CMake variable such as
${TABLE_ADDR}, or a symbol from the program
such as XRAM_LOGO.
Any other address is a name, and a named asset is a file that the
program opens while it runs. The path is ROM: and the name, and the
program opens it like any other file. Named assets are read-only, and
several can be open at once.
open("ROM:help", O_RDONLY);
The asset named help is the ROM’s help text. The monitor of an RP6502-PICO shows it, and the emulator shows it in
the ROM Help window.
The build can also make an asset, such as an image converted from a PNG.
Create the asset with add_custom_command(). Pass its output file to
rp6502_asset().
find_package(Python3 REQUIRED COMPONENTS Interpreter)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/logo.bin
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/png2bin.py
${CMAKE_CURRENT_SOURCE_DIR}/logo.png
${CMAKE_CURRENT_BINARY_DIR}/logo.bin
DEPENDS png2bin.py logo.png
VERBATIM
)
rp6502_asset(hello logo ${CMAKE_CURRENT_BINARY_DIR}/logo.bin)
Memory Map¶
Everything below $FF00 is RAM. The Picocomputer does not use or reserve any of this RAM, including zero page. A program accesses the VGA, sound, storage, keyboards, mice, gamepads, the RTC, and networking through the 32 registers of the RIA.
Address |
Description |
|---|---|
$0000-$FEFF |
RAM, 63.75 KB |
$FF00-$FFCF |
Unassigned |
$FFD0-$FFDF |
VIA, see the WDC datasheet |
$FFE0-$FFFF |
RIA, see the RIA page |
$10000-$1FFFF |
XRAM, 64 KB, see XRAM Memory Map |
No standard machine uses the unassigned space. It is open for hardware experiments, which require custom chip-select logic that the experimenter designs and supports.
XRAM Memory Map¶
XRAM is the 64 KB of memory outside the address space of the 6502. A C
program should read and write XRAM with the XRAM functions of the
API, such as xram0_read(), xram0_write(),
and xram0_peek8().
XRAM has no memory map until your program defines one. The program can use XRAM for virtual devices or for general purpose storage. The virtual devices are the keyboard, mouse, tablet, and gamepad inputs; the PSG and OPL2 sound generators; and the VGA modes with the pixels, tiles, and sprites they draw. Each device has a macro that maps it to an address in XRAM, and the section of each device on the RIA and VGA pages gives the macro.
Before your program maps its first device, decide the XRAM address of each
device and of each block of general purpose storage. A spreadsheet with every
address laid out by hand is one way, but it is tedious and error-prone.
Instead, it is strongly recommended to keep your XRAM map in one file,
xram.h for C or xram.inc for assembly. Your project already has an
xram.h, and the rest of this section describes how to change it.
The file holds the structure of each device that your program uses, and
a layout, xram_layout_t, with a member for each device and each block
of storage. The compiler calculates the address of each member, so
nothing overlaps.
To add support for a device to your program, copy its code block from
the RIA or VGA page into the file. Each code block is
labeled xram.h or xram.inc and holds the structure of the device,
its constants, and the macros for its extended register (XREG). Choose
the C, ca65, or llvm-mc tab, then use the “Copy to clipboard” button in the
corner of the code block. Then add a member for the device to the
layout.
The compiler supplies rp6502.h for C and rp6502.inc for assembly.
They provide the API calls, including xreg() and the XRAM functions,
but none of the XRAM structures, so your copy is the only definition the
program uses. Register numbers, field offsets, and sizes never change, so
a change to the SDK never breaks your copy. Only names can change in the
manual, and you can rename anything in your copy.
This xram.h is for a program that uses only the keyboard. It holds
the code block of Keyboard from the RIA
page, as copied, and a layout that puts the keyboard at address 0.
A type marked /* layout */ is a block that goes in
xram_layout_t.
#ifndef XRAM_H
#define XRAM_H
#include <rp6502.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define KEYBOARD_NO_KEY 0
#define KEYBOARD_NUM_LOCK 1
#define KEYBOARD_CAPS_LOCK 2
#define KEYBOARD_SCROLL_LOCK 3
#define KEYBOARD_PRESSED(keys, code) ((keys)[(code) >> 3] & (1 << ((code) & 7)))
#define xreg_ria_keyboard(...) xreg(0x0, 0x0, 0x00, __VA_ARGS__)
typedef struct
{
uint8_t keys[32];
} keyboard_t; /* layout */
typedef struct
{
keyboard_t keyboard;
} xram_layout_t;
#define XRAM_KEYBOARD offsetof(xram_layout_t, keyboard)
#endif
.ifndef XRAM_INC
XRAM_INC = 1
KEYBOARD_NO_KEY = 0
KEYBOARD_NUM_LOCK = 1
KEYBOARD_CAPS_LOCK = 2
KEYBOARD_SCROLL_LOCK = 3
.macro KEYBOARD_PRESSED keys, code
lda keys + (code >> 3)
and #1 << (code & 7)
.endmacro
.macro xreg_ria_keyboard addr
xreg $0, $0, $00, addr
.endmacro
.struct keyboard_t
keys .res 32
.endstruct
.struct xram_layout_t
keyboard .tag keyboard_t
.endstruct
XRAM_KEYBOARD = xram_layout_t::keyboard
.endif
.ifndef XRAM_INC
XRAM_INC = 1
KEYBOARD_NO_KEY = 0
KEYBOARD_NUM_LOCK = 1
KEYBOARD_CAPS_LOCK = 2
KEYBOARD_SCROLL_LOCK = 3
.macro KEYBOARD_PRESSED keys, code
lda \keys + (\code >> 3)
and #1 << (\code & 7)
.endm
.macro xreg_ria_keyboard addr
xreg $0, $0, $00, \addr
.endm
KEYBOARD_KEYS = 0
KEYBOARD_SIZE = 32
XRAM_KEYBOARD = 0
.endif
Each XRAM_ symbol is the XRAM address of one member of the layout.
Use these symbols for every XRAM address in the program. The build and
the tests use them too, as program symbols. This
code maps the keyboard to its address, then loops until the user
presses a key.
xreg_ria_keyboard(XRAM_KEYBOARD);
while (xram0_peek8(XRAM_KEYBOARD) & (1 << KEYBOARD_NO_KEY))
;
xreg_ria_keyboard XRAM_KEYBOARD
lda #<XRAM_KEYBOARD
sta RIA_ADDR0
lda #>XRAM_KEYBOARD
sta RIA_ADDR0+1
lda #0
sta RIA_STEP0
: lda RIA_RW0
and #1 << KEYBOARD_NO_KEY
bne :-
xreg_ria_keyboard XRAM_KEYBOARD
lda #(XRAM_KEYBOARD & $FF)
sta RIA_ADDR0
lda #((XRAM_KEYBOARD >> 8) & $FF)
sta RIA_ADDR0+1
lda #0
sta RIA_STEP0
1: lda RIA_RW0
and #(1 << KEYBOARD_NO_KEY)
bne 1b
The PSG and the OPL2 have placement rules: the 64 bytes of the PSG must
not cross a 256-byte page, and the OPL2 registers must start on a page.
Their code blocks on the RIA page define PSG_PAGE_CHECK
and OPL_PAGE_CHECK. For each sound generator that your program uses,
add its check to xram.h, after the XRAM_ symbols:
PSG_PAGE_CHECK(XRAM_PSG);
OPL_PAGE_CHECK(XRAM_OPL);
Program Symbols¶
The build writes the symbols of your program to hello.sym, beside the
ROM: each #define with a whole-number value, such as
XRAM_KEYBOARD, and each symbol of the linker. The tools read this
file, so an address that your program defines once is known everywhere:
CMakeLists.txtuses symbols as asset addresses, and can check in one call that all of them are even.An emulator script takes a symbol wherever it takes a number.
A Python test reads the symbols from
emu.sym.
Your program defines each XRAM address once, and the build and the tests
use the same definition. You can find the format of hello.sym in The
.sym File.
Symbols in CMake¶
This example loads an asset at an address from xram.h. The layout
adds a 2 KB logo after the keyboard. Only the changed part of xram.h
is shown:
typedef struct
{
keyboard_t keyboard;
uint8_t logo[2048];
} xram_layout_t;
#define XRAM_KEYBOARD offsetof(xram_layout_t, keyboard)
#define XRAM_LOGO offsetof(xram_layout_t, logo)
CMakeLists.txt then uses the symbol:
rp6502_asset(hello XRAM(XRAM_LOGO) img/logo.bin)
The build reads the value of XRAM_LOGO from hello.sym after the
link, so the asset loads at the address that the program uses. When you
change the layout, the next build packages the asset at the new address.
Any symbol in the file works, such as
#define XRAM_LOGO_ROW_2 (XRAM_LOGO + 64) or an equate of an assembly
program.
Mode configurations, palettes, and the PSG must be at even addresses, and
neither compiler pads structures. rp6502_align() checks that symbols
are even. With this call in CMakeLists.txt, the tools check every
XRAM_ symbol. The regular expression is in Python’s syntax:
rp6502_align(hello "XRAM_.*")
Symbols in the Emulator¶
An emulator script drives the machine and checks
the results. You can learn more about testing with scripts in Testing,
later on this page. A script takes a symbol or an expression wherever it takes a number, and let sets a variable.
This script is for a program that maps the keyboard as in XRAM Memory
Map, then prints “Key down” when a key is pressed. The script presses
space and waits for “Key down”. Then peek checks that the bit for
space is set in the keyboard block:
# The keyboard block has one bit for each HID keycode.
let HID_KEY_SPACE = $2C
press space
wait "Key down"
peek xram:(XRAM_KEYBOARD+HID_KEY_SPACE//8) (1<<HID_KEY_SPACE%8)
Symbols in Python¶
A Python test drives an emulator script from Python, for a
test that requires a loop, a decision, or a calculation. emu.sym holds the
symbols of the ROM as a dict. This test does the same check as the
script above, with the calculation in Python:
import rp6502
HID_KEY_SPACE = 0x2C
with rp6502.Emu() as emu:
emu.cmd("run 2")
emu.cmd("press space")
emu.cmd("run 2")
byte = emu.sym["XRAM_KEYBOARD"] + HID_KEY_SPACE // 8
emu.cmd(f"peek xram:{byte} {1 << HID_KEY_SPACE % 8}")
Multiple ROMs¶
To build more than one ROM, call add_executable() and
rp6502_executable() once for each:
add_executable(hello)
rp6502_align(hello "XRAM_.*")
rp6502_executable(hello DATA default RESET default)
target_sources(hello PRIVATE src/hello.c)
add_executable(setup)
rp6502_align(setup "XRAM_.*")
rp6502_asset(setup help src/setup.hlp)
rp6502_executable(setup DATA default RESET default)
target_sources(setup PRIVATE src/setup.c)
F5 runs the ROM of the launch target, which you select in the Launch row of the CMake view of the Side Bar:
Web Players¶
To build a web player for your program, add rp6502_web():
rp6502_web(hello)
The build writes web/hello.zip in the build directory. The zip is a
web player that holds the ROM, rp6502.js, rp6502.wasm, and
index.html. The build also unpacks the same files into
web/hello/. The “RP6502-WEB” launch configuration of Running and
Debugging opens a page with a link to each web player.
The web player has more settings, such as its title
and its footer, and can use your own index.html.
Testing¶
Unit tests are hard to run on the 6502. Memory is a constraint, and a full test framework is a lot for a small machine. Most test libraries require an int of more than 16 bits, and the 6502 is slow. Unit tests therefore usually run on the host computer. For the ROM itself, the emulator gives strong integration tests: an emulator script drives the machine and checks the results, and a Python test drives the emulator for a test that requires more logic.
To add a test, give rp6502_test() a test file. It has the form of
CMake’s own add_test():
rp6502_test(NAME <name> COMMAND <file> [<arg>...])
A .py file is a Python test, and any other file is an emulator
script. Each argument after the file is an emulator option or a ROM, and
the words after -- are the arguments for the ROM.
A Python test imports rp6502, which is tools/rp6502.py. Its
rp6502.Emu runs the emulator of the test and sends it script
commands:
Name |
Description |
|---|---|
|
Starts the emulator with the arguments that |
|
Sends one script command, and returns the text after |
|
Sends |
|
The symbols and values of the |
|
Ends the script and waits for the emulator. An exit code other
than 0 raises |
|
|
Run the tests from the Testing view of the Side Bar, or with ctest:
ctest --test-dir build/cc65/debug
Each test runs in its own directory, with its own save directory. With the
cc65/Debug preset, a test named input runs in
build/cc65/debug/test/input/, and its save directory is
build/cc65/debug/test/input/save/.
To find programs that read uninitialized memory, the emulator fills RAM and XRAM with random values. The values are different in each run, so a test or a program that reads uninitialized memory shows its poor behavior… eventually.
When a test fails, the emulator prints the reason, then a status line:
rp6502-emu: src/gamepad/tests/connect.txt:5: $0000+0 is $AC, expected $D0
rp6502-emu: PC=$FFF3 A=$18 X=$FF Y=$00 SP=$EB P=$04 OP=$18 BUSY=$00 RESB=1 SEED=1924711803 MEMSEED=3733940630 ROM=FS:/home/user/my-project/build/cc65/debug/gamepad/gamepad.rp6502
A failed Python test shows the same lines in a ScriptError at the end
of a traceback.
If you suspect uninitialized memory, you can run the test again with the
same values in memory. MEMSEED is the seed that filled memory in the
failed run. Add --memseed and the MEMSEED value to the arguments
of the test. Then build the project and run the test again.
Putting it all together, these are typical tests in CMakeLists.txt:
rp6502_test(NAME input COMMAND tests/input.txt
$<TARGET_PROPERTY:hello,RP6502_ROM>)
rp6502_test(NAME scores COMMAND tests/scores.py
--memseed 123 $<TARGET_PROPERTY:hello,RP6502_ROM>)
CMake replaces $<TARGET_PROPERTY:hello,RP6502_ROM> with the path of
the ROM of hello.
Launch Configurations¶
To change how F5 starts a ROM, edit the settings of the launch
configuration that you run. The settings are in .vscode/launch.json
for RP6502-EMU, in .rp6502 for RP6502-PICO, and in
CMakeLists.txt for RP6502-WEB. In each one, the arguments for the ROM
are separate from the other settings, such as the options of the emulator
or the settings of the web player.
A ROM can start with arguments, which a C program receives as argc
and argv of main(). Unlike on other systems, you must set up the
program to receive them. To learn how, read ARGV on the
API page.
RP6502-EMU¶
The “RP6502-EMU” launch configuration is in .vscode/launch.json.
args holds the arguments for the ROM. To pass options to the emulator, add them to debugAdapterArgs after
the options that are already there. This launch configuration passes
level2 to the ROM and runs the 6502 at 1 MHz:
{
"name": "RP6502-EMU",
"type": "lldb-dap",
"request": "launch",
"debugAdapterExecutable": "${command:cmake.launchTargetDirectory}/rp6502-emu",
"debugAdapterArgs": [
"--dap",
"--ini",
"${workspaceFolder}/.rp6502",
"--phi2",
"1000"
],
"windows": {
"debugAdapterExecutable": "${command:cmake.launchTargetDirectory}/rp6502-emu.exe"
},
"program": "${command:cmake.launchTargetPath}.rp6502",
"args": ["level2"],
"stopOnEntry": false,
"stopOnExit": true
}
RP6502-PICO¶
.rp6502 in the project directory holds the settings of your
workstation, so git ignores it. The first run of the “RP6502-EMU” or
“RP6502-PICO” launch configuration creates it.
The file has several sections. The emulator writes most of them, with the
window layout of its debug overlay, so each project reopens with the
layout of its last session. The [RP6502][PICO]
section is first. It holds the settings for running on your
RP6502-PICO, and it is the only section to edit by hand. The first run
of the “RP6502-PICO” launch configuration adds it:
[RP6502][PICO]
device = /dev/ttyACM0
key =
workdir =
args =
term = True
Setting |
Description |
|---|---|
|
How to connect to your RP6502-PICO: a serial port, or a hostname
or IP address for telnet. A new settings file fills in a hint of
the form of a serial port. Replace it with the exact port or
address. A hostname or IP address requires |
|
Passkey for telnet. Leave it empty to use a serial port. See Telnet Console. |
|
The directory to work in, from the root of the current drive. The launch configuration uploads the ROM to this directory and runs it from there. Leave it empty to work in the current directory of the RP6502-PICO. |
|
The arguments for the ROM. |
|
With |
For telnet, set device and key:
[RP6502][PICO]
device = example.com:23
key = secret
workdir =
args =
term = True
RP6502-WEB¶
The “RP6502-WEB” launch configuration opens the web players of the
build, so its settings are in rp6502_web() in CMakeLists.txt.
args in CONFIG holds the arguments for the ROM, and the other
options are settings of the web player. This call
passes level2 to the ROM and gives the web player a title:
rp6502_web(hello CONFIG [[
title: 'Hello',
args: ['level2'],
]])
Load Addresses and Linker Scripts¶
rp6502_executable() packages the linker output of a target, the file
that the linker writes, into a ROM with the assets of the target. The
keywords of rp6502_executable() give the addresses of the program:
Keyword |
Description |
|---|---|
|
The address where the linker output loads. Without |
|
The address where the 6502 starts. The ROM holds it in the reset
vector at |
|
The address of the IRQ handler. The ROM holds it in the IRQ vector
at |
|
The address of the NMI handler. The ROM holds it in the NMI vector
at |
Each keyword takes a number, such as 0x200, or file. DATA
and RESET also take default:
With
file, the build reads the address from the start of the linker output. Each keyword that isfiletakes the next 16-bit address, in the orderDATA,NMI,RESET,IRQ. The linker script of llvm-mos writes theDATAaddress and then theRESETaddress there, sofileworks for both. The linker script of cc65 writes no addresses.defaultis0x200with cc65 andfilewith llvm-mos.
The linker script is the map of 6502 RAM, as xram.h is the map of
XRAM. Most projects keep the linker script of the
compiler. To change the map, start from a copy of the linker script of
the compiler. Pass the copy to the linker with target_link_options().
Add the copy to LINK_DEPENDS, so that the build relinks the program
when the copy changes.
For cc65, copy cfg/rp6502.cfg from the directory of cc65, such as
~/.rp6502/cc65, into the project. To learn its format, read the ld65
documentation.
target_link_options(hello PRIVATE -C ${CMAKE_SOURCE_DIR}/src/hello.cfg)
set_target_properties(hello PROPERTIES
LINK_DEPENDS ${CMAKE_SOURCE_DIR}/src/hello.cfg)
For llvm-mos, copy mos-platform/rp6502/lib/link.ld from the
directory of llvm-mos, such as ~/.rp6502/llvm-mos, into the project.
To learn its format, read the llvm-mos wiki page Linker Script.
target_link_options(hello PRIVATE -T ${CMAKE_SOURCE_DIR}/src/hello.ld)
set_target_properties(hello PROPERTIES
LINK_DEPENDS ${CMAKE_SOURCE_DIR}/src/hello.ld)
Multiple Linker Outputs¶
A cc65 program can load at more than one address, with one linker output
for each address. In the linker configuration, each memory area with a
file of its own writes a separate linker output. %O is the file
name that CMake passes to the linker, so "%O.1000" names a file beside
the linker output of the target. The linker of llvm-mos writes only one
linker output.
Microsoft BASIC loads at three addresses: the CHRGET routine at
$00E8 in zero page, the init code at $1000, and the interpreter at
$C000. Its linker configuration gives each one its own file:
MEMORY {
ZP: start = $0000, size = $00E7, file = "";
CHRGETZP: start = $00E8, size = $0018, file = "%O.00E8";
INITROM: start = $1000, size = $8000, file = "%O.1000";
BASROM: start = $C000, size = $3DDE, file = "%O.C000";
# areas with file = "" write nothing; trimmed here
TOUCH: start = $0000, size = $0000, file = %O;
}
TOUCH is an empty memory area whose file is %O. The linker writes
it as an empty file, and CMake treats that file as the linker output.
Add each file that the link writes with rp6502_byproduct(). It adds
the file as an asset, as rp6502_asset() does, but packages it after
the link. rp6502_asset() cannot take a file that the link writes. Each
file that rp6502_asset() takes is an input of the link, so that a
change to the file links the program again and rewrites the ROM.
rp6502_byproduct() takes the same addresses as rp6502_asset(). A
relative path of rp6502_asset() starts in the project directory. A
relative path of rp6502_byproduct() starts in the build directory, in
the directory of the linker output:
rp6502_byproduct(<target> <address> <file>)
This CMake code adds the three files as memory chunks at their addresses, and packages them:
rp6502_asset(basic help src/help.txt)
rp6502_byproduct(basic RAM(0x00E8) basic.00E8)
rp6502_byproduct(basic RAM(0x1000) basic.1000)
rp6502_byproduct(basic RAM(0xC000) basic.C000)
rp6502_executable(basic RESET 0x1000)
rp6502_executable() has no DATA here, because the linker output
is the empty TOUCH file.
Updating the Tools¶
tools/ holds the tools that build and test your program. They change
only when you update them. To update them, run the “RP6502: update
tools” task (Terminal > Run Task), or run this command in the project
directory:
cmake -P tools/rp6502.cmake
The update does these steps:
It downloads the latest scripts from the tools directory of the
mainbranch of picocomputer/rp6502. Each new script replaces the old one, so the update discards local changes to the scripts.It updates the settings in
.vscode/,.gitignore, andCMakeLists.txtwhen they are out of date.It downloads the latest release of the emulator, the web player, and BASIC, for each one that
tools/already has.
After the update, check its output for errors. An error leaves only part of the update in place, so resolve the error and run the update again. Then test that your project still works, and commit the changes.
When CMake configures your project, it downloads each tool that the
project uses and that tools/ lacks. You do not download any tool
yourself.
The scripts, rp6502.cmake, rp6502.py, cc65-toolchain.cmake,
and cc65-config.cmake, come from the main branch of
picocomputer/rp6502. They have no versions, and they can change between
releases. Commit them with the project, so that every Git clone builds
with the same scripts.
The emulator, the web player, and BASIC come from the releases of
picocomputer/rp6502
and picocomputer/msbasic. In tools/,
each of them keeps the file name of its release:
rp6502-<version>-windows.zip
rp6502-<version>-macos.zip
rp6502-<version>-linux-x86_64.tar.gz
rp6502-<version>-linux-aarch64.tar.gz
rp6502-<version>-web.zip
msbasic-<version>.zip
You do not need to commit these files to your repository. To keep your project on a specific version, see Selecting Versions.
WSL uses the emulator for Windows. When a release has no emulator for
your system, tools/ holds rp6502-<version>-<system>.unsupported
in its place.
Selecting Versions¶
Each target and test uses the newest version in tools/. To use
another version, select it in CMakeLists.txt. A variable selects the
version for every target and test in its CMakeLists.txt:
set(RP6502_EMU_VERSION 0.40)
set(RP6502_WEB_VERSION 0.40)
set(RP6502_BASIC_VERSION 0.1)
A property selects the version for one target or one test:
set_target_properties(hello PROPERTIES RP6502_EMU_VERSION 0.39)
set_target_properties(hello PROPERTIES RP6502_WEB_VERSION 0.39)
set_target_properties(trek PROPERTIES RP6502_BASIC_VERSION 0.1)
set_tests_properties(input PROPERTIES RP6502_EMU_VERSION 0.39)
When CMake configures the project, it downloads each version that the
project selects and that tools/ lacks. tools/ can hold several
versions of each tool.
Local Files¶
To use your own build of the emulator, the web player, or BASIC, set
RP6502_EMU, RP6502_WEB, or RP6502_BASIC to the path of its
file: an emulator, the zip of a web player, or a basic.rp6502. These
settings work like the version settings, and a file overrides any
version.
This example uses a custom BASIC. Commit its file with the project, in a
place that Git does not ignore. In tools/, Git ignores only the files
whose names start with rp6502- or msbasic-, so you can put it
there as mybasic.rp6502. Then select the file in CMakeLists.txt:
set_target_properties(trek PROPERTIES RP6502_BASIC tools/mybasic.rp6502)
To test a file for a short time, set it on the command line instead:
cmake --preset cc65/Debug -DRP6502_EMU=/home/user/rp6502/build/sokol/rp6502-emu
The setting stays until you remove it, or until you delete the
build/ directory. This command removes it:
cmake --preset cc65/Debug -U RP6502_EMU
Another option to keep a setting for your system only is
CMakeUserPresets.json, which git ignores.
Command Line¶
This section is for advanced users who work with another editor, with a
6502 program from another toolchain, or for continuous integration. The
build and the automatic download of the tools are part of CMake, so they
work wherever CMake runs. CMake downloads the tools that tools/ lacks,
so the first configure of a fresh Git clone requires a network
connection.
The examples below get you started. For everything else, read the CMake
documentation and the help of
rp6502.py.
Building¶
The project builds with its CMake presets. These commands list the
presets, then configure and build cc65/Debug:
cmake --list-presets
cmake --preset cc65/Debug
cmake --build --preset cc65/Debug
The build writes the ROM to build/cc65/debug/hello/hello.rp6502.
Running on an RP6502-PICO¶
rp6502.py run runs a ROM on an RP6502-PICO from the command line. It
uploads the ROM, loads it with the monitor, and connects your terminal to
the console of the RP6502-PICO, so you can interact with the program.
Give the serial port with -d, or for telnet, the hostname or IP
address with -d and the passkey with -k:
python3 tools/rp6502.py run -d /dev/ttyUSB0 build/cc65/debug/hello/hello.rp6502
python3 tools/rp6502.py run -d 192.168.1.20 -k secret build/cc65/debug/hello/hello.rp6502
run passes the words after the path of the ROM to the ROM as its
arguments. python3 tools/rp6502.py --help lists the subcommands. To
list the options of a subcommand, put --help after it, as in
python3 tools/rp6502.py run --help. rp6502.py has these
subcommands:
Subcommand |
Description |
|---|---|
|
Upload a ROM to the RP6502-PICO, load it, and open a terminal. |
|
Upload files to the RP6502-PICO. |
|
Open a terminal on the console. |
|
Send a BASIC program to the BASIC installed on the RP6502-PICO, and run it. |
|
Package a file into a ROM, and merge ROMs into it. See Packaging a ROM Manually. |
|
Write the |
run, upload, term, and basic work through the monitor, so
they send a break first. The break stops any ROM that runs on the
RP6502-PICO and returns to the monitor.
Running in the Emulator¶
The emulator of a target is rp6502-emu in the directory of the
target, or rp6502-emu.exe on Windows. These examples run a ROM with
some of the emulator’s options:
build/cc65/debug/hello/rp6502-emu --debug build/cc65/debug/hello/hello.rp6502
build/cc65/debug/assemble/rp6502-emu --headless --phi2 0 build/cc65/debug/assemble/assemble.rp6502
build/basic/trek/rp6502-emu --script tests/play.txt build/basic/trek/trek.rp6502
With --debug, the emulator draws the debug overlay over its window.
With --headless, the emulator opens no window. The ROM’s console is
the host’s stdin and stdout. The ROM’s exit code becomes the exit code of
the emulator, so a ROM can run as a step in a shell script or a test.
--phi2 0 removes the speed limit.
With --script, the emulator script
tests/play.txt drives the machine and checks the results, as it does
in a test.
Packaging a ROM Manually¶
When an assembler or compiler that CMake does not run writes the 6502
program, package the ROM with rp6502.py create. Each run of
create writes one ROM, and can merge other ROMs into it. A ROM does
not have to hold a program. It can hold any memory chunks and named
assets, such as an image for XRAM or the help text.
ROMs therefore work like object files. Package each part of the program
in its own ROM, then merge them all in a last step, as a linker joins
object files. rp6502_asset() and rp6502_executable() build a ROM
this way. Any ROM can hold any part, but the final ROM must hold the
reset vector to run.
create uses these options:
-a: the address to load the file at, or an asset name. The address can also beRAM(<x>)orXRAM(<x>), where<x>is a number or a symbol from the.symfile that--symgives.-r: the reset vector.-iand-nset the IRQ and NMI vectors.-o: the ROM file to write.
With -a, the first file is the file to package, and every file after
it is a ROM to merge. Without -a, every file is a ROM to merge.
This example packages the help text, a data file, and an image in ROMs
of their own. The last step packages the output file of an assembler,
which loads at $0400, and merges the other ROMs into it.
# The help text, as a named asset.
python3 tools/rp6502.py create -a help -o help.rp6502 plvm.help
# A data file the program opens as ROM:level1.
python3 tools/rp6502.py create -a level1 -o level1.rp6502 level1.dat
# An image that the RIA loads into XRAM before the 6502 starts.
python3 tools/rp6502.py create -a 0x10000 -o splash.rp6502 splash.bin
# The program at $0400 with the reset vector, merged with the rest.
python3 tools/rp6502.py create -a 0x0400 -r 0x0400 -o plvm.rp6502 \
plvm.bin help.rp6502 level1.rp6502 splash.rp6502
The result is plvm.rp6502. It holds memory chunks for the program at
$0400, the reset vector at $FFFC, and the image at $10000. It
also holds two named assets, help and level1.
The .sym File¶
The build writes hello.sym beside hello.rp6502. The file has one
line for each symbol and value of the program: each #define with a
whole-number value that is in effect in a .c source file, including
the #define lines of its headers, and each symbol of the linker. A
line is the symbol, one space, and the value. A value from 0 to 0xFFFF is hex,
with 0x and four digits. Any other value is decimal:
INT8_MIN -128
XRAM_KEYBOARD 0x0000
XRAM_LOGO 0x0020
The build sorts the lines by symbol, then by value. A symbol with two
different values, such as a label that cc65 generates, like L0001, has
two lines. A lookup of that symbol fails because it is ambiguous. cc65
puts _ before each C symbol. The build writes each such symbol with
and without the _, so a C global x is in the file as x and as
_x. With cc65, a static variable of a function is in the file
only as a label that cc65 generates, such as M0001. With llvm-mos, it
is in the file as the function and the variable, such as
main.counter.
The symbols in this file work in RAM() and XRAM() addresses, in
rp6502_align(), in emulator scripts, and in Python tests. Program
Symbols shows each use.
ROM File Format¶
A ROM file is a shebang line, then a group of memory chunks, then any
number of named assets. A ROM that holds only named assets, such as an
input for a merge, has no group. Header lines are text and end with
\n or \r\n. The tools write \r\n. Spaces separate the
fields of a header line. A number may be decimal (255), C-style hex
(0xFF), or MOS-style hex ($FF). Each CRC is CRC-32 as zlib and PNG
compute it.
This is the hello.rp6502 of a new project, built with the cc65/Debug
preset. Each ... line stands for binary data. The text at the right
of each line is a note, not part of the file:
#!RP6502 shebang
#>$000002FA $DEA2E883 memory chunks, 0x2FA bytes follow
$0200 $2CC $C261269A chunk: 0x2CC bytes at $0200
... 0x2CC bytes of program
$FFFC $002 $AFD773D3 chunk: 2 bytes at $FFFC
... 2 bytes, the reset vector
#>$0000006E $E047820E help named asset: 0x6E bytes
... 0x6E bytes of help text
Shebang. The shebang is the first line of every ROM file. The tools write:
#!RP6502
Any first line that starts with #! and contains rp6502, without
regard to case, is valid. The first line can therefore give the command
that runs the ROM. A ROM file marked executable then runs from a shell
when that command is on the PATH:
#!/usr/bin/env rp6502-emu
Memory chunks. The line after the shebang starts the group of memory chunks, which the RIA loads into 6502 RAM or XRAM before the 6502 starts:
#>len crc
len is the number of bytes that follow in the group. It counts the
header line of each memory chunk as well as its binary data. crc is
the CRC-32 of those bytes. Each memory chunk is a header line followed by
its binary data:
addr len crc
Field |
Description |
|---|---|
|
Destination address in 6502 RAM ( |
|
Number of bytes of binary data that follow this line, from 1 to 1024. A memory chunk must not cross a 64 KB boundary. |
|
CRC-32 of the binary data. |
Named assets. A named asset is a file that the program opens by name while the ROM runs. Named assets follow the group, one after another, to the end of the file. Each named asset is a header line followed by its binary data. The header of a named asset has a name, and the header of the group has none:
#>len crc name
Field |
Description |
|---|---|
|
Number of bytes of binary data that follow this line. |
|
CRC-32 of the binary data. |
|
The name of the asset, to the end of the line. A program opens the
asset as |
The format sets no limit on the number of named assets. When a program opens an asset, the RIA reads each asset header in file order and skips its binary data until the name matches. In a ROM with many assets, the last assets therefore take longer to open.
Upgrading an Older Project¶
Because cc65 does not support volatile, the
macros of the old rp6502.h did not always work. The SDK removed them,
and now uses C library functions to access the RIA and XRAM. An older
project needs changes to build with the current compilers and tools.
Upgrade it in two steps.
First, make the project build and run again:
Install the current compilers with the commands in Installing the Prerequisites.
If the project has
tools/CMakeLists.txt, it comes from an older template that sets its compiler at the top ofCMakeLists.txt. Change its build to the one of the current template:Copy
tools/rp6502.cmakefrom the template over the one in the project. The first configure fetches the rest of the tools.Replace everything above
project()inCMakeLists.txtwith these lines:cmake_minimum_required(VERSION 3.28) include(${CMAKE_CURRENT_LIST_DIR}/tools/rp6502.cmake)
Delete
tools/CMakeLists.txtand theadd_subdirectory(tools)line.Copy
CMakePresets.jsonfrom the template.
Update the tools as in Updating the Tools.
Add
xram.hto the project, fromsrc/of the template or as an empty file. For an assembly project, addxram.inc. The structures and macros that the table copies from the manual go in this file.In a C project, add
rp6502-compat.hfrom the end of this section. Include"rp6502-compat.h"in place of<rp6502.h>. The header defines most of the old names, so the code that uses them builds unchanged.Build and test the project. For each error, find the old name, option, or command in the table below. Change it to its replacement. Change only what fails.
rp6502-compat.hdefines most of the old names, and the second step replaces those.
Then stop using the compat header:
Replace each name that
rp6502-compat.hdefines with its replacement from the table.Lay out XRAM in
xram_layout_t, so thatxram.hholds the XRAM memory map.Include
<rp6502.h>in place of"rp6502-compat.h". Removerp6502-compat.hfrom the project.Build and test the project.
This table lists the changes that break an older project:
Old |
New |
|---|---|
|
|
|
|
|
|
|
Copy the same macros from the RIA page into
|
|
Copy the same macro from Canvas Types into
|
|
One macro per mode from the VGA page,
|
|
|
|
|
|
|
|
None. The C function of each API call is in API. The C
library passes the ARGV arguments to |
|
None. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
None. The RIA computes local time. |
|
|
|
The same members, as |
An assembly macro, or a ca65 label, named |
|
|
|
Emulator option |
|
Emulator option |
|
Script command |
|
Script commands |
The same commands read stdout. For text that only the console
carries, add |
Script command |
|
Script command |
|
With |
A |
|
|
|
The emulator of the target. Configure deletes the old files. |
|
|
|
|
|
|
The ROM in the build directory, such as
|
The ROM in the directory of its target, such as
|
|
Remove them. |
|
|
A |
|
This is rp6502-compat.h:
#ifndef RP6502_COMPAT_H
#define RP6502_COMPAT_H
#include <rp6502.h>
#include <stddef.h>
#define RIA_READY_TX_BIT 0x80
#define RIA_READY_RX_BIT 0x40
#define RIA_BUSY_BIT 0x80
#define RIA_OP_ZXSTACK RIA_OP_DROP_XSTACK
#define RIA_OP_UNLINK RIA_OP_REMOVE
#define RIA_OP_CHDRIVE RIA_OP_CHDIR
#define phi2() ((int)ria_attr_get(RIA_ATTR_PHI2_KHZ))
#define code_page(cp) \
((cp) ? (void)ria_attr_set((cp), RIA_ATTR_CODE_PAGE) : (void)0, \
(int)ria_attr_get(RIA_ATTR_CODE_PAGE))
#define lrand() ria_attr_get(RIA_ATTR_LRAND)
#define xreg_ria_keyboard(...) xreg(0x0, 0x0, 0x00, __VA_ARGS__)
#define xreg_ria_mouse(...) xreg(0x0, 0x0, 0x01, __VA_ARGS__)
#define xreg_ria_gamepad(...) xreg(0x0, 0x0, 0x02, __VA_ARGS__)
#define xreg_ria_tablet(...) xreg(0x0, 0x0, 0x03, __VA_ARGS__)
#define xreg_vga_canvas(...) xreg(0x1, 0x0, 0x00, __VA_ARGS__)
#define xreg_vga_mode(...) xreg(0x1, 0x0, 0x01, __VA_ARGS__)
/* Bit tests, not ==, avoid a cc65 warning about constant comparisons.
The casts avoid conversion warnings from the unused branches. */
#define xram_struct_set_(n, a, size, v) \
((size) & 1 ? xram##n##_poke8(a, (unsigned char)(v)) \
: (size) & 2 ? xram##n##_poke16(a, (unsigned)(v)) \
: (xram##n##_poke16(a, (unsigned)(v)), \
xram##n##_poke16((a) + 2, (unsigned)((unsigned long)(v) >> 16))))
#define xram0_struct_set(addr, type, member, val) \
xram_struct_set_(0, (unsigned)(addr) + offsetof(type, member), \
sizeof(((type *)0)->member), val)
#define xram1_struct_set(addr, type, member, val) \
xram_struct_set_(1, (unsigned)(addr) + offsetof(type, member), \
sizeof(((type *)0)->member), val)
typedef struct {
unsigned char x_wrap, y_wrap;
int x_pos_px, y_pos_px, width_chars, height_chars;
unsigned xram_data_ptr, xram_palette_ptr, xram_font_ptr;
} vga_mode1_config_t;
typedef struct {
unsigned char x_wrap, y_wrap;
int x_pos_px, y_pos_px, width_tiles, height_tiles;
unsigned xram_data_ptr, xram_palette_ptr, xram_tile_ptr;
} vga_mode2_config_t;
typedef struct {
unsigned char x_wrap, y_wrap;
int x_pos_px, y_pos_px, width_px, height_px;
unsigned xram_data_ptr, xram_palette_ptr;
} vga_mode3_config_t;
typedef struct {
int x_pos_px, y_pos_px;
unsigned xram_sprite_ptr;
unsigned char log_size, has_opacity_metadata;
} vga_mode4_sprite_t;
typedef struct {
int transform[6];
int x_pos_px, y_pos_px;
unsigned xram_sprite_ptr;
unsigned char log_size, has_opacity_metadata;
} vga_mode4_asprite_t;
typedef struct {
int x_pos_px, y_pos_px;
unsigned xram_sprite_ptr, palette_ptr;
} vga_mode5_sprite_t;
#endif /* RP6502_COMPAT_H */