diff --git a/harness/mame/NETWORK_REPORT.md b/harness/mame/NETWORK_REPORT.md index 4115af1..3dda0e9 100644 --- a/harness/mame/NETWORK_REPORT.md +++ b/harness/mame/NETWORK_REPORT.md @@ -4983,3 +4983,41 @@ So a full-screen write costs ~33 ms, and the frame budget for video becomes: `cmd 8` (`vtcmd.py vram --fill`) is the benchmark; `cmd 7` is the byte-store control. Both report what the payload counted, not what it was asked to do. + +### 74.3 The ROM does the same thing (Ghidra cross-check) + +A static pass over the ROM found the firmware using exactly these instructions, +with values that match what `vram.asm` sets: + +| ROM site | what it does | matches | +|---|---|---| +| `rom:07e40a72` | `MOVI 0xc0000000,A1` then copies 11 words into the CRTC block, clears CONTROL bits 10-14 (raster op = replace), then copies 5 words into `0xC0000130..0x180` = CONVSP, CONVDP, PSIZE, PMASK | our CONTROL/PSIZE/PMASK setup | +| `rom:07e40b7c` (profile table) | CONVSP=`0x14`, CONVDP=`0x14`, PSIZE=`0x1`, PMASK=`0x0`; the 34010 decodes `0x14` as `1 << (~0x14 & 0x1f)` = `0x800` bits | our `DPTCH = 0x800` (256-byte stride) | +| `rom:07ee9db6`, `rom:07f6628a` | `MOVI 0x4000000,B4` — B4 is **OFFSET**, the XY-mode framebuffer base | VRAM base `0x04000000` | +| `rom:07f68996`, `rom:07f689c0` | `FILL_L`, chunked: one call for `<= 0x7FC0` pixels, then `MOVI 0x17fc0,B7` for the rest | our single `FILL L` (1280x1024 fits one call) | +| `rom:07f688d8`, `rom:07f6890a` | rectangle copy = `PIXBLT_L_L`, same `0x7FC0` chunking | the planned frame blit | +| `rom:07f66350`, `rom:07f66372` | glyph/text blit = `PIXBLT_B_XY` (packed 1-bpp source, raster op `0x0B` then `0x00`) | the planned expand path | + +Two negative results worth keeping: there is **no `VBLT`** anywhere in the ROM +(so "video" is not a DMA engine), and there is **no wait-state or CPU/VRAM +arbitration register** — the 34010's I/O file is only 32 words and the firmware +touches none of them for this. The 12 µs/byte is therefore inherent to CPU pixel +stores on this board, not a misconfiguration we could tune. + +The chunking matters for our own code: `DYDX` is 15 bits per axis, so a fill or +blit larger than `0x7FC0` pixels in a row must be split, exactly as the firmware +does. + +### 74.4 A visible check: 300 alternating frames + +Filling white alone proves nothing on a display. `cmd 8` now alternates +`0xFFFF`/`0x0000` per frame, and 300 frames were run on the unit: + +``` +filled 300 frame(s) x 1024 rows (FILL L): 24576000 stores in 9.775 s + 32.6 ms per frame -> 30.69 fps ceiling +``` + +9.8 s of full-screen black/white flash at ~31 fps — the operator's own +description was "damn that's fast". It also shows the number is sustained, not a +one-frame burst. diff --git a/harness/udpstream/build/lance_cmd.coff b/harness/udpstream/build/lance_cmd.coff index 36d2b0a..c88d29f 100644 Binary files a/harness/udpstream/build/lance_cmd.coff and b/harness/udpstream/build/lance_cmd.coff differ diff --git a/harness/udpstream/lance_cmd.c b/harness/udpstream/lance_cmd.c index 5111e74..1fd5201 100644 --- a/harness/udpstream/lance_cmd.c +++ b/harness/udpstream/lance_cmd.c @@ -505,16 +505,49 @@ int main() } wb(TXBUF + 19 * 8, (wlen >> 8) & 0xFF); wb(TXBUF + 20 * 8, wlen & 0xFF); + } else if (cmd == 9) { + /* The same byte-store loop as cmd 7 but into DRAM, so the + * CPU's store rate is measured without the VRAM arbiter. + * This is the number that sizes a frame-expansion stage. */ + unsigned int nd, fd, yd, xd, od, rd, st; + nd = rlen; + if (nd > 100) { + nd = 100; + } + rd = addr; + if (rd == 0 || rd > 1024) { + rd = 1024; + } + st = 0; + for (fd = 0; fd < nd; fd++) { + for (yd = 0; yd < rd; yd++) { + od = 0x01A10000 + yd * 2048; + for (xd = 0; xd < 160; xd++) { + *(unsigned char *)(od + xd * 8) = + (unsigned char)(yd + xd + fd); + st = st + 1; + } + } + } + wb(TXBUF + 19 * 8, (nd >> 8) & 0xFF); + wb(TXBUF + 20 * 8, nd & 0xFF); + wb(TXBUF + 21 * 8, (rd >> 8) & 0xFF); + wb(TXBUF + 22 * 8, rd & 0xFF); + wb(TXBUF + 23 * 8, (st >> 24) & 0xFF); + wb(TXBUF + 24 * 8, (st >> 16) & 0xFF); + wb(TXBUF + 25 * 8, (st >> 8) & 0xFF); + wb(TXBUF + 26 * 8, st & 0xFF); } else if (cmd == 8) { /* FILL L benchmark: the same full frame, done by the GSP's * array engine instead of byte stores. */ unsigned int nf2, f2; nf2 = rlen; - if (nf2 > 100) { - nf2 = 100; + if (nf2 > 400) { /* 400 x 32.7 ms = 13 s max */ + nf2 = 400; } for (f2 = 0; f2 < nf2; f2++) { - vram_pattern = 0xFFFF; + /* alternate black/white so the rate is visible */ + vram_pattern = (f2 & 1) ? 0x0000 : 0xFFFF; vram_fill_(); } wb(TXBUF + 19 * 8, (nf2 >> 8) & 0xFF); diff --git a/harness/udpstream/vtcmd.py b/harness/udpstream/vtcmd.py index aca334f..3dcdfcf 100644 --- a/harness/udpstream/vtcmd.py +++ b/harness/udpstream/vtcmd.py @@ -166,6 +166,9 @@ def main(): help="vram: how many frames to fill") ap.add_argument("--rows", type=int, default=1024, help="vram: how many of the 1024 rows to write") + ap.add_argument("--dram", action="store_true", + help="vram: same byte-store loop but into DRAM (no VRAM " + "arbiter) -- sizes the frame-expansion stage") ap.add_argument("--fill", action="store_true", help="vram: use the hardware FILL L instruction instead of " "byte stores") @@ -193,7 +196,7 @@ def main(): if a.cmd == "vram": n = a.frames - opcode = 8 if a.fill else 7 + opcode = 8 if a.fill else (9 if a.dram else 7) t0 = time.time() status, data = request(sock, a.iface, opcode, a.rows, n, timeout=max(120.0, a.timeout)) @@ -206,12 +209,15 @@ def main(): nf, x = struct.unpack(">HH", data[:4]) rows = 1024 stores = nf * 1024 * 80 # destination words, not bytes + elif a.dram: + nf, rows, stores = struct.unpack(">HHI", data[:8]) else: nf, rows, stores = struct.unpack(">HHI", data[:8]) per = dt / nf if nf else 0 rate = stores / dt if dt else 0 print("filled %d frame(s) x %d rows (%s): %d stores in %.3f s" - % (nf, rows, "FILL L" if a.fill else "byte stores", + % (nf, rows, "FILL L" if a.fill else + ("DRAM stores" if a.dram else "byte stores"), stores, dt)) print(" %.1f ms per frame -> %.2f fps ceiling" % (per * 1000.0, 1.0 / per if per else 0))