mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-31 12:05:05 +02:00
udpstream: alternating-frame flash (30.7 fps sustained) + DRAM store benchmark + ROM cross-check
cmd 8 alternates 0xFFFF/0x0000 per frame so the rate is visible: 300 frames in 9.775 s on the unit, 32.6 ms each, 30.69 fps sustained. cmd 9 is the same byte-store loop aimed at a DRAM buffer, to measure the CPU's store rate without the VRAM arbiter and size a frame-expansion stage. vtcmd.py --dram selects it. Report 74.3 records the independent Ghidra pass over the ROM: the firmware sets CONVSP/CONVDP=0x14 (pitch 0x800 bits = 256 B), PSIZE=1, PMASK=0 and OFFSET=0x04000000 for XY addressing -- the same values vram.asm uses -- and fills with FILL_L chunked at 0x7FC0 pixels because DYDX is 15 bits per axis. There is no VBLT and no wait-state/arbitration register in the ROM, so the 12 us/byte CPU store cost is inherent to this board, not a setting.
This commit is contained in:
1 parent
d99a9cdb6c
commit
fb095eb778
4 files changed
+82
-5
No files matched your search
@@ -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.
|
||||
Binary file not shown.
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user