A compact, table-driven Motorola 68000 assembler written in Go.
Current version: v1.3.2
The goal of this project is to provide a clean, maintainable, and easily extensible assembler for the 68k family — focusing on clarity, modularity, and full control over binary output. It is particularly well suited for educational use, embedded projects, and retro computing enthusiasts who prefer a minimal toolchain.
- Two-pass macro assembler with deterministic binary output
- Supports all mnemonics of 68000 CPU
- Include paths, pseudo ops, pre-defined symbols and rich expressions
- Local numeric labels (e.g.,
1f/1b) with validated forward/backward resolution - Simple and fast command-line tool with optimized performance
- Embeddable directly into Go programs via a public API
- Optional source listings to pair machine code with source lines
- Output formats: flat binary, Motorola S-record (S0/S3/S7), and ELF32 (m68k) with a single load segment plus standard section/symbol tables
- Documented pseudo-ops including
.org,.byte,.word,.long,.align,.even,.text,.data,.bss,.section,.macro/.endmacro, andDC.B/DC.W/DC.L - Table-driven instruction encoding (based on
InstDef,FormDef, andEmitStepstructures) - Clear modular design in Go (
lexer,parser,expr,instructions,encode,assemble) - Performance optimizations:
- Map-based effective address (EA) validation for O(1) lookups
- Consolidated validation helpers to reduce code duplication
- Priority-based instruction registration for correct opcode pattern matching
- Minimized string operations in hot paths
- Correct PC-relative displacement calculations for
d16(PC)andd8(PC,Xn)addressing modes - Proper branch displacement handling for word-sized branches and DBcc instructions
- Support for
$as current program counter in expressions - Support for
.wand.lsuffixes on labels in PC-relative expressions
- CPU Generation: Strictly targets the 68000 instruction set. Extensions for 68010, 68020+, or FPU coprocessors are not currently supported.
- Linker Support: ELF output now includes standard
.text,.data,.bss,.symtab,.strtab, and.shstrtabmetadata, and source can switch between.text,.data, and.bss, but the assembler still emits a single executable-style image rather than relocatable objects. - Section Layout: Section directives are intentionally minimal and forward-only:
.text->.data->.bss..bsscontent must remain zero-initialized. - Optimizations: The assembler prioritizes deterministic output over optimization. It does not automatically relax instructions (e.g.,
JMPtoBRA) or substitute shorter instruction forms unless explicitly handled by the instruction selection logic.
The assembler implements core principles of assembler construction:
- Lexical and syntactic clarity: each stage is well-separated and testable.
- Declarative instruction definitions: encoding logic defined via compact data tables.
- Binary precision: full control over emitted bytes without hidden abstractions.
- Go idioms: idiomatic use of Go’s type system, slices, and maps for maintainability.
You can install the CLI directly from GitHub using Go 1.26+:
go install github.com/jenska/m68kasm/cmd/m68kasm@latestOr build locally:
git clone https://github.com/jenska/m68kasm.git
cd m68kasm
go build ./cmd/m68kasmm68kasm [options] <source-files>Options
| Option | Description |
|---|---|
-o <file> |
Write binary output (default: a.out) |
| `--format <bin | srec |
-I <path> |
Add include search path |
-D name=val |
Define symbol |
--list <file> |
Generate a source listing (use - for stdout) |
--version |
Print assembler version and exit |
-v |
Verbose logging |
Example:
m68kasm -o hello.bin tests/e2e/testdata/hello.s
hexdump -C hello.binThe assembler can also be embedded directly into Go programs via the public API provided by the root module:
package main
import "github.com/jenska/m68kasm"
func main() {
// Assemble source that comes from a string and keep listing metadata.
bin, listing, err := m68kasm.AssembleStringWithListing(".byte 0x12\nMOVEQ #1,D0\n")
_ = listing // listing contains per-line PCs and bytes
_ = err
// Emit Motorola S-record text directly from the same source.
srec, _ := m68kasm.AssembleStringSRecord(".org 0x1000\n.byte 0x12,0x34\n")
// Produce an ELF image with section headers and label symbols.
elf, _ := m68kasm.AssembleStringELF(".org 0x2000\n.byte 0x12\n")
_ = bin
_ = srec
_ = elf
}Additional helpers support assembling from []byte, io.Reader, or file paths
with or without listings, and can append results into an existing destination
buffer.
Errors returned by the public API include source location context and, when
available, the original source line with a caret marker. Type-assert to
m68kasm.Error when you want structured access to line and column data.
If you want to see the assembler in action immediately, clone the repository and build the CLI, then assemble the bundled
hello.s example. The following commands will produce a binary and print it as hexadecimal bytes:
git clone https://github.com/jenska/m68kasm.git
cd m68kasm
go build ./cmd/m68kasm
./m68kasm -o hello.bin tests/e2e/testdata/hello.s
hexdump -C hello.binThe tests/e2e/testdata/hello.s file demonstrates the currently implemented instructions (MOVEQ, LEA, and BRA) and is
exercised by the automated end-to-end tests.
cmd/m68kasm/ # Command-line frontend
internal/asm/ # Assembler pipeline (lexer, parser, evaluation, encoding)
internal/asm/instructions # Declarative instruction tables and helpers
tests/e2e/ # End-to-end tests for the CLI
tests/e2e/testdata/ # Sample assembly sources and expected binaries used by the tests
docs/ # Reference material including grammar and opcode tables
docs/syntax.mddocuments the accepted assembly syntax and directives.docs/grammar.ebnfprovides the EBNF grammar used by the parser.docs/M68kOpcodes.pdfis a handy opcode reference while extending the instruction tables.
.org <expr>sets the location counter and program origin..byte,.word, and.longemit big-endian data items..align <n[, fill]>aligns the location counter with optional fill bytes..evenaligns the location counter to an even address..macro/.endmacrodefine parameterized macros.DC.B,DC.W, andDC.Lare aliases for the corresponding data directives.
A ready-to-use GitHub Actions workflow (.github/workflows/ci.yml) is provided.
It performs:
- Module verification (
go mod verify) - Vetting (
go vet) - Unit and E2E tests (
go test ./...) - CLI build validation
To keep the repository clean and consistent, please follow these steps:
# Format source code
go fmt ./...
# Lint and vet
go vet ./...
# Run all tests
go test ./... -vContributions are welcome!
If you want to add new instructions, improve encoding tables, or extend pseudo-ops:
- Fork the repository.
- Create a feature branch (
feature/add-cmp-instruction). - Add or update relevant test cases.
- Submit a pull request.
Make sure the CI passes before submitting.
Instruction Pattern Matching
- Implemented priority-based instruction registration system to fix opcode pattern conflicts (MULU/MULS vs AND/OR)
- Replaced filename-based ordering with explicit Priority field in
InstrDef
Validation & Code Consolidation
- Created reusable EA (effective address) validation helpers with map-based lookups:
isMemoryAlterable(),isDataAlterable(),isReadableEA(),isReadableDataEA(),isMovemLoadEA()
- Consolidated redundant validation patterns across 15+ validation functions
- Removed ~70 lines of duplicate switch statements in validation logic
- Eliminated 12 instances of repeated operand swapping with
swapSrcDstIfDstNone()helper
Performance Optimizations
- Replaced switch statements with O(1) map lookups in hot paths (
isPCRelativeKind(),validateControlEA()) - Removed unnecessary string operations (replaced
strings.HasPrefix()with direct byte comparison) - Simplified
reverse16()bit manipulation for MOVEM encoding - Improved immediate range validation performance
Testing & Benchmarks
- All 200+ unit tests pass
- All e2e tests pass
- All benchmarks pass with improved performance metrics
- Fixed validation benchmark tests
- Diagnostics and listing upgrades: Enhance listings with symbol resolutions, relocation notes, and per-instruction metadata, while improving error spans and suggestion text for a friendlier workflow.
- Additional output conveniences: Support formats like Intel HEX or extended S-record variants, and explore a “linkable object” mode with separated sections/symbols to integrate with broader toolchains.
- Output Optimizations: Implement instruction relaxation (e.g.,
JMP→BRA.S) and optimize internal form matching to reduce assembly time.
The assembler aims to balance authentic 68k semantics with modern Go idioms.
By representing instruction encoding as data rather than code, it reduces complexity and simplifies maintenance.
Each instruction is described declaratively, for example:
InstDef{
Mnemonic: "MOVEQ",
Forms: []FormDef{
{Mask: 0x7000, Size: Byte, Src: Imm8, Dst: Dn},
},
}This structure allows new instructions to be added without modifying the assembler’s logic — only its data tables.
Released under the MIT License.
You are free to use, modify, and distribute the project with attribution.
For the included hello.s example, assembling yields:
$ hexdump -C hello.bin
00000000 76 07 41 e9 00 10 43 fb 22 08 60 00 f4 aa bb cc |v.A...C.".`.....|
00000010
Special thanks to the open-source 68k community for documentation and references, including:
- Motorola M68000 Programmer’s Reference Manual (3rd Ed.)
- Easy68k and vasm project maintainers for inspiration on encoding tables.
- The Go community for encouraging clean, modular software design.
Author: Jens Kaiser
Repository: github.com/jenska/m68kasm
Status: Active – under continuous development