Shawn Quick 3c226221d1 Publish the QuickAsm README.
Replace the merlin README on main with the current QuickAsm page, including the qasm-legacy note.
2026-10-15 17:35:22 -07:00
2026-10-15 17:35:22 -07:00

QuickAsm

QuickAsm (qasm) is a Linux cross-assembler for the 65816, and for the 6502 and 65C02 through XC. It reads source in more than one syntax, lowers every file to one common pcode stream, and assembles from that stream. The object is an absolute binary.

The original QuickAsm repository is now qasm-legacy. This tree is the current assembler.

The syntaxes are Merlin 16+, Merlin 32, ORCA/M, LISA, and ca65. Merlin 16+ (Glen Bredon, Roger Wagner Publishing, 1988–89) is the language the encoder consumes. The other four are rewritten into the same pcode, so one program can be written in any of them and assemble to the same bytes. Identification, pseudo-op rewriting, and the limits of that lowering are in doc/SYNTAX.md. The Merlin 16+ language contract is doc/DIALECT.md.

Edit source on Linux, assemble it with qasm, and run the object on a IIgs, an emulator, or the sibling m65816 FPGA core.

From source to pcode to object

source file
    → identify the syntax, or take --syntax
    → dialect front end writes one pcode stream
    → two passes over that pcode
    → absolute BIN and a listing

A file is identified by --syntax, a marker in the first 40 lines, native high-bit Merlin text, or a score of distinctive tokens. A tie stays Merlin 16+. One --syntax name applies to every file on the command line. Omitting it identifies each file on its own, so one qasm command can assemble a Merlin file and a ca65 file together.

The front end expands macros, and pulls in Merlin PUT and USE, ORCA/M COPY, MCOPY, and APPEND, and ca65 .include, while it writes the pcode. QuickAsm then walks that stream twice. Pass 1 assigns addresses and defines symbols. Pass 2 emits the object bytes and the listing. --dump-pcode writes the stream beside the source as <file>.pcode.

Several sources on one command line assemble independently, in parallel. Each file keeps its own syntax and its own object.

Startup matches Merlin 16+ on a IIgs: 65816 opcodes, MX %11 (M and X both 8-bit), case-insensitive labels, program counter $8000, and filetype $06 (BIN). Put explicit MX and XC at the top of a source so it matches a real 16+ assembly. TYP changes the type printed with the object. The bytes stay a raw image.

Native Merlin disk files are accepted as-is (bit 7 set, $A0 column tabs, $8D line endings). Linux text .s files with spaces, tabs, and LF work too.

Labels of 26 characters or fewer, lines of 255 characters or fewer, a macro nest of 15, and a condition nest of 8 stay inside the Merlin 16+ limits for a source that will also assemble on a real 16+.

Absolute BIN is the object. Relocatable OMF and Linker.GS are unimplemented. Under REL, an unknown label is 0 and the object is still the raw image.

Build

QuickAsm builds and runs on Linux. The code is C++17. CMake is the build. The Makefile configures build/ when CMakeLists.txt is newer than the cache, then builds the requested target. Binaries are build/qasm and build/test_qasm.

make
make test
make fixtures
./build/qasm test/fixtures/hello.s
./build/qasm a.s b.s c.s

make builds qasm. Several source files on one command line assemble in parallel. make test runs the unit tests, assembles test/fixtures/hello.s and test/test.s, assembles the five coverage fixtures, and requires those five objects to be byte-identical. make fixtures assembles and compares only the coverage fixtures. Run both from the repository root. PUT and USE paths are relative to that directory.

make clean removes build/ and the .bin, .lst, and .pcode files the test targets write under test/.

Usage

usage: qasm [options] file.s [file2.s ...]
  -o FILE            object output (single file only)
  -l FILE            listing path (single file only; default: <src>.lst)
  --no-lst           do not write a listing
  -j N, --jobs N     max parallel assemblies (default: HW threads)
  -I DIR              add PUT/USE search directory
  --case-sensitive
  --algebraic
  --syntax NAME      merlin, merlin32, orca, lisa, ca65, or auto
  --dump-pcode       write <file>.pcode beside the source

With no -o, the object path is the DSK or SAV name from the source, or <file>.bin when the source sets neither. Every assembly writes a listing beside the source (foo.s → foo.lst) with the program counter, object bytes, and the original line. The listing header begins ; qasm <name> syntax=<dialect>. Override the path with -l, or skip the file with --no-lst.

--algebraic evaluates every expression with operator precedence. Merlin braces {...} do the same for one expression. Merlin expressions otherwise run left to right, so 1+2*3 is 9. ca65 and ORCA/M expressions are rewritten into fully parenthesized Merlin form, so the same text is 7. LISA stays left to right.

--syntax auto, and omitting --syntax, identifies each file. One name applies to every file on the command line. An unknown name is an error. --dump-pcode writes the lowered stream to <file>.pcode.

The assemble line reports the byte count, origin, dialect, object path, listing path, and filetype. A clean QuickAsm run looks like:

test/fixtures/hello.s: assembled 4 bytes at $008000 syntax=merlin -> test/fixtures/hello.bin  lst test/fixtures/hello.lst (typ $06)

Syntaxes

Dialect --syntax How a file is recognized
Merlin 16+ merlin Default when the text is ambiguous. Native high-bit, $A0, or $8D files.
Merlin 32 merlin32 Same language. Assembler: Merlin 32, or GEQU / GEQ.
ORCA/M orca longa, anop, mcopy, dc i1'...'.
LISA lisa .OR, .EQ, .DA, .HS, .EP.
ca65 ca65 .org, .byte, .p816, .macro.

A marker in the first 40 lines wins: syntax: ca65 or Assembler: Merlin 32.

Macros expand while the source is lowered, so Merlin ]1 text and LUP's @ letter still work when the assembler runs the pcode. PUT and USE are pulled in at that step, including when they sit in a false conditional. ca65 symbols are case-sensitive. ORCA/M CASE ON is too. Segments are flattened into the absolute image. A LISA EPZ name is forced to direct page on later instructions.

ORCA/M AIF, SETA, and LCLA, and ca65 anonymous : labels, are rejected. The full pseudo-op tables and the remaining limits are in doc/SYNTAX.md.

Tests

The coverage fixtures are one program, written once in each syntax:

  • test/fixtures/coverage-merlin.s
  • test/fixtures/coverage-merlin32.s
  • test/fixtures/coverage-orca.s
  • test/fixtures/coverage-lisa.s
  • test/fixtures/coverage-ca65.s

make test and make fixtures assemble them and compare each object with coverage-merlin.bin. A match is silent. To compare the objects by hand after a run:

cmp test/fixtures/coverage-merlin.bin test/fixtures/coverage-merlin32.bin && \
cmp test/fixtures/coverage-merlin.bin test/fixtures/coverage-orca.bin && \
cmp test/fixtures/coverage-merlin.bin test/fixtures/coverage-lisa.bin && \
cmp test/fixtures/coverage-merlin.bin test/fixtures/coverage-ca65.bin && \
echo match

testdata/ is the faddenSoft Merlin 32 corpus (Apache 2.0). It is a comparison suite and is not part of make test.

Documentation

  • doc/DIALECT.md — Merlin 16+ line format, expressions, locals, and directives
  • doc/SYNTAX.md — identification, pcode, per-dialect pseudo-ops, and limits
  • doc/manuals/Merlin_816_Macro_Assembler_Manual.pdf — Merlin 8/16 user’s manual
  • doc/manuals/Merlin_16_Supplementary_Manual.pdf — Merlin 16+ additions
  • doc/manuals/Merlin_16_Utilities_Manual.pdf — 16+ disk utilities, outside this assembler

License

MIT. See LICENSE.

S
Description
Merlin 8/16/16+ Compatible 6502/65816 assembler/linker suite in C++ for linux
Readme
41 MiB
0 Stars 1 Watchers 0 Forks
Languages
Assembly 40.4%
C++ 35.3%
C 23.6%
CMake 0.3%
Makefile 0.2%
Other 0.2%