██▓ ███▄ █ ▄▄▄█████▓ █ ██ ██▓▄▄▄█████▓ ██▓ ▒█████ ███▄ █ ▓█████ ███▄ █ ▄████ ██▓ ███▄ █ ▓█████
▓██▒ ██ ▀█ █ ▓ ██▒ ▓▒ ██ ▓██▒▓██▒▓ ██▒ ▓▒▓██▒▒██▒ ██▒ ██ ▀█ █ ▓█ ▀ ██ ▀█ █ ██▒ ▀█▒▓██▒ ██ ▀█ █ ▓█ ▀
▒██▒▓██ ▀█ ██▒▒ ▓██░ ▒░▓██ ▒██░▒██▒▒ ▓██░ ▒░▒██▒▒██░ ██▒▓██ ▀█ ██▒ ▒███ ▓██ ▀█ ██▒▒██░▄▄▄░▒██▒▓██ ▀█ ██▒▒███
░██░▓██▒ ▐▌██▒░ ▓██▓ ░ ▓▓█ ░██░░██░░ ▓██▓ ░ ░██░▒██ ██░▓██▒ ▐▌██▒ ▒▓█ ▄ ▓██▒ ▐▌██▒░▓█ ██▓░██░▓██▒ ▐▌██▒▒▓█ ▄
░██░▒██░ ▓██░ ▒██▒ ░ ▒▒█████▓ ░██░ ▒██▒ ░ ░██░░ ████▓▒░▒██░ ▓██░ ░▒████▒▒██░ ▓██░░▒▓███▀▒░██░▒██░ ▓██░░▒████▒
░▓ ░ ▒░ ▒ ▒ ▒ ░░ ░▒▓▒ ▒ ▒ ░▓ ▒ ░░ ░▓ ░ ▒░▒░▒░ ░ ▒░ ▒ ▒ ░░ ▒░ ░░ ▒░ ▒ ▒ ░▒ ▒ ░▓ ░ ▒░ ▒ ▒ ░░ ▒░ ░
▒ ░░ ░░ ░ ▒░ ░ ░░▒░ ░ ░ ▒ ░ ░ ▒ ░ ░ ▒ ▒░ ░ ░░ ░ ▒░ ░ ░ ░░ ░░ ░ ▒░ ░ ░ ▒ ░░ ░░ ░ ▒░ ░ ░ ░
▒ ░ ░ ░ ░ ░ ░░░ ░ ░ ▒ ░ ░ ▒ ░░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
See also: TUTORIAL.md - Step-by-step guide to building a complete demoscene intro with multiple CPU architectures.
IntuitionOS M16.2.1 note: public handler, device, and resource acquisition is now frozen as IPC through the kernel-serviced public exec.library port. SDK-facing AttachHandler, OpenDevice, and OpenResource wrappers use EXEC_MSG_* request/reply messages with one-page shared request buffers and opaque generation-aware tokens. This is ONLINE-only for non-library rows; it does not add public acquisition syscalls, non-library demand-load, PIE, relocation, ASLR, or third-party install policy.
| Document | Description |
|---|---|
| DEVELOPERS.md | Build, test, toolchains, and contribution guide |
| CHANGELOG.md | Release history |
| sdk/README.md | SDK developer package with examples and build scripts |
| sdk/docs/TUTORIAL.md | Step-by-step demoscene intro tutorial |
| sdk/docs/IE64_ISA.md | IE64 instruction set reference |
| sdk/docs/IE64_COOKBOOK.md | IE64 common patterns and recipes |
| sdk/docs/ehbasic_ie64.md | EhBASIC language guide |
| sdk/docs/iemon.md | Machine monitor (F9 debugger) reference |
| sdk/docs/iescript.md | IEScript Lua automation reference |
| sdk/docs/sdk-getting-started.md | SDK quick start |
| sdk/docs/toolchains.md | Assembler toolchain reference |
| sdk/docs/demo-matrix.md | Demo program coverage matrix |
| sdk/docs/platform-compatibility.md | Platform support and build profiles |
| sdk/docs/release-process.md | Release packaging guide |
| sdk/docs/ie_emutos.md | EmuTOS integration guide (GEM desktop on IE) |
| sdk/docs/IntuitionOS/IExec.md | IExec microkernel contract reference |
| sdk/docs/IntuitionOS/HostFS.md | Bootstrap HostFS confinement and path-resolution contract |
- System Overview
- CPU Options
- Audio Capabilities
- Video System
- Quick Start
- 1.7 Machine Monitor
- Architecture
- 2.1 Unified Memory
- 2.2 Hardware I/O
- Memory Map & Hardware Registers
- 3.1 System Vector Table
- 3.2 Program Space
- 3.3 Video Registers
- 3.4 Timer Registers
- 3.5 Sound Registers (Legacy and Flexible)
- 3.6 PSG Registers
- 3.7 POKEY Registers
- 3.8 SID Registers
- 3.9 TED Registers
- 3.10 TED Video Chip Registers
- 3.11 AHX Module Player Registers
- 3.12 MOD Player Registers
- 3.13 Hardware I/O Memory Map by CPU
- 3.14 VGA Video Chip
- 3.15 ULA Video Chip (ZX Spectrum)
- 3.16 ANTIC Video Chip (Atari 8-bit)
- 3.17 GTIA Color Control (Atari 8-bit)
- 3.18 File I/O Device
- 3.19 Coprocessor Subsystem
- 3.20 Voodoo 3D Graphics
- IE32 CPU Architecture
- 4.1 Register Set
- 4.2 Status Flags
- 4.3 Addressing Modes
- 4.4 Instruction Format
- 4.5 Instruction Set
- 4.6 Memory and I/O Integration
- 4.7 Interrupt Handling
- 4.8 Compatibility Notes
- MOS 6502 CPU
- 5.1 Register Set
- 5.2 Status Flags
- 5.3 Addressing Modes
- 5.4 Instruction Set
- 5.5 Memory and I/O Integration
- 5.6 Interrupts and Vectors
- 5.7 Compatibility Notes
- Zilog Z80 CPU
- 6.1 Register Set
- 6.2 Status Flags
- 6.3 Addressing Modes
- 6.4 Instruction Set
- 6.5 Memory and I/O Integration
- 6.6 Interrupts
- 6.7 Compatibility Notes
- Motorola 68020 CPU with FPU
- 7.1 Register Set
- 7.2 Status Flags
- 7.3 Addressing Modes
- 7.4 Instruction Set
- 7.5 FPU (68881/68882) Features
- 7.6 Memory and I/O Integration
- 7.7 Interrupts and Exceptions
- 7.8 Compatibility Notes
- Intel x86 CPU (32-bit)
- 8.1 Register Set
- 8.2 Status Flags
- 8.3 Addressing Modes
- 8.4 Instruction Set
- 8.5 Memory and I/O Integration
- 8.6 Interrupts
- 8.7 Compatibility Notes
- IE64 CPU Architecture
- 9.1 Register Set
- 9.2 Instruction Format
- 9.3 Addressing Modes
- 9.4 Instruction Set
- 9.5 FPU Logic
- 9.6 Memory and I/O Integration
- 9.7 Interrupt Handling
- 9.8 Compatibility Notes
- 9.9 EhBASIC IE64
- 9.10 Memory Management Unit (MMU)
- Assembly Language Reference
- 10.1 Basic Program Structure
- 10.2 Assembler Directives
- 10.3 Memory Access Patterns
- 10.4 Stack Usage
- 10.5 Interrupt Handlers
- Sound System
- Custom Audio Chip Overview
- 11.1 Sound Channel Types
- 11.2 Modulation System
- 11.3 Global Effects
- 11.4 PSG Sound Chip (AY-3-8910/YM2149)
- 11.5 POKEY Sound Chip
- 11.6 SID Sound Chip
- 11.7 TED Sound Chip
- 11.8 AHX Sound Chip
- 11.9 MOD Player (ProTracker)
- Video System
- 12.1 Display Modes
- 12.2 Framebuffer Organisation
- 12.3 Dirty Rectangle Tracking
- 12.4 Double Buffering and VBlank Synchronisation
- 12.5 Direct VRAM Access Mode
- 12.6 Copper List Executor
- 12.7 DMA Blitter
- 12.8 Raster Band Fill
- 12.9 Video Compositor
- Developer's Guide
- Implementation Details
- Platform Support and Building
- EmuTOS Mode
- AROS Mode
- IntuitionOS
The Intuition Engine is a virtual machine that emulates a complete retro-style computer system. It is a modern 64-bit RISC reimagining of the Commodore, Atari, Sinclair and IBM 8/16/32-bit home computers, with IE64 as the default core and five additional CPU cores.
| CPU | Architecture | Registers | Features |
|---|---|---|---|
| IE32 | 32-bit RISC | 16 general-purpose (A-H, S-W, X-Z) | Fixed 8-byte instructions, simple and fast |
| M68K | 32-bit CISC | 8 data (D0-D7), 8 address (A0-A7) | 95%+ instruction coverage, FPU support |
| Z80 | 8-bit | AF, BC, DE, HL + alternates, IX, IY | Full instruction set, interrupt modes, x86-64 JIT with block chaining |
| 6502 | 8-bit | A, X, Y | NMOS instruction set, fast inline interpreter with direct-page-bitmap memory fast path, x86-64 JIT with 7-9x speedup on compute-bound workloads |
| x86 | 32-bit | EAX-EDX, ESI, EDI, EBP, ESP | 8086 instructions + 32-bit registers, flat memory model |
| IE64 | 64-bit RISC | 32 general-purpose (R0=zero, R31=SP) | ARM64/x86-64 JIT compiler, native FP32/FP64 FPU, compare-and-branch, no flags register, MMU with paged virtual memory and user/supervisor privilege levels, atomic memory operations (CAS/XCHG/FAA), TLS register |
Default core: IE64. Additional cores: IE32, M68K, x86, Z80, 6502.
Custom Synthesizer:
- 10-channel synth engine (4 base + 3 SID2 + 3 SID3 for multi-SID playback)
- 5 legacy register blocks (square, triangle, sine, noise, sawtooth)
- 4 flexible channels with selectable waveforms (uniform register interface)
- ADSR envelopes, PWM, frequency sweep, hard sync, ring modulation
- Global filter (LP/HP/BP), overdrive, reverb
- 44.1kHz, 32-bit floating-point processing
Classic Sound Chips (register-mapped to custom synth):
- AY/YM/PSG (AY-3-8910/YM2149) - Supports .ym, .ay, .vgm, .vgz, .vtx, .sndh, .pt3, .pt2, .pt1, .stc, .sqt, .asc, .ftc playback (VGM includes SN76489 conversion; tracker formats use Z80 emulation)
- POKEY (Atari) - Supports .sap playback
- SID (6581/8580) - Supports .sid playback
- TED (Commodore Plus/4) - Supports .ted playback
- Amiga AHX module playback
- ProTracker MOD (.mod) - 4-channel Amiga module playback with A500/A1200 filter emulation
- WAV PCM audio via SoundChip FLEX DAC mode
- Resolutions: 640×480, 800×600, 1024×768, 1280×960 (default)
- 32-bit RGBA framebuffer with double buffering
- Copper coprocessor for raster effects
- DMA blitter for fast copy/fill/line operations
- Dirty rectangle tracking for efficient updates
- Engines/chips: IEVideoChip, VGA, ULA, TED video, ANTIC/GTIA, 3DFX Voodoo
- IEScript (Lua 5.1) automation engine for programmatic control of the entire emulator
- 11 API modules:
cpu,mem,term,audio,video,repl,dbg,rec,coproc,media,sys - Frame-synchronised coroutine model, MP4+AAC recording via FFmpeg, interactive F8 REPL overlay
- See iescript.md for the full reference
# Default: start EhBASIC on IE64
./bin/IntuitionEngine
# Run IE32 program
./bin/IntuitionEngine -ie32 program.iex
# Run M68K program
./bin/IntuitionEngine -m68k program.ie68
# Run x86 program
./bin/IntuitionEngine -x86 program.ie86
# Run IE64 program
./bin/IntuitionEngine -ie64 program.ie64
# Run EhBASIC interpreter
./bin/IntuitionEngine -basic
# Run EhBASIC with console terminal (no GUI window)
./bin/IntuitionEngine -basic -term
# Play PSG music
./bin/IntuitionEngine -psg music.ym
./bin/IntuitionEngine -psg track.vtx # VTX (LHA-compressed YM)
./bin/IntuitionEngine -psg track.pt3 # ProTracker 3 (ZX Spectrum)
# Play SID music
./bin/IntuitionEngine -sid music.sid
./bin/IntuitionEngine -sid music.sid -sid-pal # Force PAL timing
./bin/IntuitionEngine -sid music.sid -sid-ntsc # Force NTSC timing
# Play POKEY/SAP music (Atari 8-bit)
./bin/IntuitionEngine -pokey music.sap
# Play TED music (Commodore Plus/4)
./bin/IntuitionEngine -ted music.prg
# Play AHX music (Amiga tracker)
./bin/IntuitionEngine -ahx module.ahx
# Play WAV audio
./bin/IntuitionEngine -wav audio.wav
# Play ProTracker MOD music
./bin/IntuitionEngine -mod music.mod
# Enhanced audio modes (4x oversampling, filtering, drive, ambience)
./bin/IntuitionEngine -psg+ music.ym
./bin/IntuitionEngine -sid+ music.sid
./bin/IntuitionEngine -pokey+ music.sap
./bin/IntuitionEngine -ted+ music.prg
./bin/IntuitionEngine -ahx+ module.ahx
# Boot EmuTOS (GEM desktop)
./bin/IntuitionEngine -emutos
# Boot AROS (Amiga Workbench)
./bin/IntuitionEngine -aros
# Run a Lua automation script alongside a program
./bin/IntuitionEngine -ie64 program.ie64 -script demo.ies
# Run with performance measurement (MIPS reporting)
./bin/IntuitionEngine -perf -ie64 program.ie64
# Disable JIT compiler (use interpreter only)
./bin/IntuitionEngine -nojit -ie64 program.ie64
# Display options
./bin/IntuitionEngine -scale 2 -ie32 program.iex # 2x window scaling
./bin/IntuitionEngine -fullscreen -m68k program.ie68 # Start in fullscreen (F11 to toggle)
./bin/IntuitionEngine -width 800 -height 600 -ie64 program.ie64 # Override output resolution
# Version information
./bin/IntuitionEngine -version
# List compiled feature flags and runtime info
./bin/IntuitionEngine -featuresCPU modes that execute binaries (-ie32, -ie64, -m68k, -m6502, -z80, -x86) require a filename unless -basic is used.
F9: Open the Machine Monitor (debugger) - freezes all CPUs, shows registers and disassembly. See iemon.md for full documentation.F10: Hard reset - performs a full power-on hardware reset (reloads EmuTOS if started in EmuTOS mode, otherwise boots IE64 BASIC)F11: Toggle fullscreen modeF12: Toggle the runtime status barPage Up/Page Down: Scroll terminal scrollback bufferMouse wheel: Scroll terminal scrollback buffer- Status bar semantics:
CPU,VIDEO, andAUDIOdevice names are shown in green when active and gray when inactive.
Opening an *.ie* file while Intuition Engine is already running sends the file to the running instance via Unix domain socket IPC. For binary files, the running instance performs a full hardware reset and loads the new binary. If the file uses a different CPU architecture (e.g., opening a .ie80 Z80 binary while an IE32 program is running), the CPU mode switches automatically. For .ies files, the script is executed without a hardware reset.
Supported extensions: .ie32/.iex (IE32), .ie64 (IE64), .ie65 (6502), .ie68 (M68K), .ie80 (Z80), .ie86 (X86), .ies (IEScript Lua automation).
EmuTOS (.tos/.img) and AROS boot are CLI-only modes (-emutos, -aros) and are not supported via single-instance IPC.
Register Intuition Engine as the default handler for *.ie* files:
# Install .desktop entry and MIME type (system-wide, requires root)
sudo make install-desktop-entry
# Set as default handler for .ie* files (per-user)
make set-default-handlerAfter registration, double-clicking any .ie* file in a file manager will open it in Intuition Engine (or send it to an already-running instance).
Press F9 at any time to freeze the entire system and enter the built-in Machine Monitor - a system-level debugger inspired by the Commodore 64/Amiga Action Replay cartridge and HRTMon. Press x or Esc to resume execution.
The monitor works with all six CPU types (IE64, IE32, M68K, Z80, 6502, X86) and handles multi-CPU scenarios including coprocessors.
| Command | Description |
|---|---|
r |
Show all registers (changed values highlighted in green) |
r <name> <value> |
Set a register value |
d [addr] [count] |
Disassemble instructions with branch annotations |
m <addr> [count] |
Hex dump memory |
s [count] |
Single-step one or more instructions |
bs |
Backstep (undo last step, restores CPU + memory) |
g [addr] |
Resume execution (optionally from a new address) |
u <addr> |
Run until address |
b <addr> [cond] |
Set breakpoint (with optional condition) |
bc <addr> / bc * |
Clear breakpoint(s) |
bl |
List all breakpoints |
ww <addr> |
Set write watchpoint |
wc <addr|*> / wl |
Clear/list watchpoints |
bt [depth] |
Stack backtrace |
f <addr> <len> <byte> |
Fill memory with a byte value |
w <addr> <bytes..> |
Write bytes to memory |
t <dst> <src> <len> |
Transfer (copy) memory |
c <addr1> <addr2> <len> |
Compare two memory regions |
h <start> <end> <bytes..> |
Search memory for byte pattern |
save <s> <e> <file> |
Save memory range to file |
load <file> <addr> |
Load file into memory |
ss / sl [file] |
Save/load machine state |
trace <count> |
Trace N instructions (+ write history) |
io [device|all] |
I/O register viewer (use all for every device) |
e <addr> |
Hex editor mode |
script <file> |
Run command script |
macro <name> ... |
Define command macro |
cpu [n] |
Switch focus to CPU #n (for multi-CPU debugging) |
fa / ta |
Audio freeze / thaw |
x |
Exit monitor and resume all CPUs |
Addresses accept $hex, 0xhex, bare hex, #decimal, or expressions like pc+$20.
The monitor is also accessible from Lua scripts via the dbg.* API module. See iescript.md for scripted debugging workflows.
Full documentation: iemon.md
All CPU cores (IE32, IE64, M68K, Z80, 6502, x86) share the same memory space through the MachineBus. This unified architecture ensures that:
- Program data loaded by any CPU is immediately visible to all peripherals
- Audio synthesis responds instantly to register writes from any CPU
- DMA operations (blitter, copper, file players) can access any memory location
- Memory-mapped I/O works consistently across all CPU types
- Video compositing blends multiple video sources (VideoChip, VGA) into final output
┌─────────────────────────────────────────────────────────────────┐
│ MachineBus Memory │
│ (autodetected guest RAM) │
├─────────────────────────────────────────────────────────────────┤
│ 0x000000 - 0x000FFF │ System Vectors │
│ 0x001000 - 0x09FFFF │ Program Space (code + data) │
│ 0x0A0000 - 0x0AFFFF │ VGA VRAM Window (64KB) │
│ 0x0B8000 - 0x0BFFFF │ VGA Text Buffer (32KB) │
│ 0x0F0000 - 0x0F0FFF │ Video/Audio I/O Registers │
│ 0x0F1000 - 0x0F13FF │ VGA Registers │
│ 0x0F2400 - 0x0F240F │ SYSINFO RAM-size MMIO (total+active) │
│ 0x100000 - 0x5FFFFF │ Video RAM (5MB, chunky RGBA) │
│ 0x600000 - top │ Extended RAM (sized per profile/CPU) │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌────────────────┐ ┌────────────────┐
│ CPU │ │ Video System │ │ Audio System │
│ (IE32/ │ │ ──────────── │ │ ──────────────│
│ IE64/ │ │ VideoChip │ │ Custom Synth │
│ M68K/ │ │ VGA Engine │ │ PSG/POKEY/SID │
│ Z80/ │ │ Compositor │ │ File Players │
│ 6502/ │ │ Blitter/Copper│ └────────────────┘
│ x86) │
└─────────┘
The video compositor blends output from all enabled video sources (VideoChip=0, VGA=10, TED=12, ANTIC/GTIA=13, ULA=15, Voodoo=20) into a single display using layer ordering. The copper coprocessor can target any video system via SETBASE for per-scanline raster effects.
The custom audio synthesizer is the core of the sound system. PSG, POKEY, and SID registers are mapped to the custom synth, providing authentic register-level compatibility with high-quality 44.1kHz output. File players drive the synth via register writes: some formats (.ay, .sndh, .sid, .sap, .ted, tracker modules) execute embedded CPU code (Z80, M68K, or 6502), while others (.ym, .vgm/vgz) are rendered from parsed register events in Go. VGM files containing SN76489 data (Sega Master System, Game Gear) are automatically converted to AY register writes during parsing.
- MachineBus (
Bus32/Bus64interfaces): The global RAM + MMIO backbone shared by all CPUs and peripherals. Total guest RAM is autodetected from the host at boot (host/proc/meminfominus a per-platform reserve, seememory_sizing.go);bootGuestRAMFromComputedbuilds the bus viaNewMachineBusSizedand attaches an mmap-backedBackingfor the high-range guest RAM.len(bus.memory)is the legacy direct-slice low compatibility window, NOT the guest RAM size authority — IE64-family modes pin it at 256 MiB and the highBackingcarries the full host-scale total. The bus.memory allocator on Linux/darwin uses anonymousmmap(demand-paged, no eager commit);MachineBus.Resetusesmadviseso guest reset releases pages instead of touching every byte. Each CPU/profile sees an active visible RAM clamped viaApplyProfileVisibleCeiling. Guest software discovers sizes via the SYSINFO MMIO pairs (SYSINFO_TOTAL_RAM_LO/HI,SYSINFO_ACTIVE_RAM_LO/HI) and IE64CR_RAM_SIZE_BYTES(read-only, live-read). - CPU bus interfaces (
Z80Bus,X86Bus,Bus6502): Per-CPU contract shapes that define the read/write/port operations each CPU core expects. - CPU bus adapters (
Z80BusAdapter,X86BusAdapter,Bus6502Adapter): Translate 8/16-bit CPU address spaces and port I/O into MachineBus calls, handling bank switching and sign extension. - Playback buses (
SAPPlaybackBus6502,SIDPlaybackBus6502,TEDPlaybackBus6502,sndhPlaybackBus68K,ayPlaybackBusZ80): Standalone lightweight bus implementations for music file playback - provide just enough memory and I/O to run embedded CPU code that drives audio register writes.
All hardware is accessed through memory-mapped registers in the $F0000-$FFFFF range:
| Subsystem | Address Range | Description |
|---|---|---|
| Video | $F0000-$F049B |
Display control, copper, blitter, raster, CLUT8 palette, extended blitter |
| Custom Audio | $F0800-$F0B7F |
Audio control, envelope shape, filter, legacy channels, flex synth |
| PSG | $F0C00-$F0C1C |
AY-3-8910 registers and file playback |
| POKEY | $F0D00-$F0D1D |
Atari POKEY registers and SAP playback |
| SID | $F0E00-$F0E2D |
MOS 6581 registers and SID playback |
| MOD | $F0BC0-$F0BD7 |
ProTracker .mod player with Amiga filters |
| Banking | $F700-$F7F0 |
Bank window control (Z80/6502/x86) |
| VGA | $F1000-$F13FF |
VGA mode, DAC, sequencer, CRTC, palette |
| File I/O | $F2200-$F221F |
Host filesystem access (read/write); supports byte-level writes for 8-bit CPUs |
| AROS DOS | $F2220-$F225F |
AROS DOS handler bridge (AROS mode only) |
| AROS Audio | $F2260-$F22AF |
Paula-compatible 4-channel DMA audio (AROS mode only) |
| Media Loader | $F2300-$F231F |
Media file auto-detection and loading |
| Bootstrap HostFS | $F23E0-$F23FF |
Read-only host-backed boot bridge for IntuitionOS bootstrap |
| Coprocessor | $F2340-$F238F |
Worker CPU lifecycle, async RPC, IRQ control |
| Coprocessor Ext | $F23B0-$F23BF |
Monitor registers (ring depth, uptime, busy%, reset) |
Additionally, VGA uses legacy PC-compatible memory windows:
$A0000-$AFFFF: VGA VRAM (64KB graphics memory)$B8000-$BFFFF: VGA Text Buffer (32KB, char+attr pairs)
For 8-bit CPUs (Z80, 6502), addresses are mapped to the 16-bit range $F000-$FFFF or accessed via I/O ports.
The system's memory layout is designed to provide efficient access to both program space and hardware features while maintaining clear separation between different memory regions.
Address Notation: This document uses both 0x prefix (C-style) and $ prefix (assembly-style) for hexadecimal addresses. The notation varies to match each CPU's assembly dialect: 0x for IE32/IE64/general discussion, $ for M68K and 6502 assembly examples.
0x000000 - 0x000FFF: System vectors (including interrupt vector)
0x001000 - 0x0EFFFF: Program space
0x0F0000 - 0x0F049B: Video registers (copper, blitter, raster, Mode7, extended blitter)
0x0F0700 - 0x0F072F: Terminal/Serial output
0x0F0730 - 0x0F073C: Mouse registers (X, Y, Buttons, Status)
0x0F0740 - 0x0F0748: Scancode registers (Code, Status, Modifiers)
0x0F0750: RTC_EPOCH (host UTC seconds since Unix epoch)
0x0F0800 - 0x0F080C: Legacy timer alias window (deprecated, no stable MMIO control ABI)
0x0F0820 - 0x0F0834: Filter registers
0x0F0900 - 0x0F0A6F: Legacy synth registers (square/triangle/sine/noise/saw)
0x0F0A80 - 0x0F0B7F: Flexible 4-channel synth registers (preferred)
0x0F0B80 - 0x0F0B91: AHX module player registers
0x0F0BC0 - 0x0F0BD7: MOD player registers (ProTracker .mod playback)
0x0F0BD8 - 0x0F0BEB: WAV player registers (PCM .wav playback)
0x0F0C00 - 0x0F0C0D: PSG registers (AY/YM synthesis)
0x0F0C0E: PSG+ control register
0x0F0C10 - 0x0F0C1C: PSG playback control (AY/YM/VGM/SNDH; VGM includes SN76489)
0x0F0D00 - 0x0F0D08: POKEY registers (Atari 8-bit audio)
0x0F0D09: POKEY+ control register
0x0F0D10 - 0x0F0D1D: SAP playback control
0x0F0E00 - 0x0F0E18: SID registers (C64 audio synthesis)
0x0F0E19: SID+ control register
0x0F0E1A - 0x0F0E1C: SID read-only registers (OSC3, ENV3)
0x0F0E20 - 0x0F0E2D: SID playback control (.SID file playback)
0x0F0E30 - 0x0F0E4C: SID2 registers (multi-SID, voices 4-6)
0x0F0E50 - 0x0F0E6C: SID3 registers (multi-SID, voices 7-9)
0x0F0F00 - 0x0F0F05: TED registers (Plus/4 audio)
0x0F0F10 - 0x0F0F1C: TED playback control
0x0F1000 - 0x0F13FF: VGA registers (IBM VGA emulation)
0x0F2200 - 0x0F221F: File I/O registers (see below)
0x0F2340 - 0x0F238F: Coprocessor MMIO registers
0x0F2390 - 0x0F23AF: Clipboard bridge (AROS mode, see below)
0x0F23B0 - 0x0F23BF: Coprocessor extended monitor registers
0x0A0000 - 0x0AFFFF: VGA VRAM window (Mode 13h/12h)
0x0B8000 - 0x0BFFFF: VGA text buffer
0x100000 - 0x5FFFFF: Video RAM (VRAM_START to VRAM_START + VRAM_SIZE)
0x800000 - 0x1DFFFFF: AROS Fast Memory (22MB, AROS mode only)
0x1E00000 - 0x1FFFFFF: AROS Video RAM (2MB, AROS mode only)
0x200000 - 0x27FFFF: Coprocessor worker region (IE32, 512KB)
0x280000 - 0x2FFFFF: Coprocessor worker region (M68K, 512KB)
0x300000 - 0x30FFFF: Coprocessor worker region (6502, 64KB)
0x310000 - 0x31FFFF: Coprocessor worker region (Z80, 64KB)
0x320000 - 0x39FFFF: Coprocessor worker region (x86, 512KB)
0x3A0000 - 0x41FFFF: Coprocessor worker region (IE64, 512KB)
0x400000 - 0x7FFFFF: User data buffers (coprocessor request/response data)
0x790000 - 0x7917FF: Coprocessor mailbox shared RAM (6KB, ring buffers)
The first 4KB of memory is reserved for system vectors. The most important vector is:
0x0000 - 0x0003: Interrupt Service Routine (ISR) vector
When interrupts are enabled via the SEI instruction, the CPU reads this vector to determine the ISR address. Programs must initialise this vector before enabling interrupts:
.org 0x0000
.word isr_address ; Store ISR location in vectorPrograms begin loading at 0x1000, providing:
- Protection of low memory from accidental overwrites
- Clear separation from system areas
- Space for program code and data
0x0F0000: VIDEO_CTRL - Video system control (0 = disabled, 1 = enabled)
0x0F0004: VIDEO_MODE - Display mode selection
0x0F0008: VIDEO_STATUS - Video status (read-only, lock-free)
bit0 = hasContent (frame has been drawn to)
bit1 = inVBlank (safe to draw without flicker)
0x0F000C: COPPER_CTRL - Copper control (bit0=enable, bit1=reset/rewind)
0x0F0010: COPPER_PTR - Copper list base address (32-bit)
0x0F0014: COPPER_PC - Copper program counter (read-only)
0x0F0018: COPPER_STATUS- Copper status (bit0=running, bit1=waiting, bit2=halted)
0x0F001C: BLT_CTRL - Blitter control (bit0=start, bit1=busy, bit2=irq enable)
0x0F0020: BLT_OP - Blitter op (copy/fill/line/masked copy/alpha/mode7)
0x0F0024: BLT_SRC - Blitter source address (32-bit)
0x0F0028: BLT_DST - Blitter dest address (32-bit)
0x0F002C: BLT_WIDTH - Blit width (pixels)
0x0F0030: BLT_HEIGHT - Blit height (pixels)
0x0F0034: BLT_SRC_STRIDE - Source stride (bytes/row)
0x0F0038: BLT_DST_STRIDE - Dest stride (bytes/row)
0x0F003C: BLT_COLOR - Fill/line color (RGBA)
0x0F0040: BLT_MASK - Mask address for masked copy (1-bit/pixel)
0x0F0044: BLT_STATUS - Blitter status (bit0=error)
0x0F0048: VIDEO_RASTER_Y - Raster band start Y
0x0F004C: VIDEO_RASTER_HEIGHT - Raster band height (pixels)
0x0F0050: VIDEO_RASTER_COLOR - Raster band color (RGBA)
0x0F0054: VIDEO_RASTER_CTRL - Raster band control (bit0=draw)
0x0F0058: BLT_MODE7_U0 - Mode7 start U (signed 16.16)
0x0F005C: BLT_MODE7_V0 - Mode7 start V (signed 16.16)
0x0F0060: BLT_MODE7_DU_COL - Mode7 U delta per column pixel (signed 16.16)
0x0F0064: BLT_MODE7_DV_COL - Mode7 V delta per column pixel (signed 16.16)
0x0F0068: BLT_MODE7_DU_ROW - Mode7 U delta per row (signed 16.16)
0x0F006C: BLT_MODE7_DV_ROW - Mode7 V delta per row (signed 16.16)
0x0F0070: BLT_MODE7_TEX_W - Mode7 texture width mask (2^n-1)
0x0F0074: BLT_MODE7_TEX_H - Mode7 texture height mask (2^n-1)
0x0F0078: VIDEO_PAL_INDEX - CLUT8 palette entry selector (auto-incrementing after data write)
0x0F007C: VIDEO_PAL_DATA - CLUT8 palette data (0x00RRGGBB, increments index)
0x0F0080: VIDEO_COLOR_MODE - Color mode (0=RGBA32, non-zero=CLUT8 indexed)
0x0F0084: VIDEO_FB_BASE - Framebuffer base address for indirect access
0x0F0088-0x0F0487: VIDEO_PAL_TABLE - 256 palette entries (direct access, 4 bytes each)
0x0F0488: BLT_FLAGS - Extended flags: BPP (bits 0-1), draw mode (bits 4-7),
JAM1 (bit 8), invert template (bit 9), invert mode (bit 10)
0x0F048C: BLT_FG - Foreground color for color expansion
0x0F0490: BLT_BG - Background color for color expansion
0x0F0494: BLT_MASK_MOD - Template row modulo in bytes (color expansion)
0x0F0498: BLT_MASK_SRCX- Starting X bit offset in template (color expansion)
Available Video Modes:
MODE_640x480 = 0x00
MODE_800x600 = 0x01
MODE_1024x768 = 0x02
MODE_1280x960 = 0x03 (default)
Video Compositor: These registers control the VideoChip, which renders as layer 0 in the compositor. The VGA chip (section 3.11) renders as layer 10 on top. Both sources are composited together for final display output. The copper coprocessor can write to either device using the SETBASE instruction (see section 12.6).
Copper lists are stored as little-endian 32-bit words in RAM. The list format is:
WAIT:(0<<30) | (y<<12) | x(wait until raster Y/X reached)MOVE:(1<<30) | (regIndex<<16)followed by a 32-bit value wordEND:(3<<30)
regIndex is (register_address - VIDEO_REG_BASE) / 4, where VIDEO_REG_BASE = 0x0F0000.
COPPER_PTR is latched on enable/reset; 8-bit CPUs should write bytes to COPPER_PTR+0..3.
Example (mid-frame mode switch):
; Copper list in RAM
.long (0 << 30) | (100 << 12) | 0 ; WAIT y=100, x=0
.long (1 << 30) | (1 << 16) ; MOVE VIDEO_MODE (index 1)
.long 0x01 ; MODE_800x600
.long (3 << 30) ; ENDIE32 and IE64 use CPU-internal countdown timers managed by atomic fields on their CPU structs.
The timer decrements once per SAMPLE_RATE instructions, can raise interrupts on expiry, and auto-reloads from its period when enabled.
Legacy timer symbols in some include files are retained for compatibility but are deprecated; there is no stable public MMIO timer control ABI in current runtime.
0x0F0820: FILTER_CUTOFF - Filter cutoff frequency (0-255)
0x0F0824: FILTER_RESONANCE - Filter resonance (0-255)
0x0F0828: FILTER_TYPE - Filter type selection
0x0F082C: FILTER_MOD_SOURCE - Filter modulation source
0x0F0830: FILTER_MOD_AMOUNT - Modulation depth (0-255)
0x0F0900: SQUARE_FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
0x0F0904: SQUARE_VOL - Volume (0-255)
0x0F0908: SQUARE_CTRL - Channel control
0x0F090C: SQUARE_DUTY - Duty cycle control
0x0F0910: SQUARE_SWEEP - Frequency sweep control
0x0F0922: SQUARE_PWM_CTRL - PWM modulation control
0x0F0930: SQUARE_ATK - Attack time (ms)
0x0F0934: SQUARE_DEC - Decay time (ms)
0x0F0938: SQUARE_SUS - Sustain level (0-255)
0x0F093C: SQUARE_REL - Release time (ms)
0x0F0940: TRI_FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
0x0F0944: TRI_VOL - Volume control
0x0F0948: TRI_CTRL - Channel control
0x0F0914: TRI_SWEEP - Frequency sweep control
0x0F0960: TRI_ATK - Attack time
0x0F0964: TRI_DEC - Decay time
0x0F0968: TRI_SUS - Sustain level
0x0F096C: TRI_REL - Release time
0x0F0980: SINE_FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
0x0F0984: SINE_VOL - Volume control
0x0F0988: SINE_CTRL - Channel control
0x0F0918: SINE_SWEEP - Frequency sweep control
0x0F0990: SINE_ATK - Attack time
0x0F0994: SINE_DEC - Decay time
0x0F0998: SINE_SUS - Sustain level
0x0F099C: SINE_REL - Release time
0x0F09C0: NOISE_FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
0x0F09C4: NOISE_VOL - Volume control
0x0F09C8: NOISE_CTRL - Channel control
0x0F09D0: NOISE_ATK - Attack time
0x0F09D4: NOISE_DEC - Decay time
0x0F09D8: NOISE_SUS - Sustain level
0x0F09DC: NOISE_REL - Release time
0x0F09E0: NOISE_MODE - Noise generation mode
Noise Modes:
NOISE_MODE_WHITE = 0 // Standard LFSR noise
NOISE_MODE_PERIODIC = 1 // Periodic/looping noise
NOISE_MODE_METALLIC = 2 // "Metallic" noise variant
0x0F0A20: SAW_FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
0x0F0A24: SAW_VOL - Volume control
0x0F0A28: SAW_CTRL - Channel control
0x0F0A2C: SAW_SWEEP - Frequency sweep control
0x0F0A30: SAW_ATK - Attack time
0x0F0A34: SAW_DEC - Decay time
0x0F0A38: SAW_SUS - Sustain level
0x0F0A3C: SAW_REL - Release time
0x0F0A00: SYNC_SOURCE_CH0 - Sync source for channel 0
0x0F0A04: SYNC_SOURCE_CH1 - Sync source for channel 1
0x0F0A08: SYNC_SOURCE_CH2 - Sync source for channel 2
0x0F0A0C: SYNC_SOURCE_CH3 - Sync source for channel 3
0x0F0A10: RING_MOD_SOURCE_CH0 - Ring mod source for channel 0
0x0F0A14: RING_MOD_SOURCE_CH1 - Ring mod source for channel 1
0x0F0A18: RING_MOD_SOURCE_CH2 - Ring mod source for channel 2
0x0F0A1C: RING_MOD_SOURCE_CH3 - Ring mod source for channel 3
0x0F0A60: SYNC_SOURCE_CH4 - Sync source for sawtooth channel
0x0F0A64: RING_MOD_SOURCE_CH4 - Ring mod source for sawtooth channel
0x0F0A40: OVERDRIVE_CTRL - Drive amount (0-255)
0x0F0A50: REVERB_MIX - Dry/wet mix (0-255)
0x0F0A54: REVERB_DECAY - Decay time (0-255)
The flexible synth block provides a modern, uniform interface for all four synthesis channels. Each channel occupies 0x40 bytes (64 bytes), supporting any waveform type.
Channel Base Addresses:
Channel 0: 0x0F0A80
Channel 1: 0x0F0AC0
Channel 2: 0x0F0B00
Channel 3: 0x0F0B40
Per-Channel Register Offsets:
+0x00: FREQ - Frequency (16.8 fixed-point Hz, value = Hz * 256)
+0x04: VOL - Volume (0-255)
+0x08: CTRL - Channel control (bit0=enable, bit1=gate)
+0x0C: DUTY - Duty cycle for square/pulse waves
+0x10: SWEEP - Frequency sweep control
+0x14: ATK - Attack time (ms)
+0x18: DEC - Decay time (ms)
+0x1C: SUS - Sustain level (0-255)
+0x20: REL - Release time (ms)
+0x24: WAVE_TYPE - Waveform selection (0=square, 1=triangle, 2=sine, 3=noise, 4=saw)
+0x28: PWM_CTRL - PWM modulation control
+0x2C: NOISEMODE - Noise mode (for noise waveform)
+0x30: PHASE - Reset phase position
+0x34: RINGMOD - Ring modulation source (bit7=enable, bits0-2=source channel)
+0x38: SYNC - Hard sync source (bit7=enable, bits0-2=source channel)
+0x3C: DAC - DAC mode: signed 8-bit sample (bypasses waveform and envelope)
Example: Configure channel 1 as a sawtooth wave at 440Hz:
; Using flexible synth registers
; Frequency is 16.8 fixed-point: 440 Hz * 256 = 112640
LOAD A, #112640
STORE A, @0x0F0AB0 ; CH1 FREQ
LOAD A, #200
STORE A, @0x0F0AB4 ; CH1 VOL
LOAD A, #4
STORE A, @0x0F0AD4 ; CH1 WAVE_TYPE = sawtooth
LOAD A, #1
STORE A, @0x0F0AB8 ; CH1 CTRL = enable0x0F0C00: PSG_REG_SELECT - Register select
0x0F0C01: PSG_REG_DATA - Register data
0x0F0C02-0x0F0C0D: PSG registers (direct access)
0x0F0C0E: PSG_PLUS_CTRL - PSG+ mode (0=standard, 1=enhanced)
PSG Playback Control (supports .ym, .ay, .vgm, .vgz, .vtx, .sndh, .pt3, .pt2, .pt1, .stc, .sqt, .asc, .ftc):
0x0F0C10: PSG_PLAY_PTR - Pointer to music file data in bus memory (32-bit)
0x0F0C14: PSG_PLAY_LEN - Length of music file data (32-bit)
0x0F0C18: PSG_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0C1C: PSG_PLAY_STATUS - Status (bit0=busy, bit1=error)
Embed the file data in your program, set PTR/LEN, then write to CTRL. Format is auto-detected from file headers. Formats using embedded CPU code (.ay, .sndh, tracker modules) execute via internal Z80 or M68K emulation. Register-dump formats (.ym) and timed-event formats (.vgm/.vgz) are rendered natively. VGM files containing SN76489 data are automatically converted to AY register writes.
0x0F0D00: POKEY_AUDF1 - Channel 1 frequency divider
0x0F0D01: POKEY_AUDC1 - Channel 1 control (distortion + volume)
0x0F0D02: POKEY_AUDF2 - Channel 2 frequency divider
0x0F0D03: POKEY_AUDC2 - Channel 2 control
0x0F0D04: POKEY_AUDF3 - Channel 3 frequency divider
0x0F0D05: POKEY_AUDC3 - Channel 3 control
0x0F0D06: POKEY_AUDF4 - Channel 4 frequency divider
0x0F0D07: POKEY_AUDC4 - Channel 4 control
0x0F0D08: POKEY_AUDCTL - Master audio control
0x0F0D09: POKEY_PLUS_CTRL - POKEY+ mode (0=standard, 1=enhanced)
AUDCTL Bit Masks:
bit 0: Use 15kHz base clock (else 64kHz)
bit 1: High-pass filter ch1 by ch3
bit 2: High-pass filter ch2 by ch4
bit 3: Ch4 clocked by ch3 (16-bit mode)
bit 4: Ch2 clocked by ch1 (16-bit mode)
bit 5: Ch3 uses 1.79MHz clock
bit 6: Ch1 uses 1.79MHz clock
bit 7: Use 9-bit poly instead of 17-bit
AUDC Distortion Modes (bits 5-7):
0x00: 17-bit + 5-bit poly
0x20: 5-bit poly only
0x40: 17-bit + 4-bit poly (most metallic)
0x60: 5-bit + 4-bit poly
0x80: 17-bit poly only (white noise)
0xA0: Pure square wave
0xC0: 4-bit poly only (buzzy)
0xE0: 17-bit + pulse
SAP Player Registers (0x0F0D10 - 0x0F0D1D, supports .sap Atari 8-bit music):
0x0F0D10: SAP_PLAY_PTR - Pointer to .sap file data in bus memory (32-bit)
0x0F0D14: SAP_PLAY_LEN - Length of .sap file data (32-bit)
0x0F0D18: SAP_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0D1C: SAP_PLAY_STATUS - Status (bit0=busy, bit1=error)
0x0F0D1D: SAP_SUBSONG - Subsong selection (0-255)
SAP TYPE B files are supported: the embedded 6502 INIT routine is called once, then the PLAYER routine is called each frame to drive POKEY registers.
Voice 1 (0x0F0E00 - 0x0F0E06):
0x0F0E00: SID_V1_FREQ_LO - Frequency low byte
0x0F0E01: SID_V1_FREQ_HI - Frequency high byte
0x0F0E02: SID_V1_PW_LO - Pulse width low byte
0x0F0E03: SID_V1_PW_HI - Pulse width high (bits 0-3)
0x0F0E04: SID_V1_CTRL - Control register
0x0F0E05: SID_V1_AD - Attack/Decay
0x0F0E06: SID_V1_SR - Sustain/Release
Voice 2 (0x0F0E07 - 0x0F0E0D):
0x0F0E07-0x0F0E0D: Same layout as Voice 1
Voice 3 (0x0F0E0E - 0x0F0E14):
0x0F0E0E-0x0F0E14: Same layout as Voice 1
Filter and Volume:
0x0F0E15: SID_FC_LO - Filter cutoff low (bits 0-2)
0x0F0E16: SID_FC_HI - Filter cutoff high byte
0x0F0E17: SID_RES_FILT - Resonance (bits 4-7) + routing (bits 0-3)
0x0F0E18: SID_MODE_VOL - Volume (bits 0-3) + filter mode (bits 4-7)
0x0F0E19: SID_PLUS_CTRL - SID+ mode (0=standard, 1=enhanced)
0x0F0E1A: SID_OSC3 - Voice 3 oscillator output (read-only)
0x0F0E1B: SID_ENV3 - Voice 3 envelope output (read-only)
0x0F0E1C: (reserved)
Voice Control Register Bits:
bit 0: Gate (trigger envelope)
bit 1: Sync with previous voice
bit 2: Ring modulation
bit 3: Test bit (resets oscillator)
bit 4: Triangle waveform
bit 5: Sawtooth waveform
bit 6: Pulse waveform
bit 7: Noise waveform
Filter Mode Bits (SID_MODE_VOL bits 4-7):
bit 4: Low-pass filter
bit 5: Band-pass filter
bit 6: High-pass filter
bit 7: Disconnect voice 3 from output
SID Player Registers (0x0F0E20 - 0x0F0E2D, supports .sid C64 music):
0x0F0E20: SID_PLAY_PTR - Pointer to .sid file data in bus memory (32-bit)
0x0F0E24: SID_PLAY_LEN - Length of .sid file data (32-bit)
0x0F0E28: SID_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0E2C: SID_PLAY_STATUS - Status (bit0=busy, bit1=error)
0x0F0E2D: SID_SUBSONG - Subsong selection (0-255)
These registers allow CPU code to trigger .SID file playback from RAM, similar to the PSG and SAP player registers. The embedded 6502 code in the SID file is executed by the internal 6502 emulator at the correct frame rate (50Hz PAL or ~60Hz NTSC).
For .SID files with Sid2Addr/Sid3Addr in PSID v3/v4 headers, the engine instantiates additional SID chips with independent register sets. Each secondary SID maps to 3 SoundChip channels (SID2 = channels 4-6, SID3 = channels 7-9). The SID model (6581/8580) is extracted per-chip from the header flags.
SID2 Registers (0x0F0E30 - 0x0F0E4C): Same layout as primary SID (voices 4-6)
SID3 Registers (0x0F0E50 - 0x0F0E6C): Same layout as primary SID (voices 7-9)
The TED (Text Editing Device) chip from the Commodore Plus/4 provides simple 2-voice square wave synthesis:
TED Sound Registers (0x0F0F00 - 0x0F0F05):
0x0F0F00: TED_FREQ1_LO - Voice 1 frequency low byte
0x0F0F01: TED_FREQ2_LO - Voice 2 frequency low byte
0x0F0F02: TED_FREQ2_HI - Voice 2 frequency high (bits 0-1)
0x0F0F03: TED_SND_CTRL - Control (DA/noise/ch2on/ch1on/volume)
0x0F0F04: TED_FREQ1_HI - Voice 1 frequency high (bits 0-1)
0x0F0F05: TED_PLUS_CTRL - TED+ enhanced audio mode (0=standard, 1=enhanced)
TED_SND_CTRL bits:
Bit 7: D/A mode
Bit 6: Voice 2 noise enable (replaces square wave with white noise)
Bit 5: Voice 2 enable
Bit 4: Voice 1 enable
Bits 0-3: Volume (0-8, where 8 is maximum)
TED Player Registers (0x0F0F10 - 0x0F0F1C, supports .ted/.prg Plus/4 music):
0x0F0F10: TED_PLAY_PTR - Pointer to .ted/.prg file data in bus memory (32-bit)
0x0F0F14: TED_PLAY_LEN - Length of file data (32-bit)
0x0F0F18: TED_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0F1C: TED_PLAY_STATUS - Status (bit0=busy, bit1=error)
Frequency formula: freq_hz = clock/8 / (1024 - register_value)
Clock rates: 886724 Hz (PAL), 894886 Hz (NTSC)
These registers allow CPU code to trigger .TED file playback from RAM. The embedded 6502 code in the TED file is executed by the internal 6502 emulator at 50Hz (PAL).
The TED chip also provides video capabilities from the Commodore Plus/4:
- 40x25 text mode (8x8 character cells)
- 320x200 pixel resolution (384x272 with border)
- 121 colors (16 hues × 8 luminances)
- Hardware cursor support
- Compositor layer 12 (between VGA=10 and ULA=15)
Current limitations: Only text mode is implemented. Control register bits for bitmap mode (BMM), extended color mode (ECM), multicolor mode (MCM), fine scrolling (XSCROLL/YSCROLL), row/column select (RSEL/CSEL), and character/video base address registers are accepted but have no effect on rendering.
TED Video Registers (0x0F0F20 - 0x0F0F5F, 4-byte aligned for copper):
0x0F0F20: TED_V_CTRL1 - Control 1 (ECM/BMM/DEN/RSEL/YSCROLL)
0x0F0F24: TED_V_CTRL2 - Control 2 (RES/MCM/CSEL/XSCROLL)
0x0F0F28: TED_V_CHAR_BASE - Character/bitmap base address
0x0F0F2C: TED_V_VIDEO_BASE - Video matrix base address
0x0F0F30: TED_V_BG_COLOR0 - Background color 0
0x0F0F34: TED_V_BG_COLOR1 - Background color 1 (multicolor)
0x0F0F38: TED_V_BG_COLOR2 - Background color 2 (multicolor)
0x0F0F3C: TED_V_BG_COLOR3 - Background color 3 (multicolor)
0x0F0F40: TED_V_BORDER - Border color
0x0F0F44: TED_V_CURSOR_HI - Cursor position high byte
0x0F0F48: TED_V_CURSOR_LO - Cursor position low byte
0x0F0F4C: TED_V_CURSOR_CLR - Cursor color
0x0F0F50: TED_V_RASTER_LO - Raster line low (read-only)
0x0F0F54: TED_V_RASTER_HI - Raster line high (read-only)
0x0F0F58: TED_V_ENABLE - Video enable (bit 0)
0x0F0F5C: TED_V_STATUS - Status (bit 0 = VBlank)
TED_V_CTRL1 bits:
Bit 6: ECM - Extended Color Mode
Bit 5: BMM - Bitmap Mode
Bit 4: DEN - Display Enable
Bit 3: RSEL - Row Select (0=24 rows, 1=25 rows)
Bits 0-2: YSCROLL - Vertical scroll (0-7)
TED_V_CTRL2 bits:
Bit 5: RES - Reset
Bit 4: MCM - Multicolor Mode
Bit 3: CSEL - Column Select (0=38 cols, 1=40 cols)
Bits 0-2: XSCROLL - Horizontal scroll (0-7)
Color byte format: Bits 4-6 = luminance (0-7), Bits 0-3 = hue (0-15)
TED Video CPU Mappings:
- IE32/IE64/M68K: Direct access at 0x0F0F20-0x0F0F5F
- 6502: Memory-mapped at $D620-$D62F
- Z80: Port I/O via 0xF2/0xF3, indices 0x20-0x2F
The AHX engine provides Amiga AHX/THX module playback with 4-channel waveform synthesis:
AHX Control Registers (0x0F0B80):
0x0F0B80: AHX_PLUS_CTRL - AHX+ enhanced audio mode (0=standard, 1=enhanced)
AHX Player Registers (0x0F0B84 - 0x0F0B91):
0x0F0B84: AHX_PLAY_PTR - Pointer to .AHX data (32-bit)
0x0F0B88: AHX_PLAY_LEN - Length of .AHX data (32-bit)
0x0F0B8C: AHX_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0B90: AHX_PLAY_STATUS - Status (bit0=busy, bit1=error)
0x0F0B91: AHX_SUBSONG - Subsong selection (0-255)
AHX+ mode provides enhanced audio processing:
- 4x oversampling for cleaner waveforms
- Second-order Butterworth lowpass (biquad, -12dB/oct anti-alias)
- Subtle saturation (drive 0.16) for analog warmth
- Allpass diffuser room ambience (mix 0.09, delay 120 samples)
- Authentic Amiga stereo panning (L-R-R-L pattern)
- Hardware PWM mapping SquarePos to duty cycle
The MOD player provides ProTracker .mod file playback using DAC mode on the SoundChip FLEX channels, with optional Amiga-style low-pass filter emulation:
MOD Player Registers (0x0F0BC0 - 0x0F0BD7):
0x0F0BC0: MOD_PLAY_PTR - Pointer to MOD data in bus memory (32-bit)
0x0F0BC4: MOD_PLAY_LEN - Length of MOD data (32-bit)
0x0F0BC8: MOD_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0BCC: MOD_PLAY_STATUS - Status (bit0=busy, bit1=error)
0x0F0BD0: MOD_FILTER_MODEL - Filter model (0=none, 1=A500 4.5kHz, 2=A1200 28kHz)
0x0F0BD4: MOD_POSITION - Current song position (read-only)
Filter models emulate the original Amiga hardware low-pass characteristics:
- 0 (None): No filtering, raw output
- 1 (A500): Amiga 500 RC filter at ~4.5kHz cutoff with LED filter (~3.3kHz 2-pole Butterworth)
- 2 (A1200): Amiga 1200 RC filter at ~28kHz cutoff with LED filter (~3.3kHz 2-pole Butterworth)
The WAV player provides PCM .wav file playback via the SoundChip FLEX DAC mode. Supports 8-bit unsigned and 16-bit signed PCM; stereo files are automatically downmixed to mono. Sample rate conversion uses linear interpolation.
WAV Player Registers (0x0F0BD8 - 0x0F0BEB):
0x0F0BD8: WAV_PLAY_PTR - Pointer to WAV data in bus memory (32-bit)
0x0F0BDC: WAV_PLAY_LEN - Length of WAV data (32-bit)
0x0F0BE0: WAV_PLAY_CTRL - Control (bit0=start, bit1=stop, bit2=loop)
0x0F0BE4: WAV_PLAY_STATUS - Status (bit0=busy, bit1=error)
0x0F0BE8: WAV_POSITION - Current playback position (read-only)
Playback Control:
- Write
1to WAV_PLAY_CTRL to start,2to stop,5to start with loop - Read WAV_PLAY_STATUS for busy/error flags
- The entire WAV file must fit in bus memory (not streaming)
All sound and video chips are accessible from all CPU architectures at different address ranges:
| Chip | IE32/IE64/M68K | Z80 | x86 | 6502 | Notes |
|---|---|---|---|---|---|
| Custom Synth | 0x0F0820-0x0F0B7F | Memory $F820-$FB7F | Memory | $F820-$FB7F | Filter, legacy channels, flex channels |
| AHX | 0x0F0B80-0x0F0B91 | Memory $FB80-$FB91 | Memory | $FB80-$FB91 | .ahx module player |
| MOD | 0x0F0BC0-0x0F0BD7 | Memory $FBC0-$FBD7 | Memory | $FBC0-$FBD7 | .mod ProTracker player |
| WAV | 0x0F0BD8-0x0F0BEB | Memory $FBD8-$FBEB | Memory | $FBD8-$FBEB | .wav PCM player |
| PSG | 0x0F0C00-0x0F0C1C | Ports 0xF0/0xF1 + Memory $FC10-$FC1C | Ports 0xF0/0xF1 | $D400-$D41C | Synth + .ym/.ay/.vgm/.vgz/.vtx/.sndh/.pt3/.pt2/.pt1/.stc/.sqt/.asc/.ftc player |
| POKEY | 0x0F0D00-0x0F0D1D | Ports 0xD0/0xD1 + Memory $FD10-$FD1D | Ports 0xD0-0xD3* | $D200-$D21D | Synth + .sap player |
| SID | 0x0F0E00-0x0F0E6C | Ports 0xE0/0xE1 + Memory $FE20-$FE6C | Ports 0xE0/0xE1 | $D500-$D56C | Synth + .sid player + SID2/SID3 multi-SID |
| TED | 0x0F0F00-0x0F0F1C | Ports 0xF2/0xF3 + Memory $FF10-$FF1C | Ports 0xF2/0xF3 | $D600-$D61C | Synth + .ted/.prg player |
* x86 POKEY uses ports 0xD0-0xD3 and 0xD8-0xDF (0xD4-0xD7 reserved for ANTIC/GTIA)
Z80 and x86 access the custom synth and player control registers via memory-mapped I/O: addresses $F000-$FFFF translate to bus $F0000-$F0FFF. Classic sound chip synthesis registers (PSG, POKEY, SID, TED) use dedicated port I/O for register select/data access; their playback control registers use memory-mapped I/O.
| Chip | IE32/IE64/M68K | Z80 | x86 | 6502 | Notes |
|---|---|---|---|---|---|
| VideoChip | 0x0F0000-0x0F0077 | Memory $F000-$F077 | Memory | $F000-$F077 | Custom copper/blitter |
| TED Video | 0x0F0F20-0x0F0F5F | Ports 0xF2/0xF3 | Ports 0xF2/0xF3 | $D620-$D62F | Plus/4 (idx 0x20-0x2F) |
| VGA | 0x0F1000-0x0F13FF | Ports 0xA0-0xAC | Ports 0x3C4-0x3DA | $D700-$D70A | IBM VGA compatible |
| Voodoo | 0x0F4000-0x0F43FF | Ports 0xB0-0xB7 | Memory | Memory | 3DFX SST-1 3D accelerator |
| ANTIC | 0x0F2100-0x0F213F | Ports 0xD4/0xD5 | Ports 0xD4/0xD5 | $D400-$D40F | Atari 8-bit video |
| GTIA | 0x0F2140-0x0F21B7 | Ports 0xD6/0xD7 | Ports 0xD6/0xD7 | $D000-$D01F | Atari 8-bit color + P/M |
| ULA | 0x0F2000-0x0F200B | Port 0xFE | Port 0xFE | $D800-$D80B | ZX Spectrum compatible |
Note: 6502 has PSG at $D400 which overlaps with ANTIC's authentic Atari address. Use M68K/Z80/x86 for ANTIC access when PSG is in use.
Z80 Port I/O: The first port selects the register index, the second reads/writes data.
Example: OUT (0xF0),A selects PSG register, OUT (0xF1),A writes data.
6502 Memory-Mapped: Direct memory access following C64/Atari/Plus4 conventions.
Example: STA $D400 writes to PSG register 0.
IE32/IE64/M68K Direct: Full 32-bit address space access.
Example: MOVE.B D0,($F0C00).L writes to PSG register 0.
The VGA chip provides IBM PC-compatible graphics modes, allowing classic PC demo effects and games:
| Mode | Resolution | Colors | Type | Description |
|---|---|---|---|---|
| 0x03 | 80×25 | 16 | Text | Standard VGA text mode with 8×16 font |
| 0x12 | 640×480 | 16 | Planar | High-res 4-plane graphics |
| 0x13 | 320×200 | 256 | Linear | Classic "Mode 13h" for demos/games |
| 0x14 | 320×240 | 256 | Mode-X | Unchained planar for page flipping |
VGA Control Registers (0x0F1000 - 0x0F100F):
0x0F1000: VGA_MODE - Mode select (0x03/0x12/0x13/0x14)
0x0F1004: VGA_STATUS - Status (bit 0=vsync, bit 3=retrace)
0x0F1008: VGA_CTRL - Control (bit 0=enable)
Sequencer Registers (0x0F1010 - 0x0F101F):
0x0F1010: VGA_SEQ_INDEX - Sequencer register index
0x0F1014: VGA_SEQ_DATA - Sequencer register data
0x0F1018: VGA_SEQ_MAPMASK - Plane write mask (direct access)
CRTC Registers (0x0F1020 - 0x0F102F):
0x0F1020: VGA_CRTC_INDEX - CRTC register index
0x0F1024: VGA_CRTC_DATA - CRTC register data
0x0F1028: VGA_CRTC_STARTHI - Display start address high
0x0F102C: VGA_CRTC_STARTLO - Display start address low
Graphics Controller (0x0F1030 - 0x0F103F):
0x0F1030: VGA_GC_INDEX - Graphics controller index
0x0F1034: VGA_GC_DATA - Graphics controller data
0x0F1038: VGA_GC_READMAP - Read plane select
0x0F103C: VGA_GC_BITMASK - Bit mask for write operations
Attribute Controller (0x0F1040 - 0x0F104F):
0x0F1040: VGA_ATTR_INDEX - Attribute index/data
0x0F1044: VGA_ATTR_DATA - Attribute read
DAC/Palette (0x0F1050 - 0x0F105F):
0x0F1050: VGA_DAC_MASK - Pixel mask (default 0xFF)
0x0F1054: VGA_DAC_RINDEX - Read index
0x0F1058: VGA_DAC_WINDEX - Write index
0x0F105C: VGA_DAC_DATA - DAC data (R,G,B sequence, 6-bit values)
Palette RAM (0x0F1100 - 0x0F13FF):
0x0F1100: VGA_PALETTE - 256 palette entries × 3 bytes (768 bytes)
0x0A0000 - 0x0AFFFF: VGA VRAM (64KB graphics memory)
0x0B8000 - 0x0BFFFF: VGA Text Buffer (32KB, char+attr pairs)
| CPU | VGA Registers | VGA VRAM | DAC Access |
|---|---|---|---|
| IE32/IE64/M68K | 0x0F1000-0x0F13FF | 0x0A0000-0x0AFFFF | Memory-mapped |
| Z80 | Ports 0xA0-0xAC | Memory 0xA000 (banked) | Port I/O |
| 6502 | $D700-$D70D | $A000 (banked) | Memory-mapped |
include "ie68.inc"
; Set Mode 13h (320x200x256)
vga_setmode VGA_MODE_13H
; Set palette entry 1 to bright red
move.b #1,VGA_DAC_WINDEX ; Select color 1
move.b #63,VGA_DAC_DATA ; R = 63 (max)
move.b #0,VGA_DAC_DATA ; G = 0
move.b #0,VGA_DAC_DATA ; B = 0
; Draw pixel at (100, 50) = offset 100 + 50*320 = 16100
move.b #1,$A0000+16100 ; Write color 1 include "ie68.inc"
; Set Mode 12h (640x480x16 planar)
vga_setmode VGA_MODE_12H
; Enable plane 0 only for writing
move.b #1,VGA_SEQ_MAPMASK
; Write to VRAM (affects only plane 0)
move.b #$FF,$A0000 ; Set 8 pixels in plane 0The VGA chip integrates with the video compositor as a separate layer (layer 10) that renders on top of the VideoChip (layer 0). Both sources are blended together for the final display output.
Per-Scanline Rendering: The VGA supports scanline-aware rendering, meaning the copper coprocessor can modify VGA palette registers on a per-scanline basis. When the copper executes a SETBASE to target VGA DAC registers followed by palette MOVE operations, those changes affect VGA rendering from that scanline onward. This enables classic PC demo effects like raster bars and gradient backgrounds.
VSync Timing: The VGA provides time-based vsync status through VGA_STATUS. The status bit automatically calculates whether the display is in vertical retrace based on a 60Hz refresh cycle, requiring no explicit signaling from the compositor.
See section 12.9 (Video Compositor) for details on the compositing architecture and section 12.6 (Copper List Executor) for examples of copper-driven VGA palette manipulation.
The ULA chip provides authentic ZX Spectrum video output, enabling classic Spectrum demos and games:
| Feature | Value |
|---|---|
| Resolution | 256×192 pixels |
| Border | 32 pixels each side (320×256 total) |
| Color Cells | 32×24 (8×8 pixels per cell) |
| Colors | 15 unique (8 base + 8 bright, black can't brighten) |
| VRAM | 6912 bytes (6144 bitmap + 768 attributes) |
| Flash Rate | ~1.6Hz (toggle every 32 frames at 50Hz) |
ULA Control Registers (0x0F2000 - 0x0F200B):
0x0F2000: ULA_BORDER - Border color (bits 0-2, values 0-7)
0x0F2004: ULA_CTRL - Control (bit 0=enable)
0x0F2008: ULA_STATUS - Status (bit 0=vblank)
The ULA uses the authentic ZX Spectrum memory layout at 0x4000:
0x4000 - 0x57FF: Bitmap (6144 bytes, non-linear Y addressing)
0x5800 - 0x5AFF: Attributes (768 bytes, 32×24 cells)
The ZX Spectrum uses a peculiar addressing formula for the bitmap. The screen is divided into three 64-line sections, with each section having interleaved line ordering:
Address = ((y & 0xC0) << 5) + ((y & 0x07) << 8) + ((y & 0x38) << 2) + (x >> 3)
| Y Range | Address Range | Description |
|---|---|---|
| 0-63 | 0x4000-0x47FF | Top third of screen |
| 64-127 | 0x4800-0x4FFF | Middle third |
| 128-191 | 0x5000-0x57FF | Bottom third |
Each attribute byte controls an 8×8 pixel cell:
Bit 7: FLASH - Swap INK/PAPER at ~1.6Hz
Bit 6: BRIGHT - Intensify both INK and PAPER
Bits 5-3: PAPER (background color, 0-7)
Bits 2-0: INK (foreground color, 0-7)
| Index | Normal RGB | Bright RGB | Color |
|---|---|---|---|
| 0 | (0,0,0) | (0,0,0) | Black |
| 1 | (0,0,205) | (0,0,255) | Blue |
| 2 | (205,0,0) | (255,0,0) | Red |
| 3 | (205,0,205) | (255,0,255) | Magenta |
| 4 | (0,205,0) | (0,255,0) | Green |
| 5 | (0,205,205) | (0,255,255) | Cyan |
| 6 | (205,205,0) | (255,255,0) | Yellow |
| 7 | (205,205,205) | (255,255,255) | White |
| CPU | ULA Registers | ULA VRAM | Notes |
|---|---|---|---|
| IE32/IE64/M68K | 0x0F2000-0x0F200B | 0x4000-0x5AFF | Direct 32-bit |
| Z80 | Port 0xFE | 0x4000-0x5AFF | Authentic Spectrum |
| 6502 | $D800-$D80F | $4000 (banked) | Memory-mapped |
include "ie68.inc"
; Draw white pixel at (128, 96) with bright attribute
; First calculate bitmap address for y=96, x=128
; ((96 & $C0) << 5) + ((96 & $07) << 8) + ((96 & $38) << 2) + (128 >> 3)
; = ($40 << 5) + ($00 << 8) + ($00 << 2) + 16
; = $800 + $0 + $0 + $10 = $810
move.b #$80,ULA_VRAM+$810 ; Set bit 7 (leftmost pixel in byte)
; Set attribute for cell at (16, 12) = offset 12*32+16 = 400 = $190
; Attribute: BRIGHT=1, PAPER=0 (black), INK=7 (white) = $47
move.b #$47,ULA_VRAM+ULA_ATTR_OFFSET+$190
; Set border to blue
ula_border 1 include "ie80.inc"
; Clear bitmap to zeros (all paper color)
ld hl,ULA_VRAM
ld de,ULA_VRAM+1
ld bc,ULA_BITMAP_SIZE-1
ld (hl),0
ldir
; Set all attributes to white ink on blue paper
; Attribute: BRIGHT=0, PAPER=1 (blue), INK=7 (white) = $0F
ld hl,ULA_ATTR_BASE
ld de,ULA_ATTR_BASE+1
ld bc,ULA_ATTR_SIZE-1
ld (hl),$0F
ldir
; Set border to blue
ULA_SET_BORDER 1The ULA integrates with the video compositor as layer 15, rendering above both the VideoChip (layer 0) and VGA (layer 10). This allows ZX Spectrum graphics to overlay other video sources.
The ULA provides its own frame timing through SignalVSync(), which handles the FLASH attribute timing (toggling every 32 frames). When disabled via ULA_CTRL, the chip returns nil frames to the compositor.
The ANTIC (Alphanumeric Television Interface Controller) provides authentic Atari 8-bit computer video output, enabling classic Atari demos and games:
| Feature | Value |
|---|---|
| Resolution | 320×192 pixels |
| Border | 32 pixels horizontal, 24 pixels vertical (384×240 total) |
| Colors | 128 (16 hues × 8 luminances) |
| Display Modes | 14 (text and graphics modes via display list) |
| Fine Scrolling | 4-bit horizontal/vertical (0-15 pixels) |
All registers are 4-byte aligned for copper coprocessor compatibility:
ANTIC Registers (0x0F2100 - 0x0F213F):
0x0F2100: ANTIC_DMACTL - DMA control (playfield width, DMA enables)
0x0F2104: ANTIC_CHACTL - Character control (inverse, reflect)
0x0F2108: ANTIC_DLISTL - Display list pointer low byte
0x0F210C: ANTIC_DLISTH - Display list pointer high byte
0x0F2110: ANTIC_HSCROL - Horizontal scroll (0-15)
0x0F2114: ANTIC_VSCROL - Vertical scroll (0-15)
0x0F2118: ANTIC_PMBASE - Player-missile base address (high byte)
0x0F211C: ANTIC_CHBASE - Character set base address (high byte)
0x0F2120: ANTIC_WSYNC - Wait for horizontal sync (write only)
0x0F2124: ANTIC_VCOUNT - Vertical line counter (read only, /2)
0x0F2128: ANTIC_PENH - Light pen horizontal (read only)
0x0F212C: ANTIC_PENV - Light pen vertical (read only)
0x0F2130: ANTIC_NMIEN - NMI enable register
0x0F2134: ANTIC_NMIST - NMI status (read) / NMIRES (write)
0x0F2138: ANTIC_ENABLE - Video enable (IE-specific)
0x0F213C: ANTIC_STATUS - Status (VBlank flag, IE-specific)
For 6502 compatibility, ANTIC uses authentic Atari addresses at 0xD400:
0xD400: DMACTL 0xD401: CHACTL 0xD402: DLISTL 0xD403: DLISTH
0xD404: HSCROL 0xD405: VSCROL 0xD407: PMBASE 0xD409: CHBASE
0xD40A: WSYNC 0xD40B: VCOUNT 0xD40C: PENH 0xD40D: PENV
0xD40E: NMIEN 0xD40F: NMIST
| Bit | Name | Description |
|---|---|---|
| 0-1 | Playfield | 00=off, 01=narrow, 10=normal, 11=wide |
| 2 | Missile DMA | Enable missile graphics DMA |
| 3 | Player DMA | Enable player graphics DMA |
| 4 | PM Resolution | 0=double-line, 1=single-line |
| 5 | DL Enable | Enable display list DMA |
ANTIC uses a 128-color palette with 16 hues and 8 luminance levels:
| Hue | Color | Hue | Color |
|---|---|---|---|
| 0 | Gray | 8 | Blue-2 |
| 1 | Gold | 9 | Light Blue |
| 2 | Orange | A | Turquoise |
| 3 | Red-Orange | B | Green-Blue |
| 4 | Pink | C | Green |
| 5 | Purple | D | Yellow-Green |
| 6 | Purple-Blue | E | Orange-Green |
| 7 | Blue | F | Light Orange |
Color format: HHHHLLLL where HHHH = hue (0-15), LLLL = luminance (0-15, but only 0-F used).
ANTIC is driven by a display list - a program that specifies what to render on each scanline. Similar to the Amiga's copper coprocessor, the display list is a sequence of instructions that control video output.
| Opcode | Constant | Scanlines |
|---|---|---|
| 0x00 | DL_BLANK1 | 1 blank scanline |
| 0x10 | DL_BLANK2 | 2 blank scanlines |
| 0x20 | DL_BLANK3 | 3 blank scanlines |
| 0x30 | DL_BLANK4 | 4 blank scanlines |
| 0x40 | DL_BLANK5 | 5 blank scanlines |
| 0x50 | DL_BLANK6 | 6 blank scanlines |
| 0x60 | DL_BLANK7 | 7 blank scanlines |
| 0x70 | DL_BLANK8 | 8 blank scanlines |
| Opcode | Constant | Description |
|---|---|---|
| 0x01 | DL_JMP | Jump to address (2 bytes follow) |
| 0x41 | DL_JVB | Jump and wait for Vertical Blank |
| Opcode | Constant | Description |
|---|---|---|
| 0x02 | DL_MODE2 | 40 column text, 8 scanlines/row |
| 0x03 | DL_MODE3 | 40 column text, 10 scanlines/row |
| 0x04 | DL_MODE4 | 40 column text, 8 scanlines, multicolor |
| 0x05 | DL_MODE5 | 40 column text, 16 scanlines, multicolor |
| 0x06 | DL_MODE6 | 20 column text, 8 scanlines |
| 0x07 | DL_MODE7 | 20 column text, 16 scanlines |
| 0x08 | DL_MODE8 | 40 pixels, 8 scanlines/row (GRAPHICS 3) |
| 0x09 | DL_MODE9 | 80 pixels, 4 scanlines (GRAPHICS 4) |
| 0x0A | DL_MODE10 | 80 pixels, 2 scanlines (GRAPHICS 5) |
| 0x0B | DL_MODE11 | 160 pixels, 1 scanline (GRAPHICS 6) |
| 0x0C | DL_MODE12 | 160 pixels, 1 scanline (GRAPHICS 6+) |
| 0x0D | DL_MODE13 | 160 pixels, 2 scanlines (GRAPHICS 7) |
| 0x0E | DL_MODE14 | 160 pixels, 1 scanline, 4 colors |
| 0x0F | DL_MODE15 | 320 pixels, 1 scanline (GRAPHICS 8) |
| Value | Constant | Description |
|---|---|---|
| 0x40 | DL_LMS | Load Memory Scan (2 address bytes follow) |
| 0x80 | DL_DLI | Display List Interrupt at end of line |
| 0x10 | DL_HSCROL | Enable horizontal fine scrolling |
| 0x20 | DL_VSCROL | Enable vertical fine scrolling |
display_list:
db DL_BLANK8 ; 8 blank lines (top border)
db DL_BLANK8 ; 8 more blank lines
db DL_BLANK8 ; 8 more blank lines
db DL_MODE2 | DL_LMS ; Mode 2 text with LMS
dw screen_memory ; Screen memory address
times 23 db DL_MODE2 ; 23 more mode 2 lines
db DL_JVB ; Jump and wait for VBlank
dw display_list ; Loop back to startANTIC integrates with the video compositor as layer 13, positioned between TED (layer 12) and ULA (layer 15). All copper commands can target ANTIC registers via SETBASE for per-scanline effects.
include "ie68.inc"
; Enable ANTIC with normal playfield and display list DMA
move.b #ANTIC_DMA_NORMAL|ANTIC_DMA_DL,ANTIC_DMACTL
; Point to display list
lea my_dlist,a0
move.b a0,ANTIC_DLISTL
lsr.w #8,d0
move.b d0,ANTIC_DLISTH
; Enable ANTIC video output
antic_enable
; Wait for VBlank
antic_wait_vblank include "ie65.inc"
; Create color bars using WSYNC timing
ldx #0
loop:
stx GTIA_COLBK ; Set background color via GTIA
sta ANTIC_WSYNC ; Wait for horizontal sync
inx
bne loopThe GTIA (Graphics Television Interface Adapter) companion chip handles color generation and player-missile graphics for Atari 8-bit systems. While ANTIC controls display timing and the display list, GTIA controls all color output.
All registers are 4-byte aligned for copper coprocessor compatibility:
GTIA Registers (0x0F2140 - 0x0F21B7):
0x0F2140: GTIA_COLPF0 - Playfield color 0
0x0F2144: GTIA_COLPF1 - Playfield color 1
0x0F2148: GTIA_COLPF2 - Playfield color 2
0x0F214C: GTIA_COLPF3 - Playfield color 3
0x0F2150: GTIA_COLBK - Background/border color
0x0F2154: GTIA_COLPM0 - Player/missile 0 color
0x0F2158: GTIA_COLPM1 - Player/missile 1 color
0x0F215C: GTIA_COLPM2 - Player/missile 2 color
0x0F2160: GTIA_COLPM3 - Player/missile 3 color
0x0F2164: GTIA_PRIOR - Priority and GTIA modes
0x0F2168: GTIA_GRACTL - Graphics control (bit 1=players, bit 0=missiles)
0x0F216C: GTIA_CONSOL - Console switches (read only)
0x0F2170: GTIA_HPOSP0 - Player 0 horizontal position
0x0F2174: GTIA_HPOSP1 - Player 1 horizontal position
0x0F2178: GTIA_HPOSP2 - Player 2 horizontal position
0x0F217C: GTIA_HPOSP3 - Player 3 horizontal position
0x0F2180: GTIA_HPOSM0 - Missile 0 horizontal position
0x0F2184: GTIA_HPOSM1 - Missile 1 horizontal position
0x0F2188: GTIA_HPOSM2 - Missile 2 horizontal position
0x0F218C: GTIA_HPOSM3 - Missile 3 horizontal position
0x0F2190: GTIA_SIZEP0 - Player 0 size (0=normal, 1=double, 3=quad)
0x0F2194: GTIA_SIZEP1 - Player 1 size
0x0F2198: GTIA_SIZEP2 - Player 2 size
0x0F219C: GTIA_SIZEP3 - Player 3 size
0x0F21A0: GTIA_SIZEM - Missile sizes (2 bits each)
0x0F21A4: GTIA_GRAFP0 - Player 0 graphics (8 pixels)
0x0F21A8: GTIA_GRAFP1 - Player 1 graphics
0x0F21AC: GTIA_GRAFP2 - Player 2 graphics
0x0F21B0: GTIA_GRAFP3 - Player 3 graphics
0x0F21B4: GTIA_GRAFM - Missile graphics (2 bits each)
For 6502 compatibility, GTIA uses authentic Atari addresses at 0xD000:
Player/Missile Position and Size:
0xD000: HPOSP0 0xD001: HPOSP1 0xD002: HPOSP2 0xD003: HPOSP3
0xD004: HPOSM0 0xD005: HPOSM1 0xD006: HPOSM2 0xD007: HPOSM3
0xD008: SIZEP0 0xD009: SIZEP1 0xD00A: SIZEP2 0xD00B: SIZEP3
0xD00C: SIZEM 0xD00D: GRAFP0 0xD00E: GRAFP1 0xD00F: GRAFP2
0xD010: GRAFP3 0xD011: GRAFM
Color and Control:
0xD012: COLPM0 0xD013: COLPM1 0xD014: COLPM2 0xD015: COLPM3
0xD016: COLPF0 0xD017: COLPF1 0xD018: COLPF2 0xD019: COLPF3
0xD01A: COLBK 0xD01B: PRIOR 0xD01D: GRACTL 0xD01F: CONSOL
Colors use the ANTIC 128-color palette format:
| Bits | Field | Description |
|---|---|---|
| 7-4 | Hue | 16 hues (0=gray, 1-15=chromatic) |
| 3-0 | Luminance | 8 levels (only even values 0-14 used) |
The PRIOR register controls display priority and special GTIA modes:
| Bit | Name | Description |
|---|---|---|
| 0-3 | Priority | Player/playfield priority selection |
| 4 | Multicolor | Enable 5th player (missiles as single player) |
| 5 | Fifth | Enable multicolor players |
| 6-7 | GTIA Mode | 00=normal, 01=16 lum, 10=9 color, 11=16 hue |
%include "ie86.inc"
; Create smooth rainbow raster bars
mov ecx, 192 ; 192 visible lines
.raster_loop:
mov byte [ANTIC_WSYNC], 0 ; Wait for HSYNC
mov eax, ecx
shl eax, 4 ; Scale line to hue
and eax, 0xF0 ; Mask hue bits
or eax, 0x08 ; Medium luminance
mov byte [GTIA_COLBK], al
loop .raster_loop include "ie68.inc"
; Set up a typical Atari display palette
move.b #$94,GTIA_COLPF0 ; Light blue
move.b #$0F,GTIA_COLPF1 ; White
move.b #$C6,GTIA_COLPF2 ; Green
move.b #$46,GTIA_COLPF3 ; Red
move.b #$00,GTIA_COLBK ; Black backgroundThe File I/O device provides sandboxed host filesystem access. Programs can read and write files within the engine's working directory.
| Address | Name | R/W | Description |
|---|---|---|---|
$F2200 |
FILE_NAME_PTR |
W | Pointer to null-terminated filename in bus memory |
$F2204 |
FILE_DATA_PTR |
W | Pointer to data buffer in bus memory |
$F2208 |
FILE_DATA_LEN |
W | Data length in bytes (for WRITE operations) |
$F220C |
FILE_CTRL |
W | Write 1=READ, 2=WRITE (triggers the operation) |
$F2210 |
FILE_STATUS |
R | 0=OK, 1=ERROR |
$F2214 |
FILE_RESULT_LEN |
R | Number of bytes actually read |
$F2218 |
FILE_ERROR_CODE |
R | 0=OK, 1=NOT_FOUND, 2=PERMISSION, 3=PATH_TRAVERSAL |
All registers support byte-level writes for 8-bit CPUs. Each 32-bit register can be written one byte at a time (little-endian); the device assembles bytes internally. Only writing byte 0 of FILE_CTRL triggers the operation.
8-bit CPUs access File I/O via bank3 switching - set bank3 = $0079 to map the registers into the bank3 window at $6200. The ie80.inc and ie65.inc include files provide FIO_* byte-address constants and convenience macros (SET_FILE_IO_BANK, SET_FIO_PTR, FILE_READ, FILE_WRITE).
The coprocessor subsystem allows any CPU to launch worker CPUs (IE32, IE64, 6502, M68K, Z80, x86) that run service binaries. Workers poll a shared mailbox ring buffer for requests, process them, and write results. The caller manages worker lifecycle and request routing via MMIO registers. Available in all CPU modes.
| Address | Name | R/W | Description |
|---|---|---|---|
| 0xF2340 | COPROC_CMD | W | Command register (triggers action on byte-0 write) |
| 0xF2344 | COPROC_CPU_TYPE | W | Target CPU type (1=IE32, 2=IE64, 3=6502, 4=M68K, 5=Z80, 6=x86) |
| 0xF2348 | COPROC_CMD_STATUS | R | 0=ok, 1=error |
| 0xF234C | COPROC_CMD_ERROR | R | Error code (see below) |
| 0xF2350 | COPROC_TICKET | R/W | Ticket ID |
| 0xF2354 | COPROC_TICKET_STATUS | R | Per-ticket status |
| 0xF2358 | COPROC_OP | W | Operation code |
| 0xF235C | COPROC_REQ_PTR | W | Request data pointer |
| 0xF2360 | COPROC_REQ_LEN | W | Request data length |
| 0xF2364 | COPROC_RESP_PTR | W | Response buffer pointer |
| 0xF2368 | COPROC_RESP_CAP | W | Response buffer capacity |
| 0xF236C | COPROC_TIMEOUT | W | Timeout in ms |
| 0xF2370 | COPROC_NAME_PTR | W | Service filename pointer |
| 0xF2374 | COPROC_WORKER_STATE | R | Bitmask of running workers |
| 0xF2378 | COPROC_STATS_OPS | R | Total operations dispatched (all workers, counted at enqueue) |
| 0xF237C | COPROC_STATS_BYTES | R | Total bytes processed (all workers, counted at enqueue) |
| 0xF2380 | COPROC_IRQ_CTRL | R/W | Completion interrupt control (bit 0=enable, Level 6/INTB_COPER) |
| 0xF2384 | COPROC_DISPATCH_OVERHEAD | R | Calibrated dispatch overhead in nanoseconds |
| 0xF2388 | COPROC_COMPLETED_TICKET | R | Ticket ID of last completed job (for IRQ handler) |
| Address | Name | R/W | Description |
|---|---|---|---|
| 0xF23B0 | COPROC_RING_DEPTH | R | IE64 ring buffer occupancy (0-16) |
| 0xF23B4 | COPROC_WORKER_UPTIME | R | Seconds since IE64 worker started (0 if stopped) |
| 0xF23B8 | COPROC_STATS_RESET | W | Write 1 to zero all Go-side stats + busy buckets |
| 0xF23BC | COPROC_BUSY_PCT | R | Worker busy % over last 1 second (0-100) |
These registers are in a separate MMIO range after the clipboard bridge (0xF2390-0xF23AF). They are used by the IEWarpMon monitoring application. See sdk/docs/iewarpmon.md.
All registers support byte-level reads and writes. This allows 8-bit CPUs to program 32-bit registers using four single-byte writes. Registers are aligned to 4-byte boundaries: sub-register byte offsets are computed as addr & 3. Writes to bytes 1-3 of a register perform read-modify-write on the shadow register. Command dispatch only fires when byte 0 of COPROC_CMD is written - writes to bytes 1-3 of COPROC_CMD do not trigger dispatch. This means the CMD register must be written last in any sequence.
Z80 and 6502 CPUs have 16-bit address spaces and cannot directly reach 0xF2340. A gateway window at 0xF200-0xF24F transparently redirects reads and writes to the coprocessor MMIO:
| Gateway Address | Maps To | Register |
|---|---|---|
| 0xF200 | 0xF2340 | COPROC_CMD |
| 0xF204 | 0xF2344 | COPROC_CPU_TYPE |
| 0xF208 | 0xF2348 | COPROC_CMD_STATUS |
| 0xF20C | 0xF234C | COPROC_CMD_ERROR |
| 0xF210 | 0xF2350 | COPROC_TICKET |
| 0xF214 | 0xF2354 | COPROC_TICKET_STATUS |
| 0xF218 | 0xF2358 | COPROC_OP |
| 0xF21C | 0xF235C | COPROC_REQ_PTR |
| 0xF220 | 0xF2360 | COPROC_REQ_LEN |
| 0xF224 | 0xF2364 | COPROC_RESP_PTR |
| 0xF228 | 0xF2368 | COPROC_RESP_CAP |
| 0xF22C | 0xF236C | COPROC_TIMEOUT |
| 0xF230 | 0xF2370 | COPROC_NAME_PTR |
| 0xF234 | 0xF2374 | COPROC_WORKER_STATE |
| 0xF238 | 0xF2378 | COPROC_STATS_OPS |
| 0xF23C | 0xF237C | COPROC_STATS_BYTES |
| 0xF240 | 0xF2380 | COPROC_IRQ_CTRL |
| 0xF244 | 0xF2384 | COPROC_DISPATCH_OVERHEAD |
| 0xF248 | 0xF2388 | COPROC_COMPLETED_TICKET |
IE32, IE64, M68K, and x86 CPUs access 0xF2340 directly (no gateway needed).
| Value | Name | Description |
|---|---|---|
| 1 | START | Load and start a worker from service binary file |
| 2 | STOP | Stop a running worker |
| 3 | ENQUEUE | Submit async request, returns ticket in COPROC_TICKET |
| 4 | POLL | Check ticket status (non-blocking) |
| 5 | WAIT | Block until ticket completes or timeout |
| Value | Meaning |
|---|---|
| 0 | Pending |
| 1 | Running |
| 2 | OK (completed successfully) |
| 3 | Error |
| 4 | Timeout |
| 5 | Worker down |
6KB ring buffer region shared between the Go manager and all worker CPUs. Contains 6 rings (one per supported CPU type), each 768 bytes with 16 request/response descriptor slots. Workers are loaded into dedicated, non-overlapping memory regions (0x200000-0x41FFFF). User data buffers for request/response payloads should be placed at 0x400000-0x7FFFFF.
| CPU | Register Addresses | Write Method | Include File |
|---|---|---|---|
| IE32 | 0xF2340 direct |
32-bit STORE |
ie32.inc |
| IE64 | 0xF2340 direct |
32-bit store.l |
ie64.inc |
| M68K | 0xF2340 direct |
32-bit move.l |
ie68.inc |
| x86 | 0xF2340 direct |
32-bit mov dword |
ie86.inc |
| Z80 | 0xF200 gateway |
4x byte ld (addr),a |
ie80.inc |
| 6502 | $F200 gateway |
4x byte sta addr |
ie65.inc |
All include files except ie32.inc (constants only, no macro support) provide coprocessor helper macros with identical semantics:
| Macro | Arguments | Description |
|---|---|---|
coproc_start |
cpuType, namePtr | Start a worker from service binary |
coproc_stop |
cpuType | Stop a running worker |
coproc_enqueue |
cpuType, op, reqPtr, reqLen, respPtr, respCap | Enqueue async request (ticket in COPROC_TICKET) |
coproc_poll |
ticket | Poll ticket status (non-blocking) |
coproc_wait |
ticket, timeoutMs | Block until ticket completes or timeout |
For 16-bit CPUs (Z80/6502), macros use STORE32 internally to compose 32-bit values from 4 byte writes through the gateway.
Example (x86 - native 32-bit):
%include "ie86.inc"
coproc_start COPROC_CPU_IE32, 0x400000 ; start IE32 worker
coproc_enqueue COPROC_CPU_IE32, 1, 0x410000, 8, 0x410100, 4
mov ebx, [COPROC_TICKET] ; save ticket
coproc_poll ebx ; poll status
mov eax, [COPROC_TICKET_STATUS] ; read resultExample (Z80 - gateway + byte writes):
.include "ie80.inc"
coproc_start COPROC_CPU_M68K 0x400000 ; start M68K worker
coproc_enqueue COPROC_CPU_M68K 1 0x410000 8 0x410100 4
ld a,(COPROC_TICKET) ; read ticket byte 0
From BASIC, use the high-level commands instead of direct MMIO access:
COSTART 3, "svc_6502.ie65" ' Start 6502 worker
T = COCALL(3, 1, &H1000, 8, &H2000, 16) ' Async RPC (op=1, add)
COWAIT T, 5000 ' Wait up to 5 seconds
IF COSTATUS(T) = 2 THEN PRINT "OK" ' Check result
COSTOP 3 ' Stop workerFull reference: ehbasic_ie64.md
Complete caller examples are provided for all CPU architectures:
| File | Caller CPU | Worker CPU | Description |
|---|---|---|---|
sdk/examples/asm/coproc_caller_ie32.asm |
IE32 | IE32 | Native 32-bit register access |
sdk/examples/asm/coproc_caller_68k.asm |
M68K | IE32 | Uses coproc_start/coproc_enqueue macros |
sdk/examples/asm/coproc_caller_x86.asm |
x86 | IE32 | Uses NASM %macro helpers |
sdk/examples/asm/coproc_caller_z80.asm |
Z80 | M68K | Gateway access via STORE32 macros |
sdk/examples/asm/coproc_caller_65.asm |
6502 | IE32 | Gateway access via STORE32 macros |
The clipboard bridge provides host OS clipboard access for AROS applications.
| Address | Name | R/W | Description |
|---|---|---|---|
$F2390 |
CLIP_DATA_PTR |
W | Guest RAM pointer for clipboard data |
$F2394 |
CLIP_DATA_LEN |
W | Data length in bytes |
$F2398 |
CLIP_CTRL |
W | Command: 1=read from host, 2=write to host |
$F239C |
CLIP_STATUS |
R | 0=ready, 1=busy, 2=empty, 3=error |
$F23A0 |
CLIP_RESULT_LEN |
R | Bytes actually read/written |
$F23A4 |
CLIP_FORMAT |
W | Format: 0=text, 1=IFF (currently text-only; IFF format accepted but not honored) |
The Voodoo chip emulates a 3DFX SST-1 graphics accelerator using High-Level Emulation (HLE). Instead of software rasterization, register writes are translated to GPU draw calls for hardware-accelerated 3D rendering with Vulkan (or software fallback).
Important: The Voodoo is disabled by default to allow per-scanline rendering for copper effects. Programs must explicitly enable it by writing 1 to VOODOO_ENABLE (0x0F4004) before using the 3D accelerator.
- Voodoo SST-1 register-compatible interface
- Gouraud shaded triangles with per-vertex color interpolation
- Z-buffering with all 8 depth compare functions (never, less, equal, lessequal, greater, notequal, greaterequal, always)
- Alpha testing with 8 comparison functions and configurable reference value
- Chroma key transparency (discard fragments matching key color)
- Configurable alpha blending with 9 blend factors per source/dest
- Texture mapping with per-vertex UV coordinates and color modulation
- Color combine modes (iterated, texture, modulate, add, decal) via fbzColorPath (register stored; backend integration pending)
- Depth-based fog with configurable fog color (register stored; backend integration pending)
- Ordered dithering with 4x4 or 2x2 Bayer matrices for reduced banding
- Point sampling with wrap/clamp addressing modes
- Texture formats: ARGB8888 (default), with register definitions for paletted, ARGB1555, ARGB4444, and others (upload currently assumes RGBA)
- Dynamic pipeline state with automatic pipeline caching for performance
- Scissor clipping
- 640x480 default, up to 800x600
- Compositor layer 20 (renders on top of all 2D chips)
Status/Control (0x0F4000 - 0x0F4007):
0x0F4000: VOODOO_STATUS - Status (busy, vsync, fifo state) [read-only]
0x0F4004: VOODOO_ENABLE - Write 1 to enable, 0 to disable (disabled by default)
Vertex Coordinates (0x0F4008 - 0x0F401F, 12.4 fixed-point):
0x0F4008: VOODOO_VERTEX_AX - Vertex A X coordinate
0x0F400C: VOODOO_VERTEX_AY - Vertex A Y coordinate
0x0F4010: VOODOO_VERTEX_BX - Vertex B X coordinate
0x0F4014: VOODOO_VERTEX_BY - Vertex B Y coordinate
0x0F4018: VOODOO_VERTEX_CX - Vertex C X coordinate
0x0F401C: VOODOO_VERTEX_CY - Vertex C Y coordinate
Vertex Attributes (0x0F4020 - 0x0F403F, 12.12 fixed-point):
0x0F4020: VOODOO_START_R - Start red (1.0 = 0x1000)
0x0F4024: VOODOO_START_G - Start green
0x0F4028: VOODOO_START_B - Start blue
0x0F402C: VOODOO_START_Z - Start Z depth (20.12 fixed-point)
0x0F4030: VOODOO_START_A - Start alpha
0x0F4034: VOODOO_START_S - Start S texture coord (14.18)
0x0F4038: VOODOO_START_T - Start T texture coord (14.18)
0x0F403C: VOODOO_START_W - Start W (perspective, 2.30)
Command Registers:
0x0F4080: VOODOO_TRIANGLE_CMD - Submit triangle for rendering
0x0F4088: VOODOO_COLOR_SELECT - Select vertex (0/1/2) for Gouraud shading
0x0F4104: VOODOO_FBZCOLOR_PATH - Color combine mode configuration
0x0F4108: VOODOO_FOG_MODE - Fog mode configuration
0x0F410C: VOODOO_ALPHA_MODE - Alpha test/blend configuration
0x0F4110: VOODOO_FBZ_MODE - Depth test/write/dither configuration
0x0F4118: VOODOO_CLIP_LEFT_RIGHT - Scissor rectangle X bounds
0x0F411C: VOODOO_CLIP_LOW_Y_HIGH - Scissor rectangle Y bounds
0x0F4120: VOODOO_NOP_CMD - No operation (synchronization)
0x0F4124: VOODOO_FAST_FILL_CMD - Clear framebuffer with COLOR0
0x0F4128: VOODOO_SWAP_BUFFER_CMD - Swap front/back buffers
Configuration:
0x0F41C4: VOODOO_FOG_COLOR - Fog color (0x00RRGGBB)
0x0F41C8: VOODOO_ZA_COLOR - Z/A constant color
0x0F41CC: VOODOO_CHROMA_KEY - Chroma key color (0x00RRGGBB)
0x0F41D8: VOODOO_COLOR0 - Fill color for FAST_FILL_CMD (ARGB)
0x0F41DC: VOODOO_COLOR1 - Constant color 1
0x0F4214: VOODOO_VIDEO_DIM - Video dimensions (width<<16 | height)
Texture Mapping (0x0F4300 - 0x0F433F):
0x0F4300: VOODOO_TEXTURE_MODE - Texture mode configuration
0x0F430C: VOODOO_TEX_BASE0 - Texture base address (LOD 0)
0x0F4330: VOODOO_TEX_WIDTH - Texture width for upload (IE extension)
0x0F4334: VOODOO_TEX_HEIGHT - Texture height for upload (IE extension)
0x0F4338: VOODOO_TEX_UPLOAD - Write to trigger texture upload (IE extension, RGBA data)
Texture Memory (0x0F5000 - 0x0F5FFF):
0x0F5000: VOODOO_TEXMEM_BASE - Texture memory base (64KB)
Write RGBA pixel data here, then trigger upload
x86 32-bit flat mode: Direct memory access to 0xF4000-0xF43FF works:
; x86 32-bit - direct access to Voodoo registers
mov dword [0xF4214], (640 << 16) | 480 ; VOODOO_VIDEO_DIM
mov dword [0xF4110], 0x0310 ; VOODOO_FBZ_MODE
mov dword [0xF4080], 0 ; VOODOO_TRIANGLE_CMDZ80 / x86 real mode: Cannot directly address 0xF4xxx. Use I/O ports 0xB0-0xB7:
| Port | Name | Description |
|---|---|---|
| 0xB0 | ADDR_LO | Register offset low byte (from VOODOO_BASE) |
| 0xB1 | ADDR_HI | Register offset high byte |
| 0xB2 | DATA0 | Data byte 0 (bits 0-7) |
| 0xB3 | DATA1 | Data byte 1 (bits 8-15) |
| 0xB4 | DATA2 | Data byte 2 (bits 16-23) |
| 0xB5 | DATA3 | Data byte 3 (bits 24-31) - triggers 32-bit write |
| 0xB6 | TEXSRC_LO | Texture source address low (RAM) |
| 0xB7 | TEXSRC_HI | Texture source address high (RAM) |
I/O Port Usage:
- Set register offset (from 0xF4000) via ports 0xB0-0xB1
- Write 4 data bytes to ports 0xB2-0xB5 (little-endian)
- Writing to port 0xB5 triggers the 32-bit write to Voodoo
Texture Upload via I/O Ports:
- Set texture dimensions via TEX_WIDTH/TEX_HEIGHT registers
- Set source address in RAM via ports 0xB6-0xB7
- Trigger upload via TEX_UPLOAD register - emulator copies from CPU RAM
; Z80 Example: Write 640x480 to VOODOO_VIDEO_DIM (offset 0x214)
ld a, 0x14
out (0xB0), a ; Offset low = 0x14
ld a, 0x02
out (0xB1), a ; Offset high = 0x02 (offset = 0x214)
ld a, 0xE0
out (0xB2), a ; Height low (480 & 0xFF)
ld a, 0x01
out (0xB3), a ; Height high (480 >> 8)
ld a, 0x80
out (0xB4), a ; Width low (640 & 0xFF)
ld a, 0x02
out (0xB5), a ; Width high - triggers write
| Format | Shift | Range | Usage |
|---|---|---|---|
| 12.4 | 4 | -2048.0 to 2047.9375 | Vertex coordinates |
| 12.12 | 12 | -2048.0 to 2047.999 | Colors (0.0-1.0 range: 0x0000-0x1000) |
| 20.12 | 12 | Large range | Z depth |
| 14.18 | 18 | 0.0 to 16383.999 | Texture coordinates |
| Bit | Name | Description |
|---|---|---|
| 0 | CLIPPING | Enable scissor clipping |
| 1 | CHROMAKEY | Enable chroma key transparency |
| 4 | DEPTH_ENABLE | Enable depth buffer test |
| 5-7 | DEPTH_FUNC | Depth compare function (see table below) |
| 8 | DITHER | Enable ordered dithering (4x4 Bayer matrix) |
| 9 | RGB_WRITE | Enable RGB buffer write |
| 10 | DEPTH_WRITE | Enable depth buffer write |
| 11 | DITHER_2X2 | Use 2x2 Bayer matrix instead of 4x4 |
The depth function (bits 5-7 of fbzMode) controls Z-buffer testing. Shift these values left by 5 to position in fbzMode.
| Value | Name | Description |
|---|---|---|
| 0 | NEVER | Never pass (always discard fragment) |
| 1 | LESS | Pass if new Z < buffer Z |
| 2 | EQUAL | Pass if new Z == buffer Z |
| 3 | LESSEQUAL | Pass if new Z <= buffer Z |
| 4 | GREATER | Pass if new Z > buffer Z |
| 5 | NOTEQUAL | Pass if new Z != buffer Z |
| 6 | GREATEREQUAL | Pass if new Z >= buffer Z |
| 7 | ALWAYS | Always pass (disable depth test) |
| Bit | Name | Description |
|---|---|---|
| 0 | ALPHA_TEST_EN | Enable alpha test |
| 1-3 | ALPHA_FUNC | Alpha test function (see table below) |
| 4 | ALPHA_BLEND_EN | Enable alpha blending |
| 8-11 | SRC_BLEND | Source blend factor |
| 12-15 | DST_BLEND | Destination blend factor |
| 24-31 | ALPHA_REF | Alpha reference value (0-255) |
The alpha test function (bits 1-3 of alphaMode) compares fragment alpha against the reference value (bits 24-31). If the test fails, the fragment is discarded. Shift function values left by 1 to position in alphaMode.
| Value | Name | Description |
|---|---|---|
| 0 | NEVER | Never pass (always discard fragment) |
| 1 | LESS | Pass if alpha < reference |
| 2 | EQUAL | Pass if alpha == reference |
| 3 | LESSEQUAL | Pass if alpha <= reference |
| 4 | GREATER | Pass if alpha > reference |
| 5 | NOTEQUAL | Pass if alpha != reference |
| 6 | GREATEREQUAL | Pass if alpha >= reference |
| 7 | ALWAYS | Always pass (alpha test disabled) |
Example: Discard fragments with alpha < 0.5 (reference=128):
; Enable alpha test with LESS function, reference = 128
move.l #(VOODOO_ALPHA_TEST_EN|(VOODOO_ALPHA_GREATER<<1)|(128<<24)),VOODOO_ALPHA_MODEUse these values in bits 8-11 (source) and 12-15 (destination) of alphaMode. The final color is computed as: result = src * srcFactor + dst * dstFactor
| Value | Name | Description |
|---|---|---|
| 0 | ZERO | Factor = 0 |
| 1 | SRC_ALPHA | Factor = source alpha |
| 2 | COLOR | Factor = constant color |
| 3 | DST_ALPHA | Factor = destination alpha |
| 4 | ONE | Factor = 1 |
| 5 | INV_SRC_A | Factor = 1 - source alpha |
| 6 | INV_COLOR | Factor = 1 - constant color |
| 7 | INV_DST_A | Factor = 1 - destination alpha |
| 15 | SATURATE | Factor = min(srcA, 1-dstA) |
Common blending modes:
- Standard alpha blend: srcFactor=SRC_ALPHA (1), dstFactor=INV_SRC_A (5)
- Additive blend: srcFactor=ONE (4), dstFactor=ONE (4)
- Pre-multiplied alpha: srcFactor=ONE (4), dstFactor=INV_SRC_A (5)
Chroma keying discards fragments that match a specific color, creating transparency without alpha blending. This is useful for sprite-based rendering where a specific color represents "transparent."
To enable chroma keying:
- Set the key color in
VOODOO_CHROMA_KEY(format: 0x00RRGGBB) - Enable chroma keying by setting bit 1 (CHROMAKEY) in
VOODOO_FBZ_MODE
When enabled, any fragment whose RGB color matches the chroma key color will be discarded.
Example: Use magenta (255, 0, 255) as transparent color:
; Set chroma key to magenta
move.l #$00FF00FF,VOODOO_CHROMA_KEY
; Enable chroma keying in fbzMode
move.l #(VOODOO_FBZ_CHROMAKEY|VOODOO_FBZ_RGB_WRITE),VOODOO_FBZ_MODE| Bit | Name | Description |
|---|---|---|
| 0 | TEX_ENABLE | Enable texture mapping |
| 1-3 | TEX_MINIFY | Minification filter (0=point) |
| 4 | TEX_MAGNIFY | Magnification filter (0=point, 1=bilinear) |
| 5 | TEX_CLAMP_S | Clamp S (U) coordinate (vs wrap) |
| 6 | TEX_CLAMP_T | Clamp T (V) coordinate (vs wrap) |
| 8-11 | TEX_FORMAT | Texture format (see table below) |
Note: Only enable and clamp bits are currently forwarded to the rendering backend. Filtering defaults to point sampling; texture upload assumes RGBA data regardless of TEX_FORMAT.
| Value | Name | Description |
|---|---|---|
| 0 | 8BIT_PALETTE | 8-bit paletted texture |
| 5 | P8 | 8-bit palette (alternative) |
| 8 | ARGB1555 | 16-bit ARGB 1555 |
| 9 | ARGB4444 | 16-bit ARGB 4444 |
| 10 | ARGB8888 | 32-bit ARGB 8888 (default) |
Note: Texture upload currently processes all data as RGBA. Format selection is stored but does not yet affect upload conversion.
Texture coordinates (S and T) use 14.18 fixed-point format. Values represent positions in texture space where 1.0 (0x40000) equals the texture width/height. Coordinates outside 0.0-1.0 wrap or clamp depending on TEX_CLAMP_S/T bits.
Per-vertex texture coordinates work like per-vertex colors: use VOODOO_COLOR_SELECT to select the vertex (0/1/2), then write to VOODOO_START_S and VOODOO_START_T.
The VOODOO_FBZCOLOR_PATH register (0x0F4104) controls how texture and vertex (iterated) colors are combined. This allows for various rendering effects from simple flat colors to complex texture blending.
| Bit | Name | Description |
|---|---|---|
| 0-1 | RGB_SELECT | RGB source select (see table below) |
| 2-3 | A_SELECT | Alpha source select (see table below) |
| 4-6 | CC_MSELECT | Color combine function mode |
| 27 | TEXTURE_ENABLE | Enable texture in color path |
| Value | Name | Description |
|---|---|---|
| 0 | ITERATED | Use iterated (vertex) color |
| 1 | TEXTURE | Use texture color |
| 2 | COLOR1 | Use constant color1 |
| 3 | LFB | Use linear framebuffer color |
| Value | Name | Description |
|---|---|---|
| 0 | ZERO | Output zero (black) |
| 1 | CSUB_CL | cother - clocal (subtract) |
| 2 | ALOCAL | clocal * alocal (modulate by local alpha) |
| 3 | AOTHER | clocal * aother (modulate by other alpha) |
| 4 | CLOCAL | clocal only (pass through) |
| 5 | ALOCAL_T | alocal * texture |
| 6 | CLOC_MUL | clocal * cother (multiply/modulate) |
| 7 | AOTHER_T | aother * texture |
For convenience, pre-computed values are provided for common operations:
| Value | Name | Description |
|---|---|---|
| 0x00 | COMBINE_ITERATED | Vertex color only (default when no texture) |
| 0x01 | COMBINE_TEXTURE | Texture color only |
| 0x61 | COMBINE_MODULATE | Texture × vertex color (most common for textured geometry) |
| 0x81 | COMBINE_ADD | Texture + vertex color (clamped, for glow effects) |
| 0x41 | COMBINE_DECAL | Texture with vertex alpha |
Example: Enable texture modulation (texture color multiplied by vertex color):
; Set color combine to MODULATE mode (tex * vert)
move.l #VOODOO_COMBINE_MODULATE,VOODOO_FBZCOLOR_PATHThe VOODOO_FOG_MODE register (0x0F4108) enables depth-based fog blending. When enabled, fragment colors are linearly blended with the fog color based on the vertex Z coordinate (depth). Objects further from the camera appear more "fogged".
| Bit | Name | Description |
|---|---|---|
| 0 | FOG_ENABLE | Enable fog processing |
| 1 | FOG_ADD | Add fog color to output (vs. blend) |
| 2 | FOG_MULT | Multiply fog factor by alpha |
| 3 | FOG_ZALPHA | Use Z alpha for fog (vs. iterated) |
| 4 | FOG_CONSTANT | Use constant fog alpha |
| 5 | FOG_DITHER | Apply dithering to fog |
| 6 | FOG_ZONES | Enable fog zones (table-based fog) |
The fog color is set in VOODOO_FOG_COLOR (0x0F41C4) using the format 0x00RRGGBB.
Fog blending formula: output.rgb = mix(color.rgb, fogColor.rgb, fogFactor)
Where fogFactor is derived from the vertex Z coordinate (0.0 = near/no fog, 1.0 = far/full fog).
Example: Enable gray fog for distance fade effect:
; Set fog color to gray
move.l #$00808080,VOODOO_FOG_COLOR
; Enable fog
move.l #VOODOO_FOG_ENABLE,VOODOO_FOG_MODEOrdered dithering reduces color banding artifacts by applying a threshold pattern to pixel colors. The Voodoo supports two dither modes controlled by fbzMode bits:
- 4x4 Bayer matrix (default): Higher quality, 16 threshold levels
- 2x2 Bayer matrix: Faster, 4 threshold levels
Enable dithering by setting bit 8 (DITHER) in VOODOO_FBZ_MODE. For 2x2 mode, also set bit 11 (DITHER_2X2).
Example: Enable 4x4 dithering:
; Enable depth test, RGB write, and 4x4 dithering
move.l #(VOODOO_FBZ_DEPTH_ENABLE|VOODOO_FBZ_RGB_WRITE|VOODOO_FBZ_DITHER|(VOODOO_DEPTH_LESS<<5)),VOODOO_FBZ_MODEExample: Fog with dithering for smooth distance fade:
; Set fog color
move.l #$00404040,VOODOO_FOG_COLOR
; Enable fog
move.l #VOODOO_FOG_ENABLE,VOODOO_FOG_MODE
; Enable depth, RGB write, and dithering
move.l #(VOODOO_FBZ_DEPTH_ENABLE|VOODOO_FBZ_RGB_WRITE|VOODOO_FBZ_DITHER|(VOODOO_DEPTH_LESS<<5)),VOODOO_FBZ_MODEThe Voodoo supports per-vertex colors for smooth Gouraud shading. Use VOODOO_COLOR_SELECT to select which vertex (0, 1, or 2 for vertices A, B, C) will receive subsequent writes to the VOODOO_START_* attribute registers:
- Write vertex index (0/1/2) to
VOODOO_COLOR_SELECT - Write R/G/B/A values to
VOODOO_START_R/G/B/A- these are stored for the selected vertex - Repeat for each vertex with different colors
- Submit triangle - colors will be smoothly interpolated across the surface
When VOODOO_COLOR_SELECT is not used (or set to 0), flat shading is applied using the last written color values.
include "ie68.inc"
; Enable the Voodoo graphics card (disabled by default)
move.l #1,VOODOO_ENABLE
; Clear screen to black
move.l #$FF000000,VOODOO_COLOR0
move.l #0,VOODOO_FAST_FILL_CMD
; Set up depth test (less-than, write enabled)
move.l #(VOODOO_FBZ_DEPTH_ENABLE|VOODOO_FBZ_RGB_WRITE|VOODOO_FBZ_DEPTH_WRITE|(VOODOO_DEPTH_LESS<<5)),VOODOO_FBZ_MODE
; Define triangle vertices (12.4 fixed-point: value << 4)
move.l #(320<<4),VOODOO_VERTEX_AX ; Top center (320, 100)
move.l #(100<<4),VOODOO_VERTEX_AY
move.l #(420<<4),VOODOO_VERTEX_BX ; Bottom right (420, 300)
move.l #(300<<4),VOODOO_VERTEX_BY
move.l #(220<<4),VOODOO_VERTEX_CX ; Bottom left (220, 300)
move.l #(300<<4),VOODOO_VERTEX_CY
; Set red color (12.12 fixed-point: 1.0 = $1000)
move.l #$1000,VOODOO_START_R ; R = 1.0
move.l #$0000,VOODOO_START_G ; G = 0.0
move.l #$0000,VOODOO_START_B ; B = 0.0
move.l #$1000,VOODOO_START_A ; A = 1.0 (opaque)
move.l #$800000,VOODOO_START_Z ; Z = 0.5
; Submit triangle
move.l #0,VOODOO_TRIANGLE_CMD
; Present frame
move.l #0,VOODOO_SWAP_BUFFER_CMDPer-vertex colors are set using VOODOO_COLOR_SELECT to specify which vertex (0/1/2) receives the subsequent VOODOO_START_* writes. The colors are smoothly interpolated across the triangle.
include "ie68.inc"
; Enable the Voodoo graphics card (disabled by default)
move.l #1,VOODOO_ENABLE
; Clear screen to black
move.l #$FF000000,VOODOO_COLOR0
move.l #0,VOODOO_FAST_FILL_CMD
; Enable depth test and RGB write
move.l #(VOODOO_FBZ_DEPTH_ENABLE|VOODOO_FBZ_RGB_WRITE|VOODOO_FBZ_DEPTH_WRITE|(VOODOO_DEPTH_LESS<<5)),VOODOO_FBZ_MODE
; Define triangle vertices (12.4 fixed-point)
move.l #(320<<4),VOODOO_VERTEX_AX ; Top center (320, 100)
move.l #(100<<4),VOODOO_VERTEX_AY
move.l #(420<<4),VOODOO_VERTEX_BX ; Bottom right (420, 300)
move.l #(300<<4),VOODOO_VERTEX_BY
move.l #(220<<4),VOODOO_VERTEX_CX ; Bottom left (220, 300)
move.l #(300<<4),VOODOO_VERTEX_CY
; Set vertex 0 (A) to RED
move.l #0,VOODOO_COLOR_SELECT ; Select vertex 0
move.l #$1000,VOODOO_START_R ; R = 1.0
move.l #$0000,VOODOO_START_G ; G = 0.0
move.l #$0000,VOODOO_START_B ; B = 0.0
move.l #$1000,VOODOO_START_A ; A = 1.0
; Set vertex 1 (B) to GREEN
move.l #1,VOODOO_COLOR_SELECT ; Select vertex 1
move.l #$0000,VOODOO_START_R ; R = 0.0
move.l #$1000,VOODOO_START_G ; G = 1.0
move.l #$0000,VOODOO_START_B ; B = 0.0
move.l #$1000,VOODOO_START_A ; A = 1.0
; Set vertex 2 (C) to BLUE
move.l #2,VOODOO_COLOR_SELECT ; Select vertex 2
move.l #$0000,VOODOO_START_R ; R = 0.0
move.l #$0000,VOODOO_START_G ; G = 0.0
move.l #$1000,VOODOO_START_B ; B = 1.0
move.l #$1000,VOODOO_START_A ; A = 1.0
; Submit triangle (colors will interpolate smoothly)
move.l #0,VOODOO_TRIANGLE_CMD
; Present frame
move.l #0,VOODOO_SWAP_BUFFER_CMDThis example demonstrates configuring alpha blending with source alpha and inverse source alpha factors (standard transparency).
include "ie68.inc"
; Enable alpha blending: src*srcA + dst*(1-srcA)
move.l #(VOODOO_ALPHA_BLEND_EN|(VOODOO_BLEND_SRC_ALPHA<<8)|(VOODOO_BLEND_INV_SRC_A<<12)),VOODOO_ALPHA_MODE
; Draw opaque background triangle first
; ... (set vertices and full alpha color)
move.l #$1000,VOODOO_START_A ; Alpha = 1.0 (opaque)
move.l #0,VOODOO_TRIANGLE_CMD
; Draw semi-transparent overlay triangle
move.l #$0800,VOODOO_START_A ; Alpha = 0.5 (50% transparent)
move.l #$1000,VOODOO_START_R ; Red
move.l #0,VOODOO_START_G
move.l #0,VOODOO_START_B
move.l #0,VOODOO_TRIANGLE_CMD
; Present frame
move.l #0,VOODOO_SWAP_BUFFER_CMD include "ie68.inc"
; Main render loop
.frame_loop:
; Clear framebuffer
move.l #$FF000000,VOODOO_COLOR0
move.l #0,VOODOO_FAST_FILL_CMD
; Draw 12 triangles (6 faces x 2 triangles each)
; Front face - red
bsr draw_face_front
; Back face - blue (will be Z-rejected when behind front)
bsr draw_face_back
; Present frame
move.l #VOODOO_SWAP_VSYNC,VOODOO_SWAP_BUFFER_CMD
; Update rotation angle
add.w #2,rotation_angle
bra .frame_loopThis example demonstrates texture-mapped rendering with per-vertex UV coordinates.
include "ie68.inc"
; Enable texturing with point sampling and wrap mode
move.l #VOODOO_TEX_ENABLE,VOODOO_TEXTURE_MODE
; Set up depth test and RGB write
move.l #(VOODOO_FBZ_DEPTH_ENABLE|VOODOO_FBZ_RGB_WRITE|VOODOO_FBZ_DEPTH_WRITE|(VOODOO_DEPTH_LESS<<5)),VOODOO_FBZ_MODE
; Define triangle vertices (12.4 fixed-point)
move.l #(320<<4),VOODOO_VERTEX_AX ; Top center
move.l #(100<<4),VOODOO_VERTEX_AY
move.l #(420<<4),VOODOO_VERTEX_BX ; Bottom right
move.l #(300<<4),VOODOO_VERTEX_BY
move.l #(220<<4),VOODOO_VERTEX_CX ; Bottom left
move.l #(300<<4),VOODOO_VERTEX_CY
; Vertex 0 (A): UV = (0.5, 0.0) - top center of texture
move.l #0,VOODOO_COLOR_SELECT
move.l #$1000,VOODOO_START_R ; White color modulation
move.l #$1000,VOODOO_START_G
move.l #$1000,VOODOO_START_B
move.l #$1000,VOODOO_START_A
move.l #$20000,VOODOO_START_S ; S = 0.5 (14.18 format: 0.5 * 0x40000)
move.l #$00000,VOODOO_START_T ; T = 0.0
; Vertex 1 (B): UV = (1.0, 1.0) - bottom right
move.l #1,VOODOO_COLOR_SELECT
move.l #$1000,VOODOO_START_R
move.l #$1000,VOODOO_START_G
move.l #$1000,VOODOO_START_B
move.l #$1000,VOODOO_START_A
move.l #$40000,VOODOO_START_S ; S = 1.0
move.l #$40000,VOODOO_START_T ; T = 1.0
; Vertex 2 (C): UV = (0.0, 1.0) - bottom left
move.l #2,VOODOO_COLOR_SELECT
move.l #$1000,VOODOO_START_R
move.l #$1000,VOODOO_START_G
move.l #$1000,VOODOO_START_B
move.l #$1000,VOODOO_START_A
move.l #$00000,VOODOO_START_S ; S = 0.0
move.l #$40000,VOODOO_START_T ; T = 1.0
; Submit triangle
move.l #0,VOODOO_TRIANGLE_CMD
; Present frame
move.l #0,VOODOO_SWAP_BUFFER_CMDThe IE32 implements a 32-bit RISC-like architecture with fixed-width instructions and a clean, orthogonal instruction set.
The CPU provides 16 general-purpose 32-bit registers organised in two logical banks:
First Bank (A-H):
A - Primary accumulator/general purpose
B - General purpose
C - General purpose
D - General purpose
E - General purpose
F - General purpose
G - General purpose
H - General purpose
Second Bank (S-Z):
S - General purpose/stack operations
T - General purpose
U - General purpose
V - General purpose
W - General purpose
X - General purpose/index
Y - General purpose/index
Z - General purpose/index
While the register naming suggests traditional roles (like X/Y/Z for indexing), all registers are fully general-purpose and can be used interchangeably.
Special Registers:
PC - Program Counter (32-bit)
SP - Stack Pointer (32-bit, initialised to 0x9F000)
The IE32 uses implicit status flags based on the result of the last operation:
- Operations that produce a zero result set an internal zero flag
- Comparison instructions (used by conditional jumps) compare register values directly
The system supports five addressing modes:
Immediate (ADDR_IMMEDIATE = 0x00)
LOAD A, #42 ; Load value 42 into register ARegister (ADDR_REGISTER = 0x01)
ADD A, X ; Add X register to A registerRegister Indirect (ADDR_REG_IND = 0x02)
LOAD A, [X] ; Load from address in X
LOAD A, [X+4] ; Load from address in X plus 4Memory Indirect (ADDR_MEM_IND = 0x03)
LOAD A, [0x1000] ; Load from address stored at memory location 0x1000Direct (ADDR_DIRECT = 0x04)
STORE A, @0xF0900 ; Store A's value directly to memory address 0xF0900
LOAD A, @0x1000 ; Load value directly from memory address 0x1000The direct addressing mode is used for memory-mapped I/O operations, providing efficient access to hardware registers without double indirection.
Every instruction is exactly 8 bytes long, providing a consistent and easy-to-decode format:
Byte 0: Opcode
Byte 1: Register specifier
Byte 2: Addressing mode
Byte 3: Reserved (must be 0)
Bytes 4-7: 32-bit operand value
; Traditional load/store
LOAD (0x01) ; Load value into register
STORE (0x02) ; Store register to memory
; Register-specific loads
LDA (0x20) ; Load to A
LDB (0x3A) ; Load to B
LDC (0x3B) ; Load to C
LDD (0x3C) ; Load to D
LDE (0x3D) ; Load to E
LDF (0x3E) ; Load to F
LDG (0x3F) ; Load to G
LDH (0x4C) ; Load to H
LDS (0x4D) ; Load to S
LDT (0x4E) ; Load to T
LDU (0x40) ; Load to U
LDV (0x41) ; Load to V
LDW (0x42) ; Load to W
LDX (0x21) ; Load to X
LDY (0x22) ; Load to Y
LDZ (0x23) ; Load to Z
; Register-specific stores
STA (0x24) ; Store from A
STB (0x43) ; Store from B
STC (0x44) ; Store from C
STD (0x45) ; Store from D
STE (0x46) ; Store from E
STF (0x47) ; Store from F
STG (0x48) ; Store from G
STH (0x4F) ; Store from H
STS (0x50) ; Store from S
STT (0x51) ; Store from T
STU (0x49) ; Store from U
STV (0x4A) ; Store from V
STW (0x4B) ; Store from W
STX (0x25) ; Store from X
STY (0x26) ; Store from Y
STZ (0x27) ; Store from Z
; Increment/Decrement
INC (0x28) ; Increment
DEC (0x29) ; Decrement
; Stack operations
PUSH (0x12) ; Push register to stack
POP (0x13) ; Pop from stack to registerADD (0x03) ; Add
SUB (0x04) ; Subtract
MUL (0x14) ; Multiply
DIV (0x15) ; Divide
MOD (0x16) ; ModulusAND (0x05) ; Bitwise AND
OR (0x09) ; Bitwise OR
XOR (0x0A) ; Bitwise XOR
NOT (0x0D) ; Bitwise NOT
SHL (0x0B) ; Shift left
SHR (0x0C) ; Shift rightJMP (0x06) ; Unconditional jump
JNZ (0x07) ; Jump if not zero
JZ (0x08) ; Jump if zero
JGT (0x0E) ; Jump if greater than
JGE (0x0F) ; Jump if greater or equal
JLT (0x10) ; Jump if less than
JLE (0x11) ; Jump if less or equal
JSR (0x18) ; Jump to subroutine
RTS (0x19) ; Return from subroutineSEI (0x1A) ; Set Enable Interrupts
CLI (0x1B) ; Clear Interrupt Enable
RTI (0x1C) ; Return from InterruptWAIT (0x17) ; Wait for specified cycles
NOP (0xEE) ; No operation
HALT (0xFF) ; Stop execution- The IE32 uses the shared MachineBus; visible RAM is the 32-bit profile ceiling clamped against the autodetected active visible RAM
- All memory-mapped devices (video, audio, PSG/POKEY/SID, terminal) are accessible
- I/O region: 0x0F0000 - 0x0FFFFF
- VRAM access: 0x100000 - 0x5FFFFF (direct 32-bit addressing)
- Stack grows downward from 0x9F000
The system implements a simple but effective interrupt system:
-
Interrupt Vector
- Located at address 0x0000
- Contains the address of the interrupt service routine (ISR)
- Must be initialised before enabling interrupts
-
Interrupt Control
- SEI enables interrupts
- CLI disables interrupts
- Interrupts automatically disabled during ISR execution
-
Interrupt Processing When an interrupt occurs:
- Current PC is pushed onto stack
- CPU jumps to ISR address from vector
- Interrupts are disabled until RTI
-
Timer Interrupts IE32/IE64 can generate periodic interrupts from their internal countdown timer. Programs should initialise the IRQ vector and enable interrupts with
SEI.
- Little-endian byte order for memory bus operations
- Fixed 8-byte instruction size
- All registers are 32-bit
- Word-aligned memory access recommended for performance
The Intuition Engine includes an NMOS 6502 core for running raw 8-bit binaries. The 6502 shares the same memory-mapped I/O and device map as IE32 and M68K, so hardware registers behave identically across CPU modes.
The 6502 core exposes the classic NMOS register file:
A - Accumulator (8-bit) for arithmetic and logic
X - Index register X (8-bit)
Y - Index register Y (8-bit)
SP - Stack Pointer (8-bit), stack page fixed at 0x0100-0x01FF
PC - Program Counter (16-bit)
SR - Status Register (8-bit flags)
The status register follows NMOS 6502 semantics:
Bit 7: N - Negative flag
Bit 6: V - Overflow flag
Bit 5: - - Unused (always 1 when pushed)
Bit 4: B - Break command
Bit 3: D - Decimal mode
Bit 2: I - IRQ Disable
Bit 1: Z - Zero flag
Bit 0: C - Carry flag
Supported addressing modes match the NMOS 6502 set used by common assemblers:
| Mode | Syntax | Description |
|---|---|---|
| Immediate | #$nn | Operand is the byte following the opcode |
| Zero Page | $nn | 8-bit address in page zero |
| Zero Page,X | $nn,X | Zero page indexed by X |
| Zero Page,Y | $nn,Y | Zero page indexed by Y |
| Absolute | $nnnn | 16-bit address |
| Absolute,X | $nnnn,X | Absolute indexed by X |
| Absolute,Y | $nnnn,Y | Absolute indexed by Y |
| Indirect | ($nnnn) | Indirect (JMP only) |
| (Indirect,X) | ($nn,X) | Indexed indirect |
| (Indirect),Y | ($nn),Y | Indirect indexed |
| Relative | $nn | Signed offset for branches |
| Accumulator | A | Operand is accumulator |
| Implied | Operand implied by instruction |
The 6502 implements all 56 documented NMOS instructions plus unofficial opcodes:
Load/Store: LDA, LDX, LDY, STA, STX, STY Transfer: TAX, TAY, TSX, TXA, TXS, TYA Stack: PHA, PHP, PLA, PLP Arithmetic: ADC, SBC, INC, INX, INY, DEC, DEX, DEY Logical: AND, EOR, ORA, BIT Shift/Rotate: ASL, LSR, ROL, ROR Compare: CMP, CPX, CPY Branch: BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS Jump/Return: JMP, JSR, RTS, RTI, BRK Flags: CLC, CLD, CLI, CLV, SEC, SED, SEI No-op: NOP
- The 6502 uses the shared system bus and all memory-mapped devices
- Native 16-bit address space (0x0000-0xFFFF)
- VRAM access via banking at 0x8000-0xBFFF (16KB window)
- Bank select register at 0xF7F0
- VRAM banking is disabled until the first write to the bank register
- Extended bank windows for IE65:
- Bank 1: 0x2000-0x3FFF (8KB, sprite data)
- Bank 2: 0x4000-0x5FFF (8KB, font data)
- Bank 3: 0x6000-0x7FFF (8KB, general/AY data)
- Bank control registers: 0xF700-0xF705
Audio Chip Access (6502 native addresses):
| Chip | Address Range |
|---|---|
| PSG | $D400-$D40D |
| POKEY | $D200-$D209 |
| SID | $D500-$D51C |
Vector locations follow standard 6502 layout:
0xFFFA-0xFFFB: NMI vector
0xFFFC-0xFFFD: RESET vector
0xFFFE-0xFFFF: IRQ/BRK vector
The loader initializes these vectors for raw binaries; custom binaries may overwrite them.
- NMOS 6502 only: 65C02 opcodes are not supported
- Decimal mode is fully implemented
- Cycle-accurate instruction timing
- Use Klaus tests to validate D-flag behavior
- Use
-m6502flag to run 6502 binaries --load-addrsets the load address (default 0x0600 for raw binaries, 0x0800 for.ie65files)--entrysets the entry address (defaults to load address)
The Intuition Engine includes a Z80 core for running raw 8-bit binaries. It shares the same memory map and device registers as the other CPU modes.
The Z80 provides a rich register set with shadow registers:
Main Registers:
A - Accumulator (8-bit)
F - Flags (8-bit)
B, C - General purpose (8-bit each, BC as 16-bit pair)
D, E - General purpose (8-bit each, DE as 16-bit pair)
H, L - General purpose (8-bit each, HL as 16-bit pair)
Shadow Registers:
A', F', B', C', D', E', H', L' - Alternate register set
Index Registers:
IX - Index register X (16-bit)
IY - Index register Y (16-bit)
Special Registers:
SP - Stack Pointer (16-bit)
PC - Program Counter (16-bit)
I - Interrupt Vector (8-bit)
R - Refresh Counter (8-bit)
The F register contains:
Bit 7: S - Sign flag
Bit 6: Z - Zero flag
Bit 5: Y - Undocumented (copy of bit 5 of result)
Bit 4: H - Half-carry flag
Bit 3: X - Undocumented (copy of bit 3 of result)
Bit 2: P/V - Parity/Overflow flag
Bit 1: N - Add/Subtract flag
Bit 0: C - Carry flag
| Mode | Syntax | Description |
|---|---|---|
| Immediate | n | 8-bit immediate value |
| Immediate Extended | nn | 16-bit immediate value |
| Register | r | Single register |
| Register Pair | rr | 16-bit register pair (BC, DE, HL, SP) |
| Indirect | (HL) | Memory at address in HL |
| Indexed | (IX+d), (IY+d) | Indexed with signed displacement |
| Extended | (nn) | Direct 16-bit address |
| Relative | e | Signed 8-bit offset for jumps |
| Bit | b | Bit number (0-7) |
The Z80 implements a comprehensive instruction set including:
8-bit Load: LD r,r' / LD r,n / LD r,(HL) / LD (HL),r 16-bit Load: LD rr,nn / LD (nn),HL / LD HL,(nn) / PUSH/POP Exchange: EX DE,HL / EX AF,AF' / EXX Arithmetic: ADD, ADC, SUB, SBC, AND, OR, XOR, CP, INC, DEC 16-bit Arithmetic: ADD HL,rr / ADC HL,rr / SBC HL,rr / INC/DEC rr Rotate/Shift: RLCA, RRCA, RLA, RRA, RLC, RRC, RL, RR, SLA, SRA, SRL Bit Operations: BIT, SET, RES Jump: JP, JR, DJNZ Call/Return: CALL, RET, RETI, RETN, RST Input/Output: IN, OUT Block Transfer: LDI, LDIR, LDD, LDDR Block Search: CPI, CPIR, CPD, CPDR Block I/O: INI, INIR, IND, INDR, OUTI, OTIR, OUTD, OTDR
- The Z80 uses the shared system bus
- Native 16-bit address space (0x0000-0xFFFF)
- Z80
IN/OUTports map to the 16-bit address space as memory-mapped registers - VRAM access via banking at 0x8000-0xBFFF (16KB window, bank register at 0xF7F0)
- Extended bank windows: Bank 1 (0x2000, 8KB), Bank 2 (0x4000, 8KB), Bank 3 (0x6000, 8KB)
- Bank control registers: 0xF700-0xF705
Port-Based Chip Access:
| Chip | Ports | Description |
|---|---|---|
| PSG | 0xF0-0xF1 | Register select, data |
| POKEY | 0xD0-0xD1 | Register select, data |
| SID | 0xE0-0xE1 | Register select, data |
| TED | 0xF2-0xF3 | Register select, data (audio + video indices 0x20-0x2F) |
| ANTIC | 0xD4-0xD5 | Register select, data |
| GTIA | 0xD6-0xD7 | Register select, data |
| ULA | 0xFE | Border color / key status |
| VGA | 0xA0-0xAC | VGA register access |
| Voodoo | 0xB0-0xB7 | Address/data ports for 32-bit Voodoo register writes |
First port selects the register, second port reads/writes data (except ULA which uses a single port, and Voodoo which uses an address/data accumulator).
The Z80 supports three interrupt modes:
Mode 0: External device places instruction on data bus (typically RST) Mode 1: Jump to fixed address 0x0038 Mode 2: Vectored interrupts using I register as high byte
Interrupt control:
DI ; Disable interrupts
EI ; Enable interrupts
IM 0/1/2 ; Set interrupt mode- Full Z80 instruction set including undocumented opcodes
- Use
-z80flag to run Z80 binaries --load-addrsets the load address (default 0x0000)--entrysets the entry address (defaults to load address)- Shadow registers fully implemented
- Block transfer and search instructions implemented
- All interrupt modes supported
In addition to the IE32 instruction set, the Intuition Engine includes a complete Motorola 68020 CPU emulator with 68881/68882 FPU (Floating Point Unit) support.
Data Registers:
D0-D7 - Eight 32-bit data registers
Can be used as byte (.B), word (.W), or long (.L)
Address Registers:
A0-A6 - Seven 32-bit address registers
A7 - Stack pointer (SSP in supervisor mode, USP in user mode)
Special Registers:
PC - Program Counter (32-bit)
SR - Status Register (16-bit)
CCR - Condition Code Register (low byte of SR)
USP - User Stack Pointer
SSP - Supervisor Stack Pointer
VBR - Vector Base Register
SFC - Source Function Code
DFC - Destination Function Code
CACR - Cache Control Register
CAAR - Cache Address Register
FPU Registers (68881/68882):
FP0-FP7 - Eight 80-bit floating-point registers
FPCR - Floating-Point Control Register
FPSR - Floating-Point Status Register
FPIAR - Floating-Point Instruction Address Register
Condition Code Register (CCR):
Bit 4: X - Extend (copy of carry for multi-precision)
Bit 3: N - Negative
Bit 2: Z - Zero
Bit 1: V - Overflow
Bit 0: C - Carry
System Byte:
Bit 15: T1 - Trace enable
Bit 14: T0 - Trace enable
Bit 13: S - Supervisor state
Bits 10-8: IPL - Interrupt priority level mask
The 68020 supports 12 basic addressing modes plus extensions:
| Mode | Syntax | Description |
|---|---|---|
| Data Register Direct | Dn | Data in register |
| Address Register Direct | An | Address in register |
| Address Register Indirect | (An) | Memory at address in An |
| Address Indirect Postincrement | (An)+ | Indirect, then increment An |
| Address Indirect Predecrement | -(An) | Decrement An, then indirect |
| Address Indirect with Displacement | (d16,An) | An + signed 16-bit offset |
| Address Indirect with Index | (d8,An,Xn) | An + Xn + signed 8-bit offset |
| Absolute Short | (xxx).W | 16-bit address, sign-extended |
| Absolute Long | (xxx).L | Full 32-bit address |
| PC with Displacement | (d16,PC) | PC + signed 16-bit offset |
| PC with Index | (d8,PC,Xn) | PC + Xn + signed 8-bit offset |
| Immediate | # | Immediate value |
68020-Specific Extensions:
| Mode | Syntax | Description |
|---|---|---|
| Memory Indirect Preindexed | ([bd,An,Xn],od) | Double indirection with preindex |
| Memory Indirect Postindexed | ([bd,An],Xn,od) | Double indirection with postindex |
| PC Memory Indirect | ([bd,PC,Xn],od) | PC-relative indirect |
| Scaled Indexing | (d8,An,Xn*scale) | Scale factor ×1, ×2, ×4, or ×8 |
Data Movement: MOVE, MOVEA, MOVEM, MOVEQ, MOVEP, LEA, PEA, EXG, SWAP, LINK, UNLK
Arithmetic: ADD, ADDA, ADDI, ADDQ, ADDX SUB, SUBA, SUBI, SUBQ, SUBX MULU, MULS, DIVU, DIVS, DIVUL, DIVSL NEG, NEGX, CLR, CMP, CMPA, CMPI, CMPM TST, EXT, EXTB
Logical: AND, ANDI, OR, ORI, EOR, EORI, NOT
Shift and Rotate: ASL, ASR, LSL, LSR, ROL, ROR, ROXL, ROXR
Bit Manipulation: BTST, BCHG, BCLR, BSET
Bit Field (68020): BFTST, BFEXTU, BFEXTS, BFCHG, BFCLR, BFSET, BFFFO, BFINS
BCD Arithmetic: ABCD, SBCD, NBCD, PACK, UNPK
Program Control: Bcc (14 conditions), DBcc, Scc, JMP, JSR, RTS, RTE, RTR, RTD, TRAP, TRAPV, CHK, CHK2, TAS
System Control: MOVE to/from SR, MOVE USP, MOVEC, MOVES, RESET, STOP, NOP, ILLEGAL, ORI/ANDI/EORI to CCR/SR
Atomic Operations (68020): CAS, CAS2
- 80-bit extended precision (IEEE 754 compliant)
- 8 floating-point registers (FP0-FP7)
- Control registers: FPCR, FPSR, FPIAR
| Instruction | Description |
|---|---|
| FMOVE | Move floating-point data |
| FADD | Add |
| FSUB | Subtract |
| FMUL | Multiply |
| FDIV | Divide |
| FNEG | Negate |
| FABS | Absolute value |
| FCMP | Compare |
| FTST | Test |
| FSQRT | Square root |
| FINT | Integer part |
| FINTRZ | Integer part (round to zero) |
| FMOD | Modulo |
| FREM | IEEE remainder |
| FSCALE | Scale by power of 2 |
| FSGLDIV | Single-precision divide |
| FSGLMUL | Single-precision multiply |
| FGETEXP | Extract exponent |
| FGETMAN | Extract mantissa |
| Instruction | Description |
|---|---|
| FSIN | Sine |
| FCOS | Cosine |
| FTAN | Tangent |
| FASIN | Arc sine |
| FACOS | Arc cosine |
| FATAN | Arc tangent |
| FATANH | Inverse hyperbolic tangent |
| FSINH | Hyperbolic sine |
| FCOSH | Hyperbolic cosine |
| FTANH | Hyperbolic tangent |
| FLOG10 | Base-10 logarithm |
| FLOGN | Natural logarithm |
| FLOGNP1 | ln(1+x) |
| FLOG2 | Base-2 logarithm |
| FETOX | e^x |
| FETOXM1 | e^x - 1 |
| FTWOTOX | 2^x |
| FTENTOX | 10^x |
The FPU provides built-in constants:
- Pi (π)
- e (Euler's number)
- log₂(e), log₁₀(e)
- ln(2), ln(10)
- Powers of 10 (10⁰ through 10⁴)
- N (Negative) - Result is negative
- Z (Zero) - Result is zero
- I (Infinity) - Result is infinite
- NAN - Result is Not a Number
- Uses the shared MachineBus; visible RAM is the M68K 32-bit profile ceiling clamped against the autodetected active visible RAM (EmuTOS and AROS profiles further restrict via
EmuTOS_PROFILE_TOP/AROS_PROFILE_TOPinprofile_bounds.go) - 32-bit address bus (full 4 GiB architectural visible range; profile bounds may expose less)
- Big-endian byte order
- I/O region: 0x00F00000 - 0x00FFFFFF
- VRAM: 0x00100000 - 0x004FFFFF (direct access)
- Exception vector table: 0x00000000 (relocatable via VBR)
- Default stack: 0x00FF0000
Exception Vector Table (256 vectors):
Vector 0: Initial SSP
Vector 1: Initial PC (reset)
Vector 2: Bus Error
Vector 3: Address Error
Vector 4: Illegal Instruction
Vector 5: Zero Divide
Vector 6: CHK/CHK2 Instruction
Vector 7: TRAPcc, TRAPV, cpTRAPcc
Vector 8: Privilege Violation
Vector 9: Trace
Vector 10: Line-A Emulator
Vector 11: Line-F Emulator (FPU)
Vectors 24-31: Spurious + Auto-vectored Interrupts
Vectors 32-47: TRAP #0-15
Vectors 48-63: FPU Exceptions
Vectors 64-255: User Defined
Interrupt Priorities:
- Level 7: Non-maskable
- Levels 1-6: Maskable (compared against SR IPL)
- Level 0: No interrupt
- 95%+ instruction coverage (68020 + 68881/68882)
- Full 68020 with 32-bit addressing (no MMU)
- Big-endian byte order (converted from host)
- F-line opcodes route to FPU when present
- Use
-m68kflag to run M68K binaries - File extension:
.ie68
Not Implemented:
- MMU and address translation
- Coprocessor interface (beyond FPU)
- Instruction cache emulation
- Trace mode (T0/T1 bits defined but not enforced)
- Dynamic bus sizing
The Intuition Engine includes an x86 core implementing the 8086 instruction set with 32-bit register extensions in a flat memory model. This provides 32-bit programming without the complexity of protected mode, segment descriptors, or paging.
The x86 provides 32-bit general purpose registers with 16-bit and 8-bit access:
General Purpose Registers:
EAX (AX, AH, AL) - Accumulator
EBX (BX, BH, BL) - Base
ECX (CX, CH, CL) - Counter
EDX (DX, DH, DL) - Data
ESI (SI) - Source Index
EDI (DI) - Destination Index
EBP (BP) - Base Pointer
ESP (SP) - Stack Pointer
Special Registers:
EIP - Instruction Pointer (32-bit)
EFLAGS - Status flags (32-bit)
Segment Registers (for 8086 compatibility):
CS - Code Segment
DS - Data Segment
ES - Extra Segment
SS - Stack Segment
FS, GS - Additional segments (386+)
The EFLAGS register contains:
Bit 0: CF - Carry flag
Bit 2: PF - Parity flag
Bit 4: AF - Auxiliary carry flag
Bit 6: ZF - Zero flag
Bit 7: SF - Sign flag
Bit 8: TF - Trap flag
Bit 9: IF - Interrupt enable flag
Bit 10: DF - Direction flag
Bit 11: OF - Overflow flag
| Mode | Syntax | Description |
|---|---|---|
| Immediate | imm8/imm16/imm32 | Immediate value |
| Register | reg | Register operand |
| Direct | [addr] | Direct memory address |
| Register Indirect | [reg] | Memory at register address |
| Base+Displacement | [reg+disp] | Base register + offset |
| SIB | [base+index*scale+disp] | Full 386 addressing |
The x86 core implements the 8086 instruction set with 32-bit register support:
Data Transfer: MOV, PUSH, POP, XCHG, LEA, LES, LDS Arithmetic: ADD, ADC, SUB, SBB, MUL, IMUL, DIV, IDIV, INC, DEC, CMP, NEG Logical: AND, OR, XOR, NOT, TEST Shift/Rotate: SHL, SHR, SAL, SAR, ROL, ROR, RCL, RCR Control Flow: JMP, Jcc, CALL, RET, LOOP, LOOPE, LOOPNE String: MOVS, STOS, LODS, CMPS, SCAS with REP/REPE/REPNE prefixes I/O: IN, OUT (port-based I/O for audio chips) Flag Control: CLC, STC, CMC, CLD, STD, CLI, STI BCD: DAA, DAS, AAA, AAS, AAM, AAD
32-bit Register Extensions:
- 32-bit register operations (EAX, EBX, etc.)
- Operand size prefix (0x66) for 16/32-bit switching
- Address size prefix (0x67)
- SIB byte addressing for complex memory operands
Additional Instructions:
- MOVZX, MOVSX (zero/sign extend)
- SETcc (conditional byte set)
- Bit test: BT, BTS, BTR, BTC, BSF, BSR
- SHLD, SHRD (double-precision shifts)
- Full 32-bit flat address space; visible RAM is the x86 32-bit profile ceiling clamped against the autodetected active visible RAM
- VGA VRAM at standard PC address 0xA0000-0xAFFFF
- Hardware registers memory-mapped at 0xF0000+
- Separate I/O port space for audio chips
Port-Based Audio Chip Access:
| Chip | Ports | Description |
|---|---|---|
| PSG | 0xF0-0xF1 | Register select, data |
| POKEY | 0xD0-0xDF | Direct register access |
| SID | 0xE0-0xE1 | Register select, data |
| TED | 0xF2-0xF3 | Register select, data |
Standard VGA Ports:
| Port | Description |
|---|---|
| 0x3C4-0x3C5 | Sequencer index/data |
| 0x3C6-0x3C9 | DAC mask, read/write index, data |
| 0x3CE-0x3CF | Graphics controller index/data |
| 0x3D4-0x3D5 | CRTC index/data |
| 0x3DA | Input status (VSync) |
The x86 supports software interrupts:
INT n ; Call interrupt n
INT 3 ; Breakpoint
INTO ; Overflow interrupt
IRET ; Return from interrupt- Use
-x86flag to run x86 binaries - File extension:
.ie86 - Use NASM or FASM for assembly with
ie86.incinclude file - Programs always load at address 0x00000000 and begin execution there
Memory Model:
The x86 core uses a simplified flat memory model:
- Segment registers exist but are ignored for address calculation
- All memory accesses use the 32-bit offset directly (no segment:offset)
- Full 32-bit address space accessible without segment arithmetic
- This is neither true real mode (1MB limit) nor protected mode
x87 FPU (387 scope):
- x87 escape opcodes
D8-DFare implemented with stack-based floating-point operations - Data movement, arithmetic, compares, control ops, and ENV/SAVE/RESTORE paths are supported
- Deliberate exclusions:
FCMOV*,FCOMI/FCOMIP,FUCOMI/FUCOMIP, and SSE-family instructions - Deliberate deviations: large-argument trig reduction always completes (
C2=0),FPREM/FPREM1complete in one step (C2=0), and ENV/SAVE always use the 32-bit layout
Not Implemented:
- Real mode segment:offset addressing
- Protected mode (descriptor tables, privilege levels)
- Virtual 8086 mode
- Paging and virtual memory
- Task switching
The IE64 is a custom 64-bit RISC processor designed for the Intuition Engine. It uses a clean load-store architecture with compare-and-branch semantics (no flags register), 32 general-purpose registers, and fixed 8-byte instructions.
| Register | Width | Description |
|---|---|---|
| R0 | 64-bit | Hardwired zero (writes ignored) |
| R1–R30 | 64-bit | General-purpose |
| R31 (SP) | 64-bit | Stack pointer |
| PC | 64-bit | Program counter (full 64-bit IE64 address; legacy 25-bit/32 MB mask retired in PLAN_MAX_RAM.md slice 3 — historical note) |
No flags register. Conditional branches embed a register comparison directly (e.g., BEQ Rs, Rt, offset).
Initial State:
- PC =
$1000(program start) - SP (R31) =
$9F000(top of stack, grows downward) - All other registers = 0
All instructions are 8 bytes (64 bits), little-endian:
Byte 0: Opcode (8 bits)
Byte 1: Rd[4:0] (5 bits) | Size[1:0] (2 bits) | X (1 bit)
Byte 2: Rs[4:0] (5 bits) | unused (3 bits)
Byte 3: Rt[4:0] (5 bits) | unused (3 bits)
Bytes 4-7: imm32 (32-bit LE immediate)
Size codes: .B (8-bit), .W (16-bit), .L (32-bit), .Q (64-bit, default)
X bit: 0 = third operand is register (Rt), 1 = third operand is immediate (imm32)
| Mode | Syntax | Description | Example |
|---|---|---|---|
| Immediate | #value |
Constant value | move.l r1, #42 |
| Register | Rn |
Register contents | add r1, r2, r3 |
| Register-indirect (data) | (Rs) |
Memory at address in Rs | load.l r1, (r2) |
| Register-indirect (control) | (Rs) |
Transfer control to address in Rs | jmp (r5) |
| Displacement | offset(Rs) |
Memory/control at Rs + offset | load.l r1, 16(r2) |
| PC-relative | label |
Branch target relative to PC | bra loop |
| Mnemonic | Description | Example |
|---|---|---|
MOVE |
Move register/immediate to register | move.l r1, #100 |
MOVT |
Move to upper 32 bits | movt r1, #$DEAD |
MOVEQ |
Move with sign-extend 32→64 | moveq r1, #-1 |
LEA |
Load effective address | lea r1, offset(r2) |
| Mnemonic | Description | Example |
|---|---|---|
LOAD.x |
Load from memory (size suffix) | load.l r1, (r2) |
STORE.x |
Store to memory (size suffix) | store.l r1, (r2) |
Size suffixes: .b (8-bit), .w (16-bit), .l (32-bit), .q (64-bit)
| Mnemonic | Description |
|---|---|
ADD |
Add (register or immediate) |
SUB |
Subtract |
MULU |
Multiply unsigned |
MULS |
Multiply signed |
DIVU |
Divide unsigned |
DIVS |
Divide signed |
MOD |
Modulo |
NEG |
Negate |
| Mnemonic | Description |
|---|---|
AND |
Bitwise AND |
OR |
Bitwise OR |
EOR |
Bitwise exclusive OR |
NOT |
Bitwise NOT |
LSL |
Logical shift left |
LSR |
Logical shift right |
ASR |
Arithmetic shift right |
CLZ |
Count leading zeros (32-bit) |
All conditional branches compare two registers directly - no flags register:
| Mnemonic | Condition | Example |
|---|---|---|
BRA |
Always | bra loop |
BEQ |
Rs == Rt | beq r1, r2, equal |
BNE |
Rs != Rt | bne r1, r0, nonzero |
BLT |
Rs < Rt (signed) | blt r1, r2, less |
BGE |
Rs >= Rt (signed) | bge r1, r2, greater_eq |
BGT |
Rs > Rt (signed) | bgt r1, r2, greater |
BLE |
Rs <= Rt (signed) | ble r1, r2, less_eq |
BHI |
Rs > Rt (unsigned) | bhi r1, r2, higher |
BLS |
Rs <= Rt (unsigned) | bls r1, r2, lower_same |
JMP |
Register-indirect | jmp (r5) / jmp 16(r5) |
| Mnemonic | Description |
|---|---|
JSR |
Jump to subroutine - PC-relative (jsr label) or register-indirect (jsr (r5)) |
RTS |
Return from subroutine |
PUSH |
Push register onto stack |
POP |
Pop from stack to register |
| Mnemonic | Description |
|---|---|
NOP |
No operation |
HALT |
Halt processor |
SEI |
Set (enable) interrupts |
CLI |
Clear (disable) interrupts |
RTI |
Return from interrupt |
WAIT |
Wait for specified microseconds |
| Mnemonic | Description |
|---|---|
MTCR CRn, Rs |
Move register to control register (supervisor only) |
MFCR Rd, CRn |
Move control register to register (supervisor only; CR6/TP is readable from user mode) |
ERET |
Exception return: PC = FAULT_PC, switch to user mode (supervisor only) |
TLBFLUSH |
Flush entire software TLB (supervisor only) |
TLBINVAL Rs |
Invalidate TLB entry for virtual address in Rs (supervisor only) |
SYSCALL #imm32 |
Trap to supervisor mode with syscall number in imm32 |
SMODE Rd |
Read current privilege mode into Rd (1=supervisor, 0=user) |
| Mnemonic | Description |
|---|---|
CAS Rd, disp(Rs), Rt |
Compare-and-swap: if [addr]==Rd then [addr]=Rt; Rd=old value |
XCHG Rd, disp(Rs), Rt |
Exchange: [addr]=Rt; Rd=old value |
FAA Rd, disp(Rs), Rt |
Fetch-and-add: [addr]+=Rt; Rd=old value |
FAND Rd, disp(Rs), Rt |
Fetch-and-and: [addr]&=Rt; Rd=old value |
FOR Rd, disp(Rs), Rt |
Fetch-and-or: [addr]|=Rt; Rd=old value |
FXOR Rd, disp(Rs), Rt |
Fetch-and-xor: [addr]^=Rt; Rd=old value |
All atomic operations are 64-bit, sequentially consistent, and require 8-byte aligned addresses. Misaligned access traps with FAULT_MISALIGNED (cause 7).
The ie64asm assembler provides these convenience pseudo-instructions:
| Pseudo | Expansion | Description |
|---|---|---|
la Rd, addr |
lea Rd, addr(r0) |
Load address into register |
li Rd, #imm32 |
move.l Rd, #imm32 |
Load 32-bit immediate |
li Rd, #imm64 |
move.l Rd, #lo32 + movt Rd, #hi32 |
Load full 64-bit immediate |
beqz Rs, label |
beq Rs, r0, label |
Branch if zero |
bnez Rs, label |
bne Rs, r0, label |
Branch if not zero |
bltz Rs, label |
blt Rs, r0, label |
Branch if less than zero |
bgez Rs, label |
bge Rs, r0, label |
Branch if greater or equal to zero |
bgtz Rs, label |
bgt Rs, r0, label |
Branch if greater than zero |
blez Rs, label |
ble Rs, r0, label |
Branch if less or equal to zero |
include "ie64.inc"
start:
; Set up video
la r1, VIDEO_CTRL
move.l r2, #1
store.l r2, (r1)
; Loop with counter
move.l r10, #0 ; counter
move.l r11, #100 ; limit
.loop:
add.l r10, r10, #1
blt r10, r11, .loop ; compare-and-branch
; Subroutine call
jsr my_func
halt
my_func:
push r5
; ... work ...
pop r5
rtsThe IE64 includes a dedicated Floating-Point Unit (FPU) for IEEE-754 single- precision and register-pair double-precision arithmetic.
- F0–F15: 16 dedicated 32-bit registers for floating-point bit patterns.
- D0–D7: FP64 register pairs mapped as
D0=F0:F1,D1=F2:F3, ...D7=F14:F15. - FPSR: Status register containing overwritten condition codes (N, Z, I, NaN) and sticky exception flags (IO, DZ, OE, UE).
- FPCR: Control register for setting the rounding mode (Nearest, Zero, Floor, Ceil).
- Arithmetic: FADD, FSUB, FMUL, FDIV, FMOD, FABS, FNEG, FSQRT, FINT
- Compare: FCMP (three-way compare: returns -1, 0, or +1 in integer register Rd)
- Transcendentals: FSIN, FCOS, FTAN, FATAN, FLOG, FEXP, FPOW
- Movement/Conversion: FMOV, FLOAD, FSTORE, FCVTIF (int→float), FCVTFI (float→int), FMOVI, FMOVO (bitwise reinterpret)
- Status/Constants: FMOVSR, FMOVCR, FMOVSC, FMOVCC, FMOVECR (load ROM Pi, e, etc.)
- FP64 Arithmetic: DADD, DSUB, DMUL, DDIV, DMOD, DABS, DNEG, DSQRT, DINT
- FP64 Movement/Conversion: DMOV, DLOAD, DSTORE, DCMP, DCVTIF, DCVTFI, FCVTSD, FCVTDS
FPU instructions are unsized; the assembler rejects size suffixes on both the
f* and d* families. FP64 uses even-numbered operands because each double
occupies an even-odd register pair.
The IE64 shares the same MachineBus and memory-mapped device address space as all other Intuition Engine CPUs. IE64 sees the full active visible RAM (which may exceed 4 GiB on hosts with sufficient memory) — discoverable via CR_RAM_SIZE_BYTES and the SYSINFO_ACTIVE_RAM_LO/HI MMIO pair:
- All hardware registers at
$F0000–$FFFFFare accessible viaLOAD/STORE - VGA VRAM at
$A0000–$AFFFF, Video RAM at$100000+ - VRAM direct-write fast path for stores to Video RAM (
$100000+, attached VRAM window) - 64-bit bus operations (
Read64/Write64) with I/O region split semantics for device registers
The IE64 has an integrated timer and interrupt system:
- Interrupt vector: Internal
interruptVectorfield (set to 0 on reset) - Timer: Integrated CPU timer, decremented every 44100 cycles
- Timer state: Internal CPU fields (not memory-mapped timer registers)
Interrupt flow:
- Timer counts down; when it reaches zero, an interrupt fires
- If
interruptEnabledis true and not already in an ISR, CPU setsinInterrupt=true - CPU pushes PC to stack and jumps to
interruptVector - ISR executes and returns via
RTI(restores PC, clearsinInterrupt)
Instructions:
SEI- Enable interruptsCLI- Disable interruptsRTI- Return from interrupt (pops PC, clearsinInterrupt)
Note: The interrupt vector is currently set internally. Assembly-level vector programming is reserved for a future update.
- Use
-ie64flag to run IE64 binaries - File extension:
.ie64 - Use
ie64asmassembler withie64.incinclude file - Little-endian byte order
- Compare-and-branch model (no flags register - unlike IE32, M68K, Z80, 6502, x86)
- R0 is hardwired to zero (reads always return 0, writes are silently ignored)
.loperations zero-mask to 32 bits; use.qfor full 64-bit arithmetic- JIT compilation is enabled by default on supported platforms (x86-64 and ARM64); use
-nojitto force interpreter mode. On x86-64, M68K, Z80, 6502, and x86 cores also have JIT backends - JIT and interpreter produce identical results for all programs (verified by test suite)
- Full ISA reference: IE64_ISA.md
- Assembly cookbook: IE64_COOKBOOK.md
The Intuition Engine includes a full port of Lee Davison's Enhanced BASIC (EhBASIC) for the IE64 CPU. The interpreter is a ground-up reimplementation in IE64 assembly, using IEEE 754 single-precision (FP32) arithmetic and providing direct access to all hardware subsystems from BASIC.
# Run with embedded BASIC image (requires 'make basic' build)
./bin/IntuitionEngine -basic
# Run with a custom BASIC binary
./bin/IntuitionEngine -basic-image path/to/custom.ie64
# Boot EmuTOS from the BASIC prompt (requires 'make basic-emutos' build)
# At the Ready prompt, type: EMUTOS- Core language: PRINT, LET, IF/THEN/ELSE, FOR/NEXT/STEP, WHILE/WEND, DO/LOOP, GOTO, GOSUB/RETURN, DATA/READ, INPUT, DIM (multi-dimensional arrays), string variables and operations
- Built-in functions: ABS, INT, SQR, RND, SGN, SIN, COS, TAN, ATN, LOG, EXP, LEN, ASC, VAL, CHR$, LEFT$, RIGHT$, MID$, STR$, HEX$, BIN$, PEEK, POKE, USR, MAX, MIN, BITTST, FRE
- Video commands: SCREEN, CLS, PLOT, LINE, CIRCLE, BOX, PALETTE, LOCATE, COLOR, SCROLL, VSYNC, plus ULA, TED, ANTIC/GTIA, and full Voodoo 3D pipeline (vertices, triangles, textures, Z-buffer, alpha blending, fog)
- Audio commands: SOUND, ENVELOPE, GATE, WAVE, FILTER, REVERB, OVERDRIVE, SWEEP, SYNC, RINGMOD, plus PSG/SID/POKEY/TED/AHX/MOD playback and STATUS queries
- System commands: CALL (machine code subroutine), USR (call with return value), POKE8/PEEK8, DOKE/DEEK, WAIT, BLIT, COPPER, TRON/TROFF (trace mode)
- Coprocessor commands: COSTART, COSTOP, COWAIT (worker lifecycle); COCALL(), COSTATUS() (async cross-CPU RPC to IE32/6502/M68K/Z80/x86 workers)
- Machine code interface: CALL and USR use register-indirect JSR to invoke IE64 assembly routines; R8 carries return values (EhBASIC convention; see
IE64_ABI.mdfor IntuitionOS ABI) - Terminal editor: Insert mode with character shifting, key repeat, Ctrl shortcuts (A/E/K/U/L), Ctrl+Arrow word movement, command history (Ctrl+Up/Down), Page Up/Down and mouse wheel scrollback navigation, Shift+Arrow text selection with clipboard copy/cut/paste (Ctrl+Shift+C/X/V)
SOUND PLAY "music.ext" [,subsong]starts music playback and auto-detects format by extension:.sid,.ym,.ay,.vgm,.vgz,.vtx,.sndh,.pt3,.pt2,.pt1,.stc,.sqt,.asc,.ftc,.sap,.ted,.prg,.ahx,.mod,.wav(stops any currently playing track first)SOUND STOPstops current music playback (SOUND PLAY STOPis also accepted)RUNexecutes the current BASIC program;RUN "program.ie64"(or.iex/.ie32/.ie68/.ie86/.ie80/.ie65) loads and launches an external binary
10 SCREEN &H13
20 FOR I = 0 TO 199
30 PLOT 160, I, I AND 255
40 NEXT
50 VSYNCFull reference: ehbasic_ie64.md
The IE64 includes a minimal MMU for memory protection and virtual address translation, designed to support microkernel operating systems.
- Paged virtual memory: 4 KiB pages, single-level page table (8192 entries, 64 KiB)
- Privilege levels: Supervisor (boot default) and User. Mode transitions only via trap entry (SYSCALL, faults) and ERET
- Per-page permissions: Present, Read, Write, Execute, User-accessible, Accessed (A), Dirty (D)
- A/D bits: Hardware-maintained Accessed and Dirty bits in each PTE for page reclamation and working-set estimation
- W^X support: Pages with X=0 are non-executable - code pages are X=1,W=0; data/stack pages are W=1,X=0
- Execute-only user text (M15.6 R4): executable user pages no longer imply readability;
validate_user_exec_rangekeys onX, and ELF/descriptor loaders preservePF_Xwithout forcingR - SMEP/SMAP-equivalent guards (M15.6):
SKEF(fault on supervisor fetch from user page) andSKAC(fault on supervisor data access to user page) bits inMMU_CTRL, plus an explicitSUAlatch toggled by privilegedSUAEN/SUADISopcodes so kernel user-memory touches must bracket an access window - Architectural trap-frame stack (M15.6): nested-trap state (
FAULT_PC,PREV_MODE,SAVED_SUA,FAULT_ADDR,FAULT_CAUSE) is preserved by the CPU across trap entry / ERET; handlers no longer need manual MFCR/MTCR save/restore to survive a nested synchronous trap - Zero-on-free confidentiality (M15.6):
FreeMemand shared-memory last-reference teardown scrub backing pages before they return to the allocator pool, so a later task cannot observe prior-owner bytes - Per-task quotas (M15.6 G3): per-task quotas (pages, ports, waiters, shared mappings, grants) cap blast radius so one task cannot exhaust kernel-tracked resources needed by its siblings
- MapShared permission preservation (M15.6): shared-memory consumers must now pass an explicit
MAPF_READ/MAPF_WRITEmask, and the kernel preserves that requested access in the consumer PTEs instead of silently widening every mapping to RW - Stack guard pages (M15.6 R1): user stacks and the kernel stack each reserve one non-present page below the mapped stack floor, so overflow faults cleanly as
FAULT_NOT_PRESENT - Minimal kernel stack canary (M15.6 R8): an optional
KERNEL_STACK_CANARY_ENABLEDbuild flag writes one sentinel word at the bottom of the mapped kernel stack page and checks it on trap / interrupt entry. This is a narrow diagnostic tripwire; the guard page remains the real hardening boundary. - Heap guard pages (M15.6 R2):
AllocMem(MEMF_GUARD)reserves one non-present page on each side of the mapped body, andMapSharedpreserves that contract for guarded shared allocations, so one-page underruns/overruns fault asFAULT_NOT_PRESENT - Size-arithmetic hardening (M15.6 R3): allocation/page-count conversions are range-clamped before
size + offsetstyle arithmetic, so oversizedAllocMemand trusted-ELF bridge requests fail cleanly instead of wrapping - Scoped atomic RMW groundwork (M15.6 R9): the kernel now ships named
m16_atomic_row_try_transition,m16_atomic_list_push_head, andm16_atomic_list_detach_allhelpers built on IE64CAS/XCHG, freezing the exact helper layer M16 will use for registry-row transitions andwaiters_head/opens_headupdates without widening M15.6 into a general lock-free-kernel effort - Software TLB: 64-entry direct-mapped cache of page table translations
- 15 control registers: Page table base (PTBR), fault address/cause/PC, trap vector, MMU control, thread pointer (TP), interrupt vector (INTR_VEC), kernel/user stack pointers (KSP/USP), timer period/count/control, previous mode (PREV_MODE), saved SUA (SAVED_SUA)
- 9 MMU instructions: MTCR, MFCR, ERET, TLBFLUSH, TLBINVAL, SYSCALL, SMODE, SUAEN, SUADIS
- Automatic stack switching: Kernel/user stack separation via KSP/USP on privilege transitions
- Unified timer interrupts: ERET-based timer delivery when MMU is enabled (legacy RTI model when MMU is off)
- JIT compatible: JIT compiler works with MMU enabled (Stage 1: memory ops bail to interpreter). Host JIT memory itself is W^X as of M15.6 (dual-mapped RW emit view + RX execution view, never simultaneously writable and executable)
Full reference: IE64_ISA.md §12 | Programming examples: IE64_COOKBOOK.md
This section documents the IE32 assembly language used with the ie32asm assembler. For IE64, 6502, Z80, M68K, and x86 programming, use their respective assemblers (ie64asm, ca65, vasmz80_std, vasmm68k_mot, NASM/FASM) with the include files documented in Section 13.4.
The Intuition Engine assembly language provides a straightforward way to program the system while maintaining access to all hardware features.
Every assembly program follows this basic structure:
; Program header with description
; Example: Simple colour toggle program
.equ VIDEO_MODE, 0xF0004 ; Define hardware constants
start: ; Main entry point
LOAD A, #0 ; Initialise mode index
JSR set_video_mode
main_loop:
JSR update_mode ; Update mode state
JMP main_loop ; Continue main loop
; Subroutines follow main program
set_video_mode:
STORE A, @VIDEO_MODE
RTSThe assembler supports these directives:
.equ SYMBOL, VALUE ; Define a constant
.word VALUE ; Define a 32-bit word
.byte VALUE ; Define an 8-bit byte
.space SIZE ; Reserve bytes of space
.org ADDRESS ; Set assembly addressThe .org directive provides control over code placement:
; Example memory organisation
.org 0x0000 ; Start at vector table
.word isr_handler ; Set up interrupt vector
.org 0x1000 ; Place main program
start:
JSR init
SEI
JMP mainWhen working with memory, consider alignment and efficiency:
; Efficient memory copy
copy_memory:
LOAD X, #0 ; Source index
LOAD Y, #0 ; Destination index
LOAD Z, #100 ; Word count
copy_loop:
LOAD A, [X] ; Load from source
STORE A, [Y] ; Store to destination
ADD X, #4 ; Next word (32-bit aligned)
ADD Y, #4
SUB Z, #1
JNZ Z, copy_loop
RTSThe stack is essential for subroutines and temporary storage:
calculate:
PUSH A ; Save registers
PUSH X
; Perform calculation
LOAD X, #0
ADD X, A
POP X ; Restore registers
POP A ; in reverse order
RTSInterrupt handlers must preserve register state:
isr_handler:
PUSH A ; Save registers
PUSH X
JSR process_irq_event
POP X ; Restore registers
POP A
RTI ; Return from interruptThe Intuition Engine provides a powerful custom audio synthesizer alongside four emulated classic sound chips (PSG, POKEY, SID, TED). The custom audio chip offers modern synthesis capabilities while maintaining the retro aesthetic.
The custom audio chip is a 10-channel synthesizer with advanced features:
- 4 Base Channels: Square, Triangle, Sine, Noise (sawtooth available via legacy register alias on channel 0)
- 6 SID Extension Channels: 3 SID2 + 3 SID3 for multi-SID .sid file playback
- 4 Flexible Synth Channels: Uniform register interface, any waveform type per channel
- Per-Voice ADSR Envelopes: 16-bit attack/decay/release times, 8-bit sustain level
- Pulse Width Modulation: Variable duty cycle with automatic LFO
- Frequency Sweep: Portamento and pitch bend effects
- Hard Sync: Slave oscillator phase reset by master
- Ring Modulation: Amplitude modulation between channels
- Global Resonant Filter: Low-pass, high-pass, band-pass with resonance
- Effects: Overdrive distortion and reverb
Oscillators → Envelopes → Mix → Filter → Overdrive → Reverb → Output
Sample rate: 44.1kHz with 32-bit floating-point internal processing
| Register Block | IE32/IE64/M68K Address | Z80/6502 Address | Description |
|---|---|---|---|
| Global Control | $F0800-$F0807 |
$F800-$F807 |
Master audio control, envelope shape |
| Filter | $F0820-$F0833 |
$F820-$F833 |
Cutoff, resonance, type, modulation |
| Square Channel | $F0900-$F093F |
$F900-$F93F |
Frequency, volume, ADSR, duty, PWM, sweep |
| Triangle Channel | $F0940-$F097F |
$F940-$F97F |
Frequency, volume, ADSR, sweep |
| Sine Channel | $F0980-$F09BF |
$F980-$F9BF |
Frequency, volume, ADSR, sweep |
| Noise Channel | $F09C0-$F09FF |
$F9C0-$F9FF |
Frequency, volume, ADSR, noise mode |
| Sync Sources | $F0A00-$F0A0F |
$FA00-$FA0F |
Hard sync source per channel |
| Ring Mod Sources | $F0A10-$F0A1F |
$FA10-$FA1F |
Ring modulation source per channel |
| Sawtooth Channel | $F0A20-$F0A5F |
$FA20-$FA5F |
Frequency, volume, ADSR, sweep |
| Overdrive | $F0A40-$F0A43 |
$FA40-$FA43 |
Drive amount (0-255) |
| Reverb | $F0A50-$F0A57 |
$FA50-$FA57 |
Mix level and decay time |
| Flex Channel 0 | $F0A80-$F0ABF |
$FA80-$FABF |
Configurable waveform channel |
| Flex Channel 1 | $F0AC0-$F0AFF |
$FAC0-$FAFF |
Configurable waveform channel |
| Flex Channel 2 | $F0B00-$F0B3F |
$FB00-$FB3F |
Configurable waveform channel |
| Flex Channel 3 | $F0B40-$F0B7F |
$FB40-$FB7F |
Configurable waveform channel |
| Offset | IE32 Address | Name | Description |
|---|---|---|---|
| +$00 | $F0800 |
AUDIO_CTRL | Master audio control |
| +$04 | $F0804 |
ENV_SHAPE | Global envelope shape (0=ADSR, 1=Saw Up, 2=Saw Down, 3=Loop, 4=SID-style) |
| Offset | IE32 Address | Name | Range | Description |
|---|---|---|---|---|
| +$00 | $F0820 |
FILTER_CUTOFF | 0-65535 | Cutoff frequency (exponential 20Hz-20kHz) |
| +$04 | $F0824 |
FILTER_RESONANCE | 0-255 | Resonance/Q factor |
| +$08 | $F0828 |
FILTER_TYPE | 0-3 | 0=Off, 1=Low-pass, 2=High-pass, 3=Band-pass |
| +$0C | $F082C |
FILTER_MOD_SOURCE | 0-3 | Modulation source channel |
| +$10 | $F0830 |
FILTER_MOD_AMOUNT | 0-255 | Modulation depth |
Each dedicated channel has a similar register layout:
| Offset | Name | Description |
|---|---|---|
| +$00 | FREQ | Frequency (16.8 fixed-point Hz, value = Hz * 256) |
| +$04 | VOL | Volume 0-255 (32-bit, only low byte used) |
| +$08 | CTRL | Control bits (see below) |
| +$0C | ATTACK | Attack time in ms (16-bit) |
| +$10 | DECAY | Decay time in ms (16-bit) |
| +$14 | SUSTAIN | Sustain level 0-255 (16-bit) |
| +$18 | RELEASE | Release time in ms (16-bit) |
| +$1C | DUTY | Duty cycle 0-65535 (square only, 32768=50%) |
| +$20 | PWM_RATE | PWM oscillation rate (square only) |
| +$24 | PWM_DEPTH | PWM depth 0-65535 (square only) |
| +$28 | SWEEP_RATE | Frequency sweep rate |
| +$2C | SWEEP_DIR | Sweep direction (0=down, 1=up) |
| +$30 | SWEEP_AMT | Sweep amount per step |
| +$34 | TARGET | Target frequency for sweep |
Noise channel additional register:
| Offset | Name | Values | Description |
|---|---|---|---|
| +$1C | NOISE_MODE | 0-3 | 0=White, 1=Periodic, 2=Metallic, 3=PSG-style |
| Bit | Name | Description |
|---|---|---|
| 0 | GATE | Trigger envelope (1=attack, 0=release) |
| 1 | PWM_EN | Enable pulse width modulation |
| 2 | SWEEP_EN | Enable frequency sweep |
| 3 | SYNC_EN | Enable hard sync to source channel |
| 4 | RING_EN | Enable ring modulation from source |
| 5 | FILTER_EN | Route channel through global filter |
Each flexible channel is 64 bytes ($40) with full synthesis control:
| Offset | Name | Description |
|---|---|---|
| +$00 | FREQ | Frequency (16.8 fixed-point Hz, value = Hz * 256) |
| +$04 | VOL | Volume 0-255 (32-bit) |
| +$08 | CTRL | Control bits (same as dedicated channels) |
| +$0C | DUTY | Duty cycle for square wave |
| +$10 | SWEEP | Frequency sweep control |
| +$14 | ATK | Attack time in ms (16-bit) |
| +$18 | DEC | Decay time in ms (16-bit) |
| +$1C | SUS | Sustain level 0-255 (16-bit) |
| +$20 | REL | Release time in ms (16-bit) |
| +$24 | WAVE_TYPE | Waveform selection (0=square, 1=triangle, 2=sine, 3=noise, 4=saw) |
| +$28 | PWM_CTRL | PWM modulation control |
| +$2C | NOISEMODE | Noise mode (0=white, 1=periodic, 2=metallic, 3=PSG-style) |
| +$30 | PHASE | Reset phase position |
| +$34 | RINGMOD | Ring modulation source (bit7=enable, bits0-2=source channel) |
| +$38 | SYNC | Hard sync source (bit7=enable, bits0-2=source channel) |
| +$3C | DAC | DAC mode: signed 8-bit sample (bypasses waveform and envelope) |
Waveform Types:
| Value | Name | Description |
|---|---|---|
| 0 | WAVE_SQUARE | Square/pulse wave with PWM support |
| 1 | WAVE_TRIANGLE | Triangle wave |
| 2 | WAVE_SINE | Pure sine wave |
| 3 | WAVE_NOISE | Noise generator |
| 4 | WAVE_SAWTOOTH | Sawtooth wave |
Each channel offers different synthesis capabilities:
Features:
- Variable duty cycle control
- PWM modulation
- Frequency sweep
- Ring modulation support
- ADSR envelope
IE32:
setup_square:
LOAD A, #112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE A, @SQUARE_FREQ
LOAD A, #128 ; 50% duty cycle
STORE A, @SQUARE_DUTY
LOAD A, #1 ; Enable PWM
STORE A, @SQUARE_PWM_CTRL
LOAD A, #10 ; 10ms attack
STORE A, @SQUARE_ATK
LOAD A, #20 ; 20ms decay
STORE A, @SQUARE_DEC
LOAD A, #192 ; 75% sustain
STORE A, @SQUARE_SUS
LOAD A, #100 ; 100ms release
STORE A, @SQUARE_REL
RTSM68K:
setup_square:
move.l #112640,SQUARE_FREQ.l ; 440 Hz (16.8 fixed-point: 440*256)
move.l #128,SQUARE_DUTY.l ; 50% duty cycle
move.l #1,SQUARE_PWM_CTRL.l ; Enable PWM
move.l #10,SQUARE_ATK.l ; Attack
move.l #20,SQUARE_DEC.l ; Decay
move.l #192,SQUARE_SUS.l ; Sustain
move.l #100,SQUARE_REL.l ; Release
rtsZ80:
setup_square:
STORE32 SQUARE_FREQ,112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE32 SQUARE_DUTY,128 ; 50% duty cycle
STORE32 SQUARE_PWM_CTRL,1 ; Enable PWM
STORE32 SQUARE_ATK,10 ; Attack
STORE32 SQUARE_DEC,20 ; Decay
STORE32 SQUARE_SUS,192 ; Sustain
STORE32 SQUARE_REL,100 ; Release
ret6502:
setup_square:
STORE32 SQUARE_FREQ, 112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE32 SQUARE_DUTY, 128 ; 50% duty cycle
STORE32 SQUARE_PWM_CTRL, 1 ; Enable PWM
STORE32 SQUARE_ATK, 10 ; Attack
STORE32 SQUARE_DEC, 20 ; Decay
STORE32 SQUARE_SUS, 192 ; Sustain
STORE32 SQUARE_REL, 100 ; Release
rtsFeatures:
- Pure harmonic content
- Frequency sweep
- Ring modulation support
- ADSR envelope
Features:
- Clean tonal output
- Frequency sweep
- Ring modulation support
- ADSR envelope
Features:
- Three noise types:
- White noise (LFSR-based)
- Periodic noise
- Metallic noise
- Frequency sweep
- ADSR envelope
Features:
- Classic sawtooth waveform (ramps from -1 to +1)
- polyBLEP anti-aliasing for cleaner high-frequency output
- Frequency sweep
- Ring modulation support
- ADSR envelope
IE32:
setup_sawtooth:
LOAD A, #112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE A, @SAW_FREQ
LOAD A, #192 ; 75% volume
STORE A, @SAW_VOL
LOAD A, #10 ; Attack
STORE A, @SAW_ATK
LOAD A, #20 ; Decay
STORE A, @SAW_DEC
LOAD A, #192 ; Sustain
STORE A, @SAW_SUS
LOAD A, #100 ; Release
STORE A, @SAW_REL
LOAD A, #1 ; Enable
STORE A, @SAW_CTRL
RTSM68K:
setup_sawtooth:
move.l #112640,SAW_FREQ.l ; 440 Hz (16.8 fixed-point: 440*256)
move.l #192,SAW_VOL.l
move.l #10,SAW_ATK.l
move.l #20,SAW_DEC.l
move.l #192,SAW_SUS.l
move.l #100,SAW_REL.l
move.l #1,SAW_CTRL.l
rtsZ80:
setup_sawtooth:
STORE32 SAW_FREQ,112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE32 SAW_VOL,192
STORE32 SAW_ATK,10
STORE32 SAW_DEC,20
STORE32 SAW_SUS,192
STORE32 SAW_REL,100
STORE32 SAW_CTRL,1
ret6502:
setup_sawtooth:
STORE32 SAW_FREQ, 112640 ; 440 Hz (16.8 fixed-point: 440*256)
STORE32 SAW_VOL, 192
STORE32 SAW_ATK, 10
STORE32 SAW_DEC, 20
STORE32 SAW_SUS, 192
STORE32 SAW_REL, 100
STORE32 SAW_CTRL, 1
rtsThe sound system supports complex inter-channel modulation for creating rich, evolving timbres.
Hard sync forces a slave oscillator to reset its phase whenever the master oscillator completes a cycle. This creates complex harmonic content that changes with the frequency ratio between oscillators.
Sync Source Registers:
| Register | IE32 Address | Description |
|---|---|---|
| SYNC_SOURCE_CH0 | $F0A00 |
Sync source for channel 0 |
| SYNC_SOURCE_CH1 | $F0A04 |
Sync source for channel 1 |
| SYNC_SOURCE_CH2 | $F0A08 |
Sync source for channel 2 |
| SYNC_SOURCE_CH3 | $F0A0C |
Sync source for channel 3 |
Set source to channel number (0-3) or $FF to disable.
IE32:
; Sync channel 0 to channel 1 (ch1 is master)
LOAD A, #1
STORE A, @SYNC_SOURCE_CH0
LOAD A, #CTRL_GATE | CTRL_SYNC_EN ; Enable sync in control
STORE A, @SQUARE_CTRLM68K:
move.l #1,SYNC_SOURCE_CH0.l
move.l #CTRL_GATE|CTRL_SYNC_EN,SQUARE_CTRL.lRing modulation multiplies two signals together, producing sum and difference frequencies (sidebands). This creates metallic, bell-like tones.
Ring Mod Source Registers:
| Register | IE32 Address | Description |
|---|---|---|
| RING_MOD_SOURCE_CH0 | $F0A10 |
Ring mod source for channel 0 |
| RING_MOD_SOURCE_CH1 | $F0A14 |
Ring mod source for channel 1 |
| RING_MOD_SOURCE_CH2 | $F0A18 |
Ring mod source for channel 2 |
| RING_MOD_SOURCE_CH3 | $F0A1C |
Ring mod source for channel 3 |
IE32:
; Ring modulate channel 0 with channel 1
LOAD A, #1
STORE A, @RING_MOD_SOURCE_CH0
LOAD A, #CTRL_GATE | CTRL_RING_EN
STORE A, @SQUARE_CTRLM68K:
move.l #1,RING_MOD_SOURCE_CH0.l
move.l #CTRL_GATE|CTRL_RING_EN,SQUARE_CTRL.lAutomatic frequency changes for pitch bend, portamento, and laser effects.
Sweep Registers (per channel):
| Offset | Name | Description |
|---|---|---|
| SWEEP_RATE | Rate of frequency change | |
| SWEEP_DIR | Direction: 0=down, 1=up | |
| SWEEP_AMT | Amount per step | |
| TARGET | Target frequency (sweep stops here) |
IE32:
; Sweep square channel up from 220Hz to 880Hz
LOAD A, #220
STORE A, @SQUARE_FREQ ; Start frequency
LOAD A, #880
STORE A, @SQUARE_TARGET ; End frequency
LOAD A, #1
STORE A, @SQUARE_SWEEP_DIR ; Sweep up
LOAD A, #10
STORE A, @SQUARE_SWEEP_RATE ; Sweep speed
LOAD A, #CTRL_GATE | CTRL_SWEEP_EN
STORE A, @SQUARE_CTRL ; Enable sweepFor square wave channels, PWM automatically varies the duty cycle for a rich, animated sound.
IE32:
; Enable PWM on square channel
LOAD A, #32768 ; 50% base duty cycle
STORE A, @SQUARE_DUTY
LOAD A, #512 ; PWM rate (Hz * 256)
STORE A, @SQUARE_PWM_RATE
LOAD A, #16384 ; PWM depth
STORE A, @SQUARE_PWM_DEPTH
LOAD A, #CTRL_GATE | CTRL_PWM_EN
STORE A, @SQUARE_CTRLThe system provides global audio processing applied after channel mixing.
A resonant state-variable filter with three modes:
| Type Value | Mode | Description |
|---|---|---|
| 0 | Off | Filter bypassed |
| 1 | Low-pass | Attenuates frequencies above cutoff |
| 2 | High-pass | Attenuates frequencies below cutoff |
| 3 | Band-pass | Passes frequencies around cutoff |
Filter Registers:
| Register | IE32 Address | Range | Description |
|---|---|---|---|
| FILTER_CUTOFF | $F0820 |
0-65535 | Cutoff frequency (exponential mapping) |
| FILTER_RESONANCE | $F0824 |
0-255 | Resonance/Q (higher = more emphasis) |
| FILTER_TYPE | $F0828 |
0-3 | Filter mode |
| FILTER_MOD_SOURCE | $F082C |
0-3 | Channel to modulate cutoff |
| FILTER_MOD_AMOUNT | $F0830 |
0-255 | Modulation depth |
The cutoff uses exponential mapping for musical control (human hearing is logarithmic). Value 0 maps to 20Hz, 65535 maps to 20kHz.
To route a channel through the filter, set bit 5 (CTRL_FILTER_EN) in its control register.
IE32:
LOAD A, #32768 ; Cutoff (~1kHz)
STORE A, @FILTER_CUTOFF
LOAD A, #128 ; Medium resonance
STORE A, @FILTER_RESONANCE
LOAD A, #1 ; Low-pass mode
STORE A, @FILTER_TYPE
; Route square channel through filter
LOAD A, #CTRL_GATE | CTRL_FILTER_EN
STORE A, @SQUARE_CTRLM68K:
move.l #32768,FILTER_CUTOFF.l
move.l #128,FILTER_RESONANCE.l
move.l #1,FILTER_TYPE.l
move.l #CTRL_GATE|CTRL_FILTER_EN,SQUARE_CTRL.lZ80:
SET_FILTER FILT_LOWPASS,32768,128
; Enable filter on square channel
FILTER_SQ_ON6502:
SET_FILTER FILT_LOWPASS, 32768, 128
FILTER_SQ_ONSoft-clipping distortion for adding grit and harmonics to the output.
| Register | IE32 Address | Range | Description |
|---|---|---|---|
| OVERDRIVE_CTRL | $F0A40 |
0-255 | 0=off, 1-255=distortion amount |
Higher values produce more aggressive clipping. Values around 32-64 add subtle warmth, while 128+ creates heavy distortion.
IE32:
LOAD A, #64 ; Moderate overdrive
STORE A, @OVERDRIVE_CTRLM68K:
move.l #64,OVERDRIVE_CTRL.lZ80:
SET_OVERDRIVE 646502:
SET_OVERDRIVE 64Stereo reverb with adjustable mix and decay time.
| Register | IE32 Address | Range | Description |
|---|---|---|---|
| REVERB_MIX | $F0A50 |
0-255 | Dry/wet mix (0=dry, 255=wet) |
| REVERB_DECAY | $F0A54 |
0-65535 | Decay time in ms |
IE32:
LOAD A, #128 ; 50% wet/dry mix
STORE A, @REVERB_MIX
LOAD A, #2000 ; 2 second decay
STORE A, @REVERB_DECAYM68K:
move.l #128,REVERB_MIX.l
move.l #2000,REVERB_DECAY.lZ80:
SET_REVERB 128,20006502:
SET_REVERB 128, 2000The PSG chip emulates the General Instrument AY-3-8910 and Yamaha YM2149, providing three channels of square wave synthesis with noise and envelope capabilities. This chip powered the sound in countless 8-bit computers including the ZX Spectrum 128, Amstrad CPC, Atari ST, and MSX. VGM files targeting the Texas Instruments SN76489 (Sega Master System, Game Gear, BBC Micro, ColecoVision) are also supported via automatic conversion to AY register writes during VGM parsing.
- Three independent square wave tone generators
- One noise generator (shared across channels)
- Hardware envelope generator with 8 shape patterns
- Per-channel mixer control (tone/noise enable)
- 4-bit volume control per channel (or envelope)
- PSG+ enhanced audio processing mode
- Support for .YM, .AY, .VGM, .VGZ, and .SNDH file playback (VGM supports both AY-3-8910 and SN76489 chip data; .AY files auto-detect ZX Spectrum, Amstrad CPC, and MSX with correct clock speeds)
Each channel has a 12-bit frequency divider:
- Frequency = Clock / (16 × TP) where TP is the tone period (1-4095)
- Channel A: Registers 0-1 (fine/coarse tune)
- Channel B: Registers 2-3 (fine/coarse tune)
- Channel C: Registers 4-5 (fine/coarse tune)
- 5-bit noise period (Register 6)
- Pseudo-random output from 17-bit LFSR
- Can be mixed with any tone channel
Bit 0: Channel A tone enable (0=on, 1=off)
Bit 1: Channel B tone enable
Bit 2: Channel C tone enable
Bit 3: Channel A noise enable (0=on, 1=off)
Bit 4: Channel B noise enable
Bit 5: Channel C noise enable
Bits 6-7: I/O port direction (directly mapped only)
- Registers 8-10: Channel A/B/C amplitude (0-15, or bit 4 set for envelope)
- Registers 11-12: Envelope period (16-bit)
- Register 13: Envelope shape (8 patterns)
| Value | Shape | Description |
|---|---|---|
| 0-3 | ╲____ | Decay to zero, hold |
| 4-7 | /‾‾‾‾ | Attack to max, hold |
| 8 | ╲╲╲╲╲ | Repeating decay (sawtooth down) |
| 9 | ╲____ | Decay to zero, hold |
| 10 | ╲/╲/╲ | Repeating decay-attack (triangle) |
| 11 | ╲‾‾‾‾ | Decay to zero, then hold max |
| 12 | ///// | Repeating attack (sawtooth up) |
| 13 | /‾‾‾‾ | Attack to max, hold |
| 14 | /╲/╲/ | Repeating attack-decay (triangle) |
| 15 | /_____ | Attack to max, then hold zero |
Configure PSG channel A for a 440Hz tone with envelope:
IE32:
; Configure PSG channel A for a 440Hz tone with envelope
LOAD A, #0xFE ; Tone period low byte (440Hz approx)
STORE A, @0x0F0C00 ; Register 0: Channel A fine tune
LOAD A, #0x00 ; Tone period high byte
STORE A, @0x0F0C01 ; Register 1: Channel A coarse tune
LOAD A, #0x3E ; Enable tone A, disable noise
STORE A, @0x0F0C07 ; Register 7: Mixer
LOAD A, #0x10 ; Use envelope for volume
STORE A, @0x0F0C08 ; Register 8: Channel A amplitude
LOAD A, #0x00 ; Envelope period low
STORE A, @0x0F0C0B ; Register 11: Envelope fine
LOAD A, #0x10 ; Envelope period high
STORE A, @0x0F0C0C ; Register 12: Envelope coarse
LOAD A, #0x0E ; Triangle envelope shape
STORE A, @0x0F0C0D ; Register 13: Envelope shapeM68K:
; Configure PSG channel A for a 440Hz tone with envelope
move.b #$FE,$F0C00.l ; Register 0: Channel A fine tune
move.b #$00,$F0C01.l ; Register 1: Channel A coarse tune
move.b #$3E,$F0C07.l ; Register 7: Mixer (tone A on)
move.b #$10,$F0C08.l ; Register 8: Envelope mode
move.b #$00,$F0C0B.l ; Register 11: Envelope fine
move.b #$10,$F0C0C.l ; Register 12: Envelope coarse
move.b #$0E,$F0C0D.l ; Register 13: Triangle shapeZ80:
; Configure PSG channel A for a 440Hz tone with envelope
; Z80 uses port I/O: port $F0 = register select, port $F1 = data
ld a,0 ; Select register 0 (fine tune)
out ($F0),a
ld a,$FE ; Tone period low byte
out ($F1),a
ld a,1 ; Select register 1 (coarse tune)
out ($F0),a
ld a,$00 ; Tone period high byte
out ($F1),a
ld a,7 ; Select register 7 (mixer)
out ($F0),a
ld a,$3E ; Enable tone A
out ($F1),a
ld a,8 ; Select register 8 (amplitude)
out ($F0),a
ld a,$10 ; Envelope mode
out ($F1),a
ld a,11 ; Select register 11 (envelope fine)
out ($F0),a
ld a,$00
out ($F1),a
ld a,12 ; Select register 12 (envelope coarse)
out ($F0),a
ld a,$10
out ($F1),a
ld a,13 ; Select register 13 (shape)
out ($F0),a
ld a,$0E ; Triangle shape
out ($F1),a6502:
; Configure PSG channel A for a 440Hz tone with envelope
; 6502 uses memory-mapped I/O at $D400-$D40D
lda #$FE
sta $D400 ; Register 0: Channel A fine tune
lda #$00
sta $D401 ; Register 1: Channel A coarse tune
lda #$3E
sta $D407 ; Register 7: Mixer (tone A on)
lda #$10
sta $D408 ; Register 8: Envelope mode
lda #$00
sta $D40B ; Register 11: Envelope fine
lda #$10
sta $D40C ; Register 12: Envelope coarse
lda #$0E
sta $D40D ; Register 13: Triangle shapeThe PSG player supports multiple music file formats with automatic detection:
- .ym - YM2149 register dump frames (50Hz playback)
- .ay - ZXAYEMUL format with embedded Z80 code (ZX Spectrum 1.77 MHz, Amstrad CPC 1.0 MHz, MSX 1.79 MHz auto-detected)
- .sndh - Atari ST format with embedded M68K code
- .vgm - Video Game Music format with timed PSG events (AY-3-8910 native; SN76489 auto-converted to AY registers)
To play a file, embed the data in your program and set the player registers:
IE32:
; Play a .ym file with looping
LOAD A, #1
STORE A, @PSG_PLUS_CTRL ; Enable PSG+ enhanced audio
LOAD A, #music_data ; Address of embedded music
STORE A, @PSG_PLAY_PTR
LOAD A, #music_data_end - music_data
STORE A, @PSG_PLAY_LEN
LOAD A, #5 ; bit0=start, bit2=loop
STORE A, @PSG_PLAY_CTRL
; Embedded music data
music_data:
.incbin "music.ym"
music_data_end:M68K:
; Play a .ym file with looping
move.b #1,PSG_PLUS_CTRL.l ; Enable PSG+ enhanced audio
lea music_data,a0
move.l a0,PSG_PLAY_PTR.l
move.l #music_data_end-music_data,PSG_PLAY_LEN.l
move.l #5,PSG_PLAY_CTRL.l ; bit0=start, bit2=loop
music_data:
incbin "music.ym"
music_data_end:Z80:
; Play a .ym file with looping
ld a,1
ld (PSG_PLUS_CTRL),a ; Enable PSG+ enhanced audio
SET_PSG_PTR music_data
SET_PSG_LEN (music_data_end-music_data)
ld a,5 ; bit0=start, bit2=loop
ld (PSG_PLAY_CTRL),a
music_data:
incbin "music.ym"
music_data_end:6502:
; Play a .ym file with looping
lda #1
sta PSG_PLUS_CTRL ; Enable PSG+ enhanced audio
STORE32 PSG_PLAY_PTR_0, music_data
STORE32 PSG_PLAY_LEN_0, (music_data_end-music_data)
lda #5 ; bit0=start, bit2=loop
sta PSG_PLAY_CTRL
music_data:
.incbin "music.ym"
music_data_end:Playback Control:
- Write
1to PSG_PLAY_CTRL to start playback - Write
2to PSG_PLAY_CTRL to stop playback - Write
5to PSG_PLAY_CTRL to start with looping (bit0 + bit2) - Read PSG_PLAY_STATUS bit 0 to check if playing (1=busy, 0=stopped)
- Read PSG_PLAY_STATUS bit 1 to check for errors
The POKEY chip emulates the Atari 8-bit computer's sound hardware, providing four channels of distinctive 8-bit audio with polynomial-based distortion.
- Four independent frequency channels
- Multiple distortion modes using polynomial counters (4-bit, 5-bit, 9-bit, 17-bit)
- 16-bit channel linking for extended frequency range
- High-pass filter clocking between channels
- Volume-only mode for sample playback
- POKEY+ enhanced audio processing mode
The POKEY's signature sound comes from its polynomial-based distortion:
- Pure Tone (0xA0): Clean square wave
- Poly5 (0x20): 5-bit polynomial for buzzy tones
- Poly4 (0xC0): 4-bit polynomial for harsh buzzy sounds
- Poly17/Poly5 (0x00): Combined for complex timbres
- Poly17 (0x80): White noise
For higher frequency resolution, channels can be linked:
- Ch1+Ch2 linked via AUDCTL bit 4
- Ch3+Ch4 linked via AUDCTL bit 3
Configure POKEY for pure tone on channel 1:
IE32:
; Configure POKEY for pure tone on channel 1
LOAD A, #0x50 ; Frequency divider
STORE A, @0xF0D00 ; AUDF1
LOAD A, #0xAF ; Pure tone + volume 15
STORE A, @0xF0D01 ; AUDC1M68K:
; Configure POKEY for pure tone on channel 1
move.b #$50,$F0D00.l ; AUDF1: Frequency divider
move.b #$AF,$F0D01.l ; AUDC1: Pure tone + volume 15Z80:
; Configure POKEY for pure tone on channel 1
; Z80 uses port I/O: port $D0 = register select, port $D1 = data
ld a,0 ; Select AUDF1
out ($D0),a
ld a,$50 ; Frequency divider
out ($D1),a
ld a,1 ; Select AUDC1
out ($D0),a
ld a,$AF ; Pure tone + volume 15
out ($D1),a6502:
; Configure POKEY for pure tone on channel 1
; 6502 uses memory-mapped I/O at $D200-$D209
lda #$50
sta $D200 ; AUDF1: Frequency divider
lda #$AF
sta $D201 ; AUDC1: Pure tone + volume 15The POKEY player supports Atari 8-bit music files (.sap) which contain embedded 6502 code that drives the POKEY chip. SAP TYPE B files are supported where INIT is called once and PLAYER is called each frame.
IE32:
; Play a .sap file with looping
LOAD A, #1
STORE A, @POKEY_PLUS_CTRL ; Enable POKEY+ enhanced audio
LOAD A, #sap_data ; Address of embedded SAP file
STORE A, @SAP_PLAY_PTR
LOAD A, #sap_data_end - sap_data
STORE A, @SAP_PLAY_LEN
LOAD A, #0
STORE A, @SAP_SUBSONG ; Select subsong 0
LOAD A, #5 ; bit0=start, bit2=loop
STORE A, @SAP_PLAY_CTRL
sap_data:
.incbin "music.sap"
sap_data_end:M68K:
; Play a .sap file with looping
move.b #1,POKEY_PLUS_CTRL.l
lea sap_data,a0
move.l a0,SAP_PLAY_PTR.l
move.l #sap_data_end-sap_data,SAP_PLAY_LEN.l
move.b #0,SAP_SUBSONG.l ; Subsong 0
move.l #5,SAP_PLAY_CTRL.l ; bit0=start, bit2=loop
sap_data:
incbin "music.sap"
sap_data_end:Z80:
; Play a .sap file with looping
ld a,1
ld (POKEY_PLUS_CTRL),a
SET_SAP_PTR sap_data
SET_SAP_LEN (sap_data_end-sap_data)
xor a
ld (SAP_SUBSONG),a ; Subsong 0
ld a,5 ; bit0=start, bit2=loop
ld (SAP_PLAY_CTRL),a
sap_data:
incbin "music.sap"
sap_data_end:6502:
; Play a .sap file with looping
lda #1
sta POKEY_PLUS_CTRL
STORE32 SAP_PLAY_PTR_0, sap_data
STORE32 SAP_PLAY_LEN_0, (sap_data_end-sap_data)
lda #0
sta SAP_SUBSONG ; Subsong 0
lda #5 ; bit0=start, bit2=loop
sta SAP_PLAY_CTRL
sap_data:
.incbin "music.sap"
sap_data_end:Playback Control:
- Write
1to SAP_PLAY_CTRL to start,2to stop,5to start with loop - Set SAP_SUBSONG before starting to select a specific subsong (0-255)
- Read SAP_PLAY_STATUS for busy/error flags
The SID chip emulates the legendary MOS 6581/8580 from the Commodore 64, providing three voices of analog-style synthesis with the distinctive warm sound that defined a generation of computer music.
- Three independent voices with full ADSR envelopes
- Four waveforms per voice: triangle, sawtooth, pulse (with variable width), noise
- Combined waveforms (AND-style mixing when multiple waveform bits set)
- Ring modulation between voices
- Hard sync for complex timbres (accurate sync timing)
- Test bit support (resets oscillator phase and holds output)
- OSC3 and ENV3 register readback (oscillator and envelope output)
- Programmable resonant filter (low-pass, band-pass, high-pass, notch)
- Rate counter ADSR with exponential decay curve
- Per-chip model selection: 6581 (non-linear filter, DC offset, warmer sound) or 8580 (linear, cleaner), extracted from PSID v3/v4 header flags per SID chip
- Multi-SID playback with up to 3 independent SID chips (9 voices total), using Sid2Addr/Sid3Addr from v3/v4 headers
- SID+ enhanced audio processing mode
- .SID file playback with embedded 6502 code execution
Each voice can output one waveform at a time via the control register:
- Triangle (0x10): Smooth, flute-like tone
- Sawtooth (0x20): Bright, brassy tone with rich harmonics
- Pulse (0x40): Square wave with variable duty cycle (PWM capable)
- Noise (0x80): White noise for percussion and effects
Each voice has a dedicated ADSR envelope generator:
- Attack: 2ms to 8 seconds (16 rates)
- Decay: 6ms to 24 seconds (16 rates)
- Sustain: 16 levels (0-15)
- Release: 6ms to 24 seconds (16 rates)
The SID's resonant filter can process any combination of voices:
- 11-bit cutoff frequency control
- 4-bit resonance control
- Selectable low-pass, band-pass, high-pass modes (combinable for notch)
Configure SID voice 1 for a pulse wave with filter:
IE32:
; Configure SID voice 1 for a pulse wave with filter
LOAD A, #0x00
STORE A, @0xF0E00 ; Freq low
LOAD A, #0x1C ; ~440Hz (A4)
STORE A, @0xF0E01 ; Freq high
LOAD A, #0x00
STORE A, @0xF0E02 ; Pulse width low
LOAD A, #0x08 ; 50% duty
STORE A, @0xF0E03 ; Pulse width high
LOAD A, #0x41 ; Pulse waveform + gate
STORE A, @0xF0E04 ; Control
LOAD A, #0x00 ; Fast attack, no decay
STORE A, @0xF0E05 ; Attack/Decay
LOAD A, #0xF0 ; Full sustain, no release
STORE A, @0xF0E06 ; Sustain/Release
LOAD A, #0x1F ; Max volume + low-pass
STORE A, @0xF0E18 ; Mode/VolumeM68K:
; Configure SID voice 1 for a pulse wave with filter
move.b #$00,$F0E00.l ; Freq low
move.b #$1C,$F0E01.l ; Freq high (~440Hz)
move.b #$00,$F0E02.l ; Pulse width low
move.b #$08,$F0E03.l ; Pulse width high (50% duty)
move.b #$41,$F0E04.l ; Pulse waveform + gate
move.b #$00,$F0E05.l ; Attack/Decay (fast attack)
move.b #$F0,$F0E06.l ; Sustain/Release (full sustain)
move.b #$1F,$F0E18.l ; Mode/Volume (max + low-pass)Z80:
; Configure SID voice 1 for a pulse wave with filter
; Z80 uses port I/O: port $E0 = register select, port $E1 = data
ld a,0 ; Select freq low register
out ($E0),a
ld a,$00
out ($E1),a
ld a,1 ; Select freq high register
out ($E0),a
ld a,$1C ; ~440Hz
out ($E1),a
ld a,2 ; Select pulse width low
out ($E0),a
ld a,$00
out ($E1),a
ld a,3 ; Select pulse width high
out ($E0),a
ld a,$08 ; 50% duty
out ($E1),a
ld a,4 ; Select control register
out ($E0),a
ld a,$41 ; Pulse waveform + gate
out ($E1),a
ld a,5 ; Select attack/decay
out ($E0),a
ld a,$00 ; Fast attack
out ($E1),a
ld a,6 ; Select sustain/release
out ($E0),a
ld a,$F0 ; Full sustain
out ($E1),a
ld a,$18 ; Select mode/volume
out ($E0),a
ld a,$1F ; Max volume + low-pass
out ($E1),a6502:
; Configure SID voice 1 for a pulse wave with filter
; 6502 uses memory-mapped I/O at $D500-$D51C (native SID location)
lda #$00
sta $D500 ; Freq low
lda #$1C
sta $D501 ; Freq high (~440Hz)
lda #$00
sta $D502 ; Pulse width low
lda #$08
sta $D503 ; Pulse width high (50% duty)
lda #$41
sta $D504 ; Pulse waveform + gate
lda #$00
sta $D505 ; Attack/Decay (fast attack)
lda #$F0
sta $D506 ; Sustain/Release (full sustain)
lda #$1F
sta $D518 ; Mode/Volume (max + low-pass)The SID player handles Commodore 64 music files (.sid) which contain embedded 6502 code that drives the SID sound chip. The player executes the 6502 init routine once, then calls the play routine each frame at the correct rate.
IE32:
; Play a .sid file with looping
LOAD A, #1
STORE A, @SID_PLUS_CTRL ; Enable SID+ enhanced audio
LOAD A, #sid_data ; Address of embedded SID file
STORE A, @SID_PLAY_PTR
LOAD A, #sid_data_end - sid_data
STORE A, @SID_PLAY_LEN
LOAD A, #0
STORE A, @SID_SUBSONG ; Select subsong 0
LOAD A, #5 ; bit0=start, bit2=loop
STORE A, @SID_PLAY_CTRL
sid_data:
.incbin "music.sid"
sid_data_end:M68K:
; Play a .sid file with looping
move.b #1,SID_PLUS_CTRL.l
lea sid_data,a0
move.l a0,SID_PLAY_PTR.l
move.l #sid_data_end-sid_data,SID_PLAY_LEN.l
move.b #0,SID_SUBSONG.l ; Subsong 0
move.l #5,SID_PLAY_CTRL.l ; bit0=start, bit2=loop
sid_data:
incbin "music.sid"
sid_data_end:Z80:
; Play a .sid file with looping
ld a,1
ld (SID_PLUS_CTRL),a
SET_SID_PTR sid_data
SET_SID_LEN (sid_data_end-sid_data)
SET_SID_SUBSONG 0
START_SID_LOOP ; Macro: start with looping
sid_data:
incbin "music.sid"
sid_data_end:6502:
; Play a .sid file with looping
lda #1
sta SID_PLUS_CTRL
STORE32 SID_PLAY_PTR_0, sid_data
STORE32 SID_PLAY_LEN_0, (sid_data_end-sid_data)
lda #0
sta SID_SUBSONG ; Subsong 0
lda #5 ; bit0=start, bit2=loop
sta SID_PLAY_CTRL
sid_data:
.incbin "music.sid"
sid_data_end:Playback Control:
- Write
1to SID_PLAY_CTRL to start,2to stop,5to start with loop - Set SID_SUBSONG before starting to select a specific subsong (0-255)
- Read SID_PLAY_STATUS for busy/error flags
- Many SID files contain multiple subsongs (tunes) - check the SID header for count
The TED (Text Editing Device) chip emulates the sound capabilities of the Commodore Plus/4 and C16, providing simple 2-voice square wave synthesis. While simpler than the SID, the TED has a distinctive lo-fi character valued by demoscene musicians.
- Two independent square wave voices
- 10-bit frequency control per voice (0-1023)
- Voice 2 can optionally produce white noise
- Global 4-bit volume control (0-8)
- TED+ enhanced audio processing mode
- .TED file playback with embedded 6502 code execution
Each voice outputs a square wave with 10-bit frequency resolution:
- Frequency range: ~107 Hz to ~110 kHz (at PAL clock)
- Formula:
freq_hz = clock/8 / (1024 - register_value) - Clock: 886724 Hz (PAL), 894886 Hz (NTSC)
The TED_SND_CTRL register controls all audio output:
- Bit 7: D/A mode (direct audio output)
- Bit 6: Voice 2 noise enable (white noise instead of square)
- Bit 5: Voice 2 enable
- Bit 4: Voice 1 enable
- Bits 0-3: Volume (0-8, where 8 is maximum)
Configure TED for two square wave voices:
IE32:
; Configure TED voice 1 at ~440Hz (A4)
LOAD A, #0xE3 ; Low byte of frequency
STORE A, @0xF0F00 ; TED_FREQ1_LO
LOAD A, #0x01 ; High byte (bits 0-1)
STORE A, @0xF0F04 ; TED_FREQ1_HI
; Configure TED voice 2 at ~880Hz (A5)
LOAD A, #0xF1 ; Low byte of frequency
STORE A, @0xF0F01 ; TED_FREQ2_LO
LOAD A, #0x02 ; High byte
STORE A, @0xF0F02 ; TED_FREQ2_HI
; Enable both voices, volume 8
LOAD A, #0x38 ; ch1on + ch2on + volume 8
STORE A, @0xF0F03 ; TED_SND_CTRLM68K:
; Configure TED voice 1 at ~440Hz
move.b #$E3,$F0F00.l ; Freq1 low
move.b #$01,$F0F04.l ; Freq1 high
; Configure TED voice 2 at ~880Hz
move.b #$F1,$F0F01.l ; Freq2 low
move.b #$02,$F0F02.l ; Freq2 high
; Enable both voices, volume 8
move.b #$38,$F0F03.l ; Control: ch1on + ch2on + vol8Z80:
; Configure TED voice 1 at ~440Hz
; Z80 uses port I/O: port $F2 = register select, port $F3 = data
ld a,0 ; Select TED_FREQ1_LO
out ($F2),a
ld a,$E3 ; Low byte
out ($F3),a
ld a,4 ; Select TED_FREQ1_HI
out ($F2),a
ld a,$01 ; High byte
out ($F3),a
; Enable both voices
ld a,3 ; Select TED_SND_CTRL
out ($F2),a
ld a,$38 ; ch1on + ch2on + volume 8
out ($F3),a6502:
; Configure TED voice 1 at ~440Hz
lda #$E3
sta TED_FREQ1_LO
lda #$01
sta TED_FREQ1_HI
; Configure TED voice 2 at ~880Hz
lda #$F1
sta TED_FREQ2_LO
lda #$02
sta TED_FREQ2_HI
; Enable both voices, volume 8
lda #$38 ; ch1on + ch2on + volume 8
sta TED_SND_CTRLPlay Commodore Plus/4 music files (.ted format from HVTC collection):
IE32:
; Play a .ted file with TED+ enhancement
LOAD A, #1
STORE A, @0xF0F05 ; Enable TED+ mode
LOAD A, @ted_data ; Point to embedded data
STORE A, @0xF0F10 ; TED_PLAY_PTR
LOAD A, @(ted_data_end - ted_data)
STORE A, @0xF0F14 ; TED_PLAY_LEN
LOAD A, #5 ; Start with looping
STORE A, @0xF0F18 ; TED_PLAY_CTRL
ted_data:
INCBIN "music.ted"
ted_data_end:6502:
; Play a .ted file with looping
lda #1
sta TED_PLUS_CTRL
STORE32 TED_PLAY_PTR_0, ted_data
STORE32 TED_PLAY_LEN_0, (ted_data_end-ted_data)
lda #5 ; bit0=start, bit2=loop
sta TED_PLAY_CTRL
ted_data:
.incbin "music.ted"
ted_data_end:Playback Control:
- Write
1to TED_PLAY_CTRL to start,2to stop,5to start with loop - Read TED_PLAY_STATUS for busy/error flags
The AHX engine provides Amiga AHX/THX module playback with 4-channel waveform synthesis. AHX modules use procedural synthesis rather than samples, creating rich sounds from simple waveforms (triangle, sawtooth, square, noise) with modulation.
- 4-channel synthesis matching original Amiga hardware
- Triangle, sawtooth, square, and noise waveforms
- Per-voice filter modulation and square pulse width modulation
- Vibrato, portamento, and envelope effects
- AHX+ enhanced mode with stereo spread and audio processing
- Subsong support for multi-tune modules
M68K:
; Play an .ahx file with looping and AHX+ enhancement
PLAY_AHX_PLUS_LOOP ahx_data,ahx_data_end-ahx_data
ahx_data:
incbin "music.ahx"
ahx_data_end:Z80:
; Play an .ahx file with looping
ENABLE_AHX_PLUS ; Enable enhanced mode
SET_AHX_PTR ahx_data
SET_AHX_LEN (ahx_data_end-ahx_data)
START_AHX_LOOP ; Start with looping
ahx_data:
incbin "music.ahx"
ahx_data_end:6502:
; Play an .ahx file with looping
lda #1
sta AHX_PLUS_CTRL ; Enable AHX+ mode
STORE32 AHX_PLAY_PTR_0, ahx_data
STORE32 AHX_PLAY_LEN_0, (ahx_data_end-ahx_data)
lda #5 ; bit0=start, bit2=loop
sta AHX_PLAY_CTRL
ahx_data:
.incbin "music.ahx"
ahx_data_end:Playback Control:
- Write
1to AHX_PLAY_CTRL to start,2to stop,5to start with loop - Set AHX_SUBSONG before starting to select a specific subsong (0-255)
- Read AHX_PLAY_STATUS for busy/error flags
- Speed 0 command in the module signals end of song
The MOD player provides full ProTracker .mod file playback with 4-channel sample-based audio. MOD files are the classic Amiga music format, using PCM samples mixed in real time. The player outputs through the SoundChip FLEX channels in DAC mode.
- Full ProTracker .mod file parsing (M.K., 4CHN, FLT4, M!K! signatures)
- 4-channel sample playback via SoundChip FLEX DAC mode
- Amiga A500 and A1200 low-pass filter emulation with LED filter
- ProTracker effects: arpeggio (0), portamento up/down (1/2), tone portamento (3), vibrato (4), vol+tone slide (5/6), sample offset (9), volume slide (A), position jump (B), set volume (C), pattern break (D), set speed/BPM (F)
- Extended effects (Exy): LED filter (E0), fine porta up/down (E1/E2), set finetune (E5), retrigger (E9), fine vol slide (EA/EB), note cut (EC), note delay (ED), pattern delay (EE)
- Song position tracking
- Loop support
- MediaLoader integration (auto-detected by
.modextension) -modcommand line flag for direct playback- Status bar indicator "Amiga MOD"
M68K:
; Play a .mod file with A500 filter and looping
move.l #1,MOD_FILTER_MODEL ; A500 filter
move.l #mod_data,MOD_PLAY_PTR
move.l #(mod_data_end-mod_data),MOD_PLAY_LEN
move.l #5,MOD_PLAY_CTRL ; start + loop
mod_data:
incbin "music.mod"
mod_data_end:Z80:
; Play a .mod file with looping
SET_MOD_FILTER 1 ; A500 filter
SET_MOD_PTR mod_data
SET_MOD_LEN (mod_data_end-mod_data)
START_MOD_LOOP ; Start with looping
mod_data:
incbin "music.mod"
mod_data_end:6502:
; Play a .mod file with looping
lda #1
sta MOD_FILTER_MODEL ; A500 filter
STORE32 MOD_PLAY_PTR_0, mod_data
STORE32 MOD_PLAY_LEN_0, (mod_data_end-mod_data)
lda #5 ; bit0=start, bit2=loop
sta MOD_PLAY_CTRL
mod_data:
.incbin "music.mod"
mod_data_end:Playback Control:
- Write
1to MOD_PLAY_CTRL to start,2to stop,5to start with loop - Set MOD_FILTER_MODEL before starting to select a filter (0=none, 1=A500, 2=A1200)
- Read MOD_PLAY_STATUS for busy/error flags
- Read MOD_POSITION for the current song position
The video system provides flexible graphics output through a memory-mapped framebuffer design.
Four resolution modes are available:
- 640x480 (MODE_640x480 = 0x00)
- 800x600 (MODE_800x600 = 0x01)
- 1024x768 (MODE_1024x768 = 0x02)
- 1280x960 (MODE_1280x960 = 0x03, default)
IE32:
init_display:
LOAD A, #0 ; MODE_640x480
STORE A, @VIDEO_MODE
LOAD A, #1 ; Enable display
STORE A, @VIDEO_CTRL
RTSM68K:
init_display:
move.l #0,VIDEO_MODE.l ; MODE_640x480
move.l #1,VIDEO_CTRL.l ; Enable display
rtsZ80:
init_display:
xor a ; MODE_640x480
ld (VIDEO_MODE),a
ld a,1 ; Enable display
ld (VIDEO_CTRL),a
ret6502:
init_display:
lda #0
sta VIDEO_MODE ; MODE_640x480
lda #1
sta VIDEO_CTRL ; Enable display
rtsThe framebuffer uses 32-bit RGBA colour format:
- Start address: 0x100000 (VRAM_START)
- Each pixel: 4 bytes (R,G,B,A)
- Linear layout: y * width + x
Address = 0x100000 + (y * width + x) * 4
The system tracks changes in 32x32 pixel blocks:
- Automatically marks modified regions
- Updates only changed areas
- Improves rendering performance
Video output uses double buffering to prevent tearing. The system provides a VBlank status bit for flicker-free animation:
VIDEO_STATUS(0x0F0008) bit 1 indicates VBlank period (safe to draw)- VBlank is read lock-free - no mutex contention during polling
- Write to back buffer during VBlank to avoid screen flicker
- Buffers swap automatically
The VBlank flag follows this timing:
inVBlank = falsewhen frame processing starts (active scan)inVBlank = trueafter frame is sent to display (CPU can safely draw)
For smooth animation, wait for a complete frame boundary (VBlank transition):
IE32:
.equ VIDEO_STATUS 0xF0008
.equ STATUS_VBLANK 2 ; bit 1
; Wait for exactly one frame - prevents animation running too fast
wait_frame:
; First wait for VBlank to END (active scan period)
.wait_not_vblank:
LDA @VIDEO_STATUS
AND A, #STATUS_VBLANK
JNZ A, .wait_not_vblank
; Then wait for VBlank to START (new frame)
.wait_vblank_start:
LDA @VIDEO_STATUS
AND A, #STATUS_VBLANK
JZ A, .wait_vblank_start
RTSM68K:
; Wait for exactly one frame
wait_frame:
.wait_not_vblank:
move.l VIDEO_STATUS.l,d0
and.l #STATUS_VBLANK,d0
bne.s .wait_not_vblank
.wait_vblank_start:
move.l VIDEO_STATUS.l,d0
and.l #STATUS_VBLANK,d0
beq.s .wait_vblank_start
rtsZ80:
; Wait for exactly one frame
wait_frame:
.wait_not_vblank:
ld a,(VIDEO_STATUS)
and STATUS_VBLANK
jr nz,.wait_not_vblank
.wait_vblank_start:
ld a,(VIDEO_STATUS)
and STATUS_VBLANK
jr z,.wait_vblank_start
ret6502:
; Wait for exactly one frame
wait_frame:
; Wait for VBlank to END
@wait_not_vblank:
lda VIDEO_STATUS
and #STATUS_VBLANK
bne @wait_not_vblank
; Wait for VBlank to START
@wait_vblank_start:
lda VIDEO_STATUS
and #STATUS_VBLANK
beq @wait_vblank_start
rtsmain_loop:
JSR wait_frame ; Wait for frame boundary (60 FPS)
JSR erase_sprite ; Erase old sprite position
JSR update_position ; Calculate new position
JSR draw_sprite ; Draw at new position
JMP main_loopThe wait_frame pattern ensures exactly one frame per loop iteration, giving smooth 60 FPS animation regardless of how fast the CPU runs.
For fullscreen effects such as plasma, fire, or tunnel demos where every pixel is updated each frame, the system provides a direct VRAM access mode with lock-free dirty tracking that bypasses the standard memory bus. This delivers approximately 4.5x video throughput compared to bus-based access.
| Mode | Writes/sec | Approx FPS | Use Case |
|---|---|---|---|
| Bus-based | ~1.4M | ~9 | Partial screen updates, sprites |
| Direct VRAM + Lock-free | ~6.3M | ~41 | Fullscreen effects, demoscene |
Direct VRAM mode eliminates per-pixel overhead by:
- Bypassing CPU and bus mutex locks
- Skipping I/O region mapping lookups
- Using lock-free atomic bitmap for dirty tile tracking
- Employing compare-and-swap operations instead of mutex locks
// Enable direct VRAM mode and get buffer pointer
vramBuffer := videoChip.EnableDirectMode()
// Attach buffer to CPU for fast writes
cpu.AttachDirectVRAM(vramBuffer, VRAM_START, VRAM_START+uint32(len(vramBuffer)))
// ... run demo ...
// Cleanup
cpu.DetachDirectVRAM()
videoChip.DisableDirectMode()- Use direct mode for fullscreen effects where most pixels change every frame
- Use bus mode for partial updates, sprites, or when dirty region tracking is beneficial
Direct VRAM mode is ideal for demoscene-style effects, real-time visualisations, and any application that redraws the entire screen each frame.
The video subsystem includes a simple copper-like list executor for mid-frame register updates. Copper lists are stored in RAM as little-endian 32-bit words and can WAIT on raster positions, MOVE values into video registers, and END the list. The copper restarts each frame while enabled.
Registers:
COPPER_CTRL(0x0F000C): bit0=enable, bit1=reset/rewindCOPPER_PTR(0x0F0010): list base address (latched on enable/reset)COPPER_PC(0x0F0014): current list address (read-only)COPPER_STATUS(0x0F0018): bit0=running, bit1=waiting, bit2=halted
List words:
WAIT:(0<<30) | (y<<12) | x- Wait until raster reaches Y/X positionMOVE:(1<<30) | (regIndex<<16)followed by a 32-bit value - Write to registerSETBASE:(2<<30) | (addr>>2)- Change base address for MOVE operationsEND:(3<<30)- Halt copper for this frame
The SETBASE instruction allows the copper to write to any memory-mapped I/O device by changing the base address for subsequent MOVE operations. This enables cross-device effects like modifying VGA palette registers from the copper.
SETBASE Format:
- Bits 30-31: Opcode (2)
- Bits 0-23: Target address right-shifted by 2 (addresses are 4-byte aligned)
Pre-defined SETBASE values:
COP_SETBASE_VIDEO(0x8003C000) - IE video registers (default)COP_SETBASE_VGA(0x8003C400) - VGA registersCOP_SETBASE_VGA_DAC(0x8003C416) - VGA DAC for palette writes
The base is reset to VIDEO_REG_BASE at the start of each frame.
The copper executes synchronously with the video compositor's scanline rendering. When the compositor renders each scanline:
- The copper advances, executing instructions until it reaches a WAIT for a future scanline
- MOVE instructions take effect immediately for the current scanline
- VGA renders its scanline using the current palette state
This means copper-driven palette changes affect only the scanlines rendered after the change, enabling classic raster effects like:
- Gradient backgrounds (changing palette entries per scanline)
- Split-screen color schemes
- Plasma and interference patterns
IE32:
.include "ie32.inc"
copper_list:
; Switch to VGA DAC registers
.long COP_SETBASE_VGA_DAC
; Set palette entry 32 to red
.long COP_MOVE_VGA_WINDEX
.long 32 ; Palette index
.long COP_MOVE_VGA_DATA
.long 63 ; R (6-bit VGA)
.long COP_MOVE_VGA_DATA
.long 0 ; G
.long COP_MOVE_VGA_DATA
.long 0 ; B
; Switch back to IE video chip
.long COP_SETBASE_VIDEO
; Draw raster bar at Y=100
.long COP_WAIT_MASK | (100 * COP_WAIT_SCALE)
.long COP_MOVE_RASTER_Y
.long 100
.long COP_MOVE_RASTER_H
.long 8
.long COP_MOVE_RASTER_COLOR
.long 0xFF00FFFF ; Cyan (RGBA)
.long COP_MOVE_RASTER_CTRL
.long 1
.long COP_ENDThe DMA blitter performs rectangle copy/fill, line drawing, and color expansion. Registers are written via memory-mapped I/O. The blitter supports both RGBA32 (4 bytes/pixel) and CLUT8 (1 byte/pixel) modes, 16 raster draw modes, and template-based text rendering.
Synchronous Execution: The blitter runs immediately when BLT_CTRL is written with bit0 set. This ensures blitter operations complete before the CPU continues, allowing safe drawing during VBlank without race conditions.
Operations (BLT_OP):
0: COPY1: FILL2: LINE (coordinates packed intoBLT_SRC/BLT_DST; extended mode below)3: MASKED COPY (1-bit mask, LSB-first per byte)4: ALPHA (alpha-aware copy with source alpha blending)5: MODE7 (affine texture mapping with 16.16 UV coordinates)6: COLOR_EXPAND (1-bit template to colored pixels; see Extended Registers below)
Line coordinates:
BLT_SRC: x0 (low 16 bits), y0 (high 16 bits)BLT_DST: x1 (low 16 bits), y1 (high 16 bits)
BLT_OP=5 uses these registers in addition to the normal blitter source/destination/size:
BLT_MODE7_U0,BLT_MODE7_V0: start coordinates in signed 16.16 fixed-point.BLT_MODE7_DU_COL,BLT_MODE7_DV_COL: UV deltas per destination X pixel.BLT_MODE7_DU_ROW,BLT_MODE7_DV_ROW: UV deltas applied when moving to next destination row.BLT_MODE7_TEX_W,BLT_MODE7_TEX_H: wrap masks (must be2^n-1, for example255for 256).BLT_SRC_STRIDE: source row stride bytes.0means auto((texWMask+1)*4).BLT_DST_STRIDE: destination row stride bytes.0keeps normal auto behavior.
Example UV mapping values:
- Identity sampling:
duCol=0x00010000,dvCol=0,duRow=0,dvRow=0x00010000. - Rotation/zoom: precompute sine/cosine in software and write fixed-point deltas once per frame.
IE32:
LOAD A, #1 ; BLT_OP_FILL
STORE A, @BLT_OP
LOAD A, #0x100000 ; VRAM_START
STORE A, @BLT_DST
LOAD A, #16
STORE A, @BLT_WIDTH
LOAD A, #16
STORE A, @BLT_HEIGHT
LOAD A, #0xFF00FF00 ; green (RGBA)
STORE A, @BLT_COLOR
LOAD A, #1
STORE A, @BLT_CTRL ; startM68K:
move.l #BLT_OP_FILL,BLT_OP.l
move.l #VRAM_START,BLT_DST.l
move.l #16,BLT_WIDTH.l
move.l #16,BLT_HEIGHT.l
move.l #$FF00FF00,BLT_COLOR.l ; green (RGBA)
move.l #1,BLT_CTRL.l ; startZ80:
SET_BLT_OP BLT_OP_FILL
SET_BLT_DST VRAM_START
SET_BLT_WIDTH 16
SET_BLT_HEIGHT 16
SET_BLT_COLOR 0xFF00FF00 ; green (RGBA)
START_BLIT6502:
lda #BLT_OP_FILL
sta BLT_OP
STORE32 BLT_DST_0, VRAM_START
SET_BLT_WIDTH 16
SET_BLT_HEIGHT 16
SET_BLT_COLOR $FF00FF00 ; green (RGBA)
START_BLITThe blitter defaults BLT_SRC_STRIDE/BLT_DST_STRIDE to the current mode row bytes when the address is in VRAM, otherwise it uses width*4. If an unaligned VRAM address is used, BLT_STATUS.bit0 is set.
The extended registers at 0xF0488-0xF049B add BPP mode selection, draw modes, and color expansion for hardware-accelerated text rendering and CLUT8 framebuffers.
| Register | Address | Description |
|---|---|---|
BLT_FLAGS |
0xF0488 |
BPP mode, draw mode, and color expansion flags (see below) |
BLT_FG |
0xF048C |
Foreground color for color expansion |
BLT_BG |
0xF0490 |
Background color for color expansion |
BLT_MASK_MOD |
0xF0494 |
Template row modulo in bytes (color expansion) |
BLT_MASK_SRCX |
0xF0498 |
Starting X bit offset in template (color expansion) |
BLT_FLAGS bit layout:
| Bits | Name | Values |
|---|---|---|
| 0-1 | BPP mode | 0 = RGBA32 (4 bpp, default), 1 = CLUT8 (1 bpp) |
| 4-7 | Draw mode | 16 standard raster ops (0=Clear, 3=Copy, 6=Xor, 10=Invert, 15=Set) |
| 8 | JAM1 | Color expand: skip BG pixels (transparent text) |
| 9 | Invert template | Flip all template bits before processing |
| 10 | Invert mode | XOR destination with all-ones for set template bits (ignore FG/BG) |
When BLT_FLAGS=0 (default), the blitter uses Copy mode with RGBA32 - fully backward compatible with existing programs.
Color Expansion (op 6): Reads a 1-bit template from BLT_MASK and expands each bit to a colored pixel at BLT_DST. Template bits are MSB-first (Amiga convention: bit 7 of first byte = leftmost pixel). Three modes:
- JAM2 (bits 8,10 clear): bit=1 writes FG color, bit=0 writes BG color
- JAM1 (bit 8 set): bit=1 writes FG color, bit=0 leaves destination unchanged
- Invert (bit 10 set): bit=1 XORs destination with all-ones, bit=0 unchanged
CLUT8 mode: When BPP=1, FILL writes 1 byte per pixel and COPY transfers 1 byte per pixel. BLT_COLOR/BLT_FG/BLT_BG use the low byte as a palette index.
Extended line mode (op 2 with BLT_FLAGS != 0): When BLT_FLAGS is non-zero, the LINE operation uses a different register layout to support arbitrary framebuffer addresses and draw modes:
| Register | Extended line usage |
|---|---|
BLT_SRC |
Start point: (y0 << 16) | x0 (same as legacy) |
BLT_WIDTH |
End point: (y1 << 16) | x1 (repurposed from width) |
BLT_DST |
Framebuffer base address (not endpoint) |
BLT_DST_STRIDE |
Row stride in bytes |
BLT_COLOR |
Line color |
BLT_FLAGS |
BPP + draw mode (must be non-zero to activate extended mode) |
When BLT_FLAGS=0 (legacy mode), BLT_DST holds the endpoint and the line is drawn into the active VRAM framebuffer at VRAM_START.
Clipping: In extended mode the blitter does not perform viewport clipping - callers must provide pre-clipped coordinates. Pixel writes are still bounds-checked against VRAM limits (out-of-range addresses are silently dropped).
VIDEO_RASTER_* registers draw a full-width horizontal band directly into the framebuffer. This is useful for copper-driven raster bars without adding a palette system.
IE32:
LOAD A, #100
STORE A, @VIDEO_RASTER_Y
LOAD A, #4
STORE A, @VIDEO_RASTER_HEIGHT
LOAD A, #0xFFFF0000 ; blue (RGBA)
STORE A, @VIDEO_RASTER_COLOR
LOAD A, #1
STORE A, @VIDEO_RASTER_CTRLM68K:
move.l #100,VIDEO_RASTER_Y.l
move.l #4,VIDEO_RASTER_HEIGHT.l
move.l #$FFFF0000,VIDEO_RASTER_COLOR.l ; blue (RGBA)
move.l #1,VIDEO_RASTER_CTRL.lZ80:
STORE32 VIDEO_RASTER_Y_LO,100
STORE32 VIDEO_RASTER_HEIGHT_LO,4
STORE32 VIDEO_RASTER_COLOR_0,0xFFFF0000 ; blue (RGBA)
ld a,1
ld (VIDEO_RASTER_CTRL),a6502:
STORE32 VIDEO_RASTER_Y_LO, 100
STORE32 VIDEO_RASTER_HEIGHT_LO, 4
STORE32 VIDEO_RASTER_COLOR_0, $FFFF0000 ; blue (RGBA)
lda #1
sta VIDEO_RASTER_CTRLThe Intuition Engine uses a video compositor to blend multiple video sources into a single display output. This architecture enables layered rendering where different video devices (VideoChip, VGA) can contribute to the final frame.
+-------------+
CPU -> VGA VRAM -> | VGAEngine | --+
+-------------+ | +-------------+ +---------+
+---> | Compositor | --> | Display |
+-------------+ | +-------------+ +---------+
CPU -> Chip VRAM -> | VideoChip | --+
+-------------+
Each video source has a layer number that determines compositing order (higher layers render on top):
| Source | Layer | Description |
|---|---|---|
| VideoChip | 0 | Base layer with copper coprocessor |
| VGA | 10 | IBM VGA compatible modes |
| TED Video | 12 | Commodore Plus/4 video |
| ANTIC/GTIA | 13 | Atari 8-bit video |
| ULA | 15 | ZX Spectrum video |
| Voodoo 3D | 20 | 3DFX SST-1 accelerator |
The compositor supports two rendering modes:
Full-Frame Mode: Each source renders its complete frame, then frames are composited. Simple but copper effects only affect video registers, not VGA palette.
Scanline-Aware Mode: Sources that implement the ScanlineAware interface render one scanline at a time. This enables:
- Copper MOVE operations to affect the current scanline immediately
- Per-scanline VGA palette changes via SETBASE
- Classic demoscene raster effects (color cycling, plasma bars)
The compositor automatically uses scanline-aware rendering when all enabled sources support it. The render sequence per scanline is:
- VideoChip processes copper list up to current Y position
- VideoChip renders its scanline
- VGA renders its scanline using current palette state
- Scanlines are composited in layer order
The copper's SETBASE instruction enables per-scanline VGA palette manipulation:
; Create a raster bar effect by changing VGA palette per scanline
copper_list:
; Wait for scanline 50
.long COP_WAIT_MASK | (50 * COP_WAIT_SCALE)
; Switch to VGA DAC registers
.long COP_SETBASE_VGA_DAC
; Set color 0 to red for this scanline
.long COP_MOVE_VGA_WINDEX
.long 0
.long COP_MOVE_VGA_DATA
.long 63 ; R
.long COP_MOVE_VGA_DATA
.long 0 ; G
.long COP_MOVE_VGA_DATA
.long 0 ; B
; Wait for scanline 60
.long COP_WAIT_MASK | (60 * COP_WAIT_SCALE)
; Set color 0 to blue
.long COP_MOVE_VGA_WINDEX
.long 0
.long COP_MOVE_VGA_DATA
.long 0 ; R
.long COP_MOVE_VGA_DATA
.long 0 ; G
.long COP_MOVE_VGA_DATA
.long 63 ; B
; Return to video chip registers
.long COP_SETBASE_VIDEO
.long COP_ENDThis creates a horizontal band where the VGA background color (palette entry 0) changes from red to blue at specific scanlines.
For the complete developer guide covering building, testing, toolchains, include files, debugging, platform support, and contribution guidance, see DEVELOPERS.md.
make # Build VM + all SDK tools (assemblers, disassembler, transpiler)
make novulkan # Build without Vulkan
make headless # Build for CI/testing (no display/audio)
make basic # Build with embedded EhBASIC interpreter
make basic-emutos # Build with embedded BASIC + EmuTOS ROM
make sdk # Sync includes + pre-assemble SDK demos
make release-all # Build release archives for all platforms
go build ./... # Quick compile check (output in cwd, not bin/)See DEVELOPERS.md for the complete development workflow, running programs (all CPU modes and music playback), assembler include files, and debugging techniques. See also docs/include-files.md for a detailed include file reference.
This section describes how the Intuition Engine emulates its hardware components.
The IE32 is a custom 32-bit RISC processor designed for simplicity and performance:
- 16 general-purpose registers: A (accumulator), B-H, S-W, X-Z (all 32-bit, orthogonal access)
- Fixed 8-byte instruction format: Simplifies fetch/decode
- Load/store architecture: Memory access only via LOAD/STORE
- Little-endian byte order: Matches system bus convention
- Hardware interrupt support: Single vector at address 0
Execution cycle: Fetch (8 bytes) → Decode → Execute → Update PC → Check interrupts
The 68020 emulation provides 95%+ instruction coverage:
- JIT compiler: Block-based native code compilation on x86-64; enabled by default on supported platforms, disable with
-nojit - 8 data registers (D0-D7) and 8 address registers (A0-A7)
- Full addressing mode support: All 18 68020 addressing modes
- Supervisor/user modes: Privilege separation
- Exception handling: Bus error, address error, illegal instruction
- FPU emulation (68881/68882): Floating-point operations
Variable instruction length (2-22 bytes) with complex decode logic.
The Z80 emulation provides complete instruction set support:
- Main and alternate register sets: AF, BC, DE, HL with shadows
- Index registers: IX, IY with displacement addressing
- Interrupt modes: IM 0, IM 1, IM 2
- I/O port access: IN/OUT instructions mapped to hardware
- JIT compiler (x86-64): Block-based native compilation with block chaining, RTS cache for CALL/RET, lazy flag elimination, and unchecked memory access for qualifying DJNZ loops. 2.7x-10.9x speedup over interpreter.
The 6502 emulation covers the original NMOS instruction set:
- Accumulator, X, Y registers: 8-bit operations
- Zero page optimisation: Fast access to first 256 bytes
- Stack at $0100-$01FF: Hardware stack page
- Status flags: N, V, B, D, I, Z, C
The x86 emulation provides a 32-bit flat memory model:
- 8 general-purpose registers: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP
- Segment-free flat model: Direct 32-bit addressing
- 8086 core instruction set: Integer instructions with 32-bit register extensions
- I/O port access: IN/OUT instructions mapped to hardware
The IE64 is a 64-bit RISC processor with a clean load-store architecture:
- JIT compiler: Block-based native code compilation (ARM64 and x86-64) with backward branch optimisation for native loops. Enabled by default on supported platforms (amd64/arm64 Linux); disable with
-nojit - 32 general-purpose registers: R0 (hardwired zero) through R31 (stack pointer), all 64-bit
- Fixed 8-byte instruction format: Consistent fetch/decode
- Compare-and-branch model: No flags register; conditional branches embed register comparison
- Size-polymorphic operations: .B/.W/.L/.Q suffixes on most instructions
- 64-bit bus support: Read64/Write64 with I/O region split semantics
Execution: JIT compiles basic blocks to native ARM64, caches them, and re-executes directly. Backward branches within a block execute as native loops with budget-based timer safety. Falls back to the interpreter for I/O, transcendental FPU ops, and unsupported platforms.
All CPUs share the autodetected guest RAM through the MachineBus. Total guest RAM is selected at boot from host /proc/meminfo minus a per-platform reserve (memory_sizing.go); each CPU/profile then sees an active visible RAM clamped to its own ceiling. Guest software discovers sizes via the SYSINFO MMIO pairs (SYSINFO_TOTAL_RAM_LO/HI for total guest RAM, SYSINFO_ACTIVE_RAM_LO/HI for active visible RAM). IE64 also reports its active visible RAM through CR_RAM_SIZE_BYTES.
- Direct memory array: Fast non-I/O access via cached pointer
- Page-based I/O callbacks: Handlers for memory-mapped registers
- Bank switching: 8KB/16KB windows for 8-bit CPUs to access full memory
| Region | Address Range | Description |
|---|---|---|
| System | $000000-$000FFF |
Vectors, system data |
| Program | $001000-$0EFFFF |
User program space |
| I/O | $0F0000-$0FFFFF |
Hardware registers |
| VRAM | $100000-$5FFFFF |
Video RAM (5MB) |
Z80 and 6502 access the full address space through bank windows:
| Window | CPU Address | Size | Control Register |
|---|---|---|---|
| Bank 1 | $2000 |
8KB | $F700-$F701 |
| Bank 2 | $4000 |
8KB | $F702-$F703 |
| Bank 3 | $6000 |
8KB | $F704-$F705 |
| VRAM | $8000 |
16KB | $F7F0 |
Audio is synthesised at 44.1kHz with 32-bit floating-point precision:
- Oscillators: Generate raw waveforms (square, triangle, sine, noise, sawtooth)
- Envelopes: Shape amplitude over time (ADSR)
- Modulation: Apply sync, ring mod, PWM
- Mixing: Combine all channels
- Effects: Apply filter, overdrive, reverb
- Output: Convert to 16-bit stereo PCM
PolyBLEP (polynomial bandlimited step) anti-aliasing is applied to square and sawtooth waveforms to reduce high-frequency aliasing artifacts.
The classic sound chips are implemented via register mapping to the custom audio synthesizer:
- PSG (AY-3-8910): Registers at
$F0C00-$F0C0Dmap to square wave channels with hardware envelope emulation - POKEY: Registers at
$F0D00-$F0D09map to channels with polynomial counter and distortion emulation - SID (6581/8580): Registers at
$F0E00-$F0E1Cmap to channels with filter, ring mod, and sync
This approach provides accurate register-level compatibility while leveraging the custom synth's high-quality output (44.1kHz, anti-aliased waveforms, 32-bit processing). VGM playback also supports SN76489 data via automatic conversion to these AY registers.
File playback (.ym, .ay, .sndh, .vgm, .sap, .sid) executes embedded CPU code that writes to the mapped registers, driving the synthesis in real-time.
For detailed build instructions, all build profiles/tags, toolchain setup, testing, and platform compatibility details, see DEVELOPERS.md.
| Platform | Architecture | Status | Notes |
|---|---|---|---|
| Linux | x86_64, aarch64 | Official | full, novulkan, headless, headless-novulkan |
| Windows | x86_64, ARM64 | Official | Pure-Go novulkan release builds |
| macOS | x86_64, arm64 | Official | Pure-Go novulkan release builds, amd64 guest JIT parity on Intel Macs, IE64 native JIT on Apple Silicon |
Release builds embed EhBASIC, EmuTOS, and the AROS ROM via embed_basic embed_emutos embed_aros, and package the full sdk/ tree plus the staged AROS/ system tree beside the binary. make release-macos builds both darwin-amd64 and darwin-arm64 archives.
Graphics: Ebiten (OpenGL on Linux, DirectX on Windows, Metal on macOS). Audio: Oto (44.1kHz stereo). Both have headless stubs for CI.
See docs/platform-compatibility.md for build profile requirements and known limitations.
# Default: start EhBASIC on IE64
./bin/IntuitionEngine
# Run an IE32 program
./bin/IntuitionEngine -ie32 program.iex
# Run an M68K program
./bin/IntuitionEngine -m68k program.ie68
# Play SID music
./bin/IntuitionEngine -sid tune.sid
# Play PSG music (YM/AY/VGM/VTX/SNDH/PT3/STC/...)
./bin/IntuitionEngine -psg track.ym
# Or from the EhBASIC prompt, load and run any binary:
# RUN "program.iex"
# RUN "demo.ie68"
# Version and features
./bin/IntuitionEngine -version
./bin/IntuitionEngine -featuresSee DEVELOPERS.md for all CPU modes, music playback options, enhanced audio modes, build commands, and testing instructions.
The sdk/ directory contains a curated developer package with example programs, include files, and build scripts for all six CPU architectures. Run make sdk to sync include files from the canonical source and pre-assemble demos into sdk/examples/prebuilt/. See sdk/README.md for the full SDK documentation and docs/demo-matrix.md for the demo coverage matrix.
- DEVELOPERS.md - Build, test, and contribute
- CHANGELOG.md - Release history
- docs/TUTORIAL.md - Demoscene intro tutorial
- docs/iemon.md - Machine monitor (F9 debugger)
- docs/IE64_ISA.md - IE64 instruction set
- docs/IE64_COOKBOOK.md - IE64 patterns and recipes
- docs/ehbasic_ie64.md - EhBASIC language guide
- docs/ie32to64.md - IE32 to IE64 migration
- docs/toolchains.md - Assembler toolchain reference
- docs/platform-compatibility.md - Platform support details
- docs/release-process.md - Release packaging
Intuition Engine runs EmuTOS directly on the IE M68K core, providing a complete GEM desktop environment with file browser, mouse/keyboard input, and host filesystem access.
# Boot EmuTOS (embedded ROM, or auto-discovers etos256us.img / emutos.img locally)
./bin/IntuitionEngine -emutos
# External ROM image
./bin/IntuitionEngine -emutos-image etos256us.img
# Map a specific host directory as drive U: (default: ~/)
./bin/IntuitionEngine -emutos -emutos-drive /path/to/files
# Boot EmuTOS from the EhBASIC prompt (requires 'make basic-emutos' build)
# At the Ready prompt, type: EMUTOS
# Load .tos/.img files through the ProgramExecutor
# RUN "emutos.img"EmuTOS runs on the IE M68K core with full access to all IE hardware: VideoChip (640x480 RGBA, set via VIDEO_MODE=0), terminal MMIO for keyboard/mouse, timer-driven interrupts, and the complete audio/video peripheral set (SoundChip, PSG, SID, POKEY, TED, AHX, VGA, ULA, TED video, ANTIC/GTIA, Voodoo 3D). TOS .PRG programs can drive any MMIO register in the hardware map via ie68.inc.
Mouse MMIO (0xF0730 - 0xF073C):
| Address | Register | Description |
|---|---|---|
0xF0730 |
MOUSE_X |
Absolute X position (16-bit) |
0xF0734 |
MOUSE_Y |
Absolute Y position (16-bit) |
0xF0738 |
MOUSE_BUTTONS |
Bit 0=left, 1=right, 2=middle |
0xF073C |
MOUSE_STATUS |
Bit 0=changed since last read (clears on read) |
Scancode MMIO (0xF0740 - 0xF0748):
| Address | Register | Description |
|---|---|---|
0xF0740 |
SCAN_CODE |
Raw ST-style scancode (dequeues on read) |
0xF0744 |
SCAN_STATUS |
Queue depth (0 = empty) |
0xF0748 |
SCAN_MODIFIERS |
Bit 0=shift, 1=ctrl, 2=alt, 3=capslock |
Interrupts: Timer IRQ level 5 at 200Hz, VBlank IRQ level 4 at 60Hz.
GEMDOS drive U:: Host directory (default ~/) is mapped as drive U: in the GEM desktop via TRAP #1 interception, providing file browsing, opening, and .PRG execution from the host filesystem.
make emutos # VM with embedded EmuTOS ROM
make basic-emutos # VM with embedded EhBASIC + EmuTOS ROM
make emutos-rom # Build EmuTOS ROM from source (auto-clones if needed)See sdk/docs/ie_emutos.md for the full hardware map, build instructions, and GEM programming guide.
Intuition Engine runs AROS (Amiga Research Operating System) on the IE M68K core, providing a full Amiga Workbench desktop with Shell, file management, and host filesystem access.
# Boot AROS (embedded ROM, or auto-discovers sdk/roms/aros-ie-m68k.rom)
./bin/IntuitionEngine -aros
# Interpreter-only validation path used for M68K AROS bring-up
./bin/IntuitionEngine -aros -nojit
# Boot from external ROM image
./bin/IntuitionEngine -aros-image sdk/roms/aros-ie-m68k.rom
# Map a host directory as the IE: volume
./bin/IntuitionEngine -aros -aros-drive /path/to/filesFor M68K AROS interpreter work, the stable bring-up path is -aros -nojit.
The bounded clean-boot contract is:
- the machine reaches the AROS ready probe without timing out
- no structured M68K fault record is emitted first
- no interpreter-originated illegal/Line-A/Line-F/bus/address exception is seen on the way there
The repo now exposes that workflow in two layers:
- Go test helpers:
ProbeAROSReadyState,AROSBootHarness, andM68KFaultManifest - IEScript diagnostics: scripts/m68k_aros_ready_probe.ies and scripts/m68k_aros_fault_capture.ies
Typical triage commands:
go test -tags headless -run 'Test(M68KFaultManifest|ProbeAROSReadyState|AROSBootHarness_)' ./...
./bin/IntuitionEngine -aros -nojit -script scripts/m68k_aros_ready_probe.ies
./bin/IntuitionEngine -aros -nojit -script scripts/m68k_aros_fault_capture.ies| Region | Range | Size | Notes |
|---|---|---|---|
| Chip RAM A | 0x000000-0x09DFFF |
630KB | |
| Chip RAM B | 0x200000-0x6FFFFF |
5MB | Upper 1MB overlaid by ROM |
| ROM | 0x600000-0x7FFFFF |
2MB | Overlays 0x600000-0x6FFFFF of Chip RAM B |
| Fast RAM | 0x800000-0x1DFFFFF |
22MB | |
| VRAM | 0x1E00000-0x1FFFFFF |
2MB |
Total: 27.6MB (5.6MB chip + 22MB fast memory). ROM overlays the top of the Chip RAM B address range, similar to real Amiga hardware. VRAM is separate.
- Full Workbench desktop with 12+ tasks and graphical file browser
- Shell commands (Path, Dir, List, Copy, MakeDir, Delete, Rename, etc.)
- DOS handler: host filesystem bridge as IE: volume via MMIO at 0xF2220
- Multi-resolution display: 640x480, 800x600, 1024x768, 1280x960 (CLUT8/RGBA32)
- Amiga rawkey scancode input, software cursor overlay
- battclock.resource via RTC_EPOCH MMIO register (0xF0750)
- Paula-compatible 4-channel audio DMA (see registers below)
- iewarp.library: AROS shared library that offloads compute to the IE64 coprocessor
Paula-compatible 4-channel sample DMA. The M68K audio.device writes sample pointers, lengths, periods, and volumes to per-channel registers, then enables DMA via DMACON. The engine reads sample bytes from guest RAM at the correct rate and outputs them via SoundChip FLEX DAC at 44.1kHz.
Per-Channel Registers (4 channels × 16 bytes, CH0=0xF2260, CH1=0xF2270, CH2=0xF2280, CH3=0xF2290):
| Offset | Name | Description |
|---|---|---|
| +$00 | AUDxPTR | Sample pointer in guest RAM (32-bit) |
| +$04 | AUDxLEN | Length in words (1 word = 2 bytes, Paula-style) |
| +$08 | AUDxPER | Period (frequency = 3546895 / period, PAL clock) |
| +$0C | AUDxVOL | Volume (0-64, Paula-compatible) |
Global Registers:
| Address | Name | Description |
|---|---|---|
$F22A0 |
DMACON | DMA control: bit 15=set/clear mode, bits 0-3=channel enables |
$F22A4 |
STATUS | Completion flags: bits 0-3 per channel (write-to-clear) |
$F22A8 |
INTENA | Interrupt enable: bit 15=set/clear, bits 0-3=channel enables |
Buffer completion triggers a Level 3 interrupt (M68K autovector 27) when the corresponding INTENA bit is set.
make aros-rom # Build AROS ROM + filesystem from source
make aros # Build VM with embedded AROS ROMIExec.library is an Amiga Exec-inspired protected microkernel for the IE64 CPU. Unlike classic Amiga Exec, which ran everything in flat supervisor space with no memory protection, IExec uses the IE64 MMU to enforce hardware-backed user/supervisor privilege separation with per-task page tables and W^X memory policy. The design preserves the Amiga programming model (signals, message ports, priority scheduling) while adding the isolation guarantees of a modern protected-mode OS.
M14 phase 5 status - native ELF contract frozen, dos.library seglist loading/shipped launch landed, shell command dispatch uses the real DOS loader path, and the retained GUI demos run through that same shipped path:
ELFis now both documented and live through DOS. M14 phase 1 froze the native subset (ELF64, little-endian,ET_EXEC,EM_IE64 = 0x4945,PT_LOADonly, page-aligned segments, no dynamic linker, noHUNK); phase 2 addedDOS_LOADSEG/DOS_UNLOADSEG; phase 3 added descriptor-based execution on top of those seglists; phase 4 routed shell command dispatch through that same DOS loader path; phase 5 makes the visibleC:command/demo path native ELF and proves the end-to-end demo flow on it.dos.librarynow owns native executable parsing and DOS-side launch.LoadSegresolves the name through DOS naming, walks the file's extent chain into a temporary contiguous image, validates the strict M14 ELF subset, allocates a DOS-owned seglist object, and copies eachPT_LOADsegment into DOS-owned memory with the file's final target VA, size, page count,R/W/Xflags, and preserved ELF entry point recorded in the seglist.DOS_RUNSEGturns that seglist into a launch descriptor and starts the child through the dual-modeExecProgrambridge.- The shell now goes through the DOS loader path.
DOS_RUNprefers the native ELF seglist path for strict M14 ELF commands and falls back to the legacy flat-image launch path only for compatibility inputs. With M14.1 phase 4, the shippedC:command/demo path and the service binaries underLIBS:,DEVS:, andRESOURCES:are all native ELF. From the user's perspective, command names, lookup rules, args, and unknown-command behavior stay the same. - Seglist lifetime is explicit and test-covered.
LoadSegreturns a DOS-owned seglist object;UnLoadSegfrees it. Successful launch copies the image into private child mappings, soUnLoadSegafter launch does not break the running task. Failed launch leaves the seglist intact. - Boot and command execution stay source-compatible. The old flat-image
ExecProgramABI still works for compatibility, but shipped boot/services now launch from the internal embedded-manifest ELF path and the visible shell path plus retained GUI demos exercise the real M14 DOS loader. - M14.1 is the follow-on full-ELF service migration, not a public API change. M14 shipped the public file-backed DOS loader API for
C:commands and apps. The planned M14.1 service migration moves boot/services to an internal embedded-manifest ELF path, replaces bootstrap-grant keying with internal manifest-entry IDs, and keeps that service source path internal rather than turning it into a second public DOS loader API. - M14.1 phase 5 completes the shipped full-ELF runtime path and locks the visible regressions. The kernel prepares staged strict-M14 ELF rows for the internal embedded boot manifest and boots
console.handlerplusdos.libraryfrom that manifest. Once DOS is online,dos.librarylaunchesShell,hardware.resource,input.device,graphics.library, andintuition.libraryfrom the same internal manifest source, and theLIBS:/DEVS:/RESOURCES:binaries are now emitted as native ELF too. This remains internal-only: the public DOS loader API is unchanged. - Phase 5 user-visible coverage is explicit. The full full-ELF runtime now has named end-to-end regressions for the clean-boot service census, shell command path (
Version,Avail,Dir,Type,Echo, unknown command), and both retained GUI demos:TestIExec_M141_Phase5_FullBootStack_ServiceCensus,..._CommandPathRegression,..._ShellUnknownRegression,..._GfxDemoRegression, and..._AboutRegression. - The contract is executable, not just prose. Focused host-side ELF validator tests remain, and phases 2-5 add red-first runtime tests for
LoadSeg,UnLoadSeg, descriptor launch, preserved ELF entry, target-VA-sensitive execution, startup-page initialization, args passing, seglist lifetime across launch, shell command dispatch through the new loader, full visible boot-stack stability, andC:GfxDemo/C:Aboutregression coverage. - Reference docs. The native subset and current loader status live in
sdk/docs/IntuitionOS/ELF.md; the broader kernel/runtime contract remains insdk/docs/IntuitionOS/IExec.md.
M14.2 phase 1 status - execution is now ELF-only at the public runtime boundary:
ExecProgramis descriptor-only. The old flat-imageExecProgram(image_ptr, image_size, args_ptr, args_len)ABI is removed; the surviving launch contract is the M14 descriptor path used by DOSRunSeg.DOS_RUNno longer falls back to legacy flat-image command files. Name-based command dispatch still uses the same DOS UX, but non-ELF executable content is rejected instead of being reflatted and launched.DOS_LOADSEGremains the strict native loader. The public DOS loader surface is unchanged:LoadSeg/UnLoadSeg/RunSegstay Amiga-shaped and ELF-backed.
M15 status - DOS expansion + system layout are now live on top of the preserved M14.2 ELF-only runtime boundary:
dos.librarynow owns the canonical M15 layout. The built-in assign table exposesRAM:,C:,L:,LIBS:,DEVS:,T:,S:, andRESOURCES:.RAM:remains the root/no-prefix compatibility view; fully qualified names resolve through the assign table; bare command search staysC:-only.DOS_ASSIGNis part of the live DOS surface. DOS now supports assign list/query/set for the mutable user assign rows while keeping the built-in layout stable.RAM:remains listed and queryable for compatibility but is not remappable throughDOS_ASSIGN.L:is a qualified helper namespace, not part of bare command fallback. Bare command names do not probeL:. Direct qualified helper access stays available throughL:.- The visible text-mode OS is richer without changing the loader contract. The shipped command/demo path now includes
Version,Avail,Dir,Type,Echo,Assign,List,Which,Help,GfxDemo, andAbout, all still launched through the DOS naming model and the M14/M14.2 ELF-backed loader path. T:is the writable temporary namespace. Temporary files round-trip through the same RAM-backed DOS store and existing open/write/read/close operations; M15 does not add hierarchical directories or persistent storage.- Boot and command execution remain ELF-only at the public boundary. The M14.2 guarantees above still hold:
ExecProgramis descriptor-only,DOS_RUNdoes not accept legacy flat images, andDOS_LOADSEG/DOS_RUNSEGremain the public executable path.
M15.2 host-backed boot status - the shipped runtime now boots from the generated host-backed system tree:
SYS:is the mounted host-backed boot volume.SYS:is now the live bootstrap root for the shipped IntuitionOS runtime rather than a future direction.IOSSYS:is the built-in system assign rooted atSYS:IOSSYS.IOSSYS:as the built-in system assign rooted atSYS:IOSSYSis the canonical system tree name, while the functional assigns (C:,S:,L:,LIBS:,DEVS:,RESOURCES:) keep their short M15-style publicDOS_ASSIGNview.DOS_ASSIGNstays compatibility-shaped. Public list/query rows remainname[16], target[16];SYS:andIOSSYS:are built-in resolver roots rather than mutable long chained rows; canonical functional assigns still expose short targets likeC/andLIBS/.exec.libraryremains the only ROM-resident runtime component. The bootstrap mounts hostfs asSYS:,console.handlernow boots fromIOSSYS:L/console.handler, anddos.librarynow boots fromIOSSYS:LIBS/dos.library.Shellnow boots fromIOSSYS:Tools/Shell. Commands, libraries, devices, resources, andS:Startup-Sequencenow come from the generatedsdk/intuitionos/system/SYS/IOSSYStree.RAM:andT:remain the writable in-memory namespaces. Their reads and writes stay on the provider-backed DOS path while hostfs remains read-only in M15.2.ASSIGN ADDis still deferred. M15.2 keeps bare command searchC:-only and does not add ordered-search assign semantics yet.
M15.3 layered assigns + writable SYS: overlay - M15.3 lifts the M15.2 ASSIGN-ADD restriction and lights up a layered model on top of the host-backed boot:
- Canonical assigns are now layered. Each of
C:,S:,L:,LIBS:,DEVS:, andRESOURCES:resolves through a built-in base list[SYS:X, IOSSYS:X]plus a per-slot mutable overlay list. DOS_ASSIGNgrows three new sub-ops.DOS_ASSIGN_LAYERED_QUERY(3) returns the FULL effective ordered list ascount × target[32].DOS_ASSIGN_ADD(4) appends to the mutable overlay (duplicate-add is a no-op).DOS_ASSIGN_REMOVE(5) removes from the overlay only - built-in base entries cannot be removed.- Old ABI keeps working unchanged.
DOS_ASSIGN_LIST/DOS_ASSIGN_QUERYkeep the M15.2name[16], target[16]row shape and project the first effective public target so existing callers keep seeing whatever the user just SET. DOS_ASSIGN_SETmirrors into both layers. A SET on a canonical layered slot replaces the mutable overlay with[TARGET]and writes the same target into the table entry, sodos_assign_lookup(and therefore the hostfs path resolver) keeps returning the user's chosen target without breaking M15.2 backward compat.- Hostfs now supports a writable
SYS:overlay. New device commandsBOOT_HOSTFS_CREATE_WRITE(6) andBOOT_HOSTFS_WRITE(7) let dos.library create/write under the host-backed root. Any path whose first component resolves toIOSSYSis rejected by the device -IOSSYS:is always read-only. - DOS_OPEN reads now fall through
SYS:→IOSSYS:. A newdos_hostfs_layered_relpath_for_resolved_namehelper prefers the writable SYS overlay (e.g.hostRoot/C/Foo) when the file is present and falls back to the embedded read-only IOSSYS path otherwise. ASSIGNshell command growsADD/REMOVE/ show.ASSIGN NAME:displays the assign's full effective ordered list.ASSIGN ADD NAME: TARGET:appends to the overlay.ASSIGN REMOVE NAME: TARGET:removes one overlay entry.ASSIGN NAME: TARGET:keeps its M15.2 replace semantics.- Non-layered assigns stay non-layered.
RAM:is non-mutable.T:is single-target writable.SYS:andIOSSYS:are built-in roots and rejectADD/REMOVEoutright. - Unchanged execution boundary. M15.3 makes no changes to
ExecProgram, ELF/seglist contracts, or the M14.2 ELF-only command boundary.
M15.5 substrate status - M15.5 is the loader/toolchain/process groundwork milestone that closes the gap between M15.4 hardening and M16 protected modules:
- full TDD remains mandatory for every M15.5 phase. Docs, focused regressions, implementation, and verification all ship together; the milestone is not treated as optional cleanup.
- PIE-capable codegen contract. The canonical IOS-native rules now live in
sdk/docs/IntuitionOS/Toolchain.md, so hand-written IE64 assembly and future compiler backends target the same forward-compatible addressing contract. - strict
ET_EXECruntime contract remains unchanged. M15.5 does not addET_DYN, runtime relocation, ASLR, or KASLR; it tightens substrate and diagnostics without widening the executable format boundary. - Internal teardown and fault substrate are now explicit. Exec now has ordered internal task-exit hooks, richer hardening diagnostics, and documented MMIO carve-outs/grant-path assumptions as groundwork for M16 rather than user-visible module behavior.
M16 protected module subsystem status - the protected library lifecycle is now shipped, documented, and test-covered:
OpenLibrary/CloseLibraryare now the canonical programmer-facing library lifecycle. Shipped library clients acquire runtime libraries through the registry-backed path first and only resolve compat ports afterward as internal transport, so module open-count and death-notification tracking stays exec-owned.graphics.libraryandintuition.libraryare no longer launched fromS:Startup-Sequence.dos.libraryremains the first disk-backed bootstrap library opened by exec;graphics.libraryandintuition.librarynow demand-load on firstOpenLibrary.- Exec owns the protected module registry. Library rows track identity, state, version, generation, owner task, compat port, open count, flags, waiter queue, and expunge deadline so crash/expunge/reload cycles stay generation-safe.
- Library registration and expunge are explicit. Library tasks self-register through
AddLibrary, exec drivesLIB_OP_EXPUNGE, andRESIDENTshell command pin/unpin flows toggleMODF_RESIDENTwithout turning libraries back into startup-sequence commands. - Crash handling is observable to clients. If an online library task dies, exec tears the row down, releases the loader bookkeeping, and signals openers with
SIGF_MODDEADbefore the nextOpenLibrarytriggers a clean reload.
M16.1 IOSM / VERSION status - version metadata is now universal and queryable:
- Every rebuilt runtime ELF carries IOS
PT_NOTEmetadata. The stripped, section-header-free runtime images carry a 128-byteIOS-MOD/IOSMdescriptor that records kind, public name, version/revision/patch, flags, message ABI, build date, and the fixed copyright string. - Resident version queries use existing IPC. Persistent services answer
MSG_GET_IOSMthrough caller-allocated shared memory, andexec.libraryexposes a public port for its own IOSM plus resident public-port enumeration. - Resident inventory is explicit IPC.
C:Residentlists exec-owned resident inventory throughEXEC_MSG_LIST_RESIDENT_INVENTORYon the publicexec.libraryport, andADD/REMOVEnow print visible status or diagnostics while preservingSYS_SET_RESIDENTas the narrow mutation primitive. VERSIONno longer reports stale milestone text. The default command output isIntuitionOS 1.16.6,exec.library 1.16.6 (2026-04-25), andCopyright © 2026 Zayn Otley.
M16.2 protected non-library module status - handlers, devices, and resources now use the protected-module lifecycle internally without absorbing PIE/ASLR work:
- Handlers, devices, and resources are first-class protected module classes internally.
console.handler,input.device, andhardware.resourceself-register through class-correctAddHandler,AddDevice, andAddResourcealiases on the same exec-owned registry and state machine used by libraries. - Boot policy replaces startup-script module launches.
console.handlerremains the only early-launch source exception, whiledos.libraryruns the eager post-DOS policy forhardware.resourceandinput.devicebefore Shell.S:Startup-Sequenceis command/configuration-only. - Module semantics come before public API and loader relocation work. M16.2 owns internal registration, naming, ownership, eager policy, hardware-resource broker generation checks, compat-port policy, and registry transitions for non-library classes; public
AttachHandler/OpenDevice/OpenResourceacquisition APIs are reserved for M16.2.1. - PIE and ASLR stay separate. M16.3 is reserved for making the shipped ELF surface consistently PIE-capable; M16.4 is reserved for real relocation and ASLR/randomized placement. M16.2 keeps the M14.2
ET_EXECplacement contract unchanged. - See
sdk/docs/IntuitionOS/M16.2-plan.mdfor the full TDD milestone plan and handoff notes.
M16.4.1 PT_NOTE runtime ELF / ASLR status - MODF_ASLR_CAPABLE is now operational:
- All runtime ELFs are stripped self-contained
ET_DYN. Shipped commands, libraries, devices, handlers, resources, host-provided DOS ELFs, and third-party DOS-loadable runtime ELFs use zero-relativePT_LOADaddresses, onePT_NOTEcarryingIOS-MODplus optionalIOS-REL, and no section header table. Section-header-only metadata is rejected. - Relocation is local and bounded. IntuitionOS accepts local runtime relocation metadata without adding dynamic linking,
PT_INTERP,PT_DYNAMIC,DT_NEEDED, imported-symbol lookup, PLT/GOT binding, lazy binding, or a shared-object namespace. Protected.libraryuse remains port/message based. - Userland ASLR is enabled. Runtime images load at chosen user image bases; M16.4.1 only readies the system for KASLR. M16.5 still owns fixed kernel VA blockers including
KERN_PAGE_TABLE,KERN_DATA_BASE,KERN_STACK_TOP, supervisor identity mapping, trap/fault paths, scheduler state access, panic/debug paths, and task page-table copying of kernel mappings. - Trusted protected-module autoload is provenance-bound. Trusted-internal launch authority requires trusted read-only system source provenance plus validated IOSM metadata, not a writable overlay filename.
- Hardening rules stay intact. W^X, SKEF/SKAC/SUA discipline, bounded inputs, and non-executable shared-memory
MAPF_READ/MAPF_WRITEtransfers remain mandatory.
M15.1 source layout status - the IntuitionOS sources are no longer forced to live in one monolithic assembly body, and the hostfs runtime now builds separately from the kernel image:
sdk/intuitionos/iexec/iexec.sremains the kernel assembly entrypoint and the top-levelexec.libraryimage/layout file.sdk/intuitionos/iexec/runtime_builder.sassembles the standalone hostfs runtime artifacts that are exported intosdk/intuitionos/system/SYS/IOSSYS.- Command sources now live under
sdk/intuitionos/iexec/cmd/. console.handler,input.device,hardware.resource,graphics.library, andintuition.librarynow live in per-component sources undersdk/intuitionos/iexec/handler/,sdk/intuitionos/iexec/dev/,sdk/intuitionos/iexec/resource/, andsdk/intuitionos/iexec/lib/.sdk/intuitionos/iexec/handler/shell.sownsprog_shell—prog_shellnow lives in a dedicated handler source file.sdk/intuitionos/iexec/lib/dos_library.sownsprog_doslib—prog_doslibnow lives in its own library source file.sdk/intuitionos/iexec/cmd/gfxdemo.s,sdk/intuitionos/iexec/cmd/about.s, andsdk/intuitionos/iexec/assets/elfseg_fixture.sare built as standalone runtime artifacts throughruntime_builder.s.sdk/intuitionos/iexec/boot/bootstrap.sandsdk/intuitionos/iexec/boot/strings.skeep the root bootstrap tables and boot strings out of the main kernel body.iexec.snow keeps the root boot/image wiring inboot/includes.make intuitionosstill builds everything from source, but onlyexec.libraryremains ROM-resident at runtime.- IntuitionOS still lives under
sdk/in M15.1 for repository-history reasons; this refactor changes source ownership, not repo packaging.
M13 phase 5 status - startup block ABI + dynamic task-image placement + live-task ceiling removal to the current ABI bound, with full boot-stack and GUI regressions green (implemented and tested):
- Booted services no longer self-locate from
CURRENT_TASK * USER_SLOT_STRIDE. The kernel now allocates a dedicated startup page for each launched task, writes the 64-byte startup block there, and places the startup-page base VA at0(sp), so the booted M12 stack reads task identity/layout from that page instead of recomputing addresses from the task ID. - Startup block fields are explicit and test-covered. Version, size, task ID, flags, and the actual code/data/stack base/page counts are written by
load_programandCreateTask.TestIExec_M13_StartupBlock_BootTaskPresentandTestIExec_M13_StartupBlock_CreateTaskPresentlock that contract in before the later dynamic-task-model phases land. - Source compatibility preserved for existing services. Services now discover the startup page from the initial stack contract rather than assuming a reserved window inside their first data page.
- Task image placement is no longer derived from
task_id * USER_SLOT_STRIDE.load_programandCreateTaskallocate code/stack/data/startup pages dynamically, using the legacy fixed image window first and then spilling into allocator-pool pages as needed. PT backing likewise uses the legacy fixed PT window first and then spills into allocator-pool-backed 64 KiB PT blocks. - The old 32-live-task ceiling is gone. The remaining fixed task-state tables now run to the current 8-bit internal-slot ABI ceiling (
MAX_TASKS = 255, with0xFFreserved in a few byte-sized kernel fields). Focused phase-4 tests now prove both >32 liveCreateTasksuccess and public task IDs advancing beyond 255 under churn. - Phase 5 boot-stack/demo validation is explicit. The final M13 gate now has named regressions for the full visible boot stack (
TestIExec_M13_Phase5_FullBootStack_ServiceCensus) and for both retained GUI demos (TestIExec_M13_Phase5_GfxDemoRegression,TestIExec_M13_Phase5_AboutRegression). The older milestone tests still exist, but M13 no longer relies on them implicitly.
Milestone 12.8 status - dos.library variable-size file storage + load_program cap removal (implemented and tested):
DOS_FILE_SIZEis gone. Per-file storage indos.librarymigrates from a fixed 16 KiBAllocMemblock to a chain of 4 KiB extents allocated on demand. Each file'sentry.file_vais now the head of an extent chain whose total length is bounded only by the kernel allocator pool. The previous bucket-BDOS_FILE_SIZE = 16384cap (bumped 4096 → 8192 → 16384 across M11/M12) has been deleted entirely.- Atomic-swap-on-rewrite.
DOS_WRITE's most load-bearing change: a new chain is allocated and populated before the old chain is freed, so an allocation failure during a rewrite leaves the previous file content fully intact. The handler never observes a half-rewritten state..dos_extent_alloc's internal.dea_fail_partialcleanup ensures that any partially-allocated new chain is freed before the error reply, so the steady-state memory usage on a failed write is identical to the pre-write state.TestIExec_DosM128_RewriteShrinksandTestIExec_DosM128_RewriteGrowsexercise both directions. - Four canonical extent operations in
iexec.s:.dos_extent_alloc(byte_count)walks upceil(byte_count / DOS_EXT_PAYLOAD)extents and links them into a chain (or returns 0 on failure with the partial chain freed);.dos_extent_free(first_va)walks the chain callingSYS_FREE_MEMon each extent;.dos_extent_walk(first_va, dst, byte_count)reads bytes from the chain intodst;.dos_extent_write(first_va, src, byte_count)writes bytes fromsrcinto the chain. The four are the only paths that touch the newDOS_EXT_*layout - every dos.library handler that used to memcpy into a fixed body now goes through them. DOS_RUNwalks extents into a temp buffer before passing them toSYS_EXEC_PROGRAM. The kernel still requires a contiguous image pointer, so the handlerAllocMems a temp buffer sized exactly to the program image, walks the extent chain into it, callsSYS_EXEC_PROGRAM, thenFreeMems the temp once the kernel has copied the image into the new task image. Steady-state dos.library memory is unaffected.- Two more arbitrary product caps removed as a prerequisite. When the dead-code skeleton bumped dos.library's code section from 8016 to 8712 bytes,
load_programrejected the image withERR_BADARGbecause of hardcodedcode_size <= 8192anddata_size <= 49152checks. Those were arbitrary product limits, not architectural bounds. Both caps were deleted in M12.8, and M13 phase 2 completes the cleanup by removing slot-derived task placement entirely:load_programnow allocates code/stack/data/PT backing dynamically inside the reserved image/PT windows, so the real failure mode is allocator/window exhaustion rather than a fake per-image byte ceiling. - Robust dos.library preamble. The other Phase 1 surprise was that dos.library's preamble hardcoded
add r29, r29, #0x3000to compute its data-page base - an offset that assumed exactly 2 code pages. Bumping dos.library to 3 code pages broke the preamble silently (it pointed at the stack page instead of the data section). Phase 1 fixed that by removing the hardcoded offset; the current M13 shape writesdata_baseat the top of the initial stack page, so aftersub sp, sp, #16the preamble recovers it withload.q r29, 8(sp)without assuming stack/data adjacency. dos.library can grow without touching its preamble again. - Stale "IntuitionOS M11" shell banner removed. The shell printed an outdated milestone label between the service-online lines and the canonical version banner. Killed in Phase 1 - the shell no longer emits its own banner; the canonical version banner from
VERSION(run byS/Startup-Sequence) is the only one. - No new syscalls. No new DOS opcodes. No widened ABI fields. No DOS protocol changes. The M12.5/M11.5 admission rule still holds. Existing dos.library clients (the shell, every M9–M12 example, every test) continue to work without recompilation. The visible change is that
DOS_WRITEno longer rejects byte counts above 16384, and reads/writes of multi-extent files transit a small chain walk inside dos.library that's invisible to clients.DOS_DELETE,DOS_TRUNCATE,DOS_SEEK, andDOS_APPENDare explicitly not added - each would be a separate future protocol-extension milestone if and when the need arises. - Test coverage. Three new tests in
iexec_test.goexercise the M12.8 storage shape:TestIExec_DosM128_FileLargerThanOldCap- 32 KiB write/read round-trip, byte-for-byte verified across 9 extents (2× the previous 16 KiB cap). Load-bearing test for the per-file cap removal AND the multi-extent walker.TestIExec_DosM128_RewriteShrinks- write 8 KiB, then rewrite the same file with 1 KiB; readback expects 1 KiB of new content. Proves atomic-swap on shrink.TestIExec_DosM128_RewriteGrows- write 1 KiB, then rewrite with 8 KiB; readback expects 8 KiB of new content. Proves atomic-swap on grow. Four other tests from the M12.8 plan are skipped with documented rationale (covered by existing tests, or test allocator-pool exhaustion which requires fragile state mocking). The full M12.5/M12.6 hardening test suite remains green throughout - no kernel data structure changes, no new ABI fields.
- Audit refresh.
IExec.md §5.13.1row forDOS_FILE_SIZEis now(removed) - B → ✓with a description of the slab/extent allocator and the atomic-swap rule. Two new(removed)rows forIMG_OFF_CODE_SIZE/IMG_OFF_DATA_SIZEdocument the load_program cap removal and explain why those rows were originally misclassified into bucket B. The §5.13 prose is updated to note that bucket B has lost its load-bearing entries - the remaining bucket-B rows are configured-policy values (KD_PORT_FIFO_SIZE,USER_DYN_PAGES,DATA_ARGS_*), not arbitrary product limits. - Boot integration. The long milestone summary line is gone. Boot now shows compact per-service version tags in each task banner,
VERSIONprints the plain OS version stringIntuitionOS 0.18, andS:Startup-SequenceprintsType HELP for commands and ASSIGN for layout. - Demo boot output:
M12 GUI demo runs unchanged from a user perspective - same About app, same close-gadget interaction. The task-model work remains mostly invisible to user-space code; the visible change is that the boot text now reflects M13 directly without replaying older milestone-summary ECHO lines.
exec.library M11 boot console.handler M11.5 [Task 0] dos.library M14 [Task 1] Shell M10 [Task 2] hardware.resource M12.5 [Task 3] input.device M11 [Task 4] graphics.library M11 [Task 5] intuition.library M12 [Task 6] IntuitionOS 0.18 Type HELP for commands and ASSIGN for layout 1>
Full kernel contract reference: sdk/docs/IntuitionOS/IExec.md (see §5.13 for the cap-removal policy and §12 Milestone 12.8 for the storage refactor)
Milestone 12.6 status (complete) - full hard-cap sweep across DOS, shmem, ports, and tasks
Milestone 12.6 status - full hard-cap sweep across DOS, shmem, ports, and tasks (implemented and tested):
-
Bucket C of the IExec audit is empty. Every previously bucket-C cap was removed (chain-allocator conversion) or reclassified into bucket A/B with a recorded reason. The four phases worked in order DOS → SHMEM → PORT → TASKS, gating on a green full-suite run between each.
-
Phase A -
dos.libraryfile/handle caps removed.DOS_MAX_FILES = 16andDOS_MAX_HANDLES = 8are gone. Both tables are now linked lists ofAllocMem'd 4 KiB pages (85 file metadata entries or 510 handle entries per page). Each file body is its ownAllocMem(DOS_FILE_SIZE)allocation rather than a packed offset into one big region. The oldstorage_vapacked-storage region is gone.dos.library'sDOS_OPEN/READ/WRITE/CLOSE/DIR/RUNhandlers all walk the chain.TestIExec_NoCap_DosFilesAndHandlesGrowopens 24 distinct files and keeps all 24 handles open simultaneously - proving both old caps are gone in one test. -
Phase B -
KD_SHMEM_MAX = 16removed. The system shmem table is unbounded: inline-first-16 + global overflow chain reachable throughKD_SHMEM_OFLOW_HDR. New helperskern_shmem_alloc_slotandkern_shmem_addr_for_id. All five shmem walkers (SYS_ALLOC_MEM,SYS_FREE_MEM,SYS_MAP_SHARED,kill_task_cleanupinline + overflow paths) updated. The 1-byte shmem ID field caps total slots at 255 (0xFF reserved).TestIExec_NoCap_ShmemMaxRemovedcreates 32 shmem regions and proves the chain head was actually allocated. -
Phase C -
KD_PORT_MAX = 32removed. The global port table is unbounded: inline-first-32 + global overflow chain reachable throughKD_PORT_OFLOW_HDR. Three new helpers (kern_port_alloc_slot,kern_port_addr_for_id,kern_port_find_public). All eight port walkers updated:.do_create_port,.do_find_port,.do_put_msg,.do_get_msg,.do_wait_port,.restore_waitport,check_name_unique, and thekill_task_cleanupport walk (which now walks inline + every chain page via a.ktc_port_clearsubroutine). The 1-byte port ID ABI ceiling is 255 (WAITPORT_NONE = 0xFFreserved).TestIExec_NoCap_PortMaxRemovedcreates 64 ports in one task and verifies all 64 IDs are distinct, with at least one in the chain range. Critical structural fix landed in this phase:build_user_ptnow copies the allocator pool mapping (supervisor-only) into every user PT, so chain helpers can run on the user PT without per-handler PT switches. The supervisor-only flag survives the copy because the source kernel PT entries do not have the U bit set; the user PT inherits the same flags. This shortcut is only safe in combination with Phase E's disjoint-VPN layout fix below - if userSYS_ALLOC_MEMcould allocate at a VPN inside the pool range, the supervisor copies would be silently overwritten and the chain walkers would dereference attacker-controlled memory. -
Phase D -
MAX_TASKSbumped from 16 to 32. The cap was bumped via a full layout adjustment (NOT a chain conversion) because the real bound on task count is the user-space slot region size, not an arbitrary kernel constant. The user-space layout was reorganized:USER_PT_BASEmoved from0x700000to0x800000(PT region grows from 1 MiB to 2 MiB), the allocator pool shifted from PPN0x800to PPN0xA00(lost 512 pages = 2 MiB to make room for the doubled PT region), andUSER_DYN_BASEshifted from0x800000to0xA00000. Every kernel data field after the TCB and region tables also shifted (TCBs grew from 512 to 1024 bytes, region tables from 2048 to 4096 bytes, etc.).MAX_TASKSis reclassified C → B (layout-bound, not arbitrary) with a named replacement plan: future slot-layout redesign milestone if 32 tasks ever isn't enough. The chain-allocator pattern was deliberately not applied here because the actual ceiling is the layout, not the kernel data structure.TestIExec_NoCap_MaxTasksBumpedTo32callsSYS_CREATE_TASK16 times and observes max task ID = 18 (proving the oldMAX_TASKS = 16cap is gone). Phase D's intermediate layout (which reused the same0xA00..0x1FFFVPN range for bothUSER_DYN_BASE..USER_DYN_ENDand the allocator pool) was later split apart by the Phase E security fix below - the final post-M12.6 layout has the pool at PPN0x1200..0x1FFFand the user-dyn window at VA0xA00000..0x1200000. -
Phase E - disjoint user-dyn / allocator-pool VPN ranges (security fix). A pre-merge security review caught a privilege-escalation path in the Phase D layout:
USER_DYN_BASE..USER_DYN_END(0xA00000..0x2000000) overlapped exactly with the allocator pool VPN range (PPN 0xA00..0x1FFF), so an unprivileged task could callSYS_ALLOC_MEMto overwrite the supervisor-only pool PTE thatbuild_user_pthad copied into its user PT, then callSYS_CREATE_PORTto allocate a port chain page at the same physical PPN, then callSYS_ALLOC_MEMagain at that VPN to remap it to attacker-RW memory. The supervisor-mode port chain walkers (which run on the user PT, not the kernel PT, per Phase C's design) would then dereference attacker-controlled bytes - yielding arbitrary kernel read/write from a pure unprivileged task. Fix: split the two ranges at the layout level. The user-dyn window now occupies the bottom half (USER_DYN_BASE..USER_DYN_END = 0xA00000..0x1200000, 8 MiB, VPN0xA00..0x11FF); the allocator pool occupies the top half (ALLOC_POOL_BASE = 0x1200,ALLOC_POOL_PAGES = 3584, PPN0x1200..0x1FFF, 14 MiB). UserSYS_ALLOC_MEMcalls now map fresh pool PPNs at user-dyn VPNs that are never in the pool VPN range, so the supervisor copies are immutable from user space. The pool shrunk from 22 MiB to 14 MiB, but the user-dyn window grew from 0 (it was aliased and unusable as a separate range) to a true 8 MiB - net usable user-controlled memory is roughly the same.TestIExec_PortChain_DisjointFromUserDynreproduces the exact attack pattern (AllocMem → 33×CreatePort → AllocMem) and asserts that both AllocMem VAs land inside the user-dyn window AND outside the pool VPN range, and that the port chain head PPN lands inside the disjoint pool range. Two files touched (iexec.incconstants,iexec_test.gotest constants + new test) - the kernel.sfile did not need any edits because every layout reference goes through named constants. -
Honest summary banner. The boot banner gains one new line:
Core OS objects: fixed product limits removed where practical; remaining limits are architectural or layout-boundNot the more ambitious wording from the original M12.6 plan ("memory or ABI-width exhaustion"). Tasks are layout-bound, so claiming "memory exhaustion" would have been misleading.
-
Audit refresh.
IExec.md §5.13.1is rewritten end-to-end. Every line number refreshed (the .inc file shifted ~50 lines from M12.6 edits). Bucket C is now empty. Every previously-C row has a recorded fate: removed (Phase A), reclassified C → A as a legacy alias for the inline range (Phases B/C and M12.5'sKD_REGION_MAX), or reclassified C → B with replacement plan (Phase D'sMAX_TASKS).DOS_FILE_SIZEwas never in scope for the chain-allocator sweep - it's a per-file byte size, not a count - and waits on M12.8's dos.library variable-size storage refactor. -
Boot integration. Version banner bumped to
IntuitionOS 0.14 (exec.library M11.6 / intuition.library M12 / hardware.resource M12.5 / cap sweep M12.6). -
Demo boot output:
exec.library M11 boot console.handler ONLINE [Task 0] dos.library ONLINE [Task 1] Shell ONLINE [Task 2] IntuitionOS M11 hardware.resource ONLINE [Task 3] input.device ONLINE [Task 4] graphics.library ONLINE [Task 5] intuition.library ONLINE [Task 6] IntuitionOS 0.14 (exec.library M11.6 / intuition.library M12 / hardware.resource M12.5 / cap sweep M12.6) Core OS objects: fixed product limits removed where practical; remaining limits are architectural or layout-bound IntuitionOS M12.6 ready All visible services are running in user space 1>M12 GUI demo runs unchanged from a user perspective - same About app, same close-gadget interaction. Only the boot banner and the kernel data structures underneath have changed.
Milestone 12.5 status (complete) - `hardware.resource` + minimal trust model + first cap removal
Milestone 12.5 status - hardware.resource + minimal trust model + first cap removal (implemented and tested):
hardware.resourceships as a user-space MMIO arbiter. A new public message port ("hardware.resource") hosts the first IntuitionOS broker. Owns the policy mapping from 4-byte region tags ('CHIP','VRAM') to physical PPN ranges. Clients sendHWRES_MSG_REQUESTnaming a tag; the broker uses the kernel-trusted sender task ID (R7 fromSYS_WAIT_PORT/SYS_GET_MSG, populated fromKD_MSG_SRC) - never anything client-supplied - and callsSYS_HWRES_OP/HWRES_CREATEto write a kernel grant row covering the right PPN range. Reply isHWRES_MSG_GRANTEDwhose data0 carries(ppn_base<<32) | page_count.SYS_MAP_IOis de-publicized. The hardcoded allowlist ((0xF0, 1)+[0x100..0x5FF]VRAM range) is gone. The handler now consults a kernel grant chain - only callers holding a covering grant entry succeed; everyone else getsERR_PERM.SYS_MAP_IOis reclassified fromnucleustobootstrap, trusted-internal - gated by KD_GRANT_TABLE. Net public ABI shrinks: one nucleus syscall leaves the public set, onetrusted-internalslot enters.- Minimal trust model with full lifecycle cleanup. A single kernel byte
KD_HWRES_TASK(initially0xFF/sentinel) holds the broker task ID. The first task to callSYS_HWRES_OP/HWRES_BECOMEclaims broker identity; subsequent claims returnERR_EXISTS.HWRES_CREATEis gated bycurrent_task == KD_HWRES_TASK.kill_task_cleanupclears the broker identity if the exiting task is the broker, walks the kernel grant chain to release all rows belonging to the exiting task, and the broker itself runs a stale-owner scrub on every request viaHWRES_TASK_ALIVEso a recycled task slot can never inherit broker privilege, granted MMIO, or per-tag owner state. There are no groups, ACLs, users, or capability masks beyond this single distinction. M12.5 deliberately stops at one privileged identity because no second consumer exists yet - full multiuser/login waits for persistent storage. - One new syscall slot, fresh number, four verbs.
SYS_HWRES_OPlives at slot 38, multiplexed by an R6 verb selector:HWRES_BECOME(claim broker identity) /HWRES_CREATE(write a grant row, broker-only) /HWRES_REVOKE(reserved for M13) /HWRES_TASK_ALIVE(broker queries kernel TCB state to reclaim stale per-tag owner slots, broker-only). Slot 37 stays a reserved hole forever per the M11.5 contract -TestIExec_HWRes_Slot37StillReservedmakes the contract executable so a future patch cannot quietly recycle it. SYS_GET_MSG/SYS_WAIT_PORTreturn R7 = sender task ID. The kernel populates R7 fromKD_MSG_SRC(whichPutMsgfilled in with the actual sender - unforgeable from user space). The broker uses R7 as its trusted sender identity instead of trusting any client-supplied data1 field. This is an extension of existing return-register usage, not a new syscall slot - net public ABI is still +1 net (one out, one in).- Bootstrap grant table. A small immutable list keyed by program-table boot index (NOT task ID, because task IDs do not exist at
kern_init). The boot-load loop resolves each row to a live grant row immediately afterload_programreturns the assigned task ID. M12.5 ships with exactly one bootstrap row:(console.handler, 'CHIP', PPN 0xF0)- this is what letsconsole.handlermap its serial-port MMIO beforehardware.resourceis alive, breaking the chicken-and-egg ofhardware.resourcedepending onconsole.handlerfor output. - Migrated callers.
input.deviceandgraphics.libraryno longer callSYS_MAP_IOambiently. Each spins onFindPort("hardware.resource")until the broker is up, sends oneHWRES_MSG_REQUESTper region needed (input.device wants'CHIP'; graphics.library wants'CHIP'and'VRAM'), waits for the reply, and only then callsSYS_MAP_IO.console.handleris the only kernel-bootstrap-granted task. KD_REGION_MAXcap removed. The first M12.5 cap removal - exactly one pre-existing fixed cap, locked in (not deferred). The per-task region table grows from a fixed 8-row block to inline-rows-plus-overflow-chain: rows 0..7 still live inline atKD_REGION_TABLEfor the fast path, rows 9+ live in allocator-backed chain pages reached through a per-task overflow header atKD_REGION_OVERFLOW_HEAD. All six region walkers (AllocMem,FreeMem,MapShared,SYS_MAP_IO,find_free_va,kill_task_cleanup) iterate inline first, then walk the overflow chain. Only failure mode is realERR_NOMEMfrom the page allocator.TestIExec_NoCap_RegionMaxRemovedproves a 9th allocation succeeds;TestIExec_HWRes_GrantTableChainGrowsexercises the same chain-allocator pattern against the grant table.- Named hard-cap policy lands as IExec.md §5.13:
IntuitionOS does not use arbitrary fixed product limits for core OS objects where dynamic allocation is practical. Remaining limits must be justified by architecture, ABI width, hardware constraints, or explicitly configured resource policy. Every existing cap is classified into A (architectural - keep), B (temporary - replace later), or C (arbitrary - remove now). M12.5 removes one C-bucket cap (
KD_REGION_MAX). (Historical M12.5 framing - the M12.6 sweep is now complete; see the M12.6 status block above for the final state. Bucket C is empty.) - Boot integration.
S/Startup-Sequenceextended withRESOURCES:hardware.resourceas the FIRST line (beforeDEVS:input.device) so it has its public port registered before any client callsFindPort. The version string is nowIntuitionOS 0.13 (exec.library M11.6 / intuition.library M12 / hardware.resource M12.5). - Demo boot output:
M12 GUI demo runs unchanged from a user perspective - same About app, same close-gadget interaction. Only the boot banner and the architecture underneath have changed.
exec.library M11 boot console.handler ONLINE [Task 0] dos.library ONLINE [Task 1] Shell ONLINE [Task 2] IntuitionOS M11 hardware.resource ONLINE [Task 3] <- new in M12.5 input.device ONLINE [Task 4] <- now grants its CHIP MMIO via hardware.resource graphics.library ONLINE [Task 5] <- now grants its CHIP+VRAM MMIO via hardware.resource intuition.library ONLINE [Task 6] IntuitionOS 0.13 (exec.library M11.6 / intuition.library M12 / hardware.resource M12.5) IntuitionOS M12.5 ready All visible services are running in user space 1>
Milestone 12 status (complete) - intuition.library + single-window compositor + structural cap cleanup
-
intuition.libraryships as a user-space service. A new public message port ("intuition.library") hosts the first IntuitionOS windowing layer. Lazy display ownership: text mode persists until the firstINTUITION_OPEN_WINDOW, at which point intuition.library opens graphics.library at 800×600 RGBA32, allocates its own 1920000-byte screen surface, registers as the SOLE graphics.library client, subscribes to input.device, andMapShareds the client's window backing buffer. Closing the (single) window tears down the entire graphics-mode subsystem (SYS_FREE_MEMof the screen + the client mapping,INPUT_CLOSEonly if intui actually owns the subscription,GFX_UNREGISTER_SURFACE,GFX_CLOSE_DISPLAY) and returns to text mode. Single-window only - secondINTUITION_OPEN_WINDOWreturnsINTUI_ERR_BUSY. M12.x will add z-order. -
Window protocol:
INTUITION_OPEN_WINDOW/INTUITION_CLOSE_WINDOW/INTUITION_DAMAGE. intuition.library composites the (mapped) client buffer into its 800×600 screen surface and paints Magic Workbench-style window decoration on top:- 1-pixel 3D bevel - WHITE highlight on top + left edges, BLACK shadow on bottom + right edges (raised look)
- 16-pixel title bar pinstripes alternating two shades of Amiga blue (light steel blue
0xFFB89878+ medium steel blue0xFF905030) - Close gadget at top-left: light grey fill
0xFFCCCCCC, black 1px outline, recessed black 4×4 centre square - Depth gadget at top-right: light grey fill, black outline, two overlapping rectangle outlines (the classic AmigaOS front/back depth icon)
All decoration is drawn via a small
.intui_fillrectjsr/rtshelper. After painting, intuition.library callsGFX_PRESENT(full-frame - graphics.library protocol unchanged). No new syscalls. No graphics.library protocol changes. The full M11.5 admission rule held. -
IDCMP-style event delivery:
IDCMP_RAWKEY/IDCMP_MOUSEMOVE/IDCMP_MOUSEBUTTONS/IDCMP_CLOSEWINDOW. intuition.library subscribes to input.device's existing raw event stream and routes events to each window's IDCMP port with screen→window-local coordinate translation. Both Esc and a mouse-button-down inside the close-gadget rect are intercepted intoIDCMP_CLOSEWINDOW. -
prog_aboutdemo client. A user-space program shipped asC/Aboutin the RAM: filesystem that demonstrates the full open→damage→close cycle and renders text via an embedded bitmap font. Allocates a 256000-byte (320×200 RGBA32) backing buffer, fills it with a dark teal backdrop, then renders five lines of white text into the content area using the embedded Topaz 8×16 bitmap font (the AmigaOS Topaz Plus font lives atsdk/include/topaz.rawand is embedded into the About app's data section viaincbin "topaz.raw"- full 256-glyph × 16-byte = 4096 bytes). Glyph rendering is done by tinydraw_char/draw_stringsubroutines in the About program itself. Lines rendered:About IntuitionOS Microkernel + intuition.library M12 demonstration window Press Esc to close (C) 2024-2026 Zayn OtleyAbout sends
INTUITION_OPEN_WINDOWwith the buffer's share handle (window centered on the 800×600 screen at(240, 200), size320×200), sends DAMAGE, services IDCMP, exits cleanly onIDCMP_CLOSEWINDOW. Reachable from the shell asC:About- not auto-launched at boot, so the existing M11 GfxDemo task budget is preserved. -
Structural caps cleaned up. M11 inherited several arbitrary 8-of-everything caps from M5/M7 that had become tight as services accumulated. M12 quadrupled the port cap, doubled the task and shared-object caps, doubled port name length, and bumped the dos.library file slot size:
MAX_TASKS: 8 → 16. Unblocked by removing the per-taskUSER_DYN_STRIDEwindow - the dynamic VA range (USER_DYN_BASE..USER_DYN_END) is now GLOBAL, with each task's own page table providing isolation. The M11 design pre-allocated each task a 3 MiB slice of the 32 MiB VA space and saturated at exactly 8 tasks; the global VA design has no such limit. Slot region shifted from 0x600000..0x67FFFF to 0x600000..0x6FFFFF, user-PT region from 0x680000 to 0x700000,ALLOC_POOL_BASEfrom PPN 0x700 to 0x800.KD_PORT_MAX: 8 → 32. 4× bump. M11 hit the 8-port wall as soon as services started creating their own anonymous reply ports.KD_SHMEM_MAX: 8 → 16. Doubled.PORT_NAME_LEN: 16 → 32. Doubled. M11's 16-byte cap was just barely big enough for"graphics.library"(exactly 16 chars, no null terminator) and outright too small for"intuition.library"(17 chars). Service names can now use the full Amiga-stylename.library/name.device/name.handlerform.DOS_FILE_SIZE: 4096 → 8192. Doubled so the intuition.library service image fits in a single RAM: slot. The fixed-stride slot table is still arbitrary in principle - replacing it with a packed-heap allocator was attempted in M12, hit a runtime bug, and was reverted to the simpler bump pending a focused follow-up.
-
Boot integration.
S:Startup-Sequenceextended withLIBS:intuition.library. The version string is nowIntuitionOS 0.12 (exec.library M11.6 / intuition.library M12). -
Demo boot output (no user input):
exec.library M11 boot console.handler ONLINE [Task 0] dos.library ONLINE [Task 1] Shell ONLINE [Task 2] IntuitionOS M11 input.device ONLINE [Task 3] <- launched by S:Startup-Sequence graphics.library ONLINE [Task 4] <- launched by S:Startup-Sequence intuition.library ONLINE [Task 5] <- launched by S:Startup-Sequence (M12, lazy display) IntuitionOS 0.12 (exec.library M11.6 / intuition.library M12) IntuitionOS M12 ready All visible services are running in user space 1>Then
1> C:Aboutopens a window, blits the backdrop, waits for IDCMP.Esc(or a click on the close gadget) sendsIDCMP_CLOSEWINDOW, About sendsINTUITION_CLOSE_WINDOW, intuition.library tears down the display, the system returns to text mode at the prompt.
Milestone 11.6 status (complete) - `SYS_EXEC_PROGRAM` legacy index path removed
- Legacy
R1 < USER_CODE_BASEindex branch deleted fromSYS_EXEC_PROGRAM. The dual-mode discriminator (if R1 >= USER_CODE_BASE → new ABI; else → table-lookup index path) is gone. The handler now begins withblt r1, USER_CODE_BASE → ERR_BADARGand falls directly into the validated image-pointer ABI body. Sub-USER_CODE_BASEvalues hard-fail withERR_BADARG. - Resolves the one item M11.5 deferred. M11.5 documented the legacy branch as
legacyand punted removal on the (incorrect) assumption that ~41 tests depended on it. The actual count was 6 test functions; 4 use the new ABI and survived unchanged, 2 tested the legacy helper directly and were deleted. - Guarded by
TestIExec_ExecProgram_LegacyIndexReturnsBadarg- callsSYS_EXEC_PROGRAMwithR1 = 0(formerly the valid index forprog_console) and assertsERR_BADARGplus the absence ofconsole.handler ONLINEin the output. - Kernel binary shrinks ~624 bytes (45620 → 44996).
program_tableitself remains because the kernel boot path still uses it directly to load console.handler / dos.library / Shell into task slots at init; that's separate from the syscall path. - Standalone milestone, landed before M12 opened so the M12 (intuition.library) branch carries no test churn unrelated to windowing.
Milestone 11.5 status (complete) - Exec boundary cleanup
- Exec boundary frozen. Syscall admission rule documented; surviving syscalls categorized as
nucleus/bootstrap/legacyinsdk/include/iexec.incand the IExec contract reference. New syscalls only land if they require kernel-privileged state AND cannot be expressed as a message to a user-space service. - 16 dead or redundant syscall slots removed. 15 reserved-but-unimplemented constants (
SYS_ALLOC_SHARED,SYS_DELETE_TASK,SYS_FIND_TASK,SYS_SET_TASK_PRI,SYS_SET_TP,SYS_GET_TASK_INFO,SYS_PEEK_PORT,SYS_ADD_TIMER,SYS_REM_TIMER,SYS_CLOSE_HANDLE,SYS_DUP_HANDLE,SYS_MAP_VRAM,SYS_DEBUG,SYS_SEND_MSG_BULK,SYS_RECV_MSG_BULK) are deleted from the header; the slot numbers remain unallocated holes that fall through toERR_BADARG.SYS_READ_INPUT(slot 37) is also removed - see below. SYS_OPEN_LIBRARYcollapsed toSYS_FIND_PORT.SYS_OPEN_LIBRARYis now a source-level alias (equ SYS_FIND_PORT) iniexec.inc. Slot 36 in the kernel dispatcher is retained as a one-instruction binary-compat redirect to.do_find_port, so any IE64 binary hardcoded to syscall number 36 still works. New code usesSYS_FIND_PORTdirectly. Guarded byTestIExec_OpenLibrary_DispatcherCollapse.- Console.handler now owns terminal MMIO directly. Previously the kernel exposed
SYS_READ_INPUT(slot 37), which readTERM_LINE_STATUS/TERM_STATUS/TERM_INfrom page0xF0on behalf of console.handler. M11.5 deletes the kernel handler. Console.handler now callsSYS_MAP_IO(0xF0, 1)at init time, caches the returned VA, and inlines the MMIO read loop into itsCON_MSG_READLINEpath. No client-visible change - the readline message protocol is unchanged. Slot 37 falls through toERR_BADARG, guarded byTestIExec_ReadInput_RemovedReturnsBadarg. SYS_MAP_IOallowlist documented as a known impurity.SYS_MAP_IOis a legitimate nucleus primitive, but its allowlist hardcodes IEVideoChip-specific knowledge (page0xF0, VRAM range[0x100..0x5FF]) inside Exec - that is policy leaking into the nucleus. The pure-microkernel solution is a futurehardware.resourceuser-space service that arbitrates physical regions; its bootstrap-ordering problem keeps it out of M11.5. The wart is documented and constrains future device additions to extend the existing allowlist rather than invent new MMIO syscalls.SYS_EXEC_PROGRAMlegacy index path documented aslegacyand deferred (as of M11.5 - subsequently removed in M11.6; see the current status block above). M11.5 punted on removal because ~41 tests appeared to touchExecProgramand the discriminator (R1 < 0x600000→ table lookup) was assumed to be load-bearing for tests that loaded programs by index. M11.6 reaudited and found 6 actual test functions, deleted the 2 that depended on the legacy helper, and removed the branch standalone before M12 began.Startup-Sequenceextended. The shippedS:Startup-Sequenceadds one trailingECHO All visible services are running in user spaceline - the milestone's user-visible artifact at the prompt.- No new syscalls. No protocol changes. No renumbering of any surviving syscall. IE64 binaries are compiled against numbers, not names, so the surviving syscall numbers remain stable forever.
- Demo boot output (no user input):
exec.library M11 boot console.handler ONLINE [Task 0] dos.library ONLINE [Task 1] Shell ONLINE [Task 2] IntuitionOS M11 input.device ONLINE [Task 3] <- launched by S:Startup-Sequence graphics.library ONLINE [Task 4] <- launched by S:Startup-Sequence IntuitionOS 0.11 (exec.library M11.5) <- VERSION run by S:Startup-Sequence IntuitionOS M11 ready <- ECHO run by S:Startup-Sequence All visible services are running in user space <- ECHO run by S:Startup-Sequence (M11.5) 1>
Milestone 11 status (complete) - input.device + graphics.library + fullscreen demo
- Everything from M1-M10: self-sufficient boot, per-task page tables with W^X, trap dispatch, preemptive round-robin, signals, named message ports with FindPort discovery, request/reply messaging, dynamic task creation/exit, AllocMem/FreeMem/MapShared with capability handles and explicit shared-mapping permission masks, GURU MEDITATION fault messages, console.handler, dos.library RAM: filesystem with assigns, interactive shell, S:Startup-Sequence execution, DOS-loaded programs
SYS_MAP_IOextended to take a page count (R1=base_ppn, R2=page_count → R1=mapped_va, R2=err). Range-aware allowlist:(0xF0, 1)for chip MMIO and[0x100, 0x5FF]for any contiguous slice of the 5 MB VRAM range. One region slot per mapping (graphics.library maps 300 VRAM pages in a single call). Backwards-compatible:R2=0is treated asR2=1for M9/M10 callers.USER_DYN_PAGESbumped from 256 to 768 (USER_DYN_STRIDE1 MB → 3 MB). Required to fit graphics.library's 1 chip + 300 VRAM + 300 surface = 601 page mapping. The 8×3MB layout uses VA0x800000-0x2000000- the top of the 32 MB VA space. (Historical M11 layout snapshot. The current post-M12.6 layout is different:USER_DYN_BASE..USER_DYN_END = 0xA00000..0x1200000(8 MiB), with the allocator pool split off into a disjoint VPN range at PPN0x1200..0x1FFFby the M12.6 Phase E security fix. See the M12.6 status block above for the current values.)load_programdata-size cap raised from 16384 to 20480 (5 data pages). Required for dos.library to grow to 5 data pages embedding the new M11 service images.- dos.library assigns extended:
LIBS:,DEVS:,RESOURCES:added to the assign table. Resolved by extending.dos_resolve_has_colonwith 4-char (LIBS/DEVS) and 9-char (RESOURCES) length checks. - Three new embedded service images in dos.library's data section:
LIBS/graphics.library,DEVS/input.device,C/GfxDemo. Installed into the RAM file table at init time. S:Startup-Sequenceupdated: now launchesDEVS:input.deviceandLIBS:graphics.librarybefore VERSION, so the M11 services are running by the time the user gets the prompt.- input.device - keyboard/mouse event service. Maps page 0xF0 once via
SYS_MAP_IO, pollsSCAN_*/MOUSE_*registers, push-deliversINPUT_EVENTmessages to a single registered subscriber port. Event format:mn_Data0 = (event_type<<24)|(code<<16)|(modifiers<<8)|flags,mn_Data1 = (mouse_x16<<48)|(mouse_y16<<32)|event_seq32. Mouse-move coalescing is a free property of the polling architecture. - graphics.library - fullscreen RGBA32 display service. Maps page 0xF0 (chip) and a 300-page VRAM range. Object model: enumerable adapters/modes, opaque display/surface handles, single-display owner, single-surface for M11. Client allocates surface buffer with
MEMF_PUBLIC, hands the share_handle to graphics.library viaGFX_REGISTER_SURFACE. Present is a synchronous CPU memcpy from the mapped surface to VRAM, replying with a per-surface monotonicpresent_seqcounter.mn_Data1reserves a packed dirty rect for future rect-bounded copies. - C/GfxDemo - minimal graphics client. Opens the M11 services, allocates a 1.2 MB surface, registers it, fills with a solid color, presents once, then waits for Escape and exits cleanly. Exercises the full M11 stack end-to-end.
- Forward-compatible by design: present takes a packed dirty rect (M11 always sends 0 = full frame), mode table queryable via
GFX_ENUMERATE_MODES/GFX_GET_MODE_INFO, opaque handles, explicit per-surface stride,GFX_WAIT_VBLANKandGFX_PRESENT_ASYNCreserved in opcode space. - Demo boot output (no user input):
exec.library M11 boot console.handler ONLINE [Task 0] dos.library ONLINE [Task 1] Shell ONLINE [Task 2] IntuitionOS M11 input.device ONLINE [Task 3] <- launched by S:Startup-Sequence graphics.library ONLINE [Task 4] <- launched by S:Startup-Sequence IntuitionOS 0.11 (exec.library M11) <- VERSION run by S:Startup-Sequence IntuitionOS M11 ready <- ECHO run by S:Startup-Sequence 1>