Put the palette on the channel, and find one start paints a whole frame
ROADMAP K1, the packed player's one open structural item. A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers. dmagate.s runs 7-9, gated by dma_run.sh and check.sh:
7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
words, read back OUT OF the registers by the 68000;
8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
palette words still read the poison the CPU wrote, which is what attributes
run 7 to the channel's MAR rather than to the readback path;
9. ONE array-chained start across two kinds of destination -- the palette and
six picture rows at the 1,024 B line stride, 2,048 B byte-exact.
So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last. The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.
The destination is POISONED first (62.1). Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as. The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.
And it opened a hardware item (62.4, ROADMAP B4). MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write. Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board. What a real palette register does with a
byte write is unmeasured. A negative costs 0.28% of a frame and nothing else.
29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.
check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -6310,3 +6310,125 @@ What the per-frame palette costs:
|
||||
and `H.build` are what would actually ship the palette. The direction is
|
||||
measured and the magnitude is about right; re-derive the per-frame figure
|
||||
against the real builder before quoting it as the player's number.
|
||||
|
||||
## 62. One channel start paints a whole frame — the palette registers take the DMA (session 30)
|
||||
|
||||
**Emulated.** MAME 0.277, `x68000 -bios ipl10`, `-exp1 cz6bs1` with a
|
||||
zero-filled `scsiexrom.bin`. No real hardware ran. 47.4 — does buffer mode blank
|
||||
the layer — is still the board question and is still open, and **62.4 adds a
|
||||
second board question this run created.**
|
||||
|
||||
61.9 left the decoder-free packed player one open structural item, ROADMAP K1:
|
||||
a frame is a picture *and* a palette, and 61.2 had only ever pointed a channel
|
||||
at GVRAM. If the palette registers at `$E82000` take a byte-wide DMA the way
|
||||
GVRAM does in buffer mode, the palette is a 193rd array-chain entry and the
|
||||
whole video path is **one channel start a frame**; if they do not, the CPU
|
||||
writes 256 words a frame (61.9 derives ~2,370 clocks, 0.28% of a frame) and the
|
||||
architecture stands anyway. It is the difference between cheap and free, and it
|
||||
took one run to know which.
|
||||
|
||||
`src/player/dmagate.s` runs 7–9, gated by `tools/bench/dma_run.sh`, which
|
||||
`check.sh` runs. **The answer is free, in this model.**
|
||||
|
||||
| run | what it does | result |
|
||||
|---|---|---|
|
||||
| **7. DMA → `$E82000`, bus held** | 512 B off the disc into the whole graphic palette | **byte-exact in 256 register words**, read back out of the registers by the 68000 |
|
||||
| **8. the SAME transfer aimed at RAM** | the attribution control: `$2C000` instead | **byte-exact at `$2C000`, and 256 of 256 palette words still read the poison** |
|
||||
| **9. ONE array-chained start** | 7 entries: the palette, then six picture rows at the 1,024 B line stride | **2,048 B byte-exact across BOTH kinds of destination**, `MAR` ended at `+5,376` |
|
||||
|
||||
In all three the discriminator reads as 59.1's and 61.2's do: **`MTC` sampled by
|
||||
the instruction after `START` is 0 and the CPU went round its wait loop once** —
|
||||
the transfer happened between two instructions, with the 68000 not executing.
|
||||
|
||||
### 62.1 The destination was POISONED first, because "it matches" has been a weak claim all along
|
||||
|
||||
Runs 4–6 wrote into RAM that was zero and GVRAM that was stale, against a record
|
||||
that is mostly pad. A destination that could already hold the right answer
|
||||
cannot distinguish a channel that wrote from a channel that did nothing — which
|
||||
is run 4's could-not-fail trap wearing different clothes (61.2).
|
||||
|
||||
So `dg_poison` fills the palette with word *i* = `$A500|i` before each palette
|
||||
run, written by the 68000 and read back by it. The host does not assume the
|
||||
poison is a discriminator, it **counts**: `PALETTE POISON IS A DISCRIMINATOR:
|
||||
511 of 512 positions differ from the disc's bytes`, and the gate refuses a run
|
||||
where fewer than 500 do. One position coincides, and the pass does not rest on
|
||||
it.
|
||||
|
||||
### 62.2 The control is an ATTRIBUTION control, not a mechanism one — and it says so
|
||||
|
||||
Run 5 could point at a mode bit; there is no mode bit here. What had to be
|
||||
excluded is that run 7's palette held the disc's bytes for some reason other
|
||||
than the channel having put them there — a readback that aliases somewhere else,
|
||||
the SPC's own path touching the registers, the poison never having landed at
|
||||
all. So run 8 is the same transfer with **one thing different, the destination
|
||||
address**, and it makes two claims from one run:
|
||||
|
||||
- the disc's bytes appear at `$2C000`, so the transfer happened;
|
||||
- **the palette still reads poison in all 256 words**, so what reached
|
||||
`$E82000` in run 7 was decided by the channel's `MAR`.
|
||||
|
||||
The second claim is also the positive half: it shows the CPU's own writes reach
|
||||
the registers the host reads back, so the readback path is not the thing under
|
||||
test.
|
||||
|
||||
### 62.3 The 193rd entry is literal, and the array is SCENE-constant
|
||||
|
||||
Run 9 is the one that changes the architecture, and it is not "the palette
|
||||
works" — it is that **one array chain crosses two kinds of destination**: device
|
||||
registers at `$E82000` and video RAM at `$C14000`, in one start, with the CPU
|
||||
halted from the first byte to the last. A frame is that shape with 192 row
|
||||
entries instead of six.
|
||||
|
||||
**And the array does not have to be rebuilt per frame.** The row bases are
|
||||
`$C00000 + row * 1024` and they do not change: the packed layout spends *both*
|
||||
256-colour pages — page 0 is the low byte of a word and page 1 the high byte
|
||||
(47.2, and 46.1's page masks) — so there is no spare page to flip into and no
|
||||
alternate set of destinations to alternate between. The 193-entry array
|
||||
(1,158 B) is built once at scene setup and started once a frame.
|
||||
|
||||
**What is left on the CPU in the video path is therefore the channel start and
|
||||
the READ(10) that fetches the record — and neither is priced here.** The command
|
||||
issue is already inside the transport's own account (58.2); the start is about a
|
||||
dozen register writes and is DERIVED as small rather than measured. Do not quote
|
||||
"no per-frame CPU work" without that sentence attached: it is no per-frame
|
||||
*paint* work.
|
||||
|
||||
### 62.4 What this does NOT settle, and it is a NEW BOARD QUESTION
|
||||
|
||||
**MAME cannot discriminate here, and the reason is in its source.** The graphic
|
||||
palette is not modelled as a register file at all: `x68k.cpp:817` maps
|
||||
`$E82000-$E821FF` to `palette_device::read16/write16`, `emupal.cpp:417` forwards
|
||||
to `memory_array::write16`, and `memarray.h:75` is a plain `COMBINE_DATA`. That
|
||||
is **RAM that honours `mem_mask`** — so a byte write lands in its half by
|
||||
construction, and a green run says *nothing in the model forbids it* rather than
|
||||
*the board takes it*.
|
||||
|
||||
This is a different kind of bound from 61.2's. GVRAM has a real handler with a
|
||||
real 256-colour arm, which is why run 5 could find a mechanism to fail on; the
|
||||
palette has no handler to be wrong about. **What a byte write to a real
|
||||
X68000 palette register does is UNMEASURED and this project has no figure for
|
||||
it** — not folklore, not an estimate, an absence. It goes on the hardware list
|
||||
as **B4**, and it is cheap: write `$A5` to `$E82000` and `$5A` to `$E82001` from
|
||||
the CPU on a real board and read the word back.
|
||||
|
||||
**The blast radius if B4 comes back negative is small and known**: the palette
|
||||
leaves the chain, the CPU writes 256 words a frame at 61.9's derived 0.28% of a
|
||||
frame, and every other claim in 61 and 62 stands. Run 9's crossing would still
|
||||
have to be re-asked, because it would no longer have a device-register end.
|
||||
|
||||
### 62.5 The chain's ORDER is a free choice with a visible consequence, and it is not decided
|
||||
|
||||
Run 9 puts the palette FIRST. It could as easily be 193rd, which is what 61.9
|
||||
called it. The two are not equivalent on screen and neither is obviously right:
|
||||
|
||||
- **palette first** — the 192 rows of the *previous* frame are displayed under
|
||||
the *new* palette until each is overwritten;
|
||||
- **palette last** — the new frame's rows are displayed under the *old* palette
|
||||
until the chain reaches the end.
|
||||
|
||||
The mismatch lasts one paint either way. Which is less visible depends on how
|
||||
much the palette moves between consecutive frames, which is a property of the
|
||||
encoder K2 has not been written yet, and the whole question is **moot if buffer
|
||||
mode blanks the layer** (47.4/B2) because nothing is displayed during the paint
|
||||
at all. **Filed, not answered.** It is named here so that the choice in the
|
||||
final player is a decision rather than an accident of which run happened first.
|
||||
|
||||
Reference in New Issue
Block a user