A Z80 and Z80N macro assembler written in Go that can also execute and test the code it assembles. It assembles Z80 source to raw machine code, packages it directly into runnable ZX Spectrum tapes and snapshots, or runs it in a built-in Z80 emulator and asserts on the result. No third-party assembler dependencies; one static binary.
# the whole loop, no other tools:
zenas assemble game.asm game.bin # bytes
zenas build game.asm --z80 --start main # runnable snapshot
zenas assert math_test.asm # run the test suite, exit non-zero on failureBeyond the conventional core, zenas carries a small set of deliberate ideas — a typed macro linkage layer, package namespacing, Go-style build tags and test files — that together form a coherent design. New users should start with the programming guide; the reasoning behind the design is in the design rationale.
| Command | Does |
|---|---|
zenas assemble |
assemble source to a raw binary (or a JSON report) |
zenas build |
assemble and package into a tape or snapshot |
zenas run |
assemble and execute in the built-in Z80 emulator |
zenas assert |
execute and check the final machine state (CI-friendly) |
zenas version |
print the version |
zenas help |
list options; help --all for the full reference |
zenas assemble <input.asm> [output.bin] [--hex] [--json=LEVEL] [--charset=NAME]
[--sym[=path]] [--define=NAME[=VAL]] [--tag NAME]
[--next | --cpu=Z80|Z80N]
# assemble to a raw binary
zenas assemble game.asm game.bin
# enable the ZX Spectrum Next instruction set
zenas assemble game.asm --next
# conditional build with a defined symbol and two build tags
zenas assemble game.asm out.bin --define DEBUG --tag debug --tag plus3
# write a pasmo-format symbol file alongside the output
zenas assemble game.asm game.bin --sym
# emit a JSON report instead of a binary
zenas assemble game.asm --json=detailed
--next (or --cpu=Z80N) turns on the Z80N extensions; the default is Z80.
--define pre-sets a symbol for IF/IFDEF. --tag selects a build tag, the
way Go build tags select variants: each tag defines ZENAS_TAG_NAME,
ZENAS_TAGBIT_NAME, and contributes to the composite ZENAS_TAGS bitmask, so
tags compose in IF conditions with AND/OR/NOT. --charset picks the
string encoding (Spectrum, MSX, CPC and regional variants; see help --all).
zenas build <input.asm> [--tap] [--tzx] [--sna] [--z80] [--loader]
[--start <addr|symbol>] [--sp <addr>]
[--model 48k|128k|plus2|plus2a|plus3] [-o <basename>]
# snapshot for development testing — loads and runs in one step
zenas build game.asm --z80 --start main --model 128k
# tapes for distribution, with a BASIC auto-run loader
zenas build game.asm --tap --tzx --loader --start main
build assembles the source and packages it into loadable artifacts. Snapshots
(--sna, --z80) carry a full machine state, so the code runs immediately at
--start; tapes (--tap, --tzx) encode the code as a CODE block, and
--loader prepends a BASIC loader so LOAD "" runs it. --start (an address
or a source label) is required for snapshots and for --loader; --sp overrides
the stack pointer; --model picks the machine a snapshot is overlaid on; -o
sets the output basename. Recommended workflow: snapshots while iterating, tapes
to ship. Examples are in examples/.
zenas can execute the code it assembles, in a built-in Z80 emulator, and assert on the result — no separate emulator, no file round-trip.
# run a routine and watch it execute
zenas run game.asm --call=main --trace
# assert a routine's contract (exits non-zero on failure, so it fits CI)
zenas assert math.asm --call=multiply --expect="A=0x0C,CF=0"
# run a whole test suite: a *_test.asm file with test_* routines and .EXPECT
zenas assert math_test.asm
run executes the assembled code and reports the final CPU and memory state;
--trace shows each instruction, --dump=START:LEN dumps memory, and
--preload=ADDR,FILE loads input data first. assert adds --expect checks
over registers, flags, and memory bytes. A *_test.asm file with test_*
routines each followed by an .EXPECT directive runs go-test style — and
.EXPECT/.MATCH are legal only in *_test.asm files, so test metadata can
never leak into a shipped binary. See docs/RUNTIME.md for
the complete run/assert reference.
go install github.com/ha1tch/zenas@latestOr build from a checkout:
make build # -> build/zenas
make test # run the Go test suite
make smoke # assemble the bundled examples as a sanity check- Go 1.25 or later (build from source only; the released binaries have no dependencies)
zenas assembles complete Z80 programs and matches pasmo byte-for-byte on real operating-system source — a multi-kilobyte kernel with many included subsystems assembles to an identical binary, with every symbol address matching. The instruction set covers three classes, each cross-checked against an established reference assembler:
- the 468 documented Z80 instructions (match pasmo and sjasmplus);
- the 48 undocumented IX/IY half-register forms (match pasmo and sjasmplus), with the illegal combinations rejected;
- the 29 Z80N (ZX Spectrum Next) extensions (match sjasmplus), off by
default, enabled with
--next.
See docs/INSTRUCTION_SET.md for the coverage
detail, including the scripts that reproduce the verification.
A note on scope: the byte-for-byte match covers the shared instruction and
data-directive core. Source that leans on pasmo's or sjasmplus's own
directive layers (macro systems, DEVICE, structs) is a different matter —
the measured state of that compatibility, gap by gap, is documented in
docs/DIALECT_COMPATIBILITY.md.
- Programming guide — coming from another assembler: what's familiar, what differs, and what zenas uniquely enables.
- Design rationale — the single idea behind the unusual features, and what the C-style layer is (and is not) for.
- Seam comparison — how far other assemblers' facilities go towards the same design, as a set of reference points.
- Packaged-program tutorial — a worked
walkthrough building and testing a program with packages,
.EXPECT,.MATCH. - Manual — full command line, language, and directives.
- Runtime reference — running and testing code with
runandassert. - Instruction set — coverage by family, with reproducible verification.
- Z80N reference — Z80N opcode encodings.
- Dialect compatibility — the measured state of pasmo and sjasmplus source compatibility.
zenas help lists the common options; zenas help --all is the full reference.
Apache-2.0. See LICENSE and NOTICE.
Copyright (c) 2026 haitch h@ual.li