Files
vt1200/harness/mame/XCROSS_REPORT.md
T
SDD f49f9984bb harness: close the instrument disagreement -- catch-all trustworthy, off = bit>>4
Closed cross-check (single run): the catch-all and VT1200_CAL proxies are
directly comparable (both base 0) and resolve identically; last-wins holds
for same- and mismatched-width overlaps. The apparent conflict was a units
error: a .rw(FUNC(offs_t)) handler receives (bitaddr-base)>>4 (tms34010
Width=1, AddrShift=3), so the catch-all logs bitaddr>>4. Its 0x400000..0x40D7FF
is bit 0x04000000..0x040D7FF0 (bytes 0x800000..0x81AFFE), outside the declared
4 MiB RAM -- genuinely unmapped. The old 0x00400000 proxy was 16x too low.
Catch-all reproduces the committed log byte-for-byte and does not perturb
coverage (56 / PC=3F208950 with and without). Regression holds: PC=3FDCE400
samples=199. Report: harness/mame/XCROSS_REPORT.md
2026-10-16 10:25:17 +03:00

13 KiB
Raw Blame History

XCROSS: which address instrument is trustworthy?

Closed instrumentation experiment, 2026-10-16. Driver: harness/mame/vt1200.cpp (modified, env-gated). MAME source: /home/arti/code/k-space/mame0289.

The question

VT1200_LOG_UNMAPPED (the catch-all) logged 123,411 accesses at 0x00400000-0x0040D7FF and called them unmapped, but the main-RAM map declares 0x00000000-0x01FFFFFF, which (taken as the same units) contains those addresses. Meanwhile a VT1200_CAL proxy registered LAST over 0x00400000-0x00400FFF saw zero hits, while one over 0x00000000-0x0000FFFF fired. Which instrument is right, and why do they disagree?

1. What MAME's source says

Precedence: LAST REGISTERED WINS (no ties, no first-wins)

  • address_map entries are kept in insertion order (src/emu/addrmap.h:597, simple_list<address_map_entry> m_entrylist), and program_map() adds them in source order via address_map::operator() (src/emu/addrmap.cpp:874-878).
  • They are installed in that same order: src/emu/emumem_aspace.cpp:783-786 loops map->m_entrylist and calls populate_map_entry(entry, READ) then (entry, WRITE).
  • populate_map_entry installs with m_root_read->populate(...) (src/emu/emumem_aspace.cpp:455, :488).
  • populate unconditionally replaces the existing dispatch slot, dropping the previous handler: src/emu/emumem_hedr.ipp:268-272
    for(offs_t ent = start_entry; ent <= end_entry; ent++) {
        m_u_dispatch[ent]->unref();
        m_u_dispatch[ent] = handler;
        m_u_ranges[ent].set(ostart, oend);
    }
    
    There is no first-wins case and no error: overlapping entries are silently permitted. DETECT_OVERLAPPING_MEMORY is 0 (src/emu/addrmap.cpp:20), and map_validity_check prints at most a warning (:1088). So "no overlap warning" proves nothing.
  • Narrower-than-native handlers follow the same rule. The mismatched path calls mismatched_patch, which also does target->unref(); target = replacement; (src/emu/emumem_hedr.ipp:380-381). Verified empirically in §4.

Conclusion: last-wins, for both same-width and different-width overlap.

Why the two instruments report different numbers: the handler offset is shifted

The handler does not receive the absolute space address. A front-end delegate receives

src/emu/emumem_hedp.cpp:15

return m_delegate(*this->m_space,
    ((offset - this->m_address_base) & this->m_address_mask) >> (Width + AddrShift),
    mem_mask);

where m_address_base = entry start and m_address_mask comes from check_optimize_all (src/emu/emumem.cpp:595: nmask = addrmask ? addrmask : changing_bits | addrselect).

For the TMS34010:

  • src/devices/cpu/tms34010/tms34010.cpp:66 — m_program_config("program", ENDIANNESS_LITTLE, 16, 32, 3, ...), i.e. data_width 16, addr_width 32, addr_shift 3. (Same in src/devices/cpu/tms34010/tms34010.h:991: memory_access<32, 1, 3, ...> — the second template parameter is Width (log2 bytes), not a 1-bit data width; Width=1 means 16-bit.)
  • Width = 1, AddrShift = 3 ⇒ the shift is 4.
  • Space (bit) address → bytes: address >> addr_shift = >> 3 (src/emu/emumem.h:2078). The 0x2000000-unit RAM map is 0x2000000 >> 3 = 0x400000 bytes = 4 MiB, matching the 4194304-byte RAM dump.

Therefore a .rw(FUNC(offs_t)) handler over an entry starting at base receives

off = (bit_address - base) >> 4

For the catch-all and RAM (base 0): off = bit_address >> 4; i.e. the catch-all's logged address is not the map address, it is the map address divided by 16. The driver's own log header already said this (# ... firmware operand = addr << 4), but the address was compared to the RAM range without applying it.

This dissolves the contradiction. The catch-all's 0x400000..0x40D7FF are bit addresses 0x04000000..0x040D7FF0 (bytes 0x800000..0x81AFFE, ~8 MiB), which is outside the declared 4 MiB RAM. The catch-all was right. The VT1200_CAL proxy over 0x00400000 was simply placed 16× too low.

Independent anchor: the board switch. The disassembly operand is 0x064000D0 (bit address); the driver's board_read reconstructs BOARD_BASE | (off & 0xffff) and logged 0x0640000D, so the delegate off was 0xD0 >> 4 = 0x0D. Same shift.

2. The closed cross-check (one run, one configuration)

All runs headless (-video none -sound none -nothrottle -debug -debugger none), progress.lua, 200 frames, ROM path harness/mame/roms. Binary: /home/arti/code/k-space/mame0289/vt1200.

S1 — catch-all (first) + CAL proxy over 0x00400000 (last) + control, same run

cd /home/arti/code/k-space/vt1200
M=/home/arti/code/k-space/mame0289/vt1200
VT1200_LOG_UNMAPPED=/tmp/opencode/xcross/S1_A.log \
VT1200_CAL=/tmp/opencode/xcross/S1_B.log \
PROG_FRAMES=200 timeout 300 $M vt1200 -rompath harness/mame/roms \
  -video none -sound none -nothrottle -debug -debugger none \
  -autoboot_script harness/mame/progress.lua -autoboot_delay 0

Raw result:

A (VT1200_LOG_UNMAPPED, registered FIRST, range 0..0xffffffff):
    183595 lines, addresses 00400000..0064000F   <-- logs the region
B (VT1200_CAL, registered LAST):
    2833  CTRL            (0x00000000-0x0000ffff)   <-- positive control FIRES
       0  VID-A-0x00400000 (0x00400000-0x004fffff)  <-- zero hits
       0  RAM-0x00800000 / RAM-0x01000000 / RAM-0x01800000 / RAM-0x02000000

So in one run: A reports the 0x00400000-style region; B's proxy over 0x00400000 reports nothing; the positive control fires. That is exactly the reported disagreement — and it is not a difference in overlap resolution.

S2 — same, plus a proxy over 0x04000000 (= 0x400000 << 4)

VT1200_LOG_UNMAPPED=/tmp/opencode/xcross/S2_A.log \
VT1200_CAL=/tmp/opencode/xcross/S2_B.log VT1200_CAL_VID4M=1 \
PROG_FRAMES=200 timeout 300 $M vt1200 -rompath harness/mame/roms \
  -video none -sound none -nothrottle -debug -debugger none \
  -autoboot_script harness/mame/progress.lua -autoboot_delay 0

Raw result:

A: 59 lines, 00580000..0064000F            <-- the 0x400000 region is GONE from A
B: 2833  CTRL
   183536 VID-4M-0x04000000                <-- the region moved here

A and B now overlap at 0x04000000; the last-registered probe (B) wins and A is silent there. Containment is exact: 183595 - 59 = 183536, i.e. every access A lost is an access B gained. Because the 0x04000000 probe caught the traffic that S1's catch-all logged as 0x400000, the shift-16 relation is proven by construction (a shift-0 reading would put the traffic at 0x00400000, where the probe range 0x04000000 would miss it).

Delegate-offset measurement (positive control, base 0, entry 0..0xffff):

CTRL W off=00000000 space4=00000000 space0=00000000 pc=3F2009E0
CTRL W off=00000001 space4=00000010 space0=00000001 pc=3F2009E0
...
max off = 0x53D   -> off is bit_address>>4, not bit_address

off never exceeds 0xFFF in a 64K-bit entry, so off = address >> 4.

VID-4M sample (PC 3F204A20 is the blitter path the old analysis identified):

VID-4M-0x04000000 W off=00000000 space4=04000000 space0=04000000 pc=3F204A20
...
VID-4M-0x04000000 W off=0000D8E1 space4=040D8E10 space0=0400D8E1 pc=3FDF9A00

Reproduction and perturbation check (step 4)

VT1200_LOG_UNMAPPED alone (handshake config) reproduces the committed harness/mame/unmapped.log byte for byte (123413 lines = 2 header + 123411 accesses, range 0x00400000..0x0040D7FF).

Handshake config, coverage:

# without catch-all
VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 \
VT1200_BOARD_SWITCH="0640000D:70:70" COV_FRAMES=600 COV_WINDOW=300 \
timeout 400 $M vt1200 ... -autoboot_script harness/mame/coverage.lua
#   frame 600  new=2  total_distinct=56  PC=3F208950

# with catch-all (VT1200_LOG_UNMAPPED=/tmp/.../perturb.log)
#   frame 600  new=2  total_distinct=56  PC=3F208950

Identical coverage and parked PC, and the log is byte-identical to the committed one. The catch-all does not perturb the machine (it is shadowed by RAM over 0..0x1FFFFFF and only handles the genuinely unmapped >=0x04000000).

Mismatched-width check (step 4 of the parallel brief)

An 8-bit (narrower-than-native) probe registered last over the same 0x00000000-0x0000FFFF slice as the 16-bit CTRL (VT1200_CAL + VT1200_CAL_W8=1):

B tags: 11195 W8-MISMATCH      (off 0x0..0x1FFF)
            0 CTRL             <-- the later 8-bit handler replaced the 16-bit one

Last-wins holds for the width-mismatched path too; width is not the explanation for the disagreement. (This run's probe replaced live low RAM with zeros and changed the boot path, which is itself a reminder that any last-registered probe over live RAM perturbs.)

3. Where does main RAM actually end? (step 3)

Probes at 0x00800000, 0x01000000, 0x01800000 (inside the declared range) and 0x02000000 (just outside) were registered last. In every unperturbed run (default wedge and the progressive handshake config, both 600 frames) all four logged zero hits. The firmware never touches bit address >= 0x800000 (byte >= 1 MiB), so its behaviour cannot localise RAM's end. The declared 4 MiB is not contradicted, but it is also not confirmed by execution.

(Only the deliberately perturbing 8-bit run, which no longer wedges, touched all four — including 0x02000000, outside the declared range — but that run's control flow is altered by the probe, so it does not establish the map.)

4. Verdict

The catch-all (VT1200_LOG_UNMAPPED) is the trustworthy instrument. Its accesses are real and its PC/kind are correct; only its printed address is in shifted units.

The VT1200_CAL proxy mechanism was also trustworthy, but the proxy at 0x00400000 was misplaced by a factor of 16. The "zero hits" was an addressing error, not a resolution disagreement. There was never a conflict between the two handlers: under last-wins the catch-all is shadowed by every later entry, so it only ever reports genuinely unmapped accesses, and the 0x400000 traffic was simply outside RAM.

Correct address convention

  • Address-map / space address = TMS34010 bit address. Bytes = bit >> 3.
  • A .rw(FUNC(offs_t)) handler on an entry starting at base receives off = (bit_address - base) >> 4 (TMS34010 Width=1, AddrShift=3).
  • The catch-all (base 0) logs bit_address >> 4. Convert:
    • bit address = logged << 4
    • byte address = logged << 1
  • The board windows are the exception: board_read builds BOARD_BASE | (off & 0xffff), which yields bit_base + (operand >> 4), a hybrid that is nevertheless consistent with the log.

Corrected locations (previously mis-stated)

item old claim corrected
blitter traffic, catch-all logged 0x00400000-0x0040D7FF (a map address) bit 0x04000000-0x040D7FF0; bytes 0x800000-0x81AFFE (~8 MiB)
VT1200_CAL proxy should be at 0x00400000 0x04000000 (bit)
catch-all logged addr vs bit addr same logged = bit >> 4

4b. Reconciliation with the parallel doc commit

While this experiment ran, a parallel session committed 99ca146fe304 ("ADDRESS UNITS RESOLVED ...") into this shared worktree. It is right that the map is in BITS and bytes = bit >> 3, but it contains two errors this experiment corrects (I did not edit that file — it is not mine):

  1. "The catch-all's 0x400000 means bytes 0x80000-0x81AFF." No: the delegate offset is bit >> 4, so 0x400000 is bit 0x04000000, i.e. bytes 0x800000-0x81AFFE (~110 KiB at 8 MiB), not a 64 KiB region at 512 KiB. Proven by S1 vs S2: the 0x04000000 probe catches the exact accesses the catch-all logged at 0x400000 (183595-59 = 183536). The doc's reading of memory_access<32, 1, 3> is also off by one field — the second parameter is Width (log2 bytes) = 1, i.e. 16-bit, not a 1-bit data width; tms34010.cpp:66 states data_width = 16.
  2. "The precedence rule must be subtler than later-wins; maybe populate_mismatched." No: precedence is plain last-wins for both same-width and mismatched-width handlers (emumem_hedr.ipp:268-272 and :380-381), verified in §4. The 0x400000 traffic was outside RAM (bit 0x04000000 > 0x01FFFFFF), so there was never a RAM/catch-all conflict to explain.

5. What I could not determine

  • The true end of main RAM. Firmware execution never reaches bit >= 0x800000, so the RAM-end probes cannot fire; a transparent (non-shadowing) probe would be needed.
  • Whether the ~8 MiB region is really video memory, and its exact size and layout. The region is established as genuinely unmapped and as the blitter's target; modelling it is explicitly out of scope here.
  • The 0x3600400 blitter target quoted from POST does not match 0x04000000; it may be an internal/TMS register or a different pointer, and I did not resolve it.

6. Regression

With no VT1200_* env var set, the driver still prints:

PC=3FDCE400  samples=199

All new instrumentation is env-gated (VT1200_CAL, VT1200_CAL_VID4M, VT1200_CAL_W8; VT1200_LOG_UNMAPPED unchanged).