Move the loader onto the 68000, and find 5,920 bytes nobody counted
src/player/load.i expands both codebooks to word-per-pixel form and packs the palette to GGGGGRRRRRBBBBBI out of the RAW container header, byte-exact against tools/bench/dlxload.py on both CPU cores. The palette half is gated on words read back out of the palette registers at $E82000, so "the words reached the hardware" is part of what passes. ROADMAP P1 is done; P2's encoder half (a reserved black entry, 23.4) is not, and is a re-encode rather than an edit. A scene change costs 18.96 ms of 68000 time, 22.8% of one 12 fps frame; boot costs 24.70 ms. The scratch tables describe the CRTC, not the scene, so pal_tables is a separate entry point built once at boot -- 5.29 ms off every scene change. The one that moves something: the scene header is 5,920 B that no rate table in this tree included, because it belongs to no frame record. In FINDINGS 51.3's currency it is divided by the surplus pipe - wire, so it is hypersensitive: 138 ms of extra refill climb at 488 KB/s and 1.099 s at 451.4 KB/s, for the same bytes. tools/analysis/22_scene_load.py prices it across explicit rates. Recorded as open: the two CPU cores agree to <3% on every stage but the table build, where they differ by 16.4%. px68k's C68K charges a flat 50 clocks for MULU/MULS (c68kmacro.h:1869) where the 68000 charges 38+2n, which explains 4,608 of the 8,703 clock gap. 4,095 clocks are unexplained. Nothing else in src/player/ multiplies, so no figure in FINDINGS 24-52 is affected. decode.s and stream.s are untouched; decode.bin is still 1,296 B at the same MD5. check.sh gains a stage that gates byte-exactness on both cores and deliberately does not gate the cycle counts -- MAME's clock is 1/55.46 s and a wall timing would make the green light host-sensitive. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+124
-3
@@ -55,6 +55,12 @@ void p6logd(const char *fmt, ...) { (void)fmt; }
|
||||
#define GV_HI 0xC80000u
|
||||
|
||||
#define FLAG 0x18000u
|
||||
#define LFLAG 0x18040u /* src/player/load.i's control block */
|
||||
#define LHDR 0x18044u
|
||||
#define LDARK 0x18048u
|
||||
#define LMODE 0x18054u
|
||||
#define LITER 0x18058u
|
||||
#define GPAL 0xE82000u
|
||||
#define ITER 0x18008u
|
||||
#define NFR 0x1800Cu
|
||||
#define FPTR 0x18010u
|
||||
@@ -106,9 +112,16 @@ static void wr8(unsigned int a, unsigned char d)
|
||||
* momentarily reads back as $FF again. Without in_exec that transient
|
||||
* recorded a run's stop cycle before the run had started, and every frame
|
||||
* after the first came out as the whole slice. */
|
||||
/* Which flag word the run watches. decode.s and stream.s use FLAG; the
|
||||
* load-time transforms of src/player/load.i use their own, so that a player
|
||||
* could eventually contain both without one clearing the other's state. The
|
||||
* VALUES mean the same thing in both (1 running, $FF done, $EE failed), which
|
||||
* is why one hook serves both. */
|
||||
static unsigned int flag_adr = FLAG;
|
||||
|
||||
static void note_flag(void)
|
||||
{
|
||||
unsigned int v = rd32(FLAG);
|
||||
unsigned int v = rd32(flag_adr);
|
||||
long long now = slice - C68K.ICount;
|
||||
if (!in_exec) return;
|
||||
if (v == 1 && cyc_start < 0) cyc_start = now;
|
||||
@@ -124,7 +137,7 @@ static void wr16(unsigned int a, unsigned short d)
|
||||
a &= ADRMASK;
|
||||
if (a >= GV_LO && a < GV_HI) { buf[a] = (unsigned char)d; buf[a+1] = 0; return; }
|
||||
buf[a] = (unsigned char)d; buf[a+1] = (unsigned char)(d >> 8);
|
||||
if (a >= FLAG && a < FLAG + 4) note_flag();
|
||||
if (a >= flag_adr && a < flag_adr + 4) note_flag();
|
||||
}
|
||||
|
||||
static void wr32(unsigned int a, unsigned int d){ wr16(a, (unsigned short)(d >> 16)); wr16(a+2, (unsigned short)d); }
|
||||
@@ -185,12 +198,104 @@ static long long run(unsigned int off, unsigned int nfr, unsigned int iter)
|
||||
return cyc_stop - cyc_start;
|
||||
}
|
||||
|
||||
/* ---- the load-time transforms (ROADMAP P1+P2, FINDINGS 53) --------------
|
||||
* The same question this harness asks of the decoder, asked of the loader: does
|
||||
* a SECOND 68000 core, with its own cycle table and its own memory model,
|
||||
* produce the same bytes and agree about what they cost? It also counts BUS
|
||||
* cycles, which MAME cannot report -- and the bus is the resource this project
|
||||
* established is the binding one (FINDINGS 38).
|
||||
*/
|
||||
static int run_load(const char *fcode, const char *fraw, const char *dump,
|
||||
unsigned int mode, unsigned int iter,
|
||||
unsigned int cb1_len, unsigned int cb4_len)
|
||||
{
|
||||
size_t nc, nr;
|
||||
unsigned char *code = slurp(fcode, &nc), *raw = slurp(fraw, &nr);
|
||||
push(STREAM, raw, nr); /* the RAW container header */
|
||||
push(CODE, code, nc);
|
||||
/* Poison every destination, so that a transform which writes NOTHING
|
||||
* cannot pass by leaving the harness's own zeros in place. */
|
||||
for (unsigned int a = CB1; a < CB1 + cb1_len; a += 2) wr16(a, 0xDEAD);
|
||||
for (unsigned int a = CB4; a < CB4 + cb4_len; a += 2) wr16(a, 0xDEAD);
|
||||
for (unsigned int c = 0; c < 256; c++) wr16(GPAL + c*2, 0xDEAD);
|
||||
wr32(LDARK, 0xFFFFFFFFu);
|
||||
/* The three scratch tables are poisoned only before a run that claims to
|
||||
* build them. A run that only PACKS the palette is entitled to find them
|
||||
* already built -- that is the point of pricing it separately -- so when
|
||||
* this process is asked for one, it does the boot pass first, untimed,
|
||||
* exactly as a player would have done at boot. Without that the pack runs
|
||||
* on zeros: every entry then takes the same branch and the darkest entry
|
||||
* comes out 0, which is a measurement of nothing. */
|
||||
if (mode & 4)
|
||||
for (unsigned int a = 0x19000; a < 0x19340; a += 2) wr16(a, 0xDEAD);
|
||||
|
||||
flag_adr = LFLAG;
|
||||
if ((mode & 2) && !(mode & 4)) {
|
||||
cyc_start = cyc_stop = -1; desync = 0;
|
||||
wr32(LFLAG, 0); wr32(LHDR, STREAM); wr32(LMODE, 4); wr32(LITER, 1);
|
||||
C68k_Reset(&C68K);
|
||||
C68k_Set_Reg(&C68K, C68K_SR, 0x2700);
|
||||
C68k_Set_Reg(&C68K, C68K_A7, STACK);
|
||||
C68k_Set_Reg(&C68K, C68K_PC, CODE);
|
||||
slice = 2000000000LL; in_exec = 1;
|
||||
C68k_Exec(&C68K, (INT32)slice);
|
||||
in_exec = 0;
|
||||
if (cyc_stop < 0) { fprintf(stderr, "TIMEOUT in the table pre-pass\n"); return 4; }
|
||||
}
|
||||
cyc_start = cyc_stop = -1; desync = 0; bus_r = bus_w = 0;
|
||||
wr32(LFLAG, 0); wr32(LHDR, STREAM); wr32(LMODE, mode); wr32(LITER, iter);
|
||||
C68k_Reset(&C68K);
|
||||
C68k_Set_Reg(&C68K, C68K_SR, 0x2700);
|
||||
C68k_Set_Reg(&C68K, C68K_A7, STACK);
|
||||
C68k_Set_Reg(&C68K, C68K_PC, CODE);
|
||||
slice = 2000000000LL;
|
||||
in_exec = 1;
|
||||
C68k_Exec(&C68K, (INT32)slice);
|
||||
in_exec = 0;
|
||||
if (cyc_stop < 0) { fprintf(stderr, "TIMEOUT -- loader never set LFLAG\n"); return 4; }
|
||||
if (desync) { fprintf(stderr, "BAD HEADER -- load.i found no 'DLX3' magic\n"); return 5; }
|
||||
|
||||
long long cyc = (cyc_stop - cyc_start) / (iter ? iter : 1);
|
||||
fprintf(stderr, "[C68K] load mode %u: %lld cyc/pass (%.2f ms at 10MHz, "
|
||||
"%.1f%% of a 12fps frame), dark=%u\n", mode, cyc, cyc / 10000.0,
|
||||
100.0 * cyc / (10000000.0 / 12), rd32(LDARK));
|
||||
/* A 68000 bus cycle is 4 clocks. Prefetch is not counted (C68K reads
|
||||
* opcodes straight through the fetch pointer), so this is a LOWER bound on
|
||||
* occupancy and the headroom it implies is an UPPER bound -- same caveat as
|
||||
* the decoder's figure above. */
|
||||
{
|
||||
double slots = (double)cyc / 4.0;
|
||||
double used = (double)(bus_r + bus_w) / (iter ? iter : 1);
|
||||
fprintf(stderr, "[C68K] data bus: %.0f reads + %.0f writes = %.0f of "
|
||||
"%.0f cycles = %.1f%% occupied (prefetch NOT counted)\n",
|
||||
(double)bus_r / iter, (double)bus_w / iter, used, slots,
|
||||
100.0 * used / slots);
|
||||
}
|
||||
if (dump) {
|
||||
FILE *g = fopen(dump, "wb");
|
||||
if (!g) { perror(dump); return 2; }
|
||||
for (unsigned int a = CB1; a < CB1 + cb1_len; a++) { unsigned char b = rd8(a); fwrite(&b,1,1,g); }
|
||||
for (unsigned int a = CB4; a < CB4 + cb4_len; a++) { unsigned char b = rd8(a); fwrite(&b,1,1,g); }
|
||||
for (unsigned int c = 0; c < 256; c++) {
|
||||
unsigned short w = rd16(GPAL + c*2);
|
||||
unsigned char b[2] = { (unsigned char)(w >> 8), (unsigned char)w };
|
||||
fwrite(b, 1, 2, g);
|
||||
}
|
||||
fclose(g);
|
||||
fprintf(stderr, "[C68K] load output dumped to %s (%u B)\n",
|
||||
dump, cb1_len + cb4_len + 512);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *fcode = "tmp/decode.bin", *fdata = "tmp/decode_data.bin", *dump = NULL;
|
||||
unsigned int cb1_len=0, cb4_len=0, pal_len=0, stream_len=0, nframes=0, H=192, W=256, fps=12;
|
||||
unsigned int dark = 255;
|
||||
unsigned int anch[32]; int nanch = 0;
|
||||
const char *fraw = NULL, *loaddump = NULL;
|
||||
unsigned int loadmode = 7, loaditer = 1;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--code")) fcode = argv[++i];
|
||||
else if (!strcmp(argv[i], "--data")) fdata = argv[++i];
|
||||
@@ -204,10 +309,15 @@ int main(int argc, char **argv)
|
||||
else if (!strcmp(argv[i], "--H")) H = (unsigned)atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--fps")) fps = (unsigned)atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--dark")) dark = (unsigned)atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--loadraw")) fraw = argv[++i];
|
||||
else if (!strcmp(argv[i], "--loaddump")) loaddump = argv[++i];
|
||||
else if (!strcmp(argv[i], "--loadmode")) loadmode = (unsigned)atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--loaditer")) loaditer = (unsigned)atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "--anchor")) { if (nanch < 32) anch[nanch++] = (unsigned)strtoul(argv[++i], NULL, 10); }
|
||||
else { fprintf(stderr, "unknown arg %s\n", argv[i]); return 2; }
|
||||
}
|
||||
if (!nframes || !stream_len) { fprintf(stderr, "need --nframes and --stream (from decode_meta.lua)\n"); return 2; }
|
||||
if (!fraw && (!nframes || !stream_len)) {
|
||||
fprintf(stderr, "need --nframes and --stream (from decode_meta.lua)\n"); return 2; }
|
||||
|
||||
/* MAP_32BIT: C68K keeps its fetch base in a UINT32, so the arena must live
|
||||
* below 4 GB or every opcode fetch reads a truncated pointer. */
|
||||
@@ -216,6 +326,17 @@ int main(int argc, char **argv)
|
||||
if (buf == MAP_FAILED) { perror("mmap MAP_32BIT"); return 2; }
|
||||
fprintf(stderr, "[C68K] arena at %p\n", (void *)buf);
|
||||
|
||||
if (fraw) {
|
||||
C68k_Init(&C68K);
|
||||
C68k_Set_ReadB (&C68K, rd8);
|
||||
C68k_Set_ReadW (&C68K, rd16);
|
||||
C68k_Set_WriteB(&C68K, wr8);
|
||||
C68k_Set_WriteW(&C68K, wr16);
|
||||
C68k_Set_Fetch (&C68K, 0x000000, 0xFFFFFF, (UINT32)(unsigned long)buf);
|
||||
return run_load(fcode, fraw, loaddump, loadmode, loaditer,
|
||||
cb1_len ? cb1_len : 8192, cb4_len ? cb4_len : 2048);
|
||||
}
|
||||
|
||||
size_t nc, nd;
|
||||
unsigned char *code = slurp(fcode, &nc), *data = slurp(fdata, &nd);
|
||||
size_t need = (size_t)cb1_len + cb4_len + pal_len + stream_len;
|
||||
|
||||
Reference in New Issue
Block a user