Sub-CPU Firmware Images
Sub-CPU Firmware Images (v1.40 / v1.41 / v1.42)
The KN5000’s sub-CPU has a 128 KB boot ROM of its own (kn5000_subcpu_boot.ic30, mapped at
0xFE0000) but no flash holding its 192 KB runtime payload: that payload is pushed across
the inter-CPU link at every boot by SubCPU_Send_Payload (see
SubCPU Payload Loading).
Where the main CPU is supposed to read it from is now settled; where our dumps put it
is not. The routine takes one of two source bases: the SLIDE4K image at Custom Data flash
0x3E0000, or — only when that fails its magic check — table-data 0x800000. The first is
the live path on every firmware version we hold, and a File Type 007 update disc is proven
to write the compressed payload there verbatim. But in our IC19 dump that region is 131,072
bytes of 0xFF with no SLIDE magic anywhere in the image, and the table-data fallback at
0x800100 is 0xF7 filler behind a pointer table. A machine matching our dumps could not
start its sub-CPU. That is a dump-provenance problem, not a firmware one; the full
evidence and the measurements that would settle it are on
Sub-CPU Payload Provenance.
Three revisions of that payload survive, and they survive in two different shapes — which turns out to matter for preservation.
The two shapes
Decompressed payload (196,608 bytes). What the sub-CPU actually executes. This is the
form the disassembly reconstructs and the form compare_roms.py checks.
Firmware-update image (~93 KB). What ships on a system-update floppy: an 11-byte
header — "SLIDE4K" + NUL, then the decompressed size as a 24-bit big-endian value
(03 00 00 = 196,608) — followed by the LZSS stream. The update handler for File Type 007
(“Technics KN5000 Program DATA FILE PCK”) writes it into Custom Data flash at 0x3E0000
verbatim, exactly 0x20000 bytes, after erasing the two sectors there. It is not a
transient staging area: the main CPU decompresses it afresh on every boot.
The SubCPU_Send_Payload fallback branch points at table-data 0x800000 (not 0x830000,
which is the start of the five unconditional tone-database transfers that run whichever
source is chosen). 0x800100 onward is preset-bank filler, so the fallback would ship
garbage. See LZSS Compression.
All three compressed images in the repository were extracted from the system update floppy disk images on the Internet Archive.
Dump provenance
The compressed images are carved artifacts, not tool output
kn5000_subprogram_v142_compressed.rom is byte-identical to bytes [0x0F03A9, +93203) of
kn5000_v10_disk.img (sha1 a892bedc…) — it was cut out of that floppy. Recompressing the
payload with this project’s own encoder produces 93,171 bytes, a different length, so the
file cannot be a tool output. Decoding the two SLIDE4K streams that disc carries reproduces
kn5000_v10_program.rom (2,097,152 B) and kn5000_subprogram_v142.rom (196,608 B) exactly,
which makes the v10 ↔ v1.42 pairing a measured fact.
The v1.41 → v7/v8 and v1.40 → v5/v6 pairings that MAME’s BIOS options assert are not measured — only the v10 update disc is available to this project. They rest on filename inference and should be treated as such until a v5–v9 disc turns up.
The boot ROM kn5000_subcpu_boot.ic30 is only 11% dumped
Owner testimony (Felipe, 2026-08-08). IC30 was dumped partially and deliberately.
The tooling could copy only small chunks at a time, so he read the regions that appeared to
hold the boot code needed to make the emulator work, analysed that code, and — as far as he
could assess at the time — found no references into the regions he had not read. He
concluded the rest was very likely 0xFF. He calls this an educated guess, not a
measurement, and intends a full dump when he regains physical access to the instrument
(it is in storage in another country).
Measured: 14,336 bytes were read (10.9% of the chip); 116,736 bytes, 89.1%, are 0xFF by
assumption; a further 9,984 bytes were read and came back 0xFF; and only 4,352 bytes
of the whole 128 KB chip — 3.3% — are real data. Turned round, 97% of the chip is
0xFF, and most of that is assumed rather than measured. The content sits in two blocks,
CPU 0xFF8000-0xFF904C and 0xFFFE80-0xFFFFFF; the second contains a 60-byte blank gap at
0xFFFFB4-0xFFFFEF between the interrupt vector table and the four reserved words at the
very top, which is why some pages count three blocks rather than two.
The guess has been tested, but it has not been settled. A structure-aware census over the
byte-identical disassembly found 220 ROM-address references, all in dumped windows and none
in an undumped range, and the loaded v1.42 payload calls back into IC30 at only two
addresses, both dumped; the one counter-example (ROM_CHECKSUM reading past the dumped
window at 0xFE0000) is content-independent and does not run in a normal boot. Against
that, an adversarial re-verification grades the guess UNDECIDED:
the enumeration it asks for — control-flow targets and vector entries taken out of a
disassembly, not byte scans — has never been run as an auditable artifact, and raw byte
scans do turn up candidate pointers into undumped space (most of them coincidences at this
data volume, but that is the point). See
Sub-CPU Boot ROM (IC30) for the carved contents and
the full provenance record, and
ROM Reconstruction for the
figures. Note that a full IC30 dump would not answer the payload-source question — the
payload is larger than the entire chip and IC30 is not in the main CPU’s address space.
MAME flags the file BAD_DUMP and states the assumption inline. That flag should stay until
the chip is read in full.
Status of each revision
| Revision | Update image | Decompressed payload | Source tree | In make all |
|---|---|---|---|---|
| v1.40 | 93,124 B | 196,608 B — recovered, committed | no | no |
| v1.41 | 93,181 B | 196,608 B | no — tracked as kn5000-v41 |
no |
| v1.42 | 93,203 B | 196,608 B | v142/subcpu/ |
yes, both shapes |
v1.42 — fully covered
The v1.42 payload is source-built and byte-identical. Since August 2026 its update image
is a verification target too: the build recompresses the source-built payload with
compress_lzss.py --strict --with-header --reference — replaying the factory encoder’s
own match/literal decisions — and cmps the result against
original_ROMs/kn5000_subprogram_v142_compressed.rom. It appears in compare_roms.py as
the subcpu v142 update image section, at 100.00%.
sha256 of the payload: 16a1b654cea132ac433c16162db4a72ef7227fcc962ff6fae0ce088aa7c6e76e.
v1.40 — a preservation recovery
Before August 2026, no decompressed copy of the v1.40 sub-CPU payload existed anywhere in this project. The only artifact was the compressed update image, and nothing in the build ever unpacked it. A corrupted or lost 93 KB file would have taken the revision with it.
The 196,608-byte payload has now been decompressed out of that single copy and committed
as original_ROMs/kn5000_subprogram_v140.rom (commit 2fb8a95):
sha256 a39025fe7c3968102196c5e20c18c76ec42e31a6c535f48addd58e13dacd7ef0
size 196,608 bytes
The recovery is self-checking in both directions: recompressing the extracted payload with
compress_lzss.py --strict --with-header --reference against the factory image reproduces
that image byte for byte (93,124/93,124). The extraction is therefore not an
interpretation — it is the exact preimage of the shipped file.
There is still no v1.40 source tree, and the payload is not part of any build target. What exists is an honest, hash-pinned artifact plus a reversible path back to the original.
v1.41 — internally consistent, no source tree
Both v1.41 artifacts verify against each other: the compressed image decompresses
byte-exactly to original_ROMs/kn5000_subprogram_v141.rom (196,608/196,608, consuming
93,181/93,181 input bytes), and recompressing that payload reproduces the image
byte-for-byte. What is missing is a v141/subcpu/ source tree, so compare_roms.py
cannot cover either artifact.
sha256 of the payload: 04212fa6799e75db64b399242b762ce7377669a0a877984659c78c148453d9fe.
This is tracked as issue kn5000-v41 in the disassembly repository. The proposed route
is the one the main-CPU v7/v9/v10 trees already use: start from the v142 source and
reconcile against the v141 ROM. The raw byte diff looks daunting but is not — most of it is
address-constant ripple that symbolic assembly absorbs for free:
| Pair | Differing bytes | Contiguous runs |
|---|---|---|
| v1.40 ↔ v1.41 | 56,018 | 2,508 |
| v1.41 ↔ v1.42 | 124,033 | 2,875 |
| v1.40 ↔ v1.42 | 124,032 | 2,590 |
v1.40 is much closer to v1.41 than to v1.42, so a future v1.40 tree should be diffed against v1.41 rather than against the current target.
Why the update-image target matters
Verifying only the decompressed payload leaves a gap: the bytes a real KN5000 receives from
a firmware-update disk are the compressed ones. Adding the compress-and-compare rule
closes it, and it does so without weakening anything — --strict aborts the build the
moment the encoder’s output diverges from the factory stream, so the check cannot degrade
into “close enough”. The same guarantee already covers the in-ROM SLIDE4K demo presets and
the SLIDE8K help databases; see
ROM Reconstruction.