Using it

When assembling is not the last step

Most dialects here produce a flat binary at one origin: you name a file, you get its bytes. Two do not, because the real toolchain does not either — and in both cases asm198x performs the step that would otherwise need a second tool.

ca65 emits object files. A .nes ROM is what ld65 makes of them, using a config that says where each segment lives. Assembling alone gets you nothing you can run.

So the ca65 front-end assembles and links, in one pass, and hands back the finished ROM:

.segment "CODE"
reset:
        sei
        cld
        ldx #$ff
        txs
loop:   jmp loop

.segment "VECTORS"
        .word reset
        .word reset
        .word reset
assembled + linked 40976 byte(s) -> game.s.bin

40,976 bytes every time: a 16-byte iNES header, 32K of PRG and 8K of CHR, filled with $00. The segments land where the NROM layout puts them — CODE at $8000, VECTORS at $FFFA, ZEROPAGE at $00, with OAM and BSS occupying RAM rather than the file.

The layout is fixed, not read from a config. There is no .cfg parser and no object-file format here: one NROM configuration, encoded directly, assembled and linked in memory in a single pass. That is a deliberate boundary rather than an unfinished one — the whole NES curriculum links with the same config, so a general linker would be machinery with one caller. A second config is the point at which generalising becomes worth it, and it has not arrived.

What this buys you is that the ROM is comparable: it is checked byte-for-byte against real ca65 + ld65 output, which a hand-rolled layout could not be. The figures are on Why asm198x.

Amiga: hunks, and who chooses the addresses

--exe writes an AmigaDOS hunk executable rather than a flat binary:

asm198x --dialect vasm --exe demo.s -o demo.exe

That is the loadable file: a hunk header, a hunk per section, and the reloc32 tables that go with them — matching vasmm68k_mot -Fhunkexe -kick1hunks for everything the AmigaDOS loader consumes. vasm’s optional debug symbol table is not emitted.

Hunks are relocatable, and this is the part that changes how you write code. An executable carries no load address; AmigaDOS decides where each hunk goes at load time and rewrites the 32-bit references in the reloc32 tables to match. Two consequences:

  • An absolute reference to a label in another section becomes a relocation, so the loader can fix it up. That is automatic and costs you nothing.
  • A PC-relative reference cannot, because the distance between two hunks is not known until they are loaded. PC-relative addressing is therefore used only within a section.

The debug sidecar follows the same logic: symbols are recorded as (section, offset) with no base address, because there is no address to record until something loads the file.

What --prg and --sna are not

Those two are containers, not link steps. A .prg is the flat binary with a two-byte load address in front of it; a .sna is the flat binary sitting at its own origin inside a snapshot of a 48K Spectrum’s memory and registers, which is why it needs an end directive to say where execution starts.

Neither resolves a reference or moves a byte relative to another: your code assembles to the same bytes with the flag as without it. The distinction matters when you are deciding which flag to reach for — --prg and --sna wrap what you already have, --exe and the ca65 path build something you did not.

Debug artifacts on the linked paths

--debug and --sym work on both, and describe what the linker produced — hunks for the Amiga, laid-out segments for the NES — rather than the flat assembly they came from. --listing is a flat-dialect artifact; combining any of them with fmt or disasm is an error rather than a silent no-op.