Compatibility
Developing software for the Atari ST, TT, and Falcon meant choosing between a variety of incompatible assemblers; there was no single portable assembly specification. Code was written for whichever tool the author owned.
Each assembler made fundamentally different design choices. They diverged on arithmetic operator precedence (left-to-right versus standard algebraic precedence), macro parameter syntax (positional \1 versus named parameters), macro-local label generation (\@, \~), local label scoping (.name, 1234$, @@name, ?name), comment delimiters (*, ;, |), and structure definition directives.
As a result, legacy Atari source archives can be difficult to build. Discovering a historical source release rarely means you can build it out of the box. Usually you must make manual edits to get things working with a modern cross-assembler.
When we built rg-asm, our primary design goal was to eliminate this friction. Rather than inventing another proprietary syntax, rg-asm is built to ingest preserved Atari code exactly as it was originally written.
To measure compatibility against real-world code rather than artificial micro-benchmarks, we test rg-asm against the heritage corpus curated by ggn at ggnkua/Atari_ST_Sources. This collection holds over 17,000 assembly source files, written between 1985 and the present day by game makers, demoscene crews, utility authors, and Atari themselves.
We continually test changes to rg-asm against this corpus to ensure that we maintain compatibility with all these legacy dialects. This page tracks some of the concrete results of those tests.
Atari Assembler Differences
While several cross-assemblers and native tools target the 68000 family, their architectural priorities differ significantly. The table below compares rg-asm with the primary tools used in Atari development: vasm (with the Motorola syntax module), rmac (the modern MADMAC successor), and native Devpac 3 running on an Atari ST/Falcon (under Hatari).
| Capability | rg-asm | vasm (mot) | rmac | Devpac 3 (ST) |
|---|---|---|---|---|
| Dialect Ingestion | Automatic detection for dialects with a content marker; others are named with --dialect |
Fixed syntax modules (-mot, -gas) selected at build time |
MADMAC syntax family only | Devpac 1, 2, and 3 syntax only |
| Operator Precedence | Each dialect's own model | Shifts and bitwise operators bind tighter than multiplication | Left-to-right (LTR) default | Same as vasm: shifts and bitwise operators bind tighter than multiplication |
| DSP56001 | Built-in: mixed 68030 + DSP56001 in a single pass | No | No | No (required separate Devpac DSP product) |
| Linking & Executable Output | Direct .PRG, GST, DRI, Pure C, ELF, flat binary |
Requires external linker (vlink) for complex layouts |
PRG or object output | Direct .PRG or GST object (CLink) |
| Branch Sizing Control | Exact fidelity mode (--no-opt-size) or sizing optimisation |
Optimisations controlled via -opt-all, -opt-jbra |
Optimisations via .opt |
Manual .s/.w sizing by programmer |
| Instruction Cycle Listing | 68000 instruction cycle annotations in the listing (--listing) |
Address and hex bytes only | Address and hex bytes only | Listing with addresses and bytes only |
Tier 1: Functional Ingestion
Functional ingestion tests whether historical source code compiles cleanly out of the box. The assembler must accept original syntax without manual edits, evaluate macros and dialect-specific expressions as intended, and emit valid executable or linkable binaries.
Tier 2: Binary Equivalence
Binary equivalence is a much stricter bar: does the emitted machine code match the original period binary byte-for-byte?
On the 68000, binary equivalence is vital for cycle-critical routines such as raster splits, border opening, and self-modifying code. If an assembler automatically shortens a 16-bit branch to an 8-bit branch, the altered cycle count breaks hardware timing.
What Binary Equivalence Requires
Matching original period binaries requires three factors to align:
- Strict Instruction Sizing (
--no-opt-size): Preserves explicit branch and addressing widths written by the author instead of automatically shortening them (e.g.bra.w→bra.s). - Isolating the Executable Payload: Strips volatile metadata (such as timestamps, host paths, or debugger symbols) to compare the true functional payload: TEXT, DATA, BSS size, and GEMDOS relocations.
- Self-Contained Code: Fully resolved sources with no missing external symbols.
Dialect Quirks
Assembling legacy Atari source code without modifying files requires addressing six specific architectural hurdles that routinely break standard cross-assemblers:
1. Operator Precedence Divergence
One of the most insidious sources of silent assembly corruption is arithmetic operator precedence. In Pure C and Sozobon, expressions follow standard algebraic/C precedence, where multiplication binds tighter than addition and addition binds tighter than shifts:
val = 1 << 2 + 1 * 3 ; Pure C / Sozobon: evaluates to 32
In Devpac 1, 2, and 3, vasm, Alcyon AS68, Brainstorm, GST ASM, and Lattice C ASM, shifts bind tighter than multiplication, which in turn binds tighter than addition:
val = 1 << 2 + 1 * 3 ; Devpac / vasm / Alcyon / GST / Lattice: evaluates to 7
In Atari Corp MADMAC and rmac, expressions are evaluated strictly from left to right across all binary operators without operator weighting, as documented in Atari's official MADMAC User's Manual ("All binary operators have the same precedence: expressions are evaluated strictly left to right. Thus the expression 1+2*3 evaluates to 9, not 7"):
val = 1 << 2 + 1 * 3 ; MADMAC / rmac: evaluates to 15
If one dialect's precedence is applied to code written for another, table offsets and raster line counts quietly calculate wrong values without generating an error. rg-asm addresses this by assigning specific precedence models per dialect. The shift-binding model (Devpac, vasm, Alcyon, GST, Lattice) and left-to-right model (MADMAC, RMAC) have been empirically verified by executing authentic period assembler binaries under godbox emulation and cross-referencing their official reference manuals. Dialects without an authentic host oracle currently retain an interim C-precedence model pending direct binary verification.
2. Macro Parameter Syntax & Variable Arguments
Different assemblers implemented distinct macro engines:
- Positional Arguments: Devpac uses
\1through\9; MADMAC supports\1..\9alongside\#(argument count) and string concatenation. - Local Label Generation: Devpac uses
\@to insert a unique invocation index; PhxAss and Devpac 3 support@@and\@; MADMAC uses\~and?0; vasm-mot handles nested macro stacks. - Named Parameters: GNU as (GAS), RMAC, and Metacomco support named parameters rather than numbered arguments.
rg-asm implements a multi-model macro expander capable of evaluating positional substitution, named parameter binding, repetition directives (REPT/ENDR and .rept/.endr), and conditional macro checks (IFMACROD, ^^defined) without syntax rejection.
3. Filesystem Conventions & Path Normalisation
Historical Atari source code was developed on GEMDOS/FAT12 filesystems, which were strictly case-insensitive and used backslashes (\) as directory separators:
INCLUDE "INCLUDES\SYS_VAR.I"
INCBIN "DATA\SPRITES.DAT"
On modern POSIX host systems (Linux and macOS), these paths fail immediately because filesystems are case-sensitive and treat \ as an escape character rather than a path separator. rg-asm incorporates transparent path normalisation:
- Converts backslashes to forward slashes automatically.
- Performs case-insensitive directory and file lookups across host file trees.
- Maintains search path hierarchies passed via
-I, matching original GEMDOS folder resolution.
4. Local Label Scoping Rules
Local labels allow subroutines to reuse common identifiers (such as loop labels). However, each era introduced a different local naming convention:
- Devpac 1, 2, and 3: Period-prefixed identifiers (
.loop,.skip) and numeric locals (1$,2$). - Turbo Assembler: Double-at identifiers (
@@loop,@@exit). - MADMAC & rmac: Question-mark identifiers (
?loop) and double-colon global scope breaks (::). - GNU as / vasm gas: Numeric jump labels (
1:, forward1f, backward1b).
rg-asm scopes local labels according to the dialect rules of the input file, clearing local symbol tables on global label transitions or section breaks exactly as expected by the original author.
5. Structure Offsets & Layout Directives
To define memory structures, developers used competing directive sets:
- Devpac 2 and 3:
RSRESET,RS.B,RS.W,RS.L, and__RScounters. - Metacomco / vasm:
OFFSET,SO, andFOoffset definitions. - PhxAss / rmac:
STRUCTUREandENDSblocks.
rg-asm maintains native support for each layout pattern, allowing structural offsets to evaluate without manual conversion to constant equates.
6. Falcon 030 + Motorola DSP56001 Mixed-Mode Assembly
On the Atari Falcon 030, high-performance routines typically split work between the 16 MHz 68030 host CPU and the 32 MHz Motorola DSP56001 digital signal processor. Historically, assembling Falcon productions required running two completely separate assemblers: an m68k assembler for the host code and Motorola's asm56000 (or Brainstorm's b56) for the DSP microkernel, followed by a conversion script to turn the DSP binary into a .P56 file or inline DC.W data statements.
rg-asm natively supports both instruction sets in a single tool. Developers can switch architectures directly in source or assemble DSP kernels into standalone .LOD, .P56, .DSP, or raw binary formats without auxiliary tooling.
Instruction Widths & Branch Sizing
A common frustration when assembling historical 68000 code with modern compilers is binary drift caused by automated size optimisation.
When optimising for size, modern assemblers automatically:
- Shorten 16-bit word branches (
bra.w label) to 8-bit short branches (bra.s label) if the displacement fits in −128 to +127 bytes. - Convert absolute memory addressing (
move.l $10000,d0) to PC-relative addressing (move.l label(pc),d0). - Collapse zero-displacement address register offsets (
0(a0)to(a0)).
While beneficial for general application code, automatic branch shortening can be catastrophic in time-critical Atari demoscene or game code. Sync scrolling, border-opening routines and some soundchip effects rely on exact instruction cycle timing. If an instruction is shortened from 12 cycles to 10 cycles (or 8 cycles for an untaken conditional branch), these effects will not work correctly.
Exact Reproduction Mode:
rg-asm includes explicit sizing control. When run with --no-opt-size, rg-asm preserves the instruction widths and addressing modes written in the source code. Without it, the default settings for some dialects may shorten an unsized branch: an unsized backward bsr that fits in the short range is assembled as the two-byte bsr.s with --dialect=devpac3 and with --dialect=vasm, and with --no-opt-size it stays the four-byte word form. Comparing output with an original release binary therefore needs --no-opt-size, and the output is only comparable where the source is otherwise compatible.
Historical Sources
Below are some examples of classic Atari sources. In each case, rg-asm ingests the preserved source and compiles cleanly, while rival cross-assemblers often stumble over period-specific syntax, macro expansions, or local label rules:
| Year | Group / Author | Production | Original Dialect | Routine / Effect | Ingestion Challenge | vasm | rmac | rg-asm |
|---|---|---|---|---|---|---|---|---|
| 1987 | The Exceptions (TEX) | Super Neo Demo | K-Seka | Bottom Border Shifter Sync | K-Seka line-based syntax, colon-free digit-only local labels (1, 2), and implicit register list masks | ✗ | ✗ | ✓ |
| 1989 | The Carebears (TCB) | The Cuddly Demos | Devpac 2 | Fullscreen Distortion & Overscan | Devpac local label scoping, macro expansion, and block allocations (blk) |
✓ | ✗ | ✓ |
| 1989 | The Lost Boys (TLB) | The Twist Scroll | Devpac 2 | Full-Screen Twist Scroll | Devpac 2 inline branch displacement sizing (.s/.w) and multi-register equates |
✓ | ✗ | ✓ |
| 1990 | Automation | Automation Packer 2.3 | Devpac 2 | Automation Packer 2.3 | Column-1 instruction formatting, numeric local labels (1$), and unquoted ASCII string operands |
✓ | ✗ | ✓ |
| 1991 | Altair / VMAX | Atomik Packer 3.3b | Devpac 2 | Atomik Packer 3.3b | Dense numeric local labels (1$, 2$), scoped backward branching, and inline data directives |
✓ | ✗ | ✓ |
| 1994 | Brainstorm | Brainstorm Language System | Brainstorm | Brainstorm String Data | Proprietary ASCII, length-prefixed ASCIIL, and zero-terminated ASCIIZ string directives |
✗ | ✗ | ✓ |
| 1995 | EKO | EKO System | Devpac 3 | 3D DSP Matrix & True-Color Pipeline | Devpac 3 OPT P=68030 CPU targeting, multi-section organisation, and scale-indexed addressing syntax ((An,Rn*s)) |
✓ | ✗ | ✓ |
| 1995 | Aggression | Motion | Devpac 3 | Bicubic Bezier Surface Engine | Devpac dotted symbols (bezi.vga), \@ macro expansion, and 68030 bitfield instructions (bfffo) |
✗ | ✗ | ✓ |
| 1997 | Avena | Sonolumineszenz | Devpac 3 | Audio Crossbar & DSP Engine | Devpac 3 structured section directives, symbol-relative displacement expressions, and 68030 control register syntax (CACR, VBR) |
✓ | ✗ | ✓ |
| 2000 | Doug Little (dml) | Bad Mood (BM307) | RMAC | Falcon 030 Doom Engine | Mixed 68030 and 68881/68882 FPU mnemonic parsing, RMAC macro argument expansion, and multi-file equate scoping | ✗ | ✓ | ✓ |
| 2003 | Dead Hackers Society | Derealization | RMAC | 3D & Voxel Demosystem | RMAC macro syntax, multi-level INCLUDE trees, section alignment directives, and strict left-to-right expression evaluation |
✗ | ✓ | ✓ |
| 2012 | Dead Hackers Society | Drone | RMAC | STe Synclock Demosystem | RMAC symbol redefinition (SET), nested macro parameter expansion, and case-insensitive filesystem path normalisation |
✗ | ✓ | ✓ |
| 2013 | Miro Kropáček (MiKRO) / Mystic Bytes | mxPlay 2.0 | Devpac 3 | SNDH Audio Driver | Devpac 3 RSSET/RSRESET structure offset blocks, conditional assembly (IFNE/ENDC), and macro parameter shifting (SHIFT) |
✗ | ✗ | ✓ |
| 2015 | Excellence In Art & ACCA & GGN | Xmas 2015 | RMAC | STe Intro Kernel | Modern RMAC optimisation switches (.opt "+O2"), LTR expression rules, and ELF32 relocation emission |
✗ | ✓ | ✓ |
| 2022 | Aske Simon Christensen (Blueberry) | Shrinkler 1.0 | vasm | Extreme Density 68000 Depacker | vasm Motorola syntax extensions, strict branch displacement sizing (.b/.w promotion), and register list alias equates |
✓ | ✗ | ✓ |
| Completion Rate (15 landmark productions across ST, STe, and Falcon) | 46% | 26% | 100% | |||||
Dialect Ingestion Comparison
Beyond individual productions, we executed each tool across our test suites covering twelve historical Atari assembler dialects. The table below records build success (without modifying source files), alongside the main issues causing tools to reject the code:
| Dialect Family | Sources Tested | Primary Ingestion Hurdle for Other Assemblers | vasm | rmac | rg-asm |
|---|---|---|---|---|---|
| Alcyon / DRI | 29 | rmac rejects idnt and offset; vasm rejects comline |
86% | 48% | 100% |
| GST ASM | 26 | rmac rejects GST header syntax and section layout directives | 92% | 0% | 100% |
| MADMAC | 46 | vasm rejects .text, .comm, .cargs, iif, and goto |
46% | 98% | 100% |
| Seka / Kuma | 21 | rmac rejects CODE directive; vasm rejects IFB and macro-local scope |
67% | 0% | 100% |
| Devpac 2 | 22 | rmac rejects unquoted .opt; vasm rejects listing control directives |
91% | 0% | 100% |
| TurboAss | 17 | rmac rejects OUTPUT directive; vasm rejects @@ local label syntax |
88% | 0% | 100% |
| AssemPro | 7 | vasm and rmac reject REDEF and AssemPro local label scoping |
43% | 14% | 100% |
| GFA ASM | 11 | vasm and rmac fail on GFA-specific macro expansion semantics | 91% | 82% | 100% |
| GenPC / sc68 | 26 | vasm and rmac reject GenPC numeric local labels and memory blocks | 27% | 12% | 100% |
| Devpac 3 | 27 | rmac rejects unquoted .opt; vasm rejects subttl and nested rept |
93% | 0% | 100% |
| RMAC | 27 | vasm rejects .macro definitions and dot-prefixed section directives |
0% | 100% | 100% |
| vasm | 77 | rmac rejects vasm macro argument syntax and carg equates |
100% | 34% | 100% |
| Total Across All Dialects | 336 | 72% | 37% | 100% |
A Single Universal Assembler
Our design goal with rg-asm is to provide a single, universal assembler capable of building any Atari source code—past, present, or future. Instead of juggling fragmented toolchains, emulated environments, or bespoke syntax flags, developers have a tool that is simple to deploy and operate: distributed as a self-contained, native executable with zero external dependencies, supporting all modern host platforms.
Preserving this level of compatibility requires ongoing testing. As new features, optimisations, and bug fixes are delivered, rg-asm continues to be regression-tested against the full heritage corpus to ensure that existing legacy code builds reliably and without regressions.