mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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
This commit is contained in:
1 parent
99ca146fe3
commit
f49f9984bb
2 files changed
+320
-2
No files matched your search
@@ -0,0 +1,305 @@
|
||||
# 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`
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
```sh
|
||||
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`)
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
# 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).
|
||||
+15
-2
@@ -183,6 +183,11 @@ private:
|
||||
u16 calram100_r(offs_t off); void calram100_w(offs_t off, u16 data); // 0x01000000
|
||||
u16 calram180_r(offs_t off); void calram180_w(offs_t off, u16 data); // 0x01800000
|
||||
u16 calram200_r(offs_t off); void calram200_w(offs_t off, u16 data); // 0x02000000 (outside RAM)
|
||||
// Mismatched-width overlap check (gated on VT1200_CAL_W8, needs VT1200_CAL
|
||||
// too): an 8-bit handler over the same bottom slice as CTRL. It is
|
||||
// registered even later, so if the mismatched populate path also follows
|
||||
// last-wins it fires and CTRL does not.
|
||||
u8 calw8_r(offs_t off); void calw8_w(offs_t off, u8 data);
|
||||
|
||||
// VT1200_LOG_UNMAPPED=<path>
|
||||
// A catch-all mapping over the whole address space, registered FIRST so every
|
||||
@@ -343,6 +348,9 @@ void vt1200_state::calram180_w(offs_t off, u16 data) { log_probe("RAM-0x01800000
|
||||
u16 vt1200_state::calram200_r(offs_t off) { log_probe("RAM-0x02000000", 'R', (u32)off, 0x02000000); return 0; }
|
||||
void vt1200_state::calram200_w(offs_t off, u16 data) { log_probe("RAM-0x02000000", 'W', (u32)off, 0x02000000); }
|
||||
|
||||
u8 vt1200_state::calw8_r(offs_t off) { log_probe("W8-MISMATCH", 'R', (u32)off, 0x00000000); return 0; }
|
||||
void vt1200_state::calw8_w(offs_t off, u8 data) { log_probe("W8-MISMATCH", 'W', (u32)off, 0x00000000); }
|
||||
|
||||
void vt1200_state::machine_reset()
|
||||
{
|
||||
if (m_log)
|
||||
@@ -428,14 +436,19 @@ void vt1200_state::program_map(address_map &map)
|
||||
// The original disputed "video" range (a space address inside RAM).
|
||||
map(0x00400000, 0x004fffff).rw(FUNC(vt1200_state::calvida_r), FUNC(vt1200_state::calvida_w));
|
||||
// The catch-all's logged 0x400000 read as a space address, times 16:
|
||||
// 0x400000 << 4 = 0x04000000.
|
||||
map(0x04000000, 0x040fffff).rw(FUNC(vt1200_state::calvid4m_r), FUNC(vt1200_state::calvid4m_w));
|
||||
// 0x400000 << 4 = 0x04000000. Gated separately so a plain VT1200_CAL run
|
||||
// leaves the region to the catch-all (the strict A-vs-B cross-check).
|
||||
if (getenv("VT1200_CAL_VID4M"))
|
||||
map(0x04000000, 0x040fffff).rw(FUNC(vt1200_state::calvid4m_r), FUNC(vt1200_state::calvid4m_w));
|
||||
// Where does RAM actually end? Probes at three MiB marks of the
|
||||
// declared 4 MiB range, plus one OUTSIDE it (0x02000000).
|
||||
map(0x00800000, 0x00800fff).rw(FUNC(vt1200_state::calram80_r), FUNC(vt1200_state::calram80_w));
|
||||
map(0x01000000, 0x01000fff).rw(FUNC(vt1200_state::calram100_r), FUNC(vt1200_state::calram100_w));
|
||||
map(0x01800000, 0x01800fff).rw(FUNC(vt1200_state::calram180_r), FUNC(vt1200_state::calram180_w));
|
||||
map(0x02000000, 0x02000fff).rw(FUNC(vt1200_state::calram200_r), FUNC(vt1200_state::calram200_w));
|
||||
// Mismatched-width check: same slice as CTRL, but an 8-bit handler.
|
||||
if (getenv("VT1200_CAL_W8"))
|
||||
map(0x00000000, 0x0000ffff).rw(FUNC(vt1200_state::calw8_r), FUNC(vt1200_state::calw8_w));
|
||||
}
|
||||
|
||||
// Undecoded board windows. Left unmapped unless an experiment asks for
|
||||
|
||||
Reference in new issue
Block a user