Sequential Six-Trak firmware reverse engineering: Ghidra extension plus ROM/NVRAM dumps
  • Assembly 78.4%
  • Python 18.1%
  • Java 1.6%
  • Lua 0.8%
  • C 0.8%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Peter Swimm 7f36bcc718 Prepare the hardware test: version byte, EPROM images, bench, runbook
- RECORD TRACK + 5 shows 61 on the release image and 71 on the hardened one
  (-DVERSION_RELEASE in shrink.DEFINES); stock, F1 and the hardening checks
  keep 11.
- tools/eprom.py and `make eprom`: release, hardened and stock-V11 images for
  a 27C128 and as four copies for a W27C512, with a manifest (SHA-256, 16-bit
  sum, version). read reads the chip twice and identifies it; burn is a dry
  run without --yes and refuses an image that left the manifest.
- midi_bench.py: backup now captures all 100 programs, which the whole-memory
  dump does not hold. Adds restore, compare, monitor, matrix, burst (F2),
  abort (F3) and realtime (F6, a program-dump readback as the oracle); writes
  need a fresh backup and --yes. Tested against a fake instrument in F6 and
  stock modes.
- docs/hardware-test.md: the runbook, expected results per image, limits and
  rollback.
2026-10-23 13:03:37 -07:00
.spel Add spelwork manifest (peterswimm / sixtrak-hacks) 2026-10-06 16:15:42 -07:00
analysis Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
disassembly Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
docs Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
firmware Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
ghidra-scripts Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
nvram Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
roms Attribute modified ROMs to Tauntek; add MAX V05 CC-fix image 2026-10-06 16:48:28 -07:00
scripts Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
Sixtrak Initial commit: Sixtrak Ghidra extension, ROM and NVRAM dumps 2026-10-06 16:12:31 -07:00
sysex Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
tests Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
tools Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
upstream/midi mame fixes 2026-10-10 13:13:43 -07:00
vendor Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
.gitignore Consolidate Six-Trak audit into this repo; add README and ROADMAP 2026-10-06 16:26:25 -07:00
Makefile Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
README.md Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
ROADMAP.md Prepare the hardware test: version byte, EPROM images, bench, runbook 2026-10-23 13:03:37 -07:00
sixtrak.code-workspace Initial commit: Sixtrak Ghidra extension, ROM and NVRAM dumps 2026-10-06 16:12:31 -07:00

Six-Trak firmware audit

Reverse engineering and hardening work on the Sequential Circuits Six-Trak (1984) Z80 firmware. The goal is an auditable path from the stock ROM to firmware changes we can justify: every claim is tied to a byte comparison, an instruction-level execution, or a hardware test, and the doc says which.

Current state: ready for the first hardware test. make eprom builds the release image (version display 61) and a hardened image (71), and the hardware-test runbook covers backup, chip reading, burning and each check. Original input images are unchanged. Everything below is static analysis, controlled instruction execution or whole-machine emulation; none of it has been run on an instrument.

See ROADMAP.md for what comes next.

Findings so far

Full write-up: analysis/behavior-audit.md. Addresses are V11 / V14 unless noted.

# Finding Origin Evidence Status
F1 CC parameter lookup for voices 5–6 lands one byte past the intended jump-table entry. Stock RLA at 160D shifts in carry left by voice bit 2. Stock Executed in Ghidra p-code Fixed in V12/V14 (RLA→RLCA); also by -DFIX_F1 (the build equals V12 except the version byte)
F2 64-byte MIDI receive ring (4340–437F) has no full check. At 64 unread bytes it reads as empty; more input overwrites unread data. Stock, inherited by all Reproduced in MAME V11/V14: bulk dump + 21 CCs loses 64 received bytes Experimental resynchronization passes emulator cases; hardware pending
F3 Bulk SysEx receive (F0 01 05 7F) busy-waits with no timeout and writes NVRAM in place, so an interrupted transfer leaves partial state. Stock Reproduced in MAME V11/V14: receive loop stalls, timer/CV updates stop, partial NVRAM writes persist Separate rejection profile preserves memory; atomic restore still open
F4 TX-space helper (1D27) gives up after a counter wraps and returns as if space were free while the ring is still full. Stock Executed Experimental backpressure profile; hardware pending
F5 Serial framing/overrun/parity errors drop the byte silently, without resetting the parser. Stock Executed (all 3 error bits) Experimental queued-marker recovery; physical errors pending
F6 Start/Continue/Stop realtime messages abandon channel running status. V11, inherited by V14/IO profile Nine MAME defect controls; fresh channel status recovers (matrix); 21 MAME cases on the fix, including SysEx and MIDI sync (realtime.md) Fixed by -DFIX_F6 (realtime bytes handled in rx_dequeue); in the shrunk release build, not flashed
F7 The CC display update (21AA) assumes H = 40h. When a CC value byte arrives after the parser has drained its queue, the parser resumes with H = its code page, so the value goes to ROM instead of 40A5 and the display write is decided by a ROM byte (14A0: never; 15A0: always). Stock, inherited by V12/V14 (same bytes) MAME: with the display on the parameter value, 320 CC 2 messages show 246 of 273 value changes on stock, all on the fix (report) Fixed by -DFIX_F7 (LD H,40h, 2 bytes); not in any image yet

For scale: one MIDI byte at 31,250 baud takes 320 µs, so the 64-byte ring holds about 20 ms of full-rate input. The measured foreground paths include a 4.6 ms IM1 tick and an 11 ms full-TX wait (v14-timing-probe.json). MAME now reproduces an overflow during a requested bulk dump: the foreground does not drain RX while transmitting, and the pending EOX plus 21 three-byte CCs fills the ring. See MAME harness.

The Sequential manuals back this up (docs/hardware-notes.md). The service manual gives the interrupt handler as "about 4½ ms" of each tick and says timer interrupts are disabled while the MIDI FIFO is unloaded. The ROM reloads the tick timer to 6.912 ms, which leaves about 2.4 ms of background time per tick. Table 2 of the owner's manual shows that the ten F1 CCs are exactly the one-bit switch parameters (waveforms, LFO routing, inverts, unison).

The V14 changes (value-knob program select, unison detune, CC39 LFO sync, version display) are inventoried hook by hook in the behavior audit. Feature tests found no V14-specific fault. That does not show timing is unchanged.

Images in this repo

The stock ROM and NVRAM set (roms/stock/, nvram/) comes from Sequential service disks. Stock images are not authenticated against physical EPROMs. All four Tauntek images are byte-identical to the downloads on tauntek.com (checked 2026-10-06). Both stock ROMs match the CRC32/SHA1 in the MAME Six-Trak driver.

Path Bytes SHA-256 What it is
roms/stock/trak-9.bin 16384 49b01a1b…dd26 Stock V9. Also seen as "Six-Trak Firmware v1.1 (omnioff).BIN" (identical).
roms/stock/trak-11.bin 16384 2cb81ea2…8133 Stock V11. Primary baseline. Also seen as "Six-Trak v1.1.BIN" (16385 bytes: this image plus a trailing FF).
roms/modified/sixtrackv12.bin 16384 9e9f2fef…7926 Tauntek V12: stock V11 + the F1 fix at 160D (17→07) + version display changed to 12 at 245E (11→21)
roms/modified/sixtrackv14.bin 16384 637b6588…93ec Tauntek V14; 419 bytes changed from V12. 16-bit sum 0x4EC3 matches Tauntek's published 27128 checksum.
roms/modified/V9_patched.bin 16385 f5b733d0…ce25 Tauntek V9 with the F1 fix at 1590 only (version number unchanged); Tauntek's file has a trailing FF. Circulates elsewhere as "Sequential Circuits Six-Trak (9).bin", mislabeled as factory.
build/v11-opt.bin (generated) 16384 The chosen base: stock V11 with -DFIX_F1 -DFIX_F7 and the V14 feature ports (-DFEATURE_LFO_SYNC -DFEATURE_POT_SELECT -DFEATURE_DETUNE, plan, release notes), shrunk by tools/shrink.py. The shrink frees 124 bytes; 131 bytes of the FF gap below 3700 are left after the features. With the features unused, it matches V11 + F1 on every MAME fixture (make shrink). Not committed and not flashed.
roms/max/max05withccbugfix.bin 16384 9e73a090…103e Tauntek's SCI MAX V05 with the same one-byte CC fix (sum 0x4693, as its readme.txt states). The MAX shares the Six-Trak CC code; included for comparison (F1–F5 in the MAX).
nvram/nvram_a-{9,11}.bin 4096 A-side state (4000–4FFF). Differs from MAME's catalog; treat as captured state, not factory defaults.
nvram/nvram_b-{9,11}.bin 2048 f8821e63…ee20 B-side state (5000–57FF), identical, matches MAME
sysex/FACTORY_0.SYX 3700 ba1f3f02…d848 100 single-program dumps; all decode to the patches in both A images (factory-sysex.md). Decoded by parameter, with factory names: factory-programs.json
docs/manuals/ Sequential service manual (TN610-0), schematics, and owner's manual with MIDI implementation. Summary: docs/hardware-notes.md

Firmware lineage

factory:  ... V9 ... V11          (other factory revisions existed; only V9 and V11 are dumped)
                 |      |
Tauntek:   V9_patched  V12 ──(V13?)──> V14
Version Display byte (LD A,n / OUT (18h)) Relation
V9 0x90 at 23D4 Factory
V9_patched 0x90 V9 + CC fix at 1590; version left alone
V11 0x11 at 245E Factory, last official release
V12 0x21 V11 + CC fix at 160D + display byte
V13 (0x31) Reported as an intermediate step to V14; no image observed
V14 0x41 V12 + 419 bytes of features; keeps the CC fix

The display byte stores the version digits swapped: low nibble is tens, high nibble is ones. Dumps of other factory revisions would be welcome.

Memory map (MAME, confirmed against the Sequential schematic but not yet a physical board): ROM 0000–3FFF (27128), NVRAM A 4000–4FFF and NVRAM B 5000–57FF (three 6116s, B mirrored to 5FFF), 6850 ACIA write 6000/6001 and read E000/E001 (R/W is wired to A15), 8253 timer at I/O 00–03, 4 MHz Z80A. Observed RAM ownership is in firmware/memory.inc.

Layout

roms/ nvram/ sysex/   Input images (stock/, modified/ = Tauntek, max/). Never edit in place.
docs/                 manuals/ (Sequential PDFs), hardware-notes.md, test-bench.md, hardware-test.md
analysis/             Reports, comparisons, execution TSVs, timing and resource maps.
                      behavior-audit.md is current; *.txt reports are historical.
disassembly/          z80dasm listings of V9/V11/V12/V14 (code/data split incomplete)
firmware/             v14.asm, v11.asm: labeled sources that reassemble V14 and
                      stock V11 byte-for-byte
                      variant.asm: V11/V12/V14 from v11.asm plus
                      v12-on-v11.asm and v14-on-v12.asm overlays
                      memory.inc: observed RAM/IO ownership
tests/                music_reference.py: host-side oracles for proposed features
                      timing_probe.c: T-state probe on the pinned Z80 core
tools/                Audit runners, rebuild, toolchain bootstrap, GhidraMCP helpers
ghidra-scripts/       AuditSixTrak.java (p-code assertions), DriveSixTrak.java
scripts/              Original one-off comparison/model scripts (historical paths)
vendor/z80/           superzazu/z80 @ d64fe10, hash-pinned in tools/toolchain-lock.json
vendor/ghidramcp/     GhidraMCP plugin + bridge, pinned
Sixtrak/              Ghidra extension skeleton (template only, not yet implemented)

Reproducing

The MAME boot harness now boots V11/V14 on the Rev A model and replays serial MIDI/SysEx fixtures. Run make mame-boot for a boot smoke check or make mame-test for the 32-case baseline replay suite. make rx-guard-test validates the experimental V14 receive-ring guard. RX overflow during dump transmission (F2) and interrupted bulk receive (F3) reproduce in whole-machine emulation on V11/V14.

Needs Python 3 with pytest and a C compiler. The Ghidra audit also needs Homebrew Ghidra 12.1.4 and OpenJDK 21.

make toolchain   # once: builds the pinned SJAsmPlus into .local/
make check       # unit tests, V14/V11 and one-tree V11/V12/V14 rebuilds,
                 # timing probe, SysEx checks;
                 # fails if any regenerated analysis/ report changes
make audit       # 949 Ghidra p-code cases (needs Ghidra)

The individual steps:

python3 tools/bootstrap_toolchain.py      # builds pinned SJAsmPlus into .local/
python3 tools/rebuild_v14.py              # firmware/v14.asm -> must equal roms/modified/sixtrackv14.bin
python3 tools/rebuild_v14.py --firmware v11  # firmware/v11.asm -> must equal roms/stock/trak-11.bin
python3 tools/rebuild_v14.py --tree --firmware v12  # variant.asm: v11.asm + overlays -> V12 (also v11, v14)
python3 tools/run_timing_probe.py         # T-state measurements -> analysis/v14-timing-probe.*
python3 tools/run_behavior_audit.py       # 949 Ghidra p-code cases across V11/V12/V14
python3 tools/inspect_factory_sysex.py sysex/FACTORY_0.SYX --output analysis/factory-sysex.json
python3 tools/decode_programs.py sysex/FACTORY_0.SYX --output analysis/factory-programs.json
python3 -m pytest tests/

For interactive work, set up an isolated Ghidra profile with GhidraMCP on 127.0.0.1:8765:

python3 -m venv .venv && .venv/bin/pip install -r vendor/ghidramcp/requirements.txt
python3 tools/build_ghidramcp.py
tools/launch-ghidra.sh            # once, then close: creates the profile's tool configs
python3 tools/install_local_profile.py
tools/launch-ghidra.sh            # opens local/ghidra/SixTrakDrivenAudit.gpr
.venv/bin/python tools/check_mcp.py

The MCP client runs .venv/bin/python vendor/ghidramcp/bridge_mcp_ghidra.py --ghidra-server http://127.0.0.1:8765/. The profile (.local/), the Ghidra project (local/) and the venv are all git-ignored.

Ground rules

  • Never modify an input image. Patched builds go to build/ and get hashed.
  • A patch starts from a byte-identical rebuild and changes one behavior.
  • Keep "proven by execution", "static reading" and "verified on hardware" separate in every report.
  • When a finding is corrected, append the correction to the report. Don't rewrite history.
  • No new RAM or ROM allocation until the resource map proves the space is free. It currently shows 0 proven reclaimable bytes of either.

Sources

  • Tauntek, Six-Trak bug and V14 notes
  • MAME sequential/sixtrak.cpp: ROM identities and memory map
  • Sequential Circuits, Model 610 Preliminary Service Data TN610-0 (1984; scan by M. Azevedo), schematic sheets A–C, and owner's manual CM610A with MIDI implementation (synthesized-dreams.com scans), in docs/manuals/

Provenance

The expanded matrix covers both board models and five firmware profiles; audio tests check modeled output and note release. Voice/interrupt checks cover allocation, stealing, release and observed NMI nesting/stack use. make mame-matrix, make mame-audio and make mame-voice rerun them. The MAME MIDI candidate includes a ROM-free reproducer and local patch for incorrect F7 escape/continuation transmission. make upstream-pack creates a ROM/NVRAM-free harness review archive. SPELWork provenance records AI involvement, execution hashes and remaining human review. These are local review artifacts.

The audit tree was copied from opsl/sixtrak-audit at commit 8ea2d2e (2026-10-06), plus its uncommitted rebuild/timing/resource work. That repo keeps the earlier history. Tool paths were changed from ~/Downloads to the in-repo copies.

Firmware images are included for research and interoperability. The stock ROMs are from Sequential Circuits (defunct since 1987). V14 is Tauntek's publicly distributed update; credit to Bob Grieb for the V14 work and the original voice 5–6 CC fix.