Firmware Architecture Flowchart

This page is the diagram view of the KN5000 firmware: how execution flows from power-on through steady-state operation, and how the subsystems sit relative to one another. The firmware runs on a TMP94C241F (TLCS-900/H2) at 16 MHz — an 8 MHz crystal through the part’s internal doubler — with a cooperative task scheduler.

The prose behind each diagram lives on the page it links to; the numbers behind each diagram live on ROM Reconstruction. Symbol addresses below are v10 and come from symbols/maincpu_v10_symbols_reference.txt in the disassembly repository.

Power-On Boot Sequence

The main CPU boots in two stages. At reset the Table Data ROM is mapped over 0xE00000-0xFFFFFF, so the reset vector at 0xFFFEE0 lands in a first-stage bootloader that lives in that ROM. The bootloader configures the memory controller, and a single store — MSAR2 := 0x80 — moves the table data down to 0x800000 and reveals the program flash underneath. Only then does the main firmware’s RESET_HANDLER run.

flowchart TD
    RESET["Reset vector 0xFFFEE0
(table-data ROM, aliased at 0xE00000)"] --> BOOT1["First-stage bootloader
0xFFB4E8"] BOOT1 --> MEMCTL["Memory-controller config
MSAR/MAMR, DRAM refresh"] MEMCTL --> REMAP["MSAR2 := 0x80 at 0x9FB6D3
table data moves to 0x800000"] REMAP --> RESETH["RESET_HANDLER
0xEF03C6 (program flash)"] RESETH --> HW_INIT["Hardware init
watchdog, clock, ports, timers, stack"] HW_INIT --> SELF_TEST["MainCPU_self_test_routines
0xEF0400"] SELF_TEST --> FW_VER{"Get_Firmware_Version
0xEF0534"} FW_VER -->|"0xFF (boot ROM)"| SHOW_WAIT["Display
'Please Wait !!'"] FW_VER -->|"Normal"| POST_TEST["Post self test"] SHOW_WAIT --> POST_TEST POST_TEST --> TASK_INIT["TaskSched_Init
0xEF1977"] TASK_INIT --> PERIPH["Init peripherals
GPIO, region code"] PERIPH --> FLASH["Flash_InitAllBanks"] FLASH --> HDAE{"HD-AE5000
present?"} HDAE -->|Yes| PPI_INIT["HD-AE5000 PPI init
i8255 at 0x160000"] HDAE -->|No| SEQ_INIT PPI_INIT --> SEQ_INIT["Seq_FullInit"] SEQ_INIT --> CPANEL["CPanel_ScanButtons
read power-on key combo"] CPANEL --> FLASH_UPD{"Key combo =
flash update?"} FLASH_UPD -->|Yes| FW_UPDATE["FLASH_MEM_UPDATE
firmware update from floppy"] FLASH_UPD -->|No| MAIN_BOOT FW_UPDATE --> HALT["Boot_MainSequence_Trampoline
(infinite loop — reboot required)"] subgraph MAIN_BOOT ["Main Boot Path"] FACTORY{"Factory reset
requested?"} FACTORY -->|Yes| RESET_DRAM["Clear work DRAM + IC21 SRAM"] FACTORY -->|No| SUBCPU_INIT RESET_DRAM --> SUBCPU_INIT["SubCPU_Init_DMA_Channels"] SUBCPU_INIT --> PAYLOAD["SubCPU_Send_Payload
192 KB over the 0x140000 latch"] PAYLOAD --> VERIFY["SubCPU_Payload_Verify
checksum validation"] VERIFY --> SCREEN0["ScreenGroup_Dispatch(0)
initial boot screen"] SCREEN0 --> CHECK_ERR{"Payload
transfer OK?"} CHECK_ERR -->|Error| ERR_SCREEN["ScreenGroup_Dispatch(2)
'ERROR in CPU data'"] CHECK_ERR -->|OK| BOOT_SCREEN["Boot_DisplayScreen
ScreenGroup_Dispatch(1)"] end BOOT_SCREEN --> MAIN_LOOP

Full detail, including the memory map on either side of the remap, is on Boot Sequence and TMP94C241 Memory Controller.

Subsystem Registration

There is no monolithic “initialise everything” routine. The UI framework is built from modules carrying developer code names, each of which registers its own object tables through InitializeObjectTable (0xFA40B3). The call sequence is literal in v10/maincpu/ui/ui_widget_defs.s: eleven named modules, then twenty generic User slots, then the root.

flowchart TD
    SETTGT["SetCurrentTarget"] --> NAMED

    subgraph NAMED ["Named modules (in call order)"]
        direction LR
        M1["Murai"]
        M2["Toshi"]
        M3["East"]
        M4["Suna"]
        M5["Cheap"]
        M6["Scoop"]
        M7["Yoko"]
        M8["Kubo"]
        M9["Hama"]
        M10["KSS"]
        M11["Naka"]
    end

    NAMED --> USERS["InitializeUser12 … InitializeUser31
(twenty generic slots)"] USERS --> ROOT["InitializeRoot"]

The code names are the original developers’ own — three of them also name directories in the disassembly (ui_widgets/ is NAKA, extensions/ is TOSHI, factory_test/ is HAMA). Each module’s init address and the object tables it registers are on UI Framework; the widget descriptors Naka registers are on UI Widget Types.

Main Event Loop

After boot the firmware runs a cooperative multitasking loop: a task scheduler that never preempts, plus an event dispatch system. A task runs to completion or yields; nothing interrupts it but the ISRs below.

flowchart TD
    BOOT["Boot_DisplayScreen"] --> SCHED

    subgraph MAIN_LOOP ["Main event loop (cooperative multitasking)"]
        SCHED["TaskSched_Dispatch
0xEF1AC0 — run next ready task"] SCHED --> EVT_CHECK{"Events
pending?"} EVT_CHECK -->|Yes| EVT_DISPATCH["Event dispatch
route to registered handler"] EVT_CHECK -->|No| TIMER_CHECK{"Timer
expired?"} EVT_DISPATCH --> SCHED TIMER_CHECK -->|Yes| TIMER_HANDLER["Timer handler
sequencer tick, UI refresh"] TIMER_CHECK -->|No| SCHED TIMER_HANDLER --> SCHED end

Interrupt Service Routines

Real-time work is interrupt-driven; the ISRs are thin and hand data to the main loop through ring buffers.

flowchart LR
    subgraph ISR ["Main-CPU interrupt handlers"]
        T1["INTT1_HANDLER
0xEF0BF9 — timer 1, SYSTEM_TIMESTAMP"] RX0["INTRX0_HANDLER
0xFCF1F0 — MIDI RX (SC0)"] TX0["INTTX0_HANDLER
0xFCF15B — MIDI TX (SC0)"] RX1["INTRX1_HANDLER
0xFC44D7 — control-panel RX (SC1)"] TX1["INTTX1_HANDLER
0xFC44B5 — control-panel TX (SC1)"] end subgraph SUBISR ["Sub-CPU"] DMA["MICRODMA_CH0_HANDLER
0x020F1F — inter-CPU command dispatch"] end TX0 --> MIDIQ["SeqAlt1 ring buffer
0x01F785 — MIDI TX queue"] RX0 --> PARSER["MidiSerial_ProcessInput
0xFCF9AE (main loop)"] RX1 --> CPPARSE["Control-panel packet parser"]

The main CPU has five sequencer ring buffers — SeqMain at 0x01F37B and SeqAlt1–SeqAlt4 at 0x01F785, 0x01FCA3, 0x0201C1 and 0x0204DF. Only SeqAlt1 has an established role: INTTX0_HANDLER dequeues MIDI output bytes from it. What fills the other three SeqAlt buffers is not established — the routines that read and write each of them are listed on Sequencer, but nothing in the disassembly has yet tied them to a producer. See MIDI Serial I/O for the two MIDI handlers in full and Control Panel Protocol for the panel pair.

Subsystem Architecture

flowchart TD
    subgraph UI_LAYER ["UI Layer"]
        NAKA["NAKA widget framework
478 registered widget objects, 9 type codes"] SCREEN["Screen manager
screen groups, mode dispatch"] DRAW["Drawing primitives
lines, boxes, text, bitmaps"] VGA["VGA controller IC206
320x240 8bpp LCD"] end subgraph AUDIO_LAYER ["Audio Layer"] TONEGEN["Tone generator IC303
64 voices, PCM wavetable"] DSP["Effects DSPs
IC310 (serial) + IC311 (parallel)"] VOICE["Voice allocator
note-on/off, velocity, pan"] SNDPARAM["Sound parameters
preset lookup, category maps"] end subgraph SEQ_LAYER ["Sequencer Layer"] SEQ["Sequencer engine
16-track, ring buffer playback"] ACCOMP["Accompaniment engine
rhythm, bass, chord patterns"] SMF["SMF player
Standard MIDI File playback"] STYLE["Style system
SSF data, variation select"] end subgraph IO_LAYER ["I/O Layer"] CPANEL["Control panel
serial protocol, buttons, LEDs, data wheel"] MIDI["MIDI
31250 baud UART, SysEx, CC routing"] FDC["Floppy disk
uPD72068 controller"] IDE["IDE/ATA
HD-AE5000 hard disk"] FLASH["Flash memory
user settings, custom data"] end subgraph CPU_LAYER ["Inter-CPU Communication"] MAIN["Main CPU
TMP94C241F @ 16 MHz"] SUB["Sub CPU
TMP94C241F @ 20 MHz (audio engine)"] LATCH["Latch IC22/IC23
0x140000 main side, 0x120000 sub side"] end UI_LAYER --> AUDIO_LAYER UI_LAYER --> SEQ_LAYER SEQ_LAYER --> AUDIO_LAYER AUDIO_LAYER --> CPU_LAYER IO_LAYER --> UI_LAYER IO_LAYER --> SEQ_LAYER MAIN <-->|"E1/E2/E3 commands"| LATCH LATCH <-->|"DMA bulk transfer"| SUB SUB --> TONEGEN SUB --> DSP

The 478 is literal in the source: InitializeNaka (v10/maincpu/storage/flash_floppy_handlers.s) registers NAKA_UIObjectTable against handler ViewableProc with an entry count of 0x1DE. The widget type codes and the descriptor record layout are on UI Widget Types.

NAKA Widget Event Flow

The UI framework dispatches on 32-bit event IDs, not on the widget’s type byte. A button press becomes an event; the event walks the class hierarchy until a handler claims it.

flowchart TD
    BUTTON["Physical button press"] --> CPANEL_ISR["INTRX1_HANDLER
serial packet decode"] CPANEL_ISR --> EVT_GEN["Generate event ID
(e.g. 0x01C00008 = EVT_ACTIVATE)"] EVT_GEN --> VIEWABLE["ViewableProc
0xFA5995 — dispatch on 32-bit event ID"] VIEWABLE --> HANDLER_TABLE["handler_table lookup
(DRAM dispatch table)"] HANDLER_TABLE --> INHERITED["InheritedProc
0xFA4409 — walk parent chain"] VIEWABLE -.->|"rendering priority
from the 8-bit type byte"| TYPE_CLASS["SeMenu_SetObjectFlags
0xF06898"] subgraph WIDGETS ["Widget handlers"] CONTAINER["Container handler
screen root, layout"] MENU_ITEM["Menu item handler
selection, navigation"] SLIDER["Slider handler
value adjustment"] DISPATCH["Dispatch handler
proc function call"] end INHERITED --> CONTAINER INHERITED --> MENU_ITEM INHERITED --> SLIDER INHERITED --> DISPATCH DISPATCH --> PROC["Proc function
(e.g. IvDrawbarProc)"] PROC --> ACTION["UI action
sound change, screen transition"]

Where the handler_table DRAM addresses (values in the 0x0003xxxx range) actually live is not resolved — they map to neither ROM nor the extension DRAM window. That question is tracked on Open Questions.

Memory Map

The address space is documented, with its chip-select blocks and the reset-time alias, on Memory Map. Note that the main and sub CPUs have separate buses: 0x100000 is audio hardware on the sub-CPU side and 0x140000 is the inter-CPU latch on the main-CPU side, so a single flat picture of “the” address space would be misleading.

Source File Organization

The v10 main-CPU firmware is one top-level file plus fifteen subsystem directories. Counts are from find v10/maincpu -name '*.s' | wc -l (and '*.c') in the disassembly repository — 156 assembly files and 104 C files.

flowchart TD
    MAIN["kn5000_v10_program.s
(ROM layout, inline data, entry point)"] subgraph BOOT_FILES ["Boot & system — 6 .s"] BOOT_HW["shared/boot_hw_init.s"] SYS_HAND["boot/system_handlers.s"] MAINTITLE["boot/main_title_ctrl_panel.s"] end subgraph UI_FILES ["UI framework — 17 .s"] WIDGET_DEFS["ui/ui_widget_defs.s"] DRAW_PRIM["ui/drawing_primitives.s"] CPANEL_RT["ui/cpanel_routines.s"] CTRLPANEL["ui/ui_control_panel.s"] end subgraph NAKA_FILES ["UI widgets, codename NAKA — 36 .s + 29 .c"] NAKA_C["ui_widgets/*.c
typed packed structs, byte-identical output"] NAKA_S["ui_widgets/*.s
descriptor tables and linker scripts"] end subgraph AUDIO_FILES ["Audio — 31 .s + 53 .c"] ACE["audio/audio_control_engine.s"] NVM["audio/note_voice_mapping.s"] SEMENU["audio/semenu_routines.s"] DSP_CFG["audio/dsp_config_sysex.s"] SND_DATA["audio/sound_data/*.c"] end subgraph SEQ_FILES ["Sequencer — 15 .s + 3 .c"] ACC_ENG["sequencer/accompaniment_engine.s"] SEQ_ENG["sequencer/sequencer_engine.s"] SMF_PLAY["sequencer/smf_playback.s"] end subgraph MIDI_FILES ["MIDI — 9 .s"] MIDI_SER["midi/midi_serial_routines.s"] MIDI_DISP["midi/midi_dispatch_handlers.s"] SYSEX["midi/sysex_routines.s"] end subgraph OTHER_FILES ["Storage 2 · file I/O 9 · display 3 · demo 4 · factory test 4 · extensions 2"] FDC_RT["storage/fdc_routines.s"] FLASH_RT["storage/flash_floppy_handlers.s"] FILEIO["file_io/*.s"] FACTORY["factory_test/*.s"] end MAIN --> BOOT_FILES MAIN --> UI_FILES MAIN --> NAKA_FILES MAIN --> AUDIO_FILES MAIN --> SEQ_FILES MAIN --> MIDI_FILES MAIN --> OTHER_FILES

Per-file descriptions are on Source Code Map, and the measured state of the disassembly — instruction census, symbol count, byte gate, remaining debt — is on ROM Reconstruction.