- Assembly 78.4%
- Python 18.1%
- Java 1.6%
- Lua 0.8%
- C 0.8%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- 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. |
||
| .spel | ||
| analysis | ||
| disassembly | ||
| docs | ||
| firmware | ||
| ghidra-scripts | ||
| nvram | ||
| roms | ||
| scripts | ||
| Sixtrak | ||
| sysex | ||
| tests | ||
| tools | ||
| upstream/midi | ||
| vendor | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
| ROADMAP.md | ||
| sixtrak.code-workspace | ||
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.