Measure the finer chain tail: 84/120 becomes 18/120, and the derivation was right by cancellation
blit.s gains v7 -- v6's 24-pixel movem chain plus a second chain whose unit is
one `move.l (a0)+,(a2)+`. Measured over 13 span lengths by span.sh, every config
pixel-exact:
cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel
fitting all 13 to within 0.2%. v5 and v6 re-measure to FINDINGS 30 exactly, so
the harness has not drifted underneath the new variant.
Rescored against the same scsi window and the same additive model, v7 takes
84/120 frames over budget to 18/120 -- exactly what FINDINGS 39.4 derived, and
that agreement is two cancelling errors: the derivation's 2-register movem tail
is 29% too dear per pixel, and its "nothing per span" for the second chain entry
is 22.3 clocks too cheap. The plain post-incrementing move.l is the right tail
instruction, and it makes the padding quantum 2 pixels, which a run of 4x4
blocks pads to exactly zero.
The DMAC stays dropped on a measurement now rather than an argument: v7 takes
back 37 of the 43 frames the array chain would, with no reserved channel and no
timing neither emulator here can verify. Break-even against all-V1 moves from
L=4 blocks to L=2.
The fine displacement is carried mid-stream rather than in the span record, so
the decoder holds nothing across the copy and keeps all 12 payload registers --
which is the whole reason the coarse unit is 24 pixels.
span.sh is now -seconds_to_run 200 (30 s wall, 36 configs) and takes its
expected snapshot count from the generated metadata instead of a literal 23.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -11,6 +11,10 @@ decoder occupies 86.7% of it once instruction prefetch is counted, and 52 of the
|
|||||||
53 frames that miss the 12fps budget miss it on the bus, not the CPU
|
53 frames that miss the 12fps budget miss it on the bus, not the CPU
|
||||||
(FINDINGS 38). Read that before optimising anything for cycles.
|
(FINDINGS 38). Read that before optimising anything for cycles.
|
||||||
|
|
||||||
|
The largest measured win on the table is the **literal span with a fine tail**
|
||||||
|
(`blit.s` v7): it takes the worst `scsi` window from 84/120 frames over budget
|
||||||
|
to 18/120, and `src/player/decode.s` does not implement it yet (FINDINGS 40).
|
||||||
|
|
||||||
**Green-light check:** `./tools/bench/check.sh` (~3 min, needs the Blu-ray
|
**Green-light check:** `./tools/bench/check.sh` (~3 min, needs the Blu-ray
|
||||||
mounted) re-runs both display regression tests, the rate-control drift test, the
|
mounted) re-runs both display regression tests, the rate-control drift test, the
|
||||||
display-path coherency counterexample and a 120-frame 68000 decode, then prints
|
display-path coherency counterexample and a 120-frame 68000 decode, then prints
|
||||||
@@ -47,8 +51,10 @@ tools/analysis/ measurement scripts, numbered in the order they were written
|
|||||||
scene cut still fits at 12fps; 13 measures what fitting the
|
scene cut still fits at 12fps; 13 measures what fitting the
|
||||||
CPU budget costs in dB (FINDINGS 31) and caches H.build so the
|
CPU budget costs in dB (FINDINGS 31) and caches H.build so the
|
||||||
search loop is seconds, not minutes.
|
search loop is seconds, not minutes.
|
||||||
14 prices the HD63450 array-chain against the v6 span
|
14 prices the HD63450 array-chain against the v6 and v7
|
||||||
(FINDINGS 39) and prints the sensitivity that decides it;
|
spans (FINDINGS 39/40) and prints the sensitivity that decides
|
||||||
|
it -- v7 is measured, and takes 37 of the 43 frames the DMAC
|
||||||
|
would, so the DMAC stays dropped;
|
||||||
15 measures how much of the 68000's LOCAL bus the decoder
|
15 measures how much of the 68000's LOCAL bus the decoder
|
||||||
occupies (FINDINGS 38) and exits non-zero if its derived
|
occupies (FINDINGS 38) and exits non-zero if its derived
|
||||||
model stops matching the harness's measurement.
|
model stops matching the harness's measurement.
|
||||||
@@ -58,9 +64,14 @@ tools/bench/ MAME Lua injection harness + 68000 benchmark sources.
|
|||||||
`blit.s`/`blit.lua` time the full-frame GVRAM blit on the
|
`blit.s`/`blit.lua` time the full-frame GVRAM blit on the
|
||||||
68000 itself (FINDINGS 24) — not part of check.sh, because
|
68000 itself (FINDINGS 24) — not part of check.sh, because
|
||||||
wall timings would make the green-light check host-sensitive.
|
wall timings would make the green-light check host-sensitive.
|
||||||
`span.sh` (prep_spans.py + span.lua + blit.s v5/v6) measures
|
`span.sh` (prep_spans.py + span.lua + blit.s v5/v6/v7)
|
||||||
the literal-span mode the same way (FINDINGS 30, ~25 s); it
|
measures the literal-span mode the same way (FINDINGS 30 and
|
||||||
also asserts all 23 timing configs drew a pixel-exact frame.
|
40, ~30 s); it also asserts that every one of its 36 timing
|
||||||
|
configs drew a pixel-exact frame, the count taken from the
|
||||||
|
generated metadata so a new config cannot weaken the gate.
|
||||||
|
v7 is v6 with a second, 2-pixel chain for the span tail:
|
||||||
|
66.0 cycles/span + 9.143 per coarse pixel + 9.978 per fine
|
||||||
|
pixel, MEASURED, which is the win FINDINGS 39.4 predicted.
|
||||||
`crtc_mode.lua` is the single source of truth for CRTC R00-R08
|
`crtc_mode.lua` is the single source of truth for CRTC R00-R08
|
||||||
and R20 — do not write CRTC values anywhere else.
|
and R20 — do not write CRTC values anywhere else.
|
||||||
`prep_dlx.py`/`decode.lua`/`verify_decode.py` load, time and
|
`prep_dlx.py`/`decode.lua`/`verify_decode.py` load, time and
|
||||||
|
|||||||
@@ -2412,3 +2412,123 @@ end applies depends on how the MB89352 drives REQ and whether cycle-steal-with-
|
|||||||
hold is used, which is a design decision the player has not made yet -- and it
|
hold is used, which is a design decision the player has not made yet -- and it
|
||||||
is worth 7 clocks a word on a 480 KB/s stream, so it is worth making
|
is worth 7 clocks a word on a 480 KB/s stream, so it is worth making
|
||||||
deliberately.
|
deliberately.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 40. The finer chain tail, measured: v7 (session 11)
|
||||||
|
|
||||||
|
FINDINGS 39.4 attributed **86% of the DMAC array-chain's advantage over v6** to
|
||||||
|
one thing that is not a property of the DMAC at all -- v6's 24-pixel padding
|
||||||
|
quantum -- and derived that fixing it in software would take the `scsi` window
|
||||||
|
from 55/120 frames over budget to 18/120. It labelled that figure DERIVED and
|
||||||
|
said it should not be believed until `span.sh` measured it. This measures it.
|
||||||
|
|
||||||
|
### 40.1 The result
|
||||||
|
`tools/bench/blit.s` gains **v7**: v6's 24-pixel coarse chain with a second,
|
||||||
|
finer chain appended. Measured over thirteen span lengths by
|
||||||
|
`tools/bench/span.sh`, every one of which drew a **pixel-exact** frame:
|
||||||
|
|
||||||
|
```
|
||||||
|
cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel
|
||||||
|
```
|
||||||
|
|
||||||
|
fitting all 13 lengths to within **0.2%** -- and the fit is not flattered by its
|
||||||
|
own configs, because the three-term model was identified on span lengths chosen
|
||||||
|
so that every fine remainder a real span can have (0, 4, 8, 12, 16, 20) appears.
|
||||||
|
v5 and v6 re-measure to 97.9 + 10.459 and 43.7 + 9.152, reproducing FINDINGS 30
|
||||||
|
exactly, so the harness has not drifted underneath the new variant.
|
||||||
|
|
||||||
|
| clocks per 4x4 block, run of L blocks | L=1 | L=2 | L=3 | L=4 | L=8 | L=64 |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| v6 as built | 1053 | 527 | 351 | 263 | 241 | 154 |
|
||||||
|
| **v7** | **424** | **292** | **248** | **226** | **183** | **151** |
|
||||||
|
| DMAC chain (datasheet) | 288 | 216 | 192 | 180 | 162 | 146 |
|
||||||
|
|
||||||
|
**Break-even against all-V1 (299.9) moves from L=4 to L=2 blocks.** 39.4
|
||||||
|
predicted L=3; the measurement is better than the derivation.
|
||||||
|
|
||||||
|
### 40.2 The scoring, and a derivation that was right for the wrong reasons
|
||||||
|
Rescoring the same `scsi` window with `14_dmac_chain.py`, the same additive
|
||||||
|
model, the same mode maps:
|
||||||
|
|
||||||
|
| | frames over budget |
|
||||||
|
|---|---:|
|
||||||
|
| today | 84/120 |
|
||||||
|
| v6 span as built | 55/120 |
|
||||||
|
| **v7, MEASURED** | **18/120** |
|
||||||
|
| DMAC chain (datasheet) | 12/120 |
|
||||||
|
|
||||||
|
**18/120, exactly the derived figure.** That agreement is a coincidence of two
|
||||||
|
cancelling errors, and it is worth writing down because a match this clean would
|
||||||
|
otherwise be read as confirmation:
|
||||||
|
|
||||||
|
- 39.4 assumed a **2-register `movem` tail**, derived at 56 clocks per 4 pixels
|
||||||
|
= 14.0 clocks/pixel. The real tail costs **9.978** -- 29% cheaper.
|
||||||
|
- 39.4 assumed the second entry point costs **nothing per span**. It costs
|
||||||
|
**22.3 clocks** (66.0 against v6's 43.7), because it is a second
|
||||||
|
`move.w (a0)+,d0` and a second `jmp`.
|
||||||
|
|
||||||
|
The per-pixel win and the per-span loss are within a frame of each other over
|
||||||
|
this window. **The conclusion survives; the reasoning behind it did not.**
|
||||||
|
|
||||||
|
### 40.3 The instruction the derivation should have picked
|
||||||
|
A 2-register `movem` is the obvious "smaller unit of the same thing", and it is
|
||||||
|
the wrong instruction. Per 4 pixels:
|
||||||
|
|
||||||
|
| tail unit | bus cycles | clocks | per pixel |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| `movem.l (a0)+,d0-d1` + `movem.l d0-d1,(a2)` + `lea` | 14 | 56 | 14.0 |
|
||||||
|
| **2 x `move.l (a0)+,(a2)+`** | **10** | **40** | **~10.0** |
|
||||||
|
|
||||||
|
`movem` pays two instruction words and a `lea` to move what two of the plainest
|
||||||
|
instructions on the machine move with post-increment on both sides. v7's fine
|
||||||
|
unit is therefore **one `move.l (a0)+,(a2)+` = 2 pixels**, which also makes the
|
||||||
|
padding quantum **2** rather than 4 -- and a span is a run of 4x4 blocks, so its
|
||||||
|
length is always a multiple of 4 and **the padding is exactly zero**. 39.4's
|
||||||
|
"caps the padding at 3 pixels" was pessimistic by three pixels.
|
||||||
|
|
||||||
|
The derived bus model predicts the measurement well once the right instruction
|
||||||
|
is in it: 5 bus cycles = 20 clocks per 2 pixels = 10.0/pixel against 9.978
|
||||||
|
measured, and 54 bus cycles = 216 clocks per 24 against 9.143*24 = 219.4.
|
||||||
|
|
||||||
|
### 40.4 Where the fine displacement lives, and why it is not in the record
|
||||||
|
Two chains need two entry points, and the second one has to survive the coarse
|
||||||
|
copy. Holding it in a register would cost a payload register -- v6's whole
|
||||||
|
reason for a 24-pixel unit is that it has exactly 12 free (`d0-d6/a1/a3-a6`).
|
||||||
|
|
||||||
|
So it is not in the span record. **It is in the stream**, after the coarse
|
||||||
|
pixels and before the fine ones. The coarse chain falls out into
|
||||||
|
`move.w (a0)+,d0 / jmp v7fh(pc,d0.w)`, and at that instant `d0` is dead payload
|
||||||
|
and `a0` is pointing exactly at it. The decoder holds nothing extra across the
|
||||||
|
copy and keeps all 12 registers.
|
||||||
|
|
||||||
|
The record is still `{u32 absolute GVRAM address, u16 coarse displacement}`;
|
||||||
|
the container costs **2 more bytes per span**, which `14_dmac_chain.py` charges.
|
||||||
|
|
||||||
|
### 40.5 The verdict, now measured rather than argued
|
||||||
|
**FINDINGS 39.5 stands: fix the quantum in software, drop the DMAC.** v7 takes
|
||||||
|
back **37 of the 43 frames** the DMAC chain would, using an instruction sequence
|
||||||
|
that needs no reserved channel, no two-region container, and no transfer timing
|
||||||
|
that neither emulator on this box can verify. 39.1 still holds if that ever
|
||||||
|
changes: the chain array and the span table are the same six bytes.
|
||||||
|
|
||||||
|
### 40.6 The 13-minute run that measured nothing
|
||||||
|
`span.sh` ran for 13 minutes producing an empty log and zero snapshots, and the
|
||||||
|
same MAME command with a shorter `-seconds_to_run` completed the identical work
|
||||||
|
in 30 seconds. The cause is still not identified. What matters is that **the
|
||||||
|
run was unobservable in both directions**: MAME's stdout did not reach the log
|
||||||
|
until exit even under `stdbuf -oL`, and the snapshots -- the one artefact that
|
||||||
|
would have shown progress -- may themselves only land at exit.
|
||||||
|
|
||||||
|
So the bisection that resolved it did not chase the hang. It **shrank the
|
||||||
|
stimulus** instead: `tmp/spans_meta.lua` carries byte offsets into a blob that
|
||||||
|
`prep_spans.py` writes once, so deleting config lines from the metadata runs any
|
||||||
|
subset in seconds against the same unmodified stream file. v5 alone, v7 alone
|
||||||
|
and the full set at a shorter run all completed; the wedge never reproduced.
|
||||||
|
|
||||||
|
This is the fourth instance of the pattern FINDINGS 34.1 named, and it is the
|
||||||
|
first where **the instrument was unobservable but the thing being measured was
|
||||||
|
fine**. `span.sh` now runs at `-seconds_to_run 200`, measured at 30 s wall for
|
||||||
|
all 36 configs, and asserts the snapshot count against the number of configs in
|
||||||
|
the generated metadata rather than a literal 23 -- so adding a config can no
|
||||||
|
longer silently weaken the pixel-exactness gate.
|
||||||
|
|||||||
+112
-4
@@ -1,7 +1,109 @@
|
|||||||
# Status & next-session handoff — end of session 10 (2026-08-23)
|
# Status & next-session handoff — end of session 11 (2026-08-23)
|
||||||
|
|
||||||
## Where this stands
|
## Where this stands
|
||||||
|
|
||||||
|
Session 11 measured the one item session 10 left at the top of the list, and it
|
||||||
|
paid: **`blit.s` v7, the literal span with a fine tail, is MEASURED and takes
|
||||||
|
the `scsi` window from 84/120 frames over budget to 18/120.** FINDINGS 40.
|
||||||
|
|
||||||
|
```
|
||||||
|
v7: cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel
|
||||||
|
13 span lengths, all fitted to within 0.2%, all pixel-exact
|
||||||
|
```
|
||||||
|
|
||||||
|
| | frames over budget, 120-frame `scsi` window |
|
||||||
|
|---|---:|
|
||||||
|
| today (no spans) | 84/120 |
|
||||||
|
| v6 span as built | 55/120 |
|
||||||
|
| **v7, measured** | **18/120** |
|
||||||
|
| DMAC chain (datasheet) | 12/120 |
|
||||||
|
|
||||||
|
**The DMAC stays dropped, and now on a measurement rather than an argument.**
|
||||||
|
v7 takes back 37 of the 43 frames the DMAC chain would, with no reserved
|
||||||
|
channel, no two-region container, and no transfer timing neither emulator here
|
||||||
|
can verify. FINDINGS 39.1 still holds if that ever changes: a chain array entry
|
||||||
|
and a v6/v7 span record are the same six bytes.
|
||||||
|
|
||||||
|
**Break-even against all-V1 moves from L=4 blocks to L=2.** 39.4 predicted L=3.
|
||||||
|
|
||||||
|
**18/120 is exactly what 39.4 derived, and that is a coincidence of two
|
||||||
|
cancelling errors** — worth knowing before the next derived figure gets trusted
|
||||||
|
for landing on its measurement. 39.4 assumed a 2-register `movem` tail at 14.0
|
||||||
|
clocks/pixel (the real tail is 9.978, 29% cheaper) and assumed the second chain
|
||||||
|
entry costs nothing per span (it costs 22.3 clocks). The two nearly cancel over
|
||||||
|
this window. FINDINGS 40.2.
|
||||||
|
|
||||||
|
**The tail instruction the derivation should have picked is `move.l (a0)+,(a2)+`.**
|
||||||
|
A 2-register `movem` pays two instruction words and a `lea` to move what two
|
||||||
|
post-incrementing `move.l`s move: 14 bus cycles against 10 for the same 4
|
||||||
|
pixels. Taking the plain instruction also makes the padding quantum **2 pixels**
|
||||||
|
instead of 4 — and a span is a run of 4x4 blocks, so **its padding is exactly
|
||||||
|
zero**. FINDINGS 40.3.
|
||||||
|
|
||||||
|
**The fine displacement lives in the STREAM, not in the span record**, after the
|
||||||
|
coarse pixels and before the fine ones: the coarse chain falls out into
|
||||||
|
`move.w (a0)+,d0 / jmp`, where `d0` is dead payload and `a0` already points at
|
||||||
|
it. That is what lets v7 keep all 12 payload registers, which is the entire
|
||||||
|
reason v6's unit is 24 pixels. The container costs 2 more bytes a span.
|
||||||
|
FINDINGS 40.4.
|
||||||
|
|
||||||
|
**One process note.** `span.sh` ran 13 minutes producing an empty log and zero
|
||||||
|
snapshots; the same command with a shorter `-seconds_to_run` did the identical
|
||||||
|
work in 30 s, and the wedge never reproduced. The cause is unidentified. What
|
||||||
|
resolved it was not chasing the hang but **shrinking the stimulus**:
|
||||||
|
`tmp/spans_meta.lua` holds byte offsets into a blob `prep_spans.py` writes once,
|
||||||
|
so deleting lines from the metadata runs any subset in seconds against the same
|
||||||
|
stream file. Keep that trick. `span.sh` is now `-seconds_to_run 200` (30 s wall
|
||||||
|
for all 36 configs) and takes its expected snapshot count from the metadata
|
||||||
|
instead of a literal 23. FINDINGS 40.6.
|
||||||
|
|
||||||
|
Green light: `./tools/bench/check.sh` **ALL GREEN** at the end of this session.
|
||||||
|
|
||||||
|
## NEXT SESSION, in order
|
||||||
|
|
||||||
|
0. **Green light first.** `./tools/bench/check.sh` (~5 min, Blu-ray mounted).
|
||||||
|
Verified green at end of session 11.
|
||||||
|
|
||||||
|
1. **Build v7 into `src/player/decode.s`.** This is now the largest thing
|
||||||
|
standing between the measured decoder and the budget: 84/120 to 18/120, the
|
||||||
|
format is fully specified (FINDINGS 30.2, costs 40.1), the executor is
|
||||||
|
written and pixel-exact in `blit.s`, and the encoder side is
|
||||||
|
`prep_spans.py`'s v7 emitter. The container is `{u32 absolute GVRAM address,
|
||||||
|
u16 coarse displacement}` per span plus one `u16` fine displacement carried
|
||||||
|
mid-stream — see FINDINGS 40.4 before changing that layout, the register
|
||||||
|
pressure is the reason for it.
|
||||||
|
|
||||||
|
2. **Then re-run `14_dmac_chain.py` and `13_cpu_ratectl.py` against a container
|
||||||
|
the encoder actually emits with spans in it.** Every span figure so far is
|
||||||
|
scored against mode maps chosen without spans available, which FINDINGS 39.3
|
||||||
|
flags as a lower bound on what a span-aware encoder would find.
|
||||||
|
|
||||||
|
3. **Make sure the player actually gets DMA** — unchanged from session 10, and
|
||||||
|
still not an optimisation. Benchmark `x68000 -exp1 cz6bs1`, **never
|
||||||
|
`x68ksupr`**; MAME's internal SCSI has no DMA glue (`// TODO: duplicate DMA
|
||||||
|
glue from CZ-6BS1`) and would measure a PIO fallback the real machine does
|
||||||
|
not have.
|
||||||
|
|
||||||
|
4. **Re-decide the framerate.** 10 fps absorbs the DMA steal on current
|
||||||
|
estimates. Still the user's call, and now cheaper to defer: v7 buys back
|
||||||
|
enough of the budget that 12 fps is no longer obviously out of reach.
|
||||||
|
|
||||||
|
5. **Re-run the ring-buffer simulation at the surviving rate** and confirm the
|
||||||
|
488 KB/s figure's provenance (FINDINGS 29.5/30.7, still open).
|
||||||
|
|
||||||
|
**Do not start by hand-optimising `decode.s`.** Unchanged and still true: the
|
||||||
|
hand-derived timings agree with the measurements to 0.5% on V1 and 1% on RAW
|
||||||
|
(FINDINGS 28.4), 34 confirms the model on a second container, and the cycles to
|
||||||
|
be won are in the budget, not the loop.
|
||||||
|
|
||||||
|
**Always `stdbuf -oL` a MAME job that prints progress — and do not trust it.**
|
||||||
|
Session 11 added the case where even that is not enough (40.6). If a run is not
|
||||||
|
producing observable output, shrink the stimulus rather than waiting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What session 10 settled
|
||||||
|
|
||||||
Session 10 cross-checked the whole cycle model against a second emulator, then
|
Session 10 cross-checked the whole cycle model against a second emulator, then
|
||||||
found that the model was denominated in the wrong currency.
|
found that the model was denominated in the wrong currency.
|
||||||
|
|
||||||
@@ -77,10 +179,16 @@ at **3.3%**, and it runs against us. Two incidental results worth keeping: MAME
|
|||||||
rather than two tables; and FINDINGS 28.8's "V4 costs more than RAW" reproduces
|
rather than two tables; and FINDINGS 28.8's "V4 costs more than RAW" reproduces
|
||||||
independently. FINDINGS 37.
|
independently. FINDINGS 37.
|
||||||
|
|
||||||
Everything below this line is from session 9 and still stands unless a session-10
|
---
|
||||||
section above says otherwise.
|
|
||||||
|
|
||||||
## NEXT SESSION, in order
|
## What session 10 put on the list
|
||||||
|
|
||||||
|
Kept for the reasoning, not the priorities: items 0 and 1 are done (FINDINGS 40)
|
||||||
|
and the rest are carried forward, reordered, in the session-11 list at the top.
|
||||||
|
Everything below this point is from session 9 or earlier and still stands unless
|
||||||
|
a later section above says otherwise.
|
||||||
|
|
||||||
|
### session 10's list
|
||||||
|
|
||||||
0. **Green light first.** `./tools/bench/check.sh` (~5 min, Blu-ray mounted).
|
0. **Green light first.** `./tools/bench/check.sh` (~5 min, Blu-ray mounted).
|
||||||
Verified green at end of session 9. The gate container is now
|
Verified green at end of session 9. The gate container is now
|
||||||
|
|||||||
@@ -41,8 +41,11 @@ CPU + DMA, additive. Session 10's first pass used max(CPU, bus) and got 53/120
|
|||||||
where the additive model gives 84/120; FINDINGS 35's flat debit was right.
|
where the additive model gives 84/120; FINDINGS 35's flat debit was right.
|
||||||
|
|
||||||
So the only material difference left is v6's 24-pixel padding quantum -- and
|
So the only material difference left is v6's 24-pixel padding quantum -- and
|
||||||
that is a property of v6's unrolled chain, not of the CPU. The `v6 fine tail`
|
that is a property of v6's unrolled chain, not of the CPU. The `v7 fine tail`
|
||||||
column prices fixing it in software instead.
|
column prices fixing it in software instead, and as of session 11 that column
|
||||||
|
is MEASURED on the 68000 (blit.s v7, tools/bench/span.sh, FINDINGS 40) rather
|
||||||
|
than derived: 66.0 clocks per span + 9.143 per coarse pixel + 9.978 per fine
|
||||||
|
pixel, with a 2-pixel quantum that a run of 4x4 blocks pads to exactly.
|
||||||
"""
|
"""
|
||||||
import sys, os, argparse
|
import sys, os, argparse
|
||||||
sys.path.insert(0, "tools/encoder")
|
sys.path.insert(0, "tools/encoder")
|
||||||
@@ -100,8 +103,8 @@ def span_cost(design, L):
|
|||||||
if design == "v6":
|
if design == "v6":
|
||||||
px = B.pad24(4 * L)
|
px = B.pad24(4 * L)
|
||||||
return 4 * px, 4 * (B.V6_SPAN_CYC + px * B.V6_PX_CYC)
|
return 4 * px, 4 * (B.V6_SPAN_CYC + px * B.V6_PX_CYC)
|
||||||
if design == "v6fine":
|
if design == "v7":
|
||||||
px, c = B.v6_fine(4 * L)
|
px, c = B.v7_span(4 * L)
|
||||||
return 4 * px, 4 * c
|
return 4 * px, 4 * c
|
||||||
px = 4 * L
|
px = 4 * L
|
||||||
return 4 * px, 4 * (B.DMA_CHAIN_CLK + px * a.dma_px_clk)
|
return 4 * px, 4 * (B.DMA_CHAIN_CLK + px * a.dma_px_clk)
|
||||||
@@ -127,7 +130,9 @@ def score(design):
|
|||||||
cur_b = sum(BLK_B[int(b)] for b in m[by][i:j])
|
cur_b = sum(BLK_B[int(b)] for b in m[by][i:j])
|
||||||
px, sc = span_cost(design, L)
|
px, sc = span_cost(design, L)
|
||||||
sc += L * C_SKIP_MIXED # the dispatch still happens
|
sc += L * C_SKIP_MIXED # the dispatch still happens
|
||||||
span_b = 4 * SPAN_HDR + px * SPAN_BYTES_PX
|
# v7 carries a second u16 (the fine displacement) per span.
|
||||||
|
hdr = B.V7_SPAN_HDR if design == "v7" else SPAN_HDR
|
||||||
|
span_b = 4 * hdr + px * SPAN_BYTES_PX
|
||||||
if sc < cur_c:
|
if sc < cur_c:
|
||||||
cand.append((cur_c - sc, span_b - cur_b, by, i, j, sc, L))
|
cand.append((cur_c - sc, span_b - cur_b, by, i, j, sc, L))
|
||||||
cand.sort(key=lambda s: -(s[0] / max(s[1], 1)))
|
cand.sort(key=lambda s: -(s[0] / max(s[1], 1)))
|
||||||
@@ -154,7 +159,7 @@ def score(design):
|
|||||||
|
|
||||||
|
|
||||||
DESIGNS = [("today", "none"), ("v6 span", "v6"),
|
DESIGNS = [("today", "none"), ("v6 span", "v6"),
|
||||||
("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")]
|
("v7 fine tail", "v7"), ("DMAC chain", "dmac")]
|
||||||
res = {n: score(k) for n, k in DESIGNS}
|
res = {n: score(k) for n, k in DESIGNS}
|
||||||
|
|
||||||
print(f"{a.container}: {d.nframes} frames, {d.nb} blocks, {a.fps:g} fps")
|
print(f"{a.container}: {d.nframes} frames, {d.nb} blocks, {a.fps:g} fps")
|
||||||
@@ -188,11 +193,11 @@ print(f"\n ADDITIVE: frame = CPU + span painting + disk DMA. The 68000 has no"
|
|||||||
|
|
||||||
# What is left of the case, isolated.
|
# What is left of the case, isolated.
|
||||||
v6m = int((res["v6 span"][0] > FRAME_CYC).sum())
|
v6m = int((res["v6 span"][0] > FRAME_CYC).sum())
|
||||||
finem = int((res["v6 fine tail"][0] > FRAME_CYC).sum())
|
finem = int((res["v7 fine tail"][0] > FRAME_CYC).sum())
|
||||||
dmam = int((res["DMAC chain"][0] > FRAME_CYC).sum())
|
dmam = int((res["DMAC chain"][0] > FRAME_CYC).sum())
|
||||||
print(f"\nWHAT THE DMAC ACTUALLY BUYS, decomposed")
|
print(f"\nWHAT THE DMAC ACTUALLY BUYS, decomposed")
|
||||||
print(f" v6 as built {v6m}/{d.nframes} frames over")
|
print(f" v6 as built {v6m}/{d.nframes} frames over")
|
||||||
print(f" v6 with a finer chain tail (software) {finem}/{d.nframes}")
|
print(f" v7, a finer chain tail (MEASURED) {finem}/{d.nframes}")
|
||||||
print(f" DMAC chain {dmam}/{d.nframes}")
|
print(f" DMAC chain {dmam}/{d.nframes}")
|
||||||
print(f" -> of the gap between v6 and the DMAC, "
|
print(f" -> of the gap between v6 and the DMAC, "
|
||||||
f"{100*(v6m-finem)/max(v6m-dmam,1):.0f}% is the 24-pixel padding")
|
f"{100*(v6m-finem)/max(v6m-dmam,1):.0f}% is the 24-pixel padding")
|
||||||
@@ -206,9 +211,9 @@ print(f" in software. The rest is 1.7% a pixel and 7.7 clocks a span.")
|
|||||||
|
|
||||||
print(f"\nbreak-even against all-V1 ({C_V1:.1f} cycles/block), clocks per block")
|
print(f"\nbreak-even against all-V1 ({C_V1:.1f} cycles/block), clocks per block")
|
||||||
print(f" {'L':<16}" + "".join(f"{L:>8}" for L in (1, 2, 3, 4, 8, 16, 64)))
|
print(f" {'L':<16}" + "".join(f"{L:>8}" for L in (1, 2, 3, 4, 8, 16, 64)))
|
||||||
for nm, dz in (("v6 as built", "v6"), ("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")):
|
for nm, dz in (("v6 as built", "v6"), ("v7 fine tail", "v7"), ("DMAC chain", "dmac")):
|
||||||
print(f" {nm:<16}" + "".join(f"{span_cost(dz, L)[1]/L:>8.0f}"
|
print(f" {nm:<16}" + "".join(f"{span_cost(dz, L)[1]/L:>8.0f}"
|
||||||
for L in (1, 2, 3, 4, 8, 16, 64)))
|
for L in (1, 2, 3, 4, 8, 16, 64)))
|
||||||
for nm, dz in (("v6 as built", "v6"), ("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")):
|
for nm, dz in (("v6 as built", "v6"), ("v7 fine tail", "v7"), ("DMAC chain", "dmac")):
|
||||||
brk = next((L for L in range(1, 65) if span_cost(dz, L)[1] < L * C_V1), None)
|
brk = next((L for L in range(1, 65) if span_cost(dz, L)[1] < L * C_V1), None)
|
||||||
print(f" {nm:<16} beats all-V1 from L={brk} blocks up")
|
print(f" {nm:<16} beats all-V1 from L={brk} blocks up")
|
||||||
|
|||||||
+48
-14
@@ -110,20 +110,54 @@ def block_bus(mode_map, spanned=None):
|
|||||||
return pref, data
|
return pref, data
|
||||||
|
|
||||||
|
|
||||||
# --- v6 with a finer tail (PROPOSAL, unmeasured -- Claude's, session 10) ----
|
# --- v7: v6 with a finer tail (MEASURED, session 11, FINDINGS 40) ----------
|
||||||
# v6 pads every span up to 24 pixels because its unrolled chain is built from
|
# v6 pads every span up to 24 pixels because its unrolled chain is built from
|
||||||
# 12-register movem units. Adding a second, finer chain of 2-register units
|
# 12-register movem units, and FINDINGS 39.3 attributed 86% of the DMAC array
|
||||||
# (4 pixels) for the tail caps the padding at 3 pixels instead of 23, for the
|
# chain's advantage over v6 to exactly that padding. v7 keeps the coarse chain
|
||||||
# price of some more unrolled code and nothing per span.
|
# and appends a second chain whose unit is one `move.l (a0)+,(a2)+` -- 2 pixels,
|
||||||
# A 4-pixel unit: movem.l (a0)+,2 = 2w instr + 4 reads; movem.l 2,(a2) = 2w +
|
# so the quantum is 2 and a run of 4x4 blocks pads to NOTHING.
|
||||||
# 4 writes; lea = 2w. 14 bus cycles for 4 pixels = 56 clocks, against a full
|
#
|
||||||
# unit's 24 x 9.152 = 220 for 24. Dearer per pixel, paid at most once a span.
|
# Session 10 proposed a 2-REGISTER MOVEM tail (4 pixels, derived at 56 clocks)
|
||||||
V6_TAIL_PX, V6_TAIL_CLK = 4, 56
|
# and that would have been the wrong instruction: movem.l (a0)+,d0-d1 plus
|
||||||
|
# movem.l d0-d1,(a2) plus the lea is 14 bus cycles for 4 pixels, where two plain
|
||||||
|
# move.l are 10. The plainest instruction on the machine wins the tail.
|
||||||
|
#
|
||||||
|
# The second entry point needs a second dispatch, and the fine displacement is
|
||||||
|
# carried MID-STREAM (after the coarse pixels, before the fine ones) rather than
|
||||||
|
# in the span record, so the decoder holds nothing extra across the copy and
|
||||||
|
# keeps all 12 payload registers. Costed as 2 more bytes per span.
|
||||||
|
#
|
||||||
|
# MEASURED by tools/bench/span.sh (blit.s v7, 13 span lengths, every config
|
||||||
|
# pixel-exact): cycles = 66.0/span + 9.143/coarse pixel + 9.978/fine pixel,
|
||||||
|
# fitting all 13 to within 0.2%.
|
||||||
|
V7_SPAN_CYC = 66.0 # MEASURED, FINDINGS 40
|
||||||
|
V7_CPX_CYC = 9.143 # MEASURED, FINDINGS 40 (24-pixel coarse unit)
|
||||||
|
V7_FPX_CYC = 9.978 # MEASURED, FINDINGS 40 (2-pixel fine unit)
|
||||||
|
V7_FINE_PX = 2
|
||||||
|
# Bus: per span v6's 9 plus a second {move.w (a0)+,d0 ; jmp} = 2 + 2.
|
||||||
|
# Per fine unit: move.l (a0)+,(a2)+ = 1 instruction word + 2 reads + 2 writes.
|
||||||
|
V7_SPAN_BUS = 13
|
||||||
|
V7_FINE_BUS = 5
|
||||||
|
V7_SPAN_HDR = 8 # {u32 address, u16 coarse disp} + u16 fine disp
|
||||||
|
|
||||||
|
|
||||||
def v6_fine(npix):
|
def pad2(npix):
|
||||||
"""(pixels carried, CPU clocks) for a span with the finer tail."""
|
return -(-npix // V7_FINE_PX) * V7_FINE_PX
|
||||||
k, r = divmod(npix, V6_UNIT_PX)
|
|
||||||
t = -(-r // V6_TAIL_PX)
|
|
||||||
return (k * V6_UNIT_PX + t * V6_TAIL_PX,
|
def v7_span(npix):
|
||||||
V6_SPAN_CYC + k * V6_UNIT_PX * V6_PX_CYC + t * V6_TAIL_CLK)
|
"""(pixels carried, CPU clocks) for a v7 span of npix pixels."""
|
||||||
|
k, r = divmod(pad2(npix), V6_UNIT_PX)
|
||||||
|
return (k * V6_UNIT_PX + r,
|
||||||
|
V7_SPAN_CYC + k * V6_UNIT_PX * V7_CPX_CYC + r * V7_FPX_CYC)
|
||||||
|
|
||||||
|
|
||||||
|
def v7_span_bus(npix):
|
||||||
|
"""Bus CYCLES a v7 span occupies -- instruction words plus data accesses."""
|
||||||
|
k, r = divmod(pad2(npix), V6_UNIT_PX)
|
||||||
|
return V7_SPAN_BUS + k * V6_UNIT_BUS + (r // V7_FINE_PX) * V7_FINE_BUS
|
||||||
|
|
||||||
|
|
||||||
|
def v6_span_bus(npix):
|
||||||
|
k = pad24(npix) // V6_UNIT_PX
|
||||||
|
return V6_SPAN_BUS + k * V6_UNIT_BUS
|
||||||
|
|||||||
@@ -69,6 +69,27 @@
|
|||||||
; And with row and remainder handling gone, 12 registers are free again
|
; And with row and remainder handling gone, 12 registers are free again
|
||||||
; (d0-d6/a1/a3-a6), which is why the unit is 24 pixels and not V5's 16.
|
; (d0-d6/a1/a3-a6), which is why the unit is 24 pixels and not V5's 16.
|
||||||
;
|
;
|
||||||
|
; V7 v6 with a SECOND, finer chain for the tail (FINDINGS 39.4). v6 pays for
|
||||||
|
; its 24-pixel quantum in padding: an average span wastes ~11 pixels, and
|
||||||
|
; FINDINGS 39.3 attributes 86% of the DMAC array-chain's advantage over v6
|
||||||
|
; to exactly that. V7 keeps the 24-pixel coarse chain and appends a chain
|
||||||
|
; of 2-pixel units, so a span is 24*c + 2*f pixels and the padding is at
|
||||||
|
; most one pixel -- ZERO for the real case, where a span is a run of 4x4
|
||||||
|
; blocks and its length is a multiple of 4.
|
||||||
|
;
|
||||||
|
; The fine unit is `move.l (a0)+,(a2)+` (20 cycles, 2 pixels), NOT a
|
||||||
|
; 2-register movem: movem.l (a0)+,d0-d1 plus movem.l d0-d1,(a2) plus the
|
||||||
|
; lea is 52+8 cycles for 4 pixels, so the obvious "smaller movem" tail is
|
||||||
|
; 50% dearer per pixel than the plainest instruction on the machine.
|
||||||
|
;
|
||||||
|
; The second entry point costs a second dispatch, and the trick that pays
|
||||||
|
; for it is that the fine displacement is NOT in the span record: it sits
|
||||||
|
; in the STREAM, after the coarse pixels and before the fine ones. The
|
||||||
|
; coarse chain falls out into `move.w (a0)+,d0 / jmp`, by which point d0
|
||||||
|
; is dead payload and a0 is pointing exactly at it. So v7 holds nothing
|
||||||
|
; extra across the copy and keeps all 12 payload registers -- a record is
|
||||||
|
; still {u32 address, u16 displacement}, with one more u16 mid-span.
|
||||||
|
;
|
||||||
; 12 registers per movem burst (d0-d7/a2-a5 = 48 bytes) is the maximum
|
; 12 registers per movem burst (d0-d7/a2-a5 = 48 bytes) is the maximum
|
||||||
; available: a0=src, a1=dst, a6=end sentinel. The row counter lives in the
|
; available: a0=src, a1=dst, a6=end sentinel. The row counter lives in the
|
||||||
; a1-vs-a6 compare rather than a d-register for exactly this reason.
|
; a1-vs-a6 compare rather than a d-register for exactly this reason.
|
||||||
@@ -88,6 +109,10 @@ DSTE = $C38000 ; GVRAM + 224*1024 (one past last)
|
|||||||
ROWS = 192 ; picture rows a V5 stream describes
|
ROWS = 192 ; picture rows a V5 stream describes
|
||||||
V6UNIT = 12 ; bytes of code per V6 chain unit
|
V6UNIT = 12 ; bytes of code per V6 chain unit
|
||||||
V6MAX = 11 ; chain units = 11*24 = 264 pixels >= one row
|
V6MAX = 11 ; chain units = 11*24 = 264 pixels >= one row
|
||||||
|
V7CU = 12 ; bytes of code per V7 COARSE unit (24 px)
|
||||||
|
V7CN = 11 ; coarse units: 11*24 = 264 px >= one row
|
||||||
|
V7FU = 2 ; bytes of code per V7 FINE unit (2 px)
|
||||||
|
V7FN = 11 ; fine units: 11*2 = 22 px > one coarse unit
|
||||||
|
|
||||||
org $10000
|
org $10000
|
||||||
start:
|
start:
|
||||||
@@ -103,6 +128,8 @@ start:
|
|||||||
beq v5
|
beq v5
|
||||||
cmp.l #6,d0
|
cmp.l #6,d0
|
||||||
beq v6
|
beq v6
|
||||||
|
cmp.l #7,d0
|
||||||
|
beq v7
|
||||||
bra v3
|
bra v3
|
||||||
|
|
||||||
; ---------------------------------------------------------------- V1
|
; ---------------------------------------------------------------- V1
|
||||||
@@ -282,5 +309,68 @@ v6ch:
|
|||||||
bne v6
|
bne v6
|
||||||
bra done
|
bra done
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------- V7
|
||||||
|
; a0 stream, a2 destination, d7 spans remaining; everything else is payload.
|
||||||
|
; Stream per span: u32 dest, u16 coarse disp, c*48 B pixels,
|
||||||
|
; u16 fine disp, f*4 B pixels.
|
||||||
|
v7: move.l SPTR.l,a0
|
||||||
|
move.w (a0)+,d7 ; total spans in the frame
|
||||||
|
subq.w #1,d7
|
||||||
|
v7span: move.l (a0)+,a2 ; absolute GVRAM destination
|
||||||
|
move.w (a0)+,d0 ; (V7CN - coarse) * V7CU
|
||||||
|
jmp v7ch(pc,d0.w)
|
||||||
|
v7ch:
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
movem.l (a0)+,d0-d6/a1/a3-a6
|
||||||
|
movem.l d0-d6/a1/a3-a6,(a2)
|
||||||
|
lea 48(a2),a2
|
||||||
|
v7cx: move.w (a0)+,d0 ; (V7FN - fine) * V7FU, from mid-stream
|
||||||
|
jmp v7fh(pc,d0.w)
|
||||||
|
v7fh:
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
move.l (a0)+,(a2)+
|
||||||
|
dbra d7,v7span
|
||||||
|
subq.l #1,ITER.l
|
||||||
|
bne v7
|
||||||
|
bra done
|
||||||
|
|
||||||
done: move.l #$FF,FLAG.l ; timer stops here
|
done: move.l #$FF,FLAG.l ; timer stops here
|
||||||
halt: bra.s halt
|
halt: bra.s halt
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ v6 -- the same spans with that arithmetic moved here, where it is free:
|
|||||||
line stride is 1024 bytes and only the first 512 are displayed. The jump
|
line stride is 1024 bytes and only the first 512 are displayed. The jump
|
||||||
displacement selects an entry point into the decoder's unrolled copy chain.
|
displacement selects an entry point into the decoder's unrolled copy chain.
|
||||||
|
|
||||||
|
v7 -- v6 plus a second, FINER chain for the tail (FINDINGS 39.4). Padding to
|
||||||
|
v6's 24-pixel quantum wastes ~11 pixels on an average span, and FINDINGS
|
||||||
|
39.3 attributes 86% of the DMAC array-chain's advantage over v6 to it. A
|
||||||
|
v7 span is 24*c + 2*f pixels, so the quantum is 2 and a run of 4x4 blocks
|
||||||
|
(always a multiple of 4 pixels) pads to NOTHING:
|
||||||
|
u16 nspans
|
||||||
|
nspans * { u32 absolute GVRAM address, u16 coarse displacement,
|
||||||
|
c * 48 bytes of pixels,
|
||||||
|
u16 fine displacement, f * 4 bytes of pixels }
|
||||||
|
The fine displacement is in the STREAM rather than the record because that
|
||||||
|
is what lets the decoder keep all 12 payload registers: the coarse chain
|
||||||
|
falls out into a `move.w (a0)+,d0 / jmp` with d0 dead and a0 pointing at
|
||||||
|
it. Costed here as an 8-byte record, since it is 2 more bytes a span.
|
||||||
|
|
||||||
Pixels are word-expanded with the palette index in the low byte; the high byte
|
Pixels are word-expanded with the palette index in the low byte; the high byte
|
||||||
is whatever we put there because gvram_w masks it off (x68k_crtc.cpp:501).
|
is whatever we put there because gvram_w masks it off (x68k_crtc.cpp:501).
|
||||||
"""
|
"""
|
||||||
@@ -58,8 +72,18 @@ CONFIGS = [(4, 0), (8, 0), (12, 0), (16, 0), (16, 1), (20, 0), (24, 0),
|
|||||||
# v6 geometry, and it must match blit.s: 12 registers per movem = 48 bytes =
|
# v6 geometry, and it must match blit.s: 12 registers per movem = 48 bytes =
|
||||||
# 24 pixels per chain unit, 11 units in the chain.
|
# 24 pixels per chain unit, 11 units in the chain.
|
||||||
UNITPX, UNITSZ, UNITS = 24, 12, 11
|
UNITPX, UNITSZ, UNITS = 24, 12, 11
|
||||||
|
# v7 geometry, and it must match blit.s: coarse unit as v6, fine unit is one
|
||||||
|
# `move.l (a0)+,(a2)+` = 2 bytes of code = 2 pixels, 11 of them (22 px > 24).
|
||||||
|
FINEPX, FINESZ, FINES = 2, 2, 11
|
||||||
GVRAM, YOFF, STRIDE = 0xC00000, 32, 1024
|
GVRAM, YOFF, STRIDE = 0xC00000, 32, 1024
|
||||||
|
|
||||||
|
# v7 span lengths, in pixels. Multiples of 4 (a real span is a run of 4x4
|
||||||
|
# blocks), chosen so the fine remainder P mod 24 takes every value a real span
|
||||||
|
# can: 0, 4, 8, 12, 16, 20. 4/8/12/16/20 are pure-fine, 24/48/72/120/240 are
|
||||||
|
# pure-coarse, the rest mix -- which is what makes the three-term fit
|
||||||
|
# cycles = A*spans + Bc*coarse_px + Bf*fine_px identifiable.
|
||||||
|
V7CONFIGS = [4, 8, 12, 16, 20, 24, 28, 44, 48, 72, 100, 120, 256]
|
||||||
|
|
||||||
blob, metas = bytearray(), []
|
blob, metas = bytearray(), []
|
||||||
for P, x0 in CONFIGS:
|
for P, x0 in CONFIGS:
|
||||||
off = len(blob)
|
off = len(blob)
|
||||||
@@ -78,7 +102,8 @@ for P, x0 in CONFIGS:
|
|||||||
blob += idx[y, x:x+n].astype(">u2").tobytes()
|
blob += idx[y, x:x+n].astype(">u2").tobytes()
|
||||||
nspans += 1; npix += n
|
nspans += 1; npix += n
|
||||||
metas.append(dict(name=f"{P}{'u' if x0 else ''}", p=P, x0=x0, off=off,
|
metas.append(dict(name=f"{P}{'u' if x0 else ''}", p=P, x0=x0, off=off,
|
||||||
len=len(blob)-off, nspans=nspans, npix=npix, var=5))
|
len=len(blob)-off, nspans=nspans, npix=npix,
|
||||||
|
cpx=npix, fpx=0, var=5))
|
||||||
|
|
||||||
# v6: one config per chain depth, so the fit sees spans from 24 to 264 pixels.
|
# v6: one config per chain depth, so the fit sees spans from 24 to 264 pixels.
|
||||||
for units in range(1, UNITS+1):
|
for units in range(1, UNITS+1):
|
||||||
@@ -100,7 +125,33 @@ for units in range(1, UNITS+1):
|
|||||||
blob += px.astype(">u2").tobytes()
|
blob += px.astype(">u2").tobytes()
|
||||||
nspans += 1; npix += P
|
nspans += 1; npix += P
|
||||||
metas.append(dict(name=f"{P}", p=P, x0=0, off=off, len=len(blob)-off,
|
metas.append(dict(name=f"{P}", p=P, x0=0, off=off, len=len(blob)-off,
|
||||||
nspans=nspans, npix=npix, var=6))
|
nspans=nspans, npix=npix, cpx=npix, fpx=0, var=6))
|
||||||
|
|
||||||
|
# v7: same tiling, but the span is cut at a 2-pixel quantum instead of 24.
|
||||||
|
for P in V7CONFIGS:
|
||||||
|
units, fine = divmod(P, UNITPX)
|
||||||
|
assert fine % FINEPX == 0 and fine // FINEPX <= FINES, P
|
||||||
|
assert units <= UNITS, P
|
||||||
|
off = len(blob)
|
||||||
|
nspans = npix = 0
|
||||||
|
rows = []
|
||||||
|
for y in range(H):
|
||||||
|
x = 0
|
||||||
|
while x < W:
|
||||||
|
rows.append((y, x)); x += P
|
||||||
|
blob += struct.pack(">H", len(rows))
|
||||||
|
for y, x in rows:
|
||||||
|
assert x + P <= STRIDE // 2, (P, x) # the overrun must stay on the line
|
||||||
|
blob += struct.pack(">IH", GVRAM + (YOFF+y)*STRIDE + x*2,
|
||||||
|
(UNITS-units)*UNITSZ)
|
||||||
|
px = np.concatenate([idx[y, x:x+P], np.zeros(max(0, x+P-W), np.uint8)])
|
||||||
|
blob += px[:units*UNITPX].astype(">u2").tobytes()
|
||||||
|
blob += struct.pack(">H", (FINES - fine//FINEPX)*FINESZ)
|
||||||
|
blob += px[units*UNITPX:].astype(">u2").tobytes()
|
||||||
|
nspans += 1; npix += P
|
||||||
|
metas.append(dict(name=f"{P}", p=P, x0=0, off=off, len=len(blob)-off,
|
||||||
|
nspans=nspans, npix=npix,
|
||||||
|
cpx=nspans*units*UNITPX, fpx=nspans*fine, var=7))
|
||||||
|
|
||||||
open(OUT, "wb").write(blob)
|
open(OUT, "wb").write(blob)
|
||||||
with open(META, "w") as f:
|
with open(META, "w") as f:
|
||||||
@@ -108,7 +159,8 @@ with open(META, "w") as f:
|
|||||||
f.write(f" W={W}, H={H}, total={len(blob)},\n configs = {{\n")
|
f.write(f" W={W}, H={H}, total={len(blob)},\n configs = {{\n")
|
||||||
for m in metas:
|
for m in metas:
|
||||||
f.write(" {{var={var}, name=\"{name}\", p={p}, x0={x0}, off={off},"
|
f.write(" {{var={var}, name=\"{name}\", p={p}, x0={x0}, off={off},"
|
||||||
" len={len}, nspans={nspans}, npix={npix}}},\n".format(**m))
|
" len={len}, nspans={nspans}, npix={npix}, cpx={cpx},"
|
||||||
|
" fpx={fpx}}},\n".format(**m))
|
||||||
f.write(" },\n}\n")
|
f.write(" },\n}\n")
|
||||||
|
|
||||||
print(f"{SRC} {W}x{H} -> {OUT} {len(blob)} B, {len(metas)} configs")
|
print(f"{SRC} {W}x{H} -> {OUT} {len(blob)} B, {len(metas)} configs")
|
||||||
|
|||||||
+57
-11
@@ -108,7 +108,8 @@ local function launch(cfg)
|
|||||||
push(STREAM, blob, cfg.off+1, cfg.len)
|
push(STREAM, blob, cfg.off+1, cfg.len)
|
||||||
clear_picture()
|
clear_picture()
|
||||||
-- ~4 emulated seconds per config: 1/55.46 s granularity costs under 0.5%.
|
-- ~4 emulated seconds per config: 1/55.46 s granularity costs under 0.5%.
|
||||||
local est = cfg.nspans*(cfg.var == 6 and 50 or 60) + cfg.npix*10
|
local est = cfg.nspans*(cfg.var == 5 and 60 or (cfg.var == 6 and 50 or 70))
|
||||||
|
+ cfg.npix*10
|
||||||
cfg.iter = math.max(4, math.floor(4*CPUHZ/est))
|
cfg.iter = math.max(4, math.floor(4*CPUHZ/est))
|
||||||
SP:write_u32(FLAG, 0)
|
SP:write_u32(FLAG, 0)
|
||||||
SP:write_u32(VAR, cfg.var)
|
SP:write_u32(VAR, cfg.var)
|
||||||
@@ -143,6 +144,34 @@ local function fit(rs)
|
|||||||
return (sy*pp - py*sp)/det, (ss*py - sp*sy)/det
|
return (sy*pp - py*sp)/det, (ss*py - sp*sy)/det
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- v7 has two per-pixel costs -- the 24-pixel coarse chain and the 2-pixel fine
|
||||||
|
-- chain -- so its fit is cycles = A*spans + Bc*coarse_px + Bf*fine_px, solved
|
||||||
|
-- by plain Gaussian elimination on the 3x3 normal equations. prep_spans.py
|
||||||
|
-- picks span lengths so every fine remainder a real span can have (0,4,..,20)
|
||||||
|
-- appears, which is what makes the three terms separable.
|
||||||
|
local function fit3(rs)
|
||||||
|
local M3 = {{0,0,0,0},{0,0,0,0},{0,0,0,0}}
|
||||||
|
for _,r in ipairs(rs) do
|
||||||
|
local x = {r.cfg.nspans, r.cfg.cpx, r.cfg.fpx}
|
||||||
|
for i=1,3 do
|
||||||
|
for j=1,3 do M3[i][j] = M3[i][j] + x[i]*x[j] end
|
||||||
|
M3[i][4] = M3[i][4] + x[i]*r.cyc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for c=1,3 do
|
||||||
|
local piv = c
|
||||||
|
for r=c+1,3 do if math.abs(M3[r][c]) > math.abs(M3[piv][c]) then piv=r end end
|
||||||
|
M3[c], M3[piv] = M3[piv], M3[c]
|
||||||
|
for r=1,3 do
|
||||||
|
if r ~= c then
|
||||||
|
local f = M3[r][c]/M3[c][c]
|
||||||
|
for k=c,4 do M3[r][k] = M3[r][k] - f*M3[c][k] end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return M3[1][4]/M3[1][1], M3[2][4]/M3[2][2], M3[3][4]/M3[3][3]
|
||||||
|
end
|
||||||
|
|
||||||
local step, st, t0 = 0, "boot", nil
|
local step, st, t0 = 0, "boot", nil
|
||||||
|
|
||||||
SUB = emu.add_machine_frame_notifier(function()
|
SUB = emu.add_machine_frame_notifier(function()
|
||||||
@@ -159,7 +188,7 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
report(SPEC.configs[step], t - (t0 or t))
|
report(SPEC.configs[step], t - (t0 or t))
|
||||||
st = "snap"; return
|
st = "snap"; return
|
||||||
end
|
end
|
||||||
if t > 300 then P("TIMEOUT flag="..string.format("%08X",fl)); M:exit() end
|
if t > 900 then P("TIMEOUT flag="..string.format("%08X",fl)); M:exit() end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if st == "snap" then
|
if st == "snap" then
|
||||||
@@ -174,7 +203,7 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
end
|
end
|
||||||
if st == "finish" then
|
if st == "finish" then
|
||||||
P("---- measured (instruction cycles only; real GVRAM adds wait states) ----")
|
P("---- measured (instruction cycles only; real GVRAM adds wait states) ----")
|
||||||
for _,v in ipairs{5,6} do
|
for _,v in ipairs{5,6,7} do
|
||||||
local sub = {}
|
local sub = {}
|
||||||
for _,r in ipairs(results) do if r.cfg.var == v then sub[#sub+1] = r end end
|
for _,r in ipairs(results) do if r.cfg.var == v then sub[#sub+1] = r end end
|
||||||
-- v5's fit is over its BURSTING configs only (span length a multiple of
|
-- v5's fit is over its BURSTING configs only (span length a multiple of
|
||||||
@@ -183,13 +212,22 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
-- instead, which is where the remainder shows up as error.
|
-- instead, which is where the remainder shows up as error.
|
||||||
local fitset = {}
|
local fitset = {}
|
||||||
for _,r in ipairs(sub) do
|
for _,r in ipairs(sub) do
|
||||||
if v == 6 or r.cfg.p % 16 == 0 then fitset[#fitset+1] = r end
|
if v ~= 5 or r.cfg.p % 16 == 0 then fitset[#fitset+1] = r end
|
||||||
|
end
|
||||||
|
local A, Bp, Bf
|
||||||
|
if v == 7 then
|
||||||
|
A, Bp, Bf = fit3(fitset)
|
||||||
|
P(string.format("-- v7: cycles = %.1f per span + %.3f per COARSE pixel"
|
||||||
|
.." + %.3f per FINE pixel (fitted on %d of %d configs)",
|
||||||
|
A, Bp, Bf, #fitset, #sub))
|
||||||
|
else
|
||||||
|
A, Bp = fit(fitset)
|
||||||
|
Bf = Bp
|
||||||
|
P(string.format("-- v%d: cycles = %.1f per span + %.3f per pixel"
|
||||||
|
.." (fitted on %d of %d configs)", v, A, Bp, #fitset, #sub))
|
||||||
end
|
end
|
||||||
local A, Bp = fit(fitset)
|
|
||||||
P(string.format("-- v%d: cycles = %.1f per span + %.3f per pixel"
|
|
||||||
.." (fitted on %d of %d configs)", v, A, Bp, #fitset, #sub))
|
|
||||||
for _,r in ipairs(sub) do
|
for _,r in ipairs(sub) do
|
||||||
local model = A*r.cfg.nspans + Bp*r.cfg.npix
|
local model = A*r.cfg.nspans + Bp*r.cfg.cpx + Bf*r.cfg.fpx
|
||||||
P(string.format(" span %4s px %8.0f cyc %5.2f cyc/px %6.1f cyc/span"
|
P(string.format(" span %4s px %8.0f cyc %5.2f cyc/px %6.1f cyc/span"
|
||||||
.." vs fit %+6.1f%%", r.cfg.name, r.cyc,
|
.." vs fit %+6.1f%%", r.cfg.name, r.cyc,
|
||||||
r.cyc/r.cfg.npix, r.cyc/r.cfg.nspans, 100*(model/r.cyc-1)))
|
r.cyc/r.cfg.npix, r.cyc/r.cfg.nspans, 100*(model/r.cyc-1)))
|
||||||
@@ -199,9 +237,17 @@ SUB = emu.add_machine_frame_notifier(function()
|
|||||||
-- v6 pads each to a whole 24-pixel chain unit.
|
-- v6 pads each to a whole 24-pixel chain unit.
|
||||||
local line = " -> cycles per 4x4 block in a run of L blocks: "
|
local line = " -> cycles per 4x4 block in a run of L blocks: "
|
||||||
for _,L in ipairs{1,2,4,8,16,64} do
|
for _,L in ipairs{1,2,4,8,16,64} do
|
||||||
local px = 4*L
|
local px, cyc = 4*L, nil
|
||||||
if v == 6 then px = math.ceil(px/24)*24 end
|
if v == 6 then
|
||||||
line = line..string.format("L=%d %.0f ", L, 4*(A + px*Bp)/L)
|
px = math.ceil(px/24)*24
|
||||||
|
cyc = A + px*Bp
|
||||||
|
elseif v == 7 then
|
||||||
|
local c = math.floor(px/24)*24
|
||||||
|
cyc = A + c*Bp + (px-c)*Bf -- a multiple of 4 pads to nothing
|
||||||
|
else
|
||||||
|
cyc = A + px*Bp
|
||||||
|
end
|
||||||
|
line = line..string.format("L=%d %.0f ", L, 4*cyc/L)
|
||||||
end
|
end
|
||||||
P(line.."(V1 is 299.9)")
|
P(line.."(V1 is 299.9)")
|
||||||
if v == 5 then
|
if v == 5 then
|
||||||
|
|||||||
+9
-5
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Measure the cost of a row-linear literal span on the 68000 (FINDINGS 30).
|
# Measure the cost of a row-linear literal span on the 68000 (FINDINGS 30).
|
||||||
# ~25 s. Run from the repo root. Needs tmp/frame256.bin (check.sh makes it).
|
# ~45 s. Run from the repo root. Needs tmp/frame256.bin (check.sh makes it).
|
||||||
#
|
#
|
||||||
# NOT part of check.sh, for the same reason blit.s is not: the output is a wall
|
# NOT part of check.sh, for the same reason blit.s is not: the output is a wall
|
||||||
# timing, so gating on it would make the green light host-sensitive. What IS
|
# timing, so gating on it would make the green light host-sensitive. What IS
|
||||||
@@ -14,18 +14,22 @@ python3 tools/bench/prep_spans.py
|
|||||||
tools/vasm/vasmm68k_mot -Fbin -o tmp/blit.bin tools/bench/blit.s > /dev/null
|
tools/vasm/vasmm68k_mot -Fbin -o tmp/blit.bin tools/bench/blit.s > /dev/null
|
||||||
mkdir -p tmp/snap_span
|
mkdir -p tmp/snap_span
|
||||||
rm -f tmp/snap_span/x68000/*.png
|
rm -f tmp/snap_span/x68000/*.png
|
||||||
( cd tmp && SDL_VIDEODRIVER=dummy timeout -k 5 1800 mame x68000 -bios ipl10 \
|
( cd tmp && SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 1800 mame x68000 -bios ipl10 \
|
||||||
-ramsize 2M -video soft -window -sound none -nothrottle -plugins \
|
-ramsize 2M -video soft -window -sound none -nothrottle -plugins \
|
||||||
-autoboot_script ../tools/bench/span.lua \
|
-autoboot_script ../tools/bench/span.lua \
|
||||||
-snapshot_directory ./snap_span -snapview native -seconds_to_run 150 \
|
-snapshot_directory ./snap_span -snapview native -seconds_to_run 200 \
|
||||||
> span.log 2>&1 )
|
> span.log 2>&1 )
|
||||||
grep -a "^\[SPAN\]" tmp/span.log
|
grep -a "^\[SPAN\]" tmp/span.log
|
||||||
|
|
||||||
|
# One snapshot per config, and the expected count comes from the generated
|
||||||
|
# metadata rather than a literal: adding a config must not silently weaken the
|
||||||
|
# assertion that every one of them drew the picture.
|
||||||
|
want=$(grep -c '{var=' tmp/spans_meta.lua)
|
||||||
n=0
|
n=0
|
||||||
for f in tmp/snap_span/x68000/*.png; do
|
for f in tmp/snap_span/x68000/*.png; do
|
||||||
python3 tools/bench/verify_frame256.py "$f" > /dev/null || {
|
python3 tools/bench/verify_frame256.py "$f" > /dev/null || {
|
||||||
echo "FAIL: $f is not pixel-exact"; python3 tools/bench/verify_frame256.py "$f"; exit 1; }
|
echo "FAIL: $f is not pixel-exact"; python3 tools/bench/verify_frame256.py "$f"; exit 1; }
|
||||||
n=$((n+1))
|
n=$((n+1))
|
||||||
done
|
done
|
||||||
[ "$n" -eq 23 ] || { echo "FAIL: $n snapshots, expected 23"; exit 1; }
|
[ "$n" -eq "$want" ] || { echo "FAIL: $n snapshots, expected $want"; exit 1; }
|
||||||
echo "OK $n/23 span configs drew a pixel-exact frame"
|
echo "OK $n/$want span configs drew a pixel-exact frame"
|
||||||
|
|||||||
Reference in New Issue
Block a user