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:
@@ -240,6 +240,22 @@ the frame. The packed player is at 55.2% today. **The codec is kept on disk and
|
|||||||
not built on**, because B2 is unanswered and 48.1's prior leans against packing —
|
not built on**, because B2 is unanswered and 48.1's prior leans against packing —
|
||||||
if buffer mode blanks, it is the only thing left (48.3).
|
if buffer mode blanks, it is the only thing left (48.3).
|
||||||
|
|
||||||
|
**And a frame is now one channel start.** Session 30 asked the packed player's
|
||||||
|
one open structural question: a frame is a picture *and* a palette, and nothing
|
||||||
|
had ever pointed a DMA channel at the palette registers. It writes them —
|
||||||
|
512 B off the disc byte-exact into 256 registers at `$E82000`, read back out of
|
||||||
|
the registers by the 68000 — and **one array-chained start crosses from those
|
||||||
|
registers into GVRAM**, which is the shape of a whole frame: a palette entry and
|
||||||
|
192 row entries, walked by the channel with the CPU halted throughout. 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 disc read; there is no per-frame *paint*. **What it
|
||||||
|
does not settle is the board** — MAME models the palette as plain
|
||||||
|
`COMBINE_DATA` storage with no handler that could refuse a byte write, so the
|
||||||
|
run bounds the model and not the hardware, and "does a real palette register
|
||||||
|
take a byte write" joins the hardware list as B4. A negative answer costs 0.28%
|
||||||
|
of a frame and nothing else (FINDINGS 62).
|
||||||
|
|
||||||
**The scene graph is in, and the worst gap between two decision points is
|
**The scene graph is in, and the worst gap between two decision points is
|
||||||
zero.** `tools/import/scenegraph.py` imports the arcade scene graph — 40 scenes,
|
zero.** `tools/import/scenegraph.py` imports the arcade scene graph — 40 scenes,
|
||||||
516 sequences, 906 input windows — and 5.4% of the game's 612 branch transitions
|
516 sequences, 906 input windows — and 5.4% of the game's 612 branch transitions
|
||||||
|
|||||||
@@ -6310,3 +6310,125 @@ What the per-frame palette costs:
|
|||||||
and `H.build` are what would actually ship the palette. The direction is
|
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
|
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.
|
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.
|
||||||
|
|||||||
+49
-9
@@ -32,6 +32,16 @@ floor the codec is 110.4% and a decoder-free packed player is **55.2%**. It asks
|
|||||||
much headroom the player has and started deciding WHICH PLAYER EXISTS**, and B2
|
much headroom the player has and started deciding WHICH PLAYER EXISTS**, and B2
|
||||||
stopped being a nice-to-have. The codec cannot be packed: 47.6.4 is closed and
|
stopped being a nice-to-have. The codec cannot be packed: 47.6.4 is closed and
|
||||||
the answer is no, both ways (61.3).
|
the answer is no, both ways (61.3).
|
||||||
|
Amended end of session 30: **K1 IS DONE AND THE ANSWER IS THE GOOD ONE
|
||||||
|
(FINDINGS 62).** A channel writes the palette registers at `$E82000` byte-exact,
|
||||||
|
and ONE array-chained start crosses from device registers into GVRAM — so a
|
||||||
|
frame is a palette entry and 192 row entries, started once, with the CPU halted
|
||||||
|
throughout, and the array is scene-constant. **It opened B4**: MAME models the
|
||||||
|
palette as plain `COMBINE_DATA` storage with no handler to be wrong about, so
|
||||||
|
the run bounds the model and not the board, and what a real palette register
|
||||||
|
does with a byte write is UNMEASURED. B4 is the cheapest hardware item in the
|
||||||
|
project and a negative costs 0.28% of a frame. **K2, the packed container, is
|
||||||
|
next.**
|
||||||
|
|
||||||
**THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene
|
**THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene
|
||||||
tree — a decision point, two outcomes, a death clip — with audio, streaming from
|
tree — a decision point, two outcomes, a death clip — with audio, streaming from
|
||||||
@@ -119,6 +129,23 @@ the priority register `0xE82500` at `0x0000` (47.3).
|
|||||||
> dark one. **B2 and B1 are now the same decision from two sides, and B2 is the
|
> dark one. **B2 and B1 are now the same decision from two sides, and B2 is the
|
||||||
> five-minute half.**
|
> five-minute half.**
|
||||||
|
|
||||||
|
**B4. Does a real palette register take a BYTE write?** (62.4, new session 30.)
|
||||||
|
`$E82000` is 256 16-bit registers. The decoder-free packed player's per-frame
|
||||||
|
palette rides the frame's array chain as a 193rd entry, which means a
|
||||||
|
dual-address channel with an 8-bit device port writes it **one byte at a time**,
|
||||||
|
even bytes into the high half of a register and odd into the low. **MAME cannot
|
||||||
|
be asked**: it maps the palette to `palette_device::read16/write16`, which is
|
||||||
|
`memory_array`'s `COMBINE_DATA` over plain storage — RAM that honours
|
||||||
|
`mem_mask`, with no handler that could refuse. So the run in 62 is a statement
|
||||||
|
about the model and not about the board.
|
||||||
|
|
||||||
|
**It is the cheapest item on this list, cheaper than B2**: write `$A5` to
|
||||||
|
`$E82000` and `$5A` to `$E82001` from the CPU and read the word back. If it
|
||||||
|
comes back `$A55A` the palette rides the chain. **The blast radius of a negative
|
||||||
|
is small and known** — the palette leaves the chain and the CPU writes 256 words
|
||||||
|
a frame at 61.9's derived 0.28% of a frame — which is why this is B4 and not
|
||||||
|
above B2.
|
||||||
|
|
||||||
**B3. Single-address vs dual-address DMA — and now, first, DOES THE CARD DRIVE
|
**B3. Single-address vs dual-address DMA — and now, first, DOES THE CARD DRIVE
|
||||||
`#EXREQ`?** 242 KB/s and 0.69 dB was the old framing. **Session 27 promoted this
|
`#EXREQ`?** 242 KB/s and 0.69 dB was the old framing. **Session 27 promoted this
|
||||||
to the item that decides whether the design fits at all (59.7).** The frame
|
to the item that decides whether the design fits at all (59.7).** The frame
|
||||||
@@ -197,14 +224,26 @@ codec's 110.4%, and **+4.89 dB** on the shipping container because a literal
|
|||||||
frame is not tied to a scene palette the codec's codewords index into. It costs
|
frame is not tied to a scene palette the codec's codewords index into. It costs
|
||||||
**582 KB/s, fixed, with no lever.** Three items, in order.
|
**582 KB/s, fixed, with no lever.** Three items, in order.
|
||||||
|
|
||||||
- **K1. Can a DMA channel write the palette registers at `$E82000`?** (61.9.)
|
~~**K1. Can a DMA channel write the palette registers at `$E82000`?**~~
|
||||||
The cheapest item in the project and the one with the largest structural
|
**DONE, session 30 — FINDINGS 62. YES, in this model.** `dmagate.s` runs 7–9:
|
||||||
payoff: if the palette takes a byte-wide DMA the way GVRAM does in buffer mode,
|
512 B off the disc into the whole graphic palette, **byte-exact in 256
|
||||||
it becomes a **193rd array-chain entry** and one channel start paints a whole
|
register words** read back out of `$E82000` by the 68000; the same transfer
|
||||||
frame — palette included, CPU halted throughout, **no per-frame CPU work in the
|
aimed at RAM leaving the palette as the CPU poisoned it, which is what
|
||||||
video path at all**. Same shape as 61.2's three runs: a run in `dmagate.s`, a
|
attributes the first run to the channel's `MAR`; and **ONE array-chained start
|
||||||
host comparison in `dma.lua`, an assertion in `dma_run.sh`, **and a negative
|
crossing from device registers into GVRAM**, which is the shape of a whole
|
||||||
control.**
|
frame. The destination is POISONED first (62.1) because "it matches" was a
|
||||||
|
weak claim against a record that is mostly pad, and the host counts the
|
||||||
|
poison's discriminating power rather than assuming it: 511 of 512.
|
||||||
|
**And the array is SCENE-constant** (62.3) — the packed layout spends both
|
||||||
|
256-colour pages, so there is no page to flip and the 193 destinations never
|
||||||
|
change; the 1,158 B array is built once at scene setup. What is left on the
|
||||||
|
CPU per frame in the video path is the channel start and the READ(10), and
|
||||||
|
neither is priced — say "no per-frame PAINT work", not "no per-frame CPU work".
|
||||||
|
**It opened B4** (62.4): MAME models the palette as a generic `palette_device`
|
||||||
|
over `memory_array`, whose `write16` is a plain `COMBINE_DATA`, so it has no
|
||||||
|
handler to be wrong about and cannot discriminate. And it filed one open
|
||||||
|
design choice, 62.5: palette FIRST or 193rd is visible on screen and is not
|
||||||
|
decided.
|
||||||
- **K2. A packed container.** Quantise to **254** colours (index 0 is the
|
- **K2. A packed container.** Quantise to **254** colours (index 0 is the
|
||||||
transparency key, black at 255 — 47.2), interleave column *i* with *i+128*,
|
transparency key, black at 255 — 47.2), interleave column *i* with *i+128*,
|
||||||
emit a per-frame palette, sector-align the record. Simpler than a coded one:
|
emit a per-frame palette, sector-align the record. Simpler than a coded one:
|
||||||
@@ -222,7 +261,8 @@ DMAC-direct packed player has **no ring** — `ring.i`, `xfer.i` and most of
|
|||||||
simplification that large usually hides something, and 61.7.2 names the specific
|
simplification that large usually hides something, and 61.7.2 names the specific
|
||||||
untested thing: a chained transfer has never run back to back at 12 fps.
|
untested thing: a chained transfer has never run back to back at 12 fps.
|
||||||
|
|
||||||
**K1 and K2 survive a bad answer to B2. K3 does not.** Do K1 first.
|
**K1 and K2 survive a bad answer to B2. K3 does not.** ~~Do K1 first.~~ **K1 is
|
||||||
|
done (session 30, FINDINGS 62); K2 is next.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+116
@@ -1,3 +1,119 @@
|
|||||||
|
# Status & next-session handoff — end of session 30 (2026-08-25)
|
||||||
|
|
||||||
|
## Session 30: one channel start paints a whole frame, and the palette has no handler to be wrong about
|
||||||
|
|
||||||
|
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
|
||||||
|
of this (`tmp/check_s30_start.log`) and ALL GREEN after** — the same stages, with
|
||||||
|
three new runs inside the DMA gate.
|
||||||
|
|
||||||
|
**FINDINGS 62.** **Emulated** — MAME 0.277, `x68000 -bios ipl10`, `-exp1 cz6bs1`.
|
||||||
|
No real hardware ran. 47.4 — does buffer mode blank the layer — is still the
|
||||||
|
board question and is still open, and **this session opened a second one.**
|
||||||
|
|
||||||
|
**FIRST, THE UNRECORDED WORK WAS COMMITTED.** Sessions 28 and 29 were sitting in
|
||||||
|
a dirty tree with the last commit being session 27's. They are one commit now
|
||||||
|
(`1be428c`), because their edits share files and could not be split honestly
|
||||||
|
after the fact. Session 30's own work is a commit of its own.
|
||||||
|
|
||||||
|
**THE HEADLINE. ROADMAP K1 is done and the answer is the good one: a DMA channel
|
||||||
|
writes the palette registers at `$E82000`, and one array-chained start crosses
|
||||||
|
from device registers into GVRAM.** So a packed frame is **one channel start**:
|
||||||
|
a 193-entry array — the palette, then 192 picture rows at the 1,024 B line
|
||||||
|
stride — walked by the channel with the 68000 halted from the first byte to the
|
||||||
|
last. `dmagate.s` runs 7, 8 and 9; `dma_run.sh` gates all three; `check.sh` runs
|
||||||
|
it.
|
||||||
|
|
||||||
|
| run | result |
|
||||||
|
|---|---|
|
||||||
|
| **7. DMA → `$E82000`, bus held** | **512 B 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 poison** |
|
||||||
|
| **9. ONE array-chained start, palette + 6 rows** | **2,048 B byte-exact across BOTH kinds of destination**, `MAR` at `+5,376` |
|
||||||
|
|
||||||
|
**1. THE DESTINATION IS POISONED NOW, and it should have been all along** (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 a weaker claim than it read as.
|
||||||
|
`dg_poison` puts `$A500|i` in palette word *i* first, and the host **counts**
|
||||||
|
whether that actually discriminates rather than assuming it: 511 of 512
|
||||||
|
positions differ from the disc's bytes, and the gate refuses a run under 500.
|
||||||
|
|
||||||
|
**2. THE CONTROL IS AN ATTRIBUTION CONTROL, and it is a different shape from run
|
||||||
|
5's** (62.2). There is no mode bit here to point at. The same transfer aimed
|
||||||
|
20 KB away makes two claims at once: the bytes land at `$2C000`, and the palette
|
||||||
|
still reads poison — so what reached `$E82000` in run 7 was decided by the
|
||||||
|
channel's `MAR`. The second half is also the positive control on the readback
|
||||||
|
path, because the poison it finds is what the CPU wrote.
|
||||||
|
|
||||||
|
**3. THE ARRAY IS SCENE-CONSTANT** (62.3). The row bases never change: the
|
||||||
|
packed layout spends both 256-colour pages — page 0 the low byte of a word,
|
||||||
|
page 1 the high — so there is no page to flip into. The 1,158 B array is built
|
||||||
|
once at scene setup and started once a frame. **What is still on the CPU per
|
||||||
|
frame is the channel start and the READ(10)**, and neither is priced here: say
|
||||||
|
"no per-frame PAINT work", not "no per-frame CPU work".
|
||||||
|
|
||||||
|
**4. AND THE NEW BOARD QUESTION, B4** (62.4). **MAME cannot be asked this
|
||||||
|
one.** `x68k.cpp:817` maps the palette to `palette_device::read16/write16`,
|
||||||
|
`emupal.cpp:417` forwards to `memory_array::write16`, and `memarray.h:75` is a
|
||||||
|
plain `COMBINE_DATA` — RAM that honours `mem_mask`, with **no handler that could
|
||||||
|
refuse a byte write**. That is a different bound from 61.2's: GVRAM has a real
|
||||||
|
256-colour arm, which is why run 5 could find a mechanism to fail on. What a
|
||||||
|
real X68000 palette register does with a byte write is **UNMEASURED — not
|
||||||
|
folklore, an absence.** B4 is the cheapest hardware item in the project: write
|
||||||
|
`$A5` to `$E82000`, `$5A` to `$E82001`, read the word back. A negative costs
|
||||||
|
0.28% of a frame and nothing else, which is why it sits below B2.
|
||||||
|
|
||||||
|
**5. ONE DESIGN CHOICE IS FILED, NOT ANSWERED** (62.5). Palette FIRST or 193rd
|
||||||
|
is visible on screen — old rows under the new palette, or new rows under the old
|
||||||
|
one — for one paint either way. It is moot if buffer mode blanks (B2). It is
|
||||||
|
named so the final player's order is a decision and not an accident of which run
|
||||||
|
happened to be written first.
|
||||||
|
|
||||||
|
## HANDOFF — start here
|
||||||
|
|
||||||
|
**THE TREE IS ALL GREEN AND COMMITTED.** Two commits: `1be428c` (sessions 28+29,
|
||||||
|
unrecorded until now) and session 30's own.
|
||||||
|
|
||||||
|
### The work, in the order it should be done
|
||||||
|
|
||||||
|
**1. K2 — A PACKED CONTAINER.** Simpler than a coded one: quantise to **254**
|
||||||
|
colours (index 0 is the transparency key, black at 255 — 47.2), interleave
|
||||||
|
column *i* with *i+128*, emit a per-frame palette, sector-align the record. No
|
||||||
|
VQ, no rate control, no mode map, no `lam`. `encode.py` already sector-aligns
|
||||||
|
(DLX5) and `prep_frame.py --pack-transparent` already emits the exact pixel
|
||||||
|
layout. **Re-derive 61.9's 34.08 dB against `vq.scene_palette` / `H.build`
|
||||||
|
here** — the measured figure is PIL's MEDIANCUT and is a direction, not the
|
||||||
|
player's number (risk 2 below).
|
||||||
|
|
||||||
|
**2. K3 — END TO END, OFF THE DISC.** Scene setup — palette, page-1 X-scroll
|
||||||
|
384, priority `vc1 = 0x0002`, R20 bit 11 — then **one chained DMA a frame**,
|
||||||
|
now including the palette entry K1 just established. Gate it pixel-exact over
|
||||||
|
the same 120-frame window the decoder is gated on.
|
||||||
|
|
||||||
|
### What is PARKED, so it is not re-derived
|
||||||
|
|
||||||
|
**E7, E4 and C1** (61.8), and **P4a's wiring** for the video path: a DMAC-direct
|
||||||
|
packed player has no ring at all. Do not delete any of it — B2 is unanswered and
|
||||||
|
48.1's prior leans against packing.
|
||||||
|
|
||||||
|
### Risks that are OURS, not hardware
|
||||||
|
|
||||||
|
1. **A chained transfer has never run back to back at 12 fps.** Unchanged by
|
||||||
|
this session: run 9 was seven entries, once. A frame is 193 and a second is
|
||||||
|
twelve frames.
|
||||||
|
2. **34.08 dB is PIL's MEDIANCUT**, not `vq.scene_palette` / `H.build`.
|
||||||
|
3. **The packed player deletes `ring.i`, `xfer.i` and most of `stream.s` from
|
||||||
|
the video path.** A simplification that large usually hides something.
|
||||||
|
4. **B2 sits over all of it**, and now B4 sits over the palette entry
|
||||||
|
specifically — with a known, small blast radius.
|
||||||
|
|
||||||
|
### Reproducing this session
|
||||||
|
|
||||||
|
./tools/bench/check.sh # ALL GREEN, ~5 min
|
||||||
|
bash tools/bench/dma_run.sh # runs 1-10, with controls
|
||||||
|
|
||||||
|
**WHAT IS NEXT.** K2: the packed container.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Status & next-session handoff — end of session 29 (2026-08-25)
|
# Status & next-session handoff — end of session 29 (2026-08-25)
|
||||||
|
|
||||||
## Session 29: the decoder-free packed player fits the budget the codec misses
|
## Session 29: the decoder-free packed player fits the budget the codec misses
|
||||||
|
|||||||
+163
-3
@@ -37,13 +37,21 @@
|
|||||||
; burst modelled as held.
|
; burst modelled as held.
|
||||||
|
|
||||||
DGFLAG = $18600 ; 0 idle / 1 done
|
DGFLAG = $18600 ; 0 idle / 1 done
|
||||||
DGREC = $18610 ; 6 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
|
DGREC = $18800 ; 9 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
|
||||||
|
; $18800 AND NOT $18610, WHERE THIS LIVED: nine
|
||||||
|
; records of 32 B run to $188FF, and from $18610
|
||||||
|
; they would have run over DGWIN at $18700 --
|
||||||
|
; the window run's own result, which run 10 then
|
||||||
|
; writes back. A silent overlap between two
|
||||||
|
; runs' evidence is the kind of thing that makes
|
||||||
|
; a gate report the wrong run's numbers.
|
||||||
DGREC_SZ = 32
|
DGREC_SZ = 32
|
||||||
DGWIN = $18700 ; u32 return of the WINDOWED dma read (want -1)
|
DGWIN = $18700 ; u32 return of the WINDOWED dma read (want -1)
|
||||||
DGWERR = $18704 ; u32 SC_ERR after it (want SCE_WINDOW)
|
DGWERR = $18704 ; u32 SC_ERR after it (want SCE_WINDOW)
|
||||||
DGR20 = $18708 ; u32 R20 as it stood during run 4
|
DGR20 = $18708 ; u32 R20 as it stood during run 4
|
||||||
DGR20N = $1870C ; u32 R20 as it stood during run 5 (the control)
|
DGR20N = $1870C ; u32 R20 as it stood during run 5 (the control)
|
||||||
DGR20C = $18710 ; u32 R20 as it stood during run 6 (chained)
|
DGR20C = $18710 ; u32 R20 as it stood during run 6 (chained)
|
||||||
|
DGR20P = $18714 ; u32 R20 as it stood during run 9 (palette+rows)
|
||||||
R20_BUF = $0916 ; 256-colour, 31.5 kHz, G-VRAM SET TO BUFFER
|
R20_BUF = $0916 ; 256-colour, 31.5 kHz, G-VRAM SET TO BUFFER
|
||||||
R20_MSK = $0116 ; the SAME, bit 11 CLEAR -- the negative control
|
R20_MSK = $0116 ; the SAME, bit 11 CLEAR -- the negative control
|
||||||
DGDST4 = $C0C000 ; where the control writes
|
DGDST4 = $C0C000 ; where the control writes
|
||||||
@@ -60,6 +68,25 @@ DGDST0 = $20000 ; PIO
|
|||||||
DGDST1 = $24000 ; DMA, bus held
|
DGDST1 = $24000 ; DMA, bus held
|
||||||
DGDST2 = $28000 ; DMA, cycle stealing
|
DGDST2 = $28000 ; DMA, cycle stealing
|
||||||
|
|
||||||
|
; ---- runs 7-9: THE PALETTE (ROADMAP K1, FINDINGS 61.9's first open item).
|
||||||
|
DGPAL = $E82000 ; the GRAPHIC palette: 256 words, GGGGGRRRRRBBBBBI
|
||||||
|
DGPALN = 256 ; words in it -- and 512 B is exactly ONE sector
|
||||||
|
DGPBLK = 1 ; so the whole palette is one block off the disc
|
||||||
|
DGPOIS = $A500 ; the poison: word i = DGPOIS|i. A palette that
|
||||||
|
; still reads this was not written by anything.
|
||||||
|
DGDST6 = $2C000 ; run 8's destination: RAM, so the palette is
|
||||||
|
; left alone and must still read poison
|
||||||
|
DGDST7 = $C14000 ; run 9's six GVRAM rows, at the 1024 B stride
|
||||||
|
DGCHA2 = $19100 ; run 9's array: 7 x {u32 MAR, u16 MTC}
|
||||||
|
DGCHN2 = 7 ; palette 512 B + 6 rows x 256 B = 2,048 B
|
||||||
|
DGCROW2 = 256
|
||||||
|
DGPS7 = $1A000 ; the palette as it stood after run 7 ...
|
||||||
|
DGPS8 = $1A200 ; ... after run 8 (the control: poison) ...
|
||||||
|
DGPS9 = $1A400 ; ... and after run 9 (chained). SNAPSHOTS, not
|
||||||
|
; a late read: each run overwrites the previous
|
||||||
|
; run's palette, so a host that looks once at
|
||||||
|
; the end sees only the last of the three.
|
||||||
|
|
||||||
org $10000
|
org $10000
|
||||||
start:
|
start:
|
||||||
clr.l DGFLAG.l
|
clr.l DGFLAG.l
|
||||||
@@ -167,7 +194,7 @@ start:
|
|||||||
; 256 B of a 1024 B line stride, so a frame is 192 destinations and not one.
|
; 256 B of a 1024 B line stride, so a frame is 192 destinations and not one.
|
||||||
; 46.6 said "no stride for a DMAC to skip" about the bytes WITHIN a row and left
|
; 46.6 said "no stride for a DMAC to skip" about the bytes WITHIN a row and left
|
||||||
; the rows themselves unexamined; a channel cannot skip 768 B any more than it
|
; the rows themselves unexamined; a channel cannot skip 768 B any more than it
|
||||||
; could skip the 300 B in front of a record (run 7 below).
|
; could skip the 300 B in front of a record (run 10 below).
|
||||||
;
|
;
|
||||||
; The MC68450 answers this with SEQUENTIAL ARRAY CHAINING -- an array of 6-byte
|
; The MC68450 answers this with SEQUENTIAL ARRAY CHAINING -- an array of 6-byte
|
||||||
; {u32 MAR, u16 MTC} entries it walks by itself -- and MAME implements it. So
|
; {u32 MAR, u16 MTC} entries it walks by itself -- and MAME implements it. So
|
||||||
@@ -199,7 +226,109 @@ dg_mkch:
|
|||||||
bsr dg_save
|
bsr dg_save
|
||||||
clr.l DM_BARV.l ; and OFF again, so run 7 is unchained
|
clr.l DM_BARV.l ; and OFF again, so run 7 is unchained
|
||||||
|
|
||||||
; ---- 7. and a WINDOWED read through the channel, which must be REFUSED. This
|
; ---- 7. THE PALETTE. Can a channel write $E82000? (FINDINGS 61.9, ROADMAP
|
||||||
|
; K1.) Runs 4-6 put the PICTURE on the channel; a packed frame is a picture AND
|
||||||
|
; a palette, and if the palette registers take a byte-wide DMA the way GVRAM
|
||||||
|
; does in buffer mode then the palette is a 193rd array-chain entry and ONE
|
||||||
|
; channel start paints a whole frame -- no per-frame CPU work in the video path
|
||||||
|
; at all. If they do not, the CPU writes 256 words a frame (61.9 derives that
|
||||||
|
; at ~2,370 clocks, 0.28% of a frame) and the architecture still stands; this is
|
||||||
|
; the difference between cheap and free, and it is worth one run to know which.
|
||||||
|
;
|
||||||
|
; 512 B IS THE WHOLE PALETTE AND EXACTLY ONE SECTOR, which is why this run reads
|
||||||
|
; one block where the others read four: a transfer that ran long would write
|
||||||
|
; $E82200 (the TEXT palette) and then $E82400 (the video controller's own
|
||||||
|
; registers, priority included), and a probe that reconfigures the video
|
||||||
|
; controller as a side effect is not a probe.
|
||||||
|
;
|
||||||
|
; THE PALETTE IS POISONED FIRST, and that is what stops this being run 4's trap
|
||||||
|
; a second time. A destination that already holds the right bytes cannot tell a
|
||||||
|
; channel that wrote them from a channel that did nothing; RAM at $20000 was
|
||||||
|
; zero and the record is mostly pad, so "it matches" has been a weak claim all
|
||||||
|
; session. Word i is set to $A500|i, which no 512 B of container matches by
|
||||||
|
; accident, and the host reports how many of the 512 positions the poison and
|
||||||
|
; the disc actually differ in rather than assuming all of them.
|
||||||
|
bsr scsi_init
|
||||||
|
bsr dg_poison
|
||||||
|
move.l #DM_HELD_DCR,DM_DCRV.l
|
||||||
|
move.l #DM_HELD_OCR,DM_OCRV.l
|
||||||
|
move.l #1,DM_USE.l
|
||||||
|
move.l #DGLBA,d3
|
||||||
|
moveq #DGPBLK,d4
|
||||||
|
lea DGPAL,a1
|
||||||
|
bsr scsi_read
|
||||||
|
lea DGPS7,a1
|
||||||
|
bsr dg_palsnap ; before run 8 overwrites it
|
||||||
|
lea DGREC+6*DGREC_SZ,a0
|
||||||
|
bsr dg_save
|
||||||
|
|
||||||
|
; ---- 8. THE NEGATIVE CONTROL, and it is an ATTRIBUTION control rather than a
|
||||||
|
; mechanism one. Run 5's control could point at a mode bit; there is no mode
|
||||||
|
; bit here, so what has to be excluded is that run 7's palette held the disc's
|
||||||
|
; bytes for some reason OTHER than the channel having written them there --
|
||||||
|
; a readback that aliases somewhere else, the SPC's own path touching the
|
||||||
|
; registers, the poison never having landed.
|
||||||
|
;
|
||||||
|
; Same transfer, same channel, same bytes; ONE thing different, the destination
|
||||||
|
; address. The disc's bytes must appear at $2C000, and the palette must still
|
||||||
|
; read poison in all 256 words. Two claims from one run, and the second is the
|
||||||
|
; one that makes run 7 mean anything: it also proves the CPU's poison writes
|
||||||
|
; reach the registers the host reads back, which is the positive half.
|
||||||
|
bsr scsi_init
|
||||||
|
bsr dg_poison
|
||||||
|
move.l #DM_HELD_DCR,DM_DCRV.l
|
||||||
|
move.l #DM_HELD_OCR,DM_OCRV.l
|
||||||
|
move.l #1,DM_USE.l
|
||||||
|
move.l #DGLBA,d3
|
||||||
|
moveq #DGPBLK,d4
|
||||||
|
lea DGDST6,a1
|
||||||
|
bsr scsi_read
|
||||||
|
lea DGPS8,a1
|
||||||
|
bsr dg_palsnap ; must be poison, word for word
|
||||||
|
lea DGREC+7*DGREC_SZ,a0
|
||||||
|
bsr dg_save
|
||||||
|
|
||||||
|
; ---- 9. THE 193rd ENTRY: one start, the palette AND the picture rows. This is
|
||||||
|
; the run K1 exists for. Runs 7 and 8 only show that a channel can write the
|
||||||
|
; palette registers; what the architecture needs is that ONE array chain can
|
||||||
|
; cross from a hardware register area into GVRAM without the CPU between them,
|
||||||
|
; because a frame is a palette entry followed by 192 row entries and the whole
|
||||||
|
; claim is that the CPU starts the channel once.
|
||||||
|
;
|
||||||
|
; Seven entries, 2,048 B, out of the same LBA as everything else: 512 B into the
|
||||||
|
; palette and then six rows of 256 B at the 1024 B line stride. The destination
|
||||||
|
; regions are of two different KINDS -- device registers and video RAM in buffer
|
||||||
|
; mode -- which is exactly the crossing that has never been run.
|
||||||
|
bsr scsi_init
|
||||||
|
bsr dg_poison
|
||||||
|
move.w #R20_BUF,CRTC20.l
|
||||||
|
move.l #R20_BUF,DGR20P.l
|
||||||
|
lea DGCHA2,a0
|
||||||
|
move.l #DGPAL,(a0)+ ; entry 0: the palette, a whole sector
|
||||||
|
move.w #DGPALN*2,(a0)+
|
||||||
|
lea DGDST7,a1
|
||||||
|
moveq #DGCHN2-2,d5 ; the remaining six are picture rows
|
||||||
|
dg_mkch2:
|
||||||
|
move.l a1,(a0)+
|
||||||
|
move.w #DGCROW2,(a0)+
|
||||||
|
lea 1024(a1),a1
|
||||||
|
dbra d5,dg_mkch2
|
||||||
|
move.l #DM_HELD_DCR,DM_DCRV.l
|
||||||
|
move.l #DM_HELD_OCR|$08,DM_OCRV.l ; OCR CHAIN = %10, array chain
|
||||||
|
move.l #DGCHA2,DM_BARV.l
|
||||||
|
move.l #DGCHN2,DM_BTCV.l
|
||||||
|
move.l #1,DM_USE.l
|
||||||
|
move.l #DGLBA,d3
|
||||||
|
moveq #DGBLK,d4
|
||||||
|
lea DGDST7,a1 ; ignored under chaining, as in run 6
|
||||||
|
bsr scsi_read
|
||||||
|
lea DGPS9,a1
|
||||||
|
bsr dg_palsnap
|
||||||
|
lea DGREC+8*DGREC_SZ,a0
|
||||||
|
bsr dg_save
|
||||||
|
clr.l DM_BARV.l ; and OFF again, so run 10 is unchained
|
||||||
|
|
||||||
|
; ---- 10. and a WINDOWED read through the channel, which must be REFUSED. This
|
||||||
; is the one test here that is expected to fail, and it has to fail LOUDLY: the
|
; is the one test here that is expected to fail, and it has to fail LOUDLY: the
|
||||||
; alternative is a channel writing a whole sector into a ring that has room for
|
; alternative is a channel writing a whole sector into a ring that has room for
|
||||||
; a record, over the top of records the decoder has not finished with.
|
; a record, over the top of records the decoder has not finished with.
|
||||||
@@ -240,4 +369,35 @@ dg_save:
|
|||||||
clr.l DM_MARF.l
|
clr.l DM_MARF.l
|
||||||
rts
|
rts
|
||||||
|
|
||||||
|
; ---- the poison. Word i of the graphic palette <- $A500|i, written by the
|
||||||
|
; 68000 itself. Two jobs: a destination that cannot hold the right answer by
|
||||||
|
; accident, and a known pattern the control run reads back out of the registers
|
||||||
|
; to show the readback path shows what is actually in them.
|
||||||
|
dg_poison:
|
||||||
|
movem.l d0-d2/a0,-(sp)
|
||||||
|
lea DGPAL,a0
|
||||||
|
moveq #0,d1 ; i
|
||||||
|
move.w #DGPALN-1,d0
|
||||||
|
.p: move.w d1,d2
|
||||||
|
ori.w #DGPOIS,d2 ; $A500|i, and i never exceeds 255
|
||||||
|
move.w d2,(a0)+
|
||||||
|
addq.w #1,d1
|
||||||
|
dbra d0,.p
|
||||||
|
movem.l (sp)+,d0-d2/a0
|
||||||
|
rts
|
||||||
|
|
||||||
|
; ---- the palette as it stands, copied to (a1) by the 68000 READING THE
|
||||||
|
; REGISTERS. A snapshot and not a late host read: each palette run overwrites
|
||||||
|
; the last one's result, so all three have to be kept while they are true. It
|
||||||
|
; is also the same shape of evidence 53.3 used for pal_pack -- the words come
|
||||||
|
; back out of $E82000 rather than out of the buffer they were built in.
|
||||||
|
dg_palsnap:
|
||||||
|
movem.l d0/a0-a1,-(sp)
|
||||||
|
lea DGPAL,a0
|
||||||
|
move.w #DGPALN-1,d0
|
||||||
|
.s: move.w (a0)+,(a1)+
|
||||||
|
dbra d0,.s
|
||||||
|
movem.l (sp)+,d0/a0-a1
|
||||||
|
rts
|
||||||
|
|
||||||
include "src/player/scsi.i"
|
include "src/player/scsi.i"
|
||||||
|
|||||||
@@ -330,10 +330,14 @@ print(f""" PSNR against the 24-bit source, 18_text_plane_16col.py over the same
|
|||||||
one entry. The tree has already measured a reserved entry at
|
one entry. The tree has already measured a reserved entry at
|
||||||
0.04 dB (60.3), so this is noise against {PSNR_FRAME_256-PSNR_SHIPPED:+.2f}.
|
0.04 dB (60.3), so this is noise against {PSNR_FRAME_256-PSNR_SHIPPED:+.2f}.
|
||||||
|
|
||||||
NOT SETTLED, and it is the obvious next probe: whether a DMA CHANNEL can write
|
SETTLED IN SESSION 30, AND THE ANSWER IS YES (FINDINGS 62): a channel writes
|
||||||
the palette registers at $E82000, which would make the palette a 193rd chain
|
the palette registers at $E82000 byte-exact, and ONE array-chained start
|
||||||
entry and cost the CPU nothing at all. Untested; 61.2 only ever pointed a
|
crosses from those registers into GVRAM -- so the palette IS a 193rd chain
|
||||||
channel at GVRAM.
|
entry and the clocks row above is what the CPU pays only if it does the write
|
||||||
|
itself. dmagate.s runs 7-9. What that does NOT settle is the board: MAME maps
|
||||||
|
the palette to palette_device over memory_array, whose write16 is a plain
|
||||||
|
COMBINE_DATA, so there is no handler that could refuse a byte write and the
|
||||||
|
model cannot discriminate. ROADMAP B4.
|
||||||
|
|
||||||
AND THE PSNR FIGURES ARE PIL's MEDIANCUT, not this project's own palette
|
AND THE PSNR FIGURES ARE PIL's MEDIANCUT, not this project's own palette
|
||||||
builder (vq.scene_palette / H.build). The DIRECTION is measured and the
|
builder (vq.scene_palette / H.build). The DIRECTION is measured and the
|
||||||
@@ -344,6 +348,14 @@ print(f""" PSNR against the 24-bit source, 18_text_plane_16col.py over the same
|
|||||||
w9 = 9.0
|
w9 = 9.0
|
||||||
free_packed_dma = PACKED_BPF * w9 + CHAIN_CLK + AUD_CLK
|
free_packed_dma = PACKED_BPF * w9 + CHAIN_CLK + AUD_CLK
|
||||||
free_packed_cpu = PACKED_BPF * w9 + blit[8] + AUD_CLK
|
free_packed_cpu = PACKED_BPF * w9 + blit[8] + AUD_CLK
|
||||||
|
# ... and the same two rows with the PER-FRAME PALETTE actually charged, which
|
||||||
|
# is what a player ships. The picture rows above are the comparison against the
|
||||||
|
# codec and are left alone so the published 55.2% / 81.6% do not drift; these
|
||||||
|
# are the shipping figures. Session 30 (FINDINGS 62) made the DMAC row's
|
||||||
|
# version legal: the palette is a 193rd chain ENTRY, so it costs 512 more
|
||||||
|
# delivered bytes and one more entry rather than 256 CPU word writes.
|
||||||
|
pal_dma = (PACKED_BPF + PAL_BYTES) * w9 + CHAIN_CLK + B.DMA_CHAIN_CLK + AUD_CLK
|
||||||
|
pal_cpu = (PACKED_BPF + PAL_BYTES) * w9 + blit[8] + pal_clk + AUD_CLK
|
||||||
codec_9 = codec_bpf * w9 + codec_decode + AUD_CLK
|
codec_9 = codec_bpf * w9 + codec_decode + AUD_CLK
|
||||||
print("\n" + "=" * 78)
|
print("\n" + "=" * 78)
|
||||||
print(f"""THE ANSWER, AT THE ONE RUNG THIS MACHINE CAN BE SHOWN TO RUN (W=9)
|
print(f"""THE ANSWER, AT THE ONE RUNG THIS MACHINE CAN BE SHOWN TO RUN (W=9)
|
||||||
@@ -352,6 +364,22 @@ print(f"""THE ANSWER, AT THE ONE RUNG THIS MACHINE CAN BE SHOWN TO RUN (W=9)
|
|||||||
free / DMAC->GVRAM / PACKED {100*free_packed_dma/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_dma/FRAME_CLK:.0f}% to spare
|
free / DMAC->GVRAM / PACKED {100*free_packed_dma/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_dma/FRAME_CLK:.0f}% to spare
|
||||||
free / CPU-painted / PACKED {100*free_packed_cpu/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_cpu/FRAME_CLK:.0f}% to spare
|
free / CPU-painted / PACKED {100*free_packed_cpu/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_cpu/FRAME_CLK:.0f}% to spare
|
||||||
|
|
||||||
|
WITH THE PER-FRAME PALETTE CHARGED, which is what would ship:
|
||||||
|
|
||||||
|
DMAC-direct, palette on the CHAIN (62) {100*pal_dma/FRAME_CLK:6.1f}% of the frame, {(PACKED_BPF+PAL_BYTES)*FPS/1024:.0f} KB/s
|
||||||
|
CPU-painted, palette written by the CPU {100*pal_cpu/FRAME_CLK:6.1f}% of the frame, {(PACKED_BPF+PAL_BYTES)*FPS/1024:.0f} KB/s
|
||||||
|
|
||||||
|
The palette costs the same on the WIRE either way -- {PAL_BYTES} B a frame,
|
||||||
|
+{100*PAL_BYTES/PACKED_BPF:.1f}% -- and the wire is where this design is expensive. The gap
|
||||||
|
between the two rows is the PAINT, not the palette.
|
||||||
|
|
||||||
|
What session 30 bought is smaller than either and is worth stating exactly:
|
||||||
|
{pal_clk:,.0f} CPU clocks of palette writing replaced by one more chain entry at
|
||||||
|
{B.DMA_CHAIN_CLK} clocks, a net {100*(pal_clk-B.DMA_CHAIN_CLK)/FRAME_CLK:.2f}% of a frame -- plus the structural half,
|
||||||
|
which is that the video path then contains no per-frame PAINT at all. The
|
||||||
|
CPU still issues the READ(10) and starts the channel, and neither of those
|
||||||
|
is priced anywhere in this tree.
|
||||||
|
|
||||||
THE DECODER-FREE PACKED PLAYER FITS THE CLOCK BUDGET THAT THE CODEC MISSES.
|
THE DECODER-FREE PACKED PLAYER FITS THE CLOCK BUDGET THAT THE CODEC MISSES.
|
||||||
That is not a small correction to 47.5, it is the reverse of the reason the
|
That is not a small correction to 47.5, it is the reverse of the reason the
|
||||||
codec exists. 44.7 said it in advance and on a different cost model: "the
|
codec exists. 44.7 said it in advance and on a different cost model: "the
|
||||||
|
|||||||
+12
-1
@@ -550,6 +550,16 @@ echo "--- session 27: the DMAC drives the data phase, and holds the bus (FINDING
|
|||||||
# again, so the run asserts the contrast and not just the held value;
|
# again, so the run asserts the contrast and not just the held value;
|
||||||
# * the channel's own CSR/CER/MTC/MAR, which must say it moved every byte
|
# * the channel's own CSR/CER/MTC/MAR, which must say it moved every byte
|
||||||
# without error;
|
# without error;
|
||||||
|
# * THE PALETTE REGISTERS AT $E82000 (session 30, ROADMAP K1): the same
|
||||||
|
# transfer aimed at the palette, byte-exact into 256 register words read
|
||||||
|
# back by the 68000; the SAME transfer aimed 20 KB away leaving the palette
|
||||||
|
# as the CPU poisoned it, which is what attributes the first run to the
|
||||||
|
# channel's MAR; and ONE array-chained start crossing from the registers
|
||||||
|
# into GVRAM, which is the shape of a whole frame -- a palette entry and
|
||||||
|
# 192 row entries, started once. What this does NOT settle is the board:
|
||||||
|
# MAME models the palette as a generic palette_device over memory_array,
|
||||||
|
# whose write16 is a plain COMBINE_DATA, so it cannot tell a register file
|
||||||
|
# that takes byte writes from one that does not (FINDINGS 62.4);
|
||||||
# * and a WINDOWED read through the channel REFUSED. 117 of 120 records start
|
# * and a WINDOWED read through the channel REFUSED. 117 of 120 records start
|
||||||
# part way into a sector (58.3); a channel writes a contiguous run and cannot
|
# part way into a sector (58.3); a channel writes a contiguous run and cannot
|
||||||
# drop the bytes in front of one, so it would write the neighbouring records
|
# drop the bytes in front of one, so it would write the neighbouring records
|
||||||
@@ -565,7 +575,8 @@ if command -v chdman > /dev/null; then
|
|||||||
bash tools/bench/dma_run.sh "$DLX" > tmp/dma_gate.log 2>&1 || {
|
bash tools/bench/dma_run.sh "$DLX" > tmp/dma_gate.log 2>&1 || {
|
||||||
echo "FAIL: the DMAC did not drive the SCSI data phase."
|
echo "FAIL: the DMAC did not drive the SCSI data phase."
|
||||||
tail -16 tmp/dma_gate.log; exit 1; }
|
tail -16 tmp/dma_gate.log; exit 1; }
|
||||||
grep -aE "BYTES OK|MTC one instruction|trips round|REFUSED" tmp/dma_gate.log \
|
grep -aE "BYTES OK|MTC one instruction|trips round|REFUSED|PALETTE|ONE START" \
|
||||||
|
tmp/dma_gate.log \
|
||||||
| sed 's/^ *//;s/^/ /'
|
| sed 's/^ *//;s/^/ /'
|
||||||
else
|
else
|
||||||
echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume"
|
echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume"
|
||||||
|
|||||||
+117
-15
@@ -24,25 +24,40 @@ local SP = M.devices[":maincpu"].spaces["program"]
|
|||||||
local function P(s) print("[DMA] "..s) end
|
local function P(s) print("[DMA] "..s) end
|
||||||
local function T() local t=M.time; return t.seconds + t.attoseconds/1e18 end
|
local function T() local t=M.time; return t.seconds + t.attoseconds/1e18 end
|
||||||
|
|
||||||
local DGFLAG, DGREC, DGREC_SZ = 0x18600, 0x18610, 32
|
local DGFLAG, DGREC, DGREC_SZ = 0x18600, 0x18800, 32
|
||||||
local DGWIN, DGWERR, DGR20, DGR20N, DGR20C = 0x18700, 0x18704, 0x18708, 0x1870C, 0x18710
|
local DGWIN, DGWERR, DGR20, DGR20N, DGR20C = 0x18700, 0x18704, 0x18708, 0x1870C, 0x18710
|
||||||
|
local DGR20P = 0x18714
|
||||||
local CHROW, CHN, CHBASE = 256, 8, 0xC10000
|
local CHROW, CHN, CHBASE = 256, 8, 0xC10000
|
||||||
local R20OF -- filled in after the mailbox addresses are known
|
local R20OF -- filled in after the mailbox addresses are known
|
||||||
local GV = 0xC00000
|
local GV = 0xC00000
|
||||||
local DGLBA, DGBLK = 1000, 4
|
local DGLBA, DGBLK = 1000, 4
|
||||||
local DST = {0x20000, 0x24000, 0x28000, 0xC08000, 0xC0C000, 0xC10000}
|
-- the palette runs (ROADMAP K1). PS7/PS8/PS9 are the SNAPSHOTS dmagate.s takes
|
||||||
|
-- by reading $E82000 back with the 68000 after each run; the registers
|
||||||
|
-- themselves hold only the last of the three by the time the host looks.
|
||||||
|
local PAL, PALN, PALB, POIS = 0xE82000, 256, 512, 0xA500
|
||||||
|
local PS7, PS8, PS9 = 0x1A000, 0x1A200, 0x1A400
|
||||||
|
local CHROW2, CH2BASE, CHN2ROWS = 256, 0xC14000, 6
|
||||||
|
local DST = {0x20000, 0x24000, 0x28000, 0xC08000, 0xC0C000, 0xC10000,
|
||||||
|
PAL, 0x2C000, CH2BASE}
|
||||||
local NAME = {"PIO (the path FINDINGS 58 measured)",
|
local NAME = {"PIO (the path FINDINGS 58 measured)",
|
||||||
"DMA, BUS HELD (DCR $00 burst, OCR $81 max rate)",
|
"DMA, BUS HELD (DCR $00 burst, OCR $81 max rate)",
|
||||||
"DMA, STEALING (DCR $80 cycle steal, OCR $80 limited)",
|
"DMA, STEALING (DCR $80 cycle steal, OCR $80 limited)",
|
||||||
"DMA -> GVRAM (bus held, R20 bit 11 = BUFFER MODE) [47.6.2]",
|
"DMA -> GVRAM (bus held, R20 bit 11 = BUFFER MODE) [47.6.2]",
|
||||||
"DMA -> GVRAM (the SAME, bit 11 CLEAR -- NEGATIVE CONTROL)",
|
"DMA -> GVRAM (the SAME, bit 11 CLEAR -- NEGATIVE CONTROL)",
|
||||||
"DMA -> GVRAM (ARRAY CHAINED, 8 rows at the 1024 B line stride)"}
|
"DMA -> GVRAM (ARRAY CHAINED, 8 rows at the 1024 B line stride)",
|
||||||
local SHORT = {"pio", "held", "steal", "gvram", "masked", "chain"}
|
"DMA -> PALETTE (bus held, 512 B into $E82000) [K1, 61.9]",
|
||||||
|
"DMA -> RAM (the SAME read aimed elsewhere -- NEGATIVE CONTROL:"
|
||||||
|
.." the palette must still read poison)",
|
||||||
|
"DMA -> PALETTE + SIX ROWS (ONE array-chained start across two"
|
||||||
|
.." kinds of destination)"}
|
||||||
|
local SHORT = {"pio", "held", "steal", "gvram", "masked", "chain",
|
||||||
|
"pal", "palctl", "palchain"}
|
||||||
|
local LENOF = {[6]=PALB, [7]=PALB} -- everything else is DGBLK*512
|
||||||
local ERRNAME = {[0]="OK", "SELECTION TIMEOUT -- no target answered",
|
local ERRNAME = {[0]="OK", "SELECTION TIMEOUT -- no target answered",
|
||||||
"UNEXPECTED PHASE", "POLL TIMEOUT -- a phase never arrived",
|
"UNEXPECTED PHASE", "POLL TIMEOUT -- a phase never arrived",
|
||||||
"NON-ZERO SCSI STATUS",
|
"NON-ZERO SCSI STATUS",
|
||||||
"WINDOWED READ REFUSED -- a channel cannot drop bytes"}
|
"WINDOWED READ REFUSED -- a channel cannot drop bytes"}
|
||||||
R20OF = {[3]=DGR20, [4]=DGR20N, [5]=DGR20C}
|
R20OF = {[3]=DGR20, [4]=DGR20N, [5]=DGR20C, [8]=DGR20P}
|
||||||
local DISK = os.getenv("DLX_SCSI_IMG") or "dlxdisk.img"
|
local DISK = os.getenv("DLX_SCSI_IMG") or "dlxdisk.img"
|
||||||
|
|
||||||
local code do local f=io.open("dmagate.bin","rb"); code=f:read("a"); f:close() end
|
local code do local f=io.open("dmagate.bin","rb"); code=f:read("a"); f:close() end
|
||||||
@@ -75,8 +90,8 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
if not want then P("no "..DISK.." to check against"); P("done"); M:exit(); return end
|
if not want then P("no "..DISK.." to check against"); P("done"); M:exit(); return end
|
||||||
local LEN = DGBLK*512
|
for i = 0, 8 do
|
||||||
for i = 0, 5 do
|
local LEN = LENOF[i] or DGBLK*512
|
||||||
local b = DGREC + i*DGREC_SZ
|
local b = DGREC + i*DGREC_SZ
|
||||||
local rc = SP:read_u32(b)
|
local rc = SP:read_u32(b)
|
||||||
local e = SP:read_u32(b+4)
|
local e = SP:read_u32(b+4)
|
||||||
@@ -101,21 +116,43 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
local pg1, pg0, bad_hi, bad_lo = 0, 0, 0, 0
|
local pg1, pg0, bad_hi, bad_lo = 0, 0, 0, 0
|
||||||
for k = 1, LEN do
|
for k = 1, LEN do
|
||||||
local got
|
local got
|
||||||
|
-- `a` is set for every destination that has to be read a WORD at a
|
||||||
|
-- time and split by hand -- GVRAM in buffer mode, and the palette
|
||||||
|
-- snapshots, whose words are what the 68000 read back out of
|
||||||
|
-- $E82000. Where it stays nil the destination is plain RAM.
|
||||||
|
local off, a
|
||||||
if i == 5 then
|
if i == 5 then
|
||||||
-- The chained run's destination is not linear: byte k of the
|
-- The chained run's destination is not linear: byte k of the
|
||||||
-- transfer is byte k%256 of row k//256, and the rows are a full
|
-- transfer is byte k%256 of row k//256, and the rows are a full
|
||||||
-- 1024 B line stride apart. If the channel had ignored the array
|
-- 1024 B line stride apart. If the channel had ignored the array
|
||||||
-- and run contiguously, every byte past the first row would be
|
-- and run contiguously, every byte past the first row would be
|
||||||
-- in the wrong place and this comparison would say so.
|
-- in the wrong place and this comparison would say so.
|
||||||
local off = (k-1) % CHROW
|
off = (k-1) % CHROW
|
||||||
local a = CHBASE + ((k-1) // CHROW) * 1024 + (off & ~1)
|
a = CHBASE + ((k-1) // CHROW) * 1024 + (off & ~1)
|
||||||
|
elseif i == 6 then
|
||||||
|
off = (k-1) % 2
|
||||||
|
a = PS7 + ((k-1) & ~1)
|
||||||
|
elseif i == 8 then
|
||||||
|
-- ONE transfer across two kinds of destination: the first sector
|
||||||
|
-- is the palette, the rest is six picture rows at the line
|
||||||
|
-- stride. The split is the array's, and this walks it the same
|
||||||
|
-- way the channel was told to.
|
||||||
|
if k <= PALB then
|
||||||
|
off = (k-1) % 2
|
||||||
|
a = PS9 + ((k-1) & ~1)
|
||||||
|
else
|
||||||
|
local idx = k - PALB - 1
|
||||||
|
off = idx % 2
|
||||||
|
a = CH2BASE + (idx // CHROW2) * 1024 + ((idx % CHROW2) & ~1)
|
||||||
|
end
|
||||||
|
elseif i >= 3 and i ~= 7 then
|
||||||
|
off = (k-1) % 2
|
||||||
|
a = DST[i+1] + ((k-1) & ~1)
|
||||||
|
end
|
||||||
|
if a then
|
||||||
local w = SP:read_u16(a)
|
local w = SP:read_u16(a)
|
||||||
if (off % 2) == 0 then got = (w >> 8) & 0xff; pg1 = pg1 + 1
|
if (off % 2) == 0 then got = (w >> 8) & 0xff; pg1 = pg1 + 1
|
||||||
else got = w & 0xff; pg0 = pg0 + 1 end
|
else got = w & 0xff; pg0 = pg0 + 1 end
|
||||||
elseif i >= 3 then
|
|
||||||
local w = SP:read_u16(DST[i+1] + ((k-1) & ~1))
|
|
||||||
if ((k-1) % 2) == 0 then got = (w >> 8) & 0xff; pg1 = pg1 + 1
|
|
||||||
else got = w & 0xff; pg0 = pg0 + 1 end
|
|
||||||
else
|
else
|
||||||
got = SP:read_u8(DST[i+1]+k-1)
|
got = SP:read_u8(DST[i+1]+k-1)
|
||||||
end
|
end
|
||||||
@@ -125,14 +162,35 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
else bad_lo = bad_lo + 1 end
|
else bad_lo = bad_lo + 1 end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if i >= 3 then
|
if R20OF[i] then
|
||||||
P(string.format(" R20 during the run = $%04X (bit 11 %s); %d bytes "
|
P(string.format(" R20 during the run = $%04X (bit 11 %s); %d bytes "
|
||||||
.."read back as page 1 (high half) and %d as page 0",
|
.."read back out of the HIGH half of a destination "
|
||||||
|
.."word and %d out of the LOW half",
|
||||||
SP:read_u32(R20OF[i]),
|
SP:read_u32(R20OF[i]),
|
||||||
((SP:read_u32(R20OF[i]) & 0x0800) ~= 0)
|
((SP:read_u32(R20OF[i]) & 0x0800) ~= 0)
|
||||||
and "SET" or "CLEAR",
|
and "SET" or "CLEAR",
|
||||||
pg1, pg0))
|
pg1, pg0))
|
||||||
end
|
end
|
||||||
|
-- THE PALETTE RUNS' OWN VACUITY CHECK. Run 7's destination was
|
||||||
|
-- poisoned by the 68000 first, so "it matches the disc" cannot be
|
||||||
|
-- satisfied by a channel that did nothing -- but only if the poison
|
||||||
|
-- and the disc actually differ everywhere they are compared. That is
|
||||||
|
-- a property of THIS record and is counted rather than assumed.
|
||||||
|
if i == 6 then
|
||||||
|
local diff = 0
|
||||||
|
for j = 0, PALN-1 do
|
||||||
|
local w = (POIS | j) & 0xffff
|
||||||
|
if ((w >> 8) & 0xff) ~= string.byte(want, 2*j+1) then diff = diff + 1 end
|
||||||
|
if (w & 0xff) ~= string.byte(want, 2*j+2) then diff = diff + 1 end
|
||||||
|
end
|
||||||
|
P(string.format(" PALETTE POISON IS A DISCRIMINATOR: %d of %d "
|
||||||
|
.."positions differ from the disc's bytes -- a "
|
||||||
|
.."channel that wrote nothing could not have passed "
|
||||||
|
.."in those.", diff, PALB))
|
||||||
|
P(string.format(" %d bytes read back out of the HIGH half of a "
|
||||||
|
.."palette word (G and the top of R) and %d out of "
|
||||||
|
.."the LOW half", pg1, pg0))
|
||||||
|
end
|
||||||
if bad == 0 then
|
if bad == 0 then
|
||||||
P(string.format(" BYTES OK: %d B from LBA %d match %s byte for byte "
|
P(string.format(" BYTES OK: %d B from LBA %d match %s byte for byte "
|
||||||
.."[%s]", LEN, DGLBA, DISK, SHORT[i+1]))
|
.."[%s]", LEN, DGLBA, DISK, SHORT[i+1]))
|
||||||
@@ -152,6 +210,26 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
P(" CONTROL DID NOT FAIL: the masked write path delivered every "
|
P(" CONTROL DID NOT FAIL: the masked write path delivered every "
|
||||||
.."byte too, so the run above is not evidence about R20 bit 11.")
|
.."byte too, so the run above is not evidence about R20 bit 11.")
|
||||||
end
|
end
|
||||||
|
if i == 6 then
|
||||||
|
P(" A CHANNEL WRITES THE PALETTE REGISTERS: 512 B off the disc "
|
||||||
|
.."became 256 palette words, read back OUT OF $E82000 by the "
|
||||||
|
.."68000 itself, with the CPU halted for the transfer. Each "
|
||||||
|
.."disc byte landed in its own half of a register word, so a "
|
||||||
|
.."per-frame palette needs no CPU (61.9, ROADMAP K1).")
|
||||||
|
end
|
||||||
|
if i == 8 then
|
||||||
|
P(string.format(" ONE START PAINTED THE PALETTE AND %d ROWS: a "
|
||||||
|
.."single array-chained transfer crossed from device registers "
|
||||||
|
.."at $%06X into GVRAM at $%06X, %d B in %d entries, CPU halted "
|
||||||
|
.."throughout. A frame is that shape with %d row entries "
|
||||||
|
.."instead of %d.", CHN2ROWS, PAL, CH2BASE, LEN, CHN2ROWS+1,
|
||||||
|
192, CHN2ROWS))
|
||||||
|
end
|
||||||
|
elseif i == 6 or i == 8 then
|
||||||
|
P(string.format(" PALETTE WRONG [%s]: %d of %d differ, first at "
|
||||||
|
.."+%d -- %d at EVEN offsets (the HIGH half of a "
|
||||||
|
.."word), %d at ODD.",
|
||||||
|
SHORT[i+1], bad, LEN, first, bad_hi, bad_lo))
|
||||||
elseif i == 4 then
|
elseif i == 4 then
|
||||||
-- THE CLAIM IS NOT "half the bytes differ". In masked 256-colour
|
-- THE CLAIM IS NOT "half the bytes differ". In masked 256-colour
|
||||||
-- mode gvram_w takes `data & 0x00ff` and ignores mem_mask, so a byte
|
-- mode gvram_w takes `data & 0x00ff` and ignores mem_mask, so a byte
|
||||||
@@ -175,6 +253,30 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
SHORT[i+1], bad, LEN, first))
|
SHORT[i+1], bad, LEN, first))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if i == 7 then
|
||||||
|
-- THE ATTRIBUTION CONTROL'S SECOND CLAIM, and the one that makes run
|
||||||
|
-- 7 mean something: the same transfer aimed 20 KB away leaves the
|
||||||
|
-- palette exactly as the 68000 poisoned it. If this comes back with
|
||||||
|
-- the disc's bytes in it, something other than the channel's MAR
|
||||||
|
-- decides what reaches $E82000 and run 7 measured that instead.
|
||||||
|
local stale, first_s = 0, nil
|
||||||
|
for j = 0, PALN-1 do
|
||||||
|
if SP:read_u16(PS8 + 2*j) ~= ((POIS | j) & 0xffff) then
|
||||||
|
stale = stale + 1; first_s = first_s or j
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if stale == 0 then
|
||||||
|
P(string.format(" PALETTE UNTOUCHED BY THE CONTROL: %d of %d words "
|
||||||
|
.."still read the poison the 68000 wrote, so the "
|
||||||
|
.."bytes in run 7 got there because the channel's "
|
||||||
|
.."MAR pointed at $%06X.", PALN, PALN, PAL))
|
||||||
|
else
|
||||||
|
P(string.format(" CONTROL DID NOT FAIL [palctl]: %d of %d palette "
|
||||||
|
.."words are no longer poison (first at entry %d) "
|
||||||
|
.."-- the palette changed without a channel aimed "
|
||||||
|
.."at it.", stale, PALN, first_s))
|
||||||
|
end
|
||||||
|
end
|
||||||
if i > 0 then
|
if i > 0 then
|
||||||
-- THE DISCRIMINATOR. MTC as the instruction after START saw it, and
|
-- THE DISCRIMINATOR. MTC as the instruction after START saw it, and
|
||||||
-- the number of times the CPU went round its own wait loop.
|
-- the number of times the CPU went round its own wait loop.
|
||||||
|
|||||||
@@ -108,6 +108,43 @@ grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[chain\]" tmp/dma_run.log || \
|
|||||||
grep -aq "THE CHANNEL WALKED THE ARRAY ITSELF" tmp/dma_run.log || \
|
grep -aq "THE CHANNEL WALKED THE ARRAY ITSELF" tmp/dma_run.log || \
|
||||||
fail "the chained run did not report walking its own array."
|
fail "the chained run did not report walking its own array."
|
||||||
|
|
||||||
|
# ---- THE PALETTE (ROADMAP K1, FINDINGS 61.9). If the registers at $E82000 take
|
||||||
|
# a byte-wide DMA the way GVRAM does in buffer mode, a per-frame palette is a
|
||||||
|
# 193rd array-chain entry and ONE channel start paints a whole frame; if they do
|
||||||
|
# not, the CPU writes 256 words a frame and the architecture still stands. The
|
||||||
|
# run is poisoned first and controlled twice -- once by aiming the same transfer
|
||||||
|
# elsewhere, once by counting how many of the 512 positions the poison and the
|
||||||
|
# disc actually differ in.
|
||||||
|
grep -aq "BYTES OK: 512 B from LBA 1000 .*\[pal\]" tmp/dma_run.log || \
|
||||||
|
fail "the channel did not write the palette registers at \$E82000 -- so a
|
||||||
|
per-frame palette costs the CPU 256 word writes and cannot ride the
|
||||||
|
frame's array chain (61.9). That is a RESULT, not a broken run: check the
|
||||||
|
PALETTE WRONG line above for whether the bytes were dropped or misplaced."
|
||||||
|
DIFF=$(sed -n 's/.*PALETTE POISON IS A DISCRIMINATOR: \([0-9]*\) of 512.*/\1/p' \
|
||||||
|
tmp/dma_run.log)
|
||||||
|
[ -n "$DIFF" ] && [ "$DIFF" -ge 500 ] || \
|
||||||
|
fail "the poison and the disc's bytes agree in ${DIFF:-?} of 512 positions, so
|
||||||
|
the palette run could have passed without a channel writing anything --
|
||||||
|
this is run 4's could-not-fail trap in a new place. Change DGPOIS."
|
||||||
|
grep -aq "BYTES OK: 512 B from LBA 1000 .*\[palctl\]" tmp/dma_run.log || \
|
||||||
|
fail "the ATTRIBUTION control's read did not land in RAM, so its palette claim
|
||||||
|
is about a transfer that did not happen."
|
||||||
|
grep -aq "PALETTE UNTOUCHED BY THE CONTROL: 256 of 256 words" tmp/dma_run.log || \
|
||||||
|
fail "the palette changed during a transfer aimed 20 KB away from it. Then
|
||||||
|
what reached \$E82000 in the run above was not decided by the channel's
|
||||||
|
MAR, and that run measured something else."
|
||||||
|
if grep -aq "CONTROL DID NOT FAIL \[palctl\]" tmp/dma_run.log
|
||||||
|
then
|
||||||
|
fail "the control reported its own failure -- see the line above it."
|
||||||
|
fi
|
||||||
|
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[palchain\]" tmp/dma_run.log || \
|
||||||
|
fail "ONE array-chained start could not cross from the palette registers into
|
||||||
|
GVRAM. A frame is one palette entry and 192 row entries; if the two kinds
|
||||||
|
of destination cannot share a chain, the CPU is back in the video path
|
||||||
|
once a frame to start the second half of it."
|
||||||
|
grep -aq "ONE START PAINTED THE PALETTE AND 6 ROWS" tmp/dma_run.log || \
|
||||||
|
fail "the palette+rows run did not report the crossing it exists to show."
|
||||||
|
|
||||||
grep -aq "WINDOWED DMA READ REFUSED" tmp/dma_run.log || \
|
grep -aq "WINDOWED DMA READ REFUSED" tmp/dma_run.log || \
|
||||||
fail "a WINDOWED read through the channel was not refused. 117 of 120 records
|
fail "a WINDOWED read through the channel was not refused. 117 of 120 records
|
||||||
start part way into a sector (58.3), and a channel cannot drop the bytes
|
start part way into a sector (58.3), and a channel cannot drop the bytes
|
||||||
|
|||||||
Reference in New Issue
Block a user