TMP94C241 Memory Controller
TMP94C241 Memory Controller & Chip Selects
Both KN5000 CPUs are Toshiba TMP94C241F (TLCS-900/H1) parts whose external address decode is software defined: six programmable chip-select blocks, each with a start address register, an address mask register and two control bytes. Nothing on the board tells the CPU where the ROMs are — the firmware tells it, at boot, and can change its mind later.
It does change its mind exactly once, and that single register write is the bootloader-to-program-flash handover. This page records what is measured, what is reconstructed, and what is still unknown.
Confidence summary.
- Proven from ROM bytes: the register addresses; the exact values every firmware writes; that the map is written once per CPU in one init block; that CS2 is reprogrammed once at the boot handover; that no firmware ever moves the block containing the custom-data flash.
- Reconstruction (strong, not proven): the MSAR/MAMR decode rule and therefore every address range in the tables below. No TMP94C241 datasheet exists anywhere on this project’s machines, and two mutually incompatible readings of the mask semantics were each “confirmed” by different analyses before the contradiction was spotted. Treat window boundaries as interpretation.
- Unknown: the reset-default configuration; the priority rule when two blocks overlap; which physical /CS pin drives which IC.
- MAME models none of this — see what MAME does below.
The registers
Twenty-four bytes at SFR 0x140–0x157, four per block
(kn5000-roms-disasm/v10/maincpu/shared/sfr_tmp94c241.s):
| Block | BnCSL | BnCSH | MAMRn | MSARn |
|---|---|---|---|---|
| 0 | 0x140 |
0x141 |
0x142 |
0x143 |
| 1 | 0x144 |
0x145 |
0x146 |
0x147 |
| 2 | 0x148 |
0x149 |
0x14A |
0x14B |
| 3 | 0x14C |
0x14D |
0x14E |
0x14F |
| 4 | 0x150 |
0x151 |
0x152 |
0x153 |
| 5 | 0x154 |
0x155 |
0x156 |
0x157 |
- MSARn — block start address, in units of 64 KB (
MSAR << 16). - MAMRn — address mask / block size.
- BnCSL — bus timing (wait states).
- BnCSH — bus width and output mode.
A documentation trap worth recording. An earlier scouting pass grepped the disassembly for writes to these registers and found nothing, and concluded the firmware never programs them. That was a false negative: the disassembler emits direct SFR addresses in decimal, so the writes appear as
stdi8 (331), 192rather thanLD (MSAR2), 0xC0. Search the binary, or search for320–343, not for the names.
What the firmware actually writes
Measured by scanning all six dumped images for the F1 <addr16> 00 <imm8> direct-store
form. The scan is exhaustive over that encoding and reproduces independently across two
analyses.
Main CPU — the identical 24-write block appears twice, once in the table-data bootloader and once in the program flash, byte for byte:
| Register | Value | Bootloader copy | Program-flash copy |
|---|---|---|---|
| MSAR0–5 | 1E 10 C0 00 80 00 |
0x9FB57B–0x9FB594 |
0xEF0459–0xEF0472 |
| MAMR0–5 | 0F 3F 7F 1F FF FF |
0x9FB599–0x9FB5B2 |
0xEF0477–0xEF0490 |
| B0–5CSL | 11 33 11 22 11 22 |
0x9FB5E4–0x9FB5FD |
0xEF04C2–0xEF04DB |
| B0–5CSH | 80 81 C2 8A 82 81 |
0x9FB602–0x9FB61B |
0xEF04E0–0xEF04F9 |
The same block also programs the DRAM controller (DRAM1REF 0x81 then 0x71,
DRAM1CRL 0x8B, DRAM1CRH 0x58) and clears bit 4 of PMEMCR. Note that the last
instruction is RES 4,(PMEMCR), a read-modify-write on one bit — some earlier pages
render it as LD (PMEMCR), 0xF1, which is a mis-decode of the F1 opcode prefix.
Sub CPU — a separate, different map, programmed by its boot ROM and then re-programmed with the same address decode but different timings by the payload:
| Register | IC30 boot ROM | v1.42 payload |
|---|---|---|
| MSAR0–5 | 10 11 FF 00 12 13 |
10 11 FF 00 12 13 |
| MAMR0–5 | 07 03 01 1F/0F 01 01 |
07 03 01 1F 01 01 |
| B0–5CSL | 66 66 22 22 66 66 |
55 55 22 22 62 66 |
| B0–5CSH | 81 81 C0 8A/89 80 81 |
81 81 C0 8A 80 81 |
IC30’s writes are at 0xFF8323–0xFF83EA; the payload’s are at file offset
0x010AD7–0x010F95 of kn5000_subprogram_v142.rom (sub-CPU address 0x01F9D7 once
loaded). The payload hardcodes MAMR3 = 0x1F — it does not re-test the DRAM strap
that the boot ROM tests (see below).
Nothing else writes these registers anywhere. kn5000_custom_data.ic19 and
hd-ae5000_v2_06i.ic4 contain zero accesses to 0x140–0x157 in any encoding.
The one runtime change: the boot handover
Outside the init block, the main-CPU firmware touches a chip-select register in only two places:
MSAR2 := 0x80— the bootloader-to-program-flash handover. Two sites, both in the table-data ROM:0x9FB6D3(normal boot,Boot_PrepareJump) and0x9FC806(the HD-AE5000 factory path). A third copy lives in the program flash at0xEF4B4B.B5CSL := 0x66(from0x22) — at0x9FC6C7,0x9FC887,0xEF4A0Cand0xEF4BCC, all immediately before HD-AE5000 PPI / flash access. This changes timing, not addresses;MSAR5/MAMR5are never rewritten.
The handover is a single store, deliberately surrounded by padding. Bytes at table-data
file offset 0x1FB6BE (CPU 0xFFB6BE at boot time):
47 00 0C 00 00 LD XSP, 0x000C00
40 DC FE FF 00 LD XWA, 0x00FFFEDC ; the address to jump to
34 4B 01 LDW IX, 0x014B ; MSAR2
EC 12 EXTZ XIX
E9 EE 00 SLL 0, XBC ; no-op padding
E9 EE 00 SLL 0, XBC ; no-op padding
B4 00 80 LD (XIX), 0x80 ; <-- the memory map changes here
B0 D8 JP (XWA)
Why this is a real remap, independent of any decode rule. Two measurements settle it
without needing to know what MAMR2 = 0x7F means:
- The code doing the store is table-data content executing at
0xFFB6D3. Inkn5000_v10_program.romthe same file offsets0x1FB6B0–0x1FB6DFare all0xFF— there is no such code in the program flash. So before the store,0xFFB6xxis the table-data ROM. - The jump target
0xFFFEDCreadsFF FF FF FFin the table-data image but1B 0F 05 EF=JP 0xEF050Fin the program image. So after the store,0xFFFExxis the program flash.
The device answering 0xFFxxxx therefore demonstrably differs before and after one
instruction. The two 3-byte SLL 0,XBC no-ops exist so that only the already-prefetched
2-byte JP (XWA) has to execute after the swap.
Control enters the program flash at Boot_InitIOPorts (0xEF050F), not at the
program flash’s own RESET_HANDLER (0xEF03C6): the handover deliberately skips the
program flash’s duplicate copy of the hardware-init block, which the bootloader has
already run.
The interrupt vector table swaps with it
The IVT is ROM-resident at 0xFFFF00 and is never copied to RAM. Before the handover it
is the table-data IVT (handlers at 0xFFB705 and friends, inside the bootloader); after
it, the very same addresses read the program-flash IVT (0xEF086A, …). One store to
MSAR2 re-points every interrupt in the machine. Any future emulation of the dynamic map
must swap the IVT atomically with the code.
The +0x600000 boot alias
Because the block that holds the ROM is larger than the ROM, the table-data image appears more than once inside its window, and its boot-time address is its normal address
0x600000. The firmware relies on this arithmetic literally:boot_cpserial.sbuilds its vector table as.long BootSerial_Init + 0x600000, andBoot_ClearRAMcopies from0xFFB4DC/0xFFB4D2, which are the table-data ROM’s0x9FB4DC/0x9FB4D2.
The data side confirms it independently: the runtime firmware reads the table-data
timestamp string at 0x9FFFC4 ("hkt_87.ssf" at file offset 0x1FFFC0), while the
bootloader reads its own tables through 0xFFxxxx. Same chip, two windows, two eras.
Decoding MSAR/MAMR — a reconstruction, not a reading
This is the weakest link on the page and it should be treated as such.
The reconstruction that fits the most independent constraints is:
block size = 32 KB × (MAMRn + 1); MSARn bit k is address bit A(16+k); MAMRn bit k masks (don’t-cares) A(15+k); MSAR bits below the block size are ignored.
Under that rule MAMR2 = 0x7F is a 4 MB window with only A23/A22 compared, so
MSAR2 = 0xC0 → 0xC00000-0xFFFFFF and MSAR2 = 0x80 → 0x800000-0xBFFFFF: the
handover is a real 4 MB swap. Four things must hold simultaneously and do:
- the
0xFFFEDCbyte difference proves some decode change happens; - a 2 MB device in a 4 MB window mirrors at base and base+
0x200000, which reproduces the firmware’s own+0x600000alias constant exactly; - CS4 (
80/FF) and CS5 (00/FF) then tile the whole 16 MB space, with CS2 carving its 4 MB out of CS4’s half — so the program flash occupies whichever half CS2 vacates; - the BnCSH bus widths line up with the actual chips (below).
The competing reading. A “MAMR bit k masks A(16+k)” rule — 64 KB granularity —
was independently calibrated on the sub-CPU and appeared to fit two data points exactly
(IC30 as 128 KB at 0xFE0000, sub DRAM as 1 MB at 0x000000). It cannot be right for the
main CPU, because under it MSAR2 0xC0 → 0x80 changes nothing at all and the proven
handover becomes a no-op. Conversely the 32 KB rule makes sub-CPU CS2 a 64 KB window at
0xFF0000, which is smaller than the 128 KB IC30 part.
Both analyses reported “exact confirmation” from the same MAME map. At most one can be correct, and possibly neither is; the honest position is that only the CS2 swap is established, and every window boundary below is provisional.
Reconstructed windows (provisional)
Main CPU, after the handover:
| Block | MSAR/MAMR | Window (32 KB rule) | Believed to select |
|---|---|---|---|
| CS0 | 1E/0F |
512 KB @ 0x180000 |
IC21 battery-backed SRAM |
| CS1 | 10/3F |
2 MB @ 0x000000 |
I/O block (FDC, latches, LCD) |
| CS2 | C0→80 / 7F |
4 MB @ 0xC00000 → 0x800000 |
table-data ROM pair IC1/IC3 |
| CS3 | 00/1F |
1 MB @ 0x000000 |
DRAM IC9/IC10 |
| CS4 | 80/FF |
8 MB @ 0x800000 |
program flash pair IC4/IC6 |
| CS5 | 00/FF |
8 MB @ 0x000000 |
custom-data flash IC19 (+ IC14) |
Sub CPU:
| Block | MSAR/MAMR | Window (32 KB rule) | Believed to select |
|---|---|---|---|
| CS0 | 10/07 |
256 KB @ 0x100000 |
— |
| CS1 | 11/03 |
128 KB @ 0x110000 |
— |
| CS2 | FF/01 |
64 KB @ 0xFF0000 |
IC30 boot ROM |
| CS3 | 00/1F |
1 MB @ 0x000000 (strap) |
DRAM IC28/IC29 |
| CS4 | 12/01 |
64 KB @ 0x120000 |
inter-CPU latches |
| CS5 | 13/01 |
64 KB @ 0x130000 |
tone-generator registers |
Which physical /CS pin drives which IC has never been read off the schematic. The
right-hand column is inference from firmware behaviour plus the MAME driver’s own
comments, and the two sources do not fully agree — a comment in
shared/boot_hw_init.s labels Block 4 as “Table Data”, whereas the reading above (and
kn5000.cpp) puts the table-data pair on CS2. The CS2/CS4 assignment above is preferred
because it is the only one under which both firmware-update paths make sense: the
bootloader’s copy of the updater writes a program image to 0x800000 (pre-handover,
where CS4’s residue is the program flash), and the program flash’s copy writes a table
image to the same address (post-handover, where CS2 holds the table data). Two updaters,
each able to program only the chip the other executes from.
Service-manual page 32 (“CPU SECTION (A) P.C. Diagram”) additionally shows a discrete
decode network — IC11 TC74VHC138F fed by A16/A17/A18, IC12 T7W139F fed by A19, IC13
TC74VHC139F fed by A22/A20/A19 — sitting between the CPU’s chip selects and the
individual devices. So the CPU registers are only half the story, and the sub-division
inside a block (IC19 vs IC14 vs the HD-AE5000 window) is done in glue logic that no
register can move. Tracing IC19 pin 12 (/CE) and pin 14 (/OE) back to their drivers would
settle the physical half of the question in an afternoon.
BnCSL and BnCSH
The local TMP94C241 register reference documents BnCSL as read/write wait-state counts
and BnCSH as bus width plus output mode, but does not describe MSAR/MAMR at all.
Every BnCSL value the KN5000 writes has two equal nibbles — 11, 22, 33, 55,
66 — except the sub-CPU payload’s B4CSL = 0x62. That is consistent with a
nibble-aligned “write waits / read waits” pair, and it makes B5CSL 0x22 → 0x66
(only ever written immediately before HD-AE5000 access, and never restored) read as
“switch this block to external /WAIT handshaking for the slow expansion board”. Graded
plausible: it is a pattern argument, not a datasheet reading.
BnCSH is more useful, because its low nibble cross-checks against bus widths that are
known independently from the flash unlock addresses:
| Value | Block | Low nibble | Independent evidence |
|---|---|---|---|
0xC2 |
main CS2 | 2 | table-data pair — MAME loads it with ROM_LOAD32_WORD |
0x82 |
main CS4 | 2 | program pair — unlock cycles at 0x815554/0x80AAA8 = 4 × word address |
0x81 |
main CS5 | 1 | IC19, single ×16 device — unlock at 0xAAAA/0x5554 = 2 × word address |
0x80 |
main CS0 | 0 | IC21 SRAM, 8-bit |
0xC0 |
sub CS2 | 0 | IC30 mask ROM, 8-bit |
Five agreements with independently-known chip widths. An earlier note speculated that
BnCSH bit 7 is a per-block enable (every value written has it set); the local register
reference assigns bit 7 to nothing, so that speculation should be dropped.
The sub-CPU DRAM strap
The sub-CPU boot ROM contains the system’s only strap-conditional chip-select writes.
It tests Port G bit 0 (SFR 0x40) twice:
FF8352: f0 40 c8 BIT 0,(PG)
6e 07 JR NZ,+7
f1 4e 01 00 1f LD (MAMR3), 0x1F
68 05 JR +5
f1 4e 01 00 0f LD (MAMR3), 0x0F
and again at 0xFF83D3, selecting B3CSH = 0x8A versus 0x89 — i.e. a different DRAM
bus width to match. This is a genuine board variant: two sub-CPU DRAM
configurations exist and the firmware detects which one it is running on. MAME hardcodes
one of them and does not implement sub-CPU port G at all.
Reset defaults: unknown
What the six blocks decode to before the firmware programs them is not established, and cannot be established from the material this project holds. No TMP94C241 datasheet is present on any of the project’s machines.
The firmware constrains the answer only weakly. Disassembling forward from
Boot_Init (0xFFB4E8), every instruction up to the chip-select block targets internal
SFRs only — watchdog 0x0110/0x0111, DMA 0x010A, ports, timers — and the stack
pointer is not loaded until after the block. So the reset default has to make the
table-data ROM readable at 0xFFFF00 and across 0xFFB4E8–0xFFB61F, and nothing more.
Which chip answers 0xFFFF00 at reset on the real board is likewise not settled by the
images: both ROMs carry a self-consistent reset vector at file offset 0x1FFF00
(table data → 0xFFFEE0 → JP 0xFFB4E8; program flash → 0xEF03C6). Only the schematic
or a scope on the /CE lines can decide.
Also unknown: the priority rule when blocks overlap. Under the reconstruction above,
main-CPU CS0, CS1, CS3 and CS5 all claim parts of low memory simultaneously, and
MAMR4 = MAMR5 = 0xFF claim 8 MB each. Either lower-numbered blocks win, or MAMR = 0xFF
means something other than “match the maximum window”, or both.
What MAME models
Nothing. src/devices/cpu/tlcs900/tmp94c241.cpp stores the registers and hands them
back; m_msar and m_mamr appear only in the constructor, save_item(), device_reset()
and the plain accessor templates, and are never consulted by any address computation.
bNcs_w() is a bare COMBINE_DATA. device_reset() fills m_msar/m_mamr with 0xFF
and sets m_block_cs[2] = 0x1000; //FIXME!. BnCSL/BnCSH are mapped write-only.
The KN5000 driver therefore uses a static map, and because it places the program flash at
the top of memory the emulated CPU reads its reset vector from program[0x1FFF00] and
starts at 0xEF03C6 — it never executes one instruction of the table-data first-stage
bootloader.
The static map happens to equal the post-handover steady state, so this is accidentally correct rather than wrong, with two known deviations:
map(0xe00000, 0xffffff).mask(0x1fffff)models only the upper of the program flash’s two mirrors; under the reconstruction the0xC00000–0xDFFFFFmirror also exists on hardware.- everything reachable only through the bootloader is dead code under MAME: the boot FDC
driver (
0x9FD8A5–0x9FEA9C), the 16-bit flash routines (0x9FB812+),Detect_Disk_Type(0x9FBFC4),Boot_ProbeExternalDevice, and the boot CP-serial stack.
See MAME Emulation Gaps for what implementing this would take and in what order.
What would settle the open questions
| Question | Measurement |
|---|---|
| Exact MSAR/MAMR decode | The TMP94C241 (or TMP94C241F) hardware manual, chip-select/wait-controller section. Nothing short of it is conclusive. |
| Reset defaults | Same document; or a logic analyser on /CE of IC1/IC3 and IC4/IC6 through the first few hundred cycles after reset. |
| Which /CS drives which IC | Service-manual page 32: trace IC19 pin 12 and pin 14, IC14’s /CE, and IC11 pins 4 and 6 back to their drivers. |
| Overlap priority | Datasheet; or an in-emulator experiment once a decoder exists. |
Is the 0xC00000 program mirror real? |
Scan the program and table-data images for 24-bit immediates in 0xC00000–0xDFFFFF. If the firmware uses that mirror, MAME’s map is incomplete. |
See also
- Memory Map — the address space as the runtime firmware sees it
- Boot Sequence — the two-stage boot in full
- CPU Subsystem
- Sub-CPU Payload Provenance — where the dynamic-map hypothesis was tested and refuted
- MAME Emulation Gaps