38static void fail(
const char* what,
unsigned long wanted,
unsigned long got)
41 printf(
" FAIL %s: want %lx got %lx\n", what, wanted, got);
44static void expectRegion(
const char* what, uint32_t addr, uint32_t wantFlags, uint32_t wantEnd)
46 uint32_t end = 0xffffffffu;
47 uint32_t flags = pico9918_debug_region(addr, &end);
49 if (flags != wantFlags) fail(what, wantFlags, flags);
50 if (end != wantEnd) fail(what, wantEnd, end);
53#if PICO9918_BUILD_LAYER_MASK
55static const uint8_t solidRow[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
57static void fillPattern(uint32_t at, uint32_t bytes, uint8_t seed)
59 for (uint32_t i = 0; i < bytes; ++i)
61 const uint8_t
byte = (uint8_t)(seed + i * 37u);
67static void sceneLockedGm2(
void)
69 fillPattern(0x0000, 0x1800, 0x5a);
70 fillPattern(0x2000, 0x1800, 0xc3);
71 fillPattern(0x3800, 0x0300, 0x11);
75 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_NAME_TABLE, 0x0e);
76 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_COLOR_TABLE, 0xff);
77 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_PATTERN_TABLE, 0x03);
78 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_FG_BG_COLOR, 0x01);
81static void unlock(
void)
83 tms9918->isUnlocked =
true;
84 tms9918->lockedMask = 0x3f;
88static void sceneBlanked(
void)
95static void sceneTile2Only(
void)
106static void sceneBmlOnly(
void)
120static void sceneSprites(
void)
123 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_SPRITE_ATTR_TABLE, 0x70);
124 pico9918_debug_reg_write(
PICO9918_INST TMS_REG_SPRITE_PATT_TABLE, 0x00);
125 pico9918_debug_write(
PICO9918_INST 0x0000, solidRow,
sizeof(solidRow));
127 for (
unsigned i = 0; i < 6; ++i)
129 const uint8_t attr[4] = {16, (uint8_t)(20 + i * 4), 0, 0x0f};
130 pico9918_debug_write(
PICO9918_INST 0x3800 + i * 4, attr,
sizeof(attr));
137static uint32_t suppressDigest(uint32_t mask,
void (*scene)(
void), uint16_t y, uint8_t* status)
139 uint32_t digest = 2166136261u;
147 if (status) *status = got;
168 expectRegion(
"pram-first", 0x5000,
170 expectRegion(
"pram-last", 0x5fff,
183 if (size <= 0x10000) fail(
"map-stops-at-64k", 0x10001, size);
186 expectRegion(
"past-end", size, 0, size);
187 expectRegion(
"far-past-end", 0xfffffff0u, 0, 0xfffffff0u);
192 uint32_t at = 0, regions = 0;
196 if (pico9918_debug_region(at, &end) == 0) fail(
"walk-hole", 1, at);
199 fail(
"walk-stuck", at + 1, end);
205 if (at != size) fail(
"walk-end", size, at);
206 if (regions != 6) fail(
"walk-regions", 6, regions);
210 if (pico9918_debug_region(0x5000, NULL) !=
212 fail(
"region-null-end", 1, 0);
217 static const uint32_t probes[] = {0x0000, 0x3fff, 0x4000, 0x5000, 0x6000,
218 0x6040, 0xb000, 0xb010, 0xffff};
219 for (
unsigned i = 0; i <
sizeof(probes) /
sizeof(probes[0]); ++i)
223 ((uint8_t*)&tms9918->vram)[probes[i]] = (uint8_t)(0x5a + i);
224 if (pico9918_debug_read(
PICO9918_INST probes[i], &got, 1) != 1) fail(
"probe-short", 1, 0);
225 if (got != (uint8_t)(0x5a + i)) fail(
"probe-value", (uint8_t)(0x5a + i), got);
232 tms9918->vram.map.wrksp[4] = 0xa5;
236 if (pico9918_debug_read(
PICO9918_INST 0x10004, &got, 1) != 1) fail(
"wrksp-short", 1, 0);
237 if (got != 0xa5) fail(
"wrksp-value", 0xa5, got);
245 memset(buf, 0,
sizeof(buf));
246 if (pico9918_debug_read(
PICO9918_INST 0x5ff0, buf,
sizeof(buf)) !=
sizeof(buf))
247 fail(
"read-across-window", (
unsigned long)
sizeof(buf), 0);
254 if (pico9918_debug_read(
PICO9918_INST 0, buf, 0) != 0) fail(
"read-zero-len", 0, 1);
255 if (pico9918_debug_read(
PICO9918_INST 0, NULL,
sizeof(buf)) != 0) fail(
"read-null", 0, 1);
256 if (pico9918_debug_read(
PICO9918_INST size, buf,
sizeof(buf)) != 0) fail(
"read-at-end", 0, 1);
257 if (pico9918_debug_read(
PICO9918_INST size + 1, buf,
sizeof(buf)) != 0)
258 fail(
"read-past-end", 0, 1);
261 const size_t got = pico9918_debug_read(
PICO9918_INST size - 4, buf,
sizeof(buf));
262 if (got != 4) fail(
"read-short-at-end", 4, (
unsigned long)got);
270 memset(buf, 0x77,
sizeof(buf));
271 if (pico9918_debug_write(
PICO9918_INST 0x1000, buf,
sizeof(buf)) !=
sizeof(buf))
272 fail(
"write-vram", (
unsigned long)
sizeof(buf), 0);
273 if (((uint8_t*)&tms9918->vram)[0x1000] != 0x77) fail(
"write-vram-first", 0x77, 0);
274 if (((uint8_t*)&tms9918->vram)[0x101f] != 0x77) fail(
"write-vram-last", 0x77, 0);
277 ((uint8_t*)&tms9918->vram)[0x6000] = 0x11;
278 if (pico9918_debug_write(
PICO9918_INST 0x5ff0, buf,
sizeof(buf)) != 0x10)
279 fail(
"write-into-regs", 0x10, 0);
280 if (((uint8_t*)&tms9918->vram)[0x6000] != 0x11) fail(
"write-regs-leaked", 0x11, 0x77);
283 if (pico9918_debug_write(
PICO9918_INST 0x6010, buf,
sizeof(buf)) != 0)
284 fail(
"write-in-regs", 0, 1);
287 ((uint8_t*)&tms9918->vram)[0xb000] = 0x22;
288 if (pico9918_debug_write(
PICO9918_INST 0xaff0, buf,
sizeof(buf)) != 0x10)
289 fail(
"write-into-status", 0x10, 0);
290 if (((uint8_t*)&tms9918->vram)[0xb000] != 0x22) fail(
"write-status-leaked", 0x22, 0x77);
291 if (pico9918_debug_write(
PICO9918_INST 0xb008, buf,
sizeof(buf)) != 0)
292 fail(
"write-in-status", 0, 1);
296 if (pico9918_debug_write(
PICO9918_INST 0x4ff0, buf,
sizeof(buf)) !=
sizeof(buf))
297 fail(
"write-across-pram", (
unsigned long)
sizeof(buf), 0);
300 if (pico9918_debug_write(
PICO9918_INST 0, buf, 0) != 0) fail(
"write-zero-len", 0, 1);
301 if (pico9918_debug_write(
PICO9918_INST 0, NULL,
sizeof(buf)) != 0) fail(
"write-null", 0, 1);
302 if (pico9918_debug_write(
PICO9918_INST size, buf,
sizeof(buf)) != 0) fail(
"write-at-end", 0, 1);
304 const size_t tail = pico9918_debug_write(
PICO9918_INST size - 4, buf,
sizeof(buf));
305 if (tail != 4) fail(
"write-short-at-end", 4, (
unsigned long)tail);
311 const uint8_t entry[2] = {0x0f, 0x0a};
313 tms9918->palDirty = 0;
314 if (pico9918_debug_write(
PICO9918_INST 0x5000, entry,
sizeof(entry)) !=
sizeof(entry))
315 fail(
"write-pram", 2, 0);
316 if (!tms9918->palDirty) fail(
"write-pram-not-dirty", 1, 0);
319 tms9918->palDirty = 0;
320 if (pico9918_debug_write(
PICO9918_INST 0x1000, entry,
sizeof(entry)) !=
sizeof(entry))
321 fail(
"write-vram-again", 2, 0);
322 if (tms9918->palDirty) fail(
"write-vram-dirtied-palette", 0, 1);
325 tms9918->palDirty = 0;
326 if (pico9918_debug_write(
PICO9918_INST 0x6000, entry,
sizeof(entry)) != 0)
327 fail(
"write-regs-again", 0, 1);
328 if (tms9918->palDirty) fail(
"write-regs-dirtied-palette", 0, 1);
334 TMS_REGISTER(tms9918, 6) = 0x66;
335 TMS_REGISTER(tms9918, 30) = 0x30;
336 if (tms9918->isUnlocked) fail(
"locked-precondition", 0, 1);
338 fail(
"reg-locked-30", 0x30, pico9918_debug_reg(
PICO9918_INST 30));
341 if (pico9918_debug_reg(
PICO9918_INST 64) != 0) fail(
"reg-out-of-range", 0, 1);
349 tms9918->vram.map.pram[7] = __builtin_bswap16(0x0abc);
351 fail(
"palette-host-order", 0x0abc, pico9918_debug_palette(
PICO9918_INST 7));
352 if (pico9918_debug_palette(
PICO9918_INST 64) != 0) fail(
"palette-out-of-range", 0, 1);
358 const uint8_t entry[2] = {0x0a, 0xcb};
360 if (pico9918_debug_write(
PICO9918_INST 0x5000 + 7 * 2, entry,
sizeof(entry)) != 2)
361 fail(
"palette-write", 2, 0);
363 fail(
"palette-round-trip", 0x0acb, pico9918_debug_palette(
PICO9918_INST 7));
369 tms9918->currentAddress = 0x1234;
377 tms9918->currentAddress = 0x1ffff;
378 if (pico9918_debug_vram_address(
PICO9918_INST_ONLY) > 0xffff) fail(
"vram-addr-wide", 0, 1);
386 tms9918->restart = 0;
400 tms9918->restart = 1;
404 tms9918->restart = 0;
413 TMS_REGISTER(tms9918, 6) = 0x66;
414 if (tms9918->isUnlocked) fail(
"write-locked-precondition", 0, 1);
415 if (!pico9918_debug_reg_write(
PICO9918_INST 30, 0x3c)) fail(
"write-reg-30", 1, 0);
417 fail(
"write-reg-30-value", 0x3c, pico9918_debug_reg(
PICO9918_INST 30));
419 fail(
"write-reg-30-hit-6", 0x66, pico9918_debug_reg(
PICO9918_INST 6));
422 TMS_REGISTER(tms9918, 0) = 0x0d;
423 if (pico9918_debug_reg_write(
PICO9918_INST 64, 0xff)) fail(
"write-reg-64", 0, 1);
424 if (pico9918_debug_reg(
PICO9918_INST 0) != 0x0d) fail(
"write-reg-64-wrapped", 0x0d, 0);
427 tms9918->palDirty = 0;
429 if (!tms9918->palDirty) fail(
"write-reg-not-dirty", 1, 0);
451 if (!tms9918->frameInt) fail(
"write-reg-int-not-raised", 1, 0);
453 if (tms9918->frameInt) fail(
"write-reg-int-not-dropped", 0, 1);
460 if (tms9918->isUnlocked) fail(
"write-r57-once-unlocked", 0, 1);
462 if (tms9918->isUnlocked) fail(
"write-r57-twice-unlocked", 0, 1);
463 if (tms9918->lockedMask != 0x07) fail(
"write-r57-moved-mask", 0x07, tms9918->lockedMask);
470 tms9918->restart = 0;
472 tms9918->configDirty =
false;
473 TMS_REGISTER(tms9918, 9) = 0x99;
484 if (tms9918->flash) fail(
"write-r63-flashed", 0, 1);
488 fail(
"write-r50-reset-file", 0x99, pico9918_debug_reg(
PICO9918_INST 9));
489 if (tms9918->configDirty) fail(
"write-r50-config-dirty", 0, 1);
494 fail(
"write-r30-zero-folded", 0,
498 TMS_STATUS(tms9918, 0x0f) = 0xee;
500 if (TMS_STATUS(tms9918, 0x0f) != 0xee) fail(
"write-r15-snapped", 0xee, TMS_STATUS(tms9918, 0x0f));
509 uint8_t*
const before = malloc(
sizeof(pico9918_t));
510 const uint8_t*
const live = (
const uint8_t*)tms9918;
511 const uint32_t regByte = 0x6000 + 20;
512 const uint32_t dirty = (uint32_t)offsetof(pico9918_t, palDirty);
515 if (!before)
return fail(
"snapshot-alloc", 1, 0), 1;
517 TMS_REGISTER(tms9918, 20) = 0x00;
518 tms9918->palDirty = 0;
519 memcpy(before, tms9918,
sizeof(pico9918_t));
521 if (!pico9918_debug_reg_write(
PICO9918_INST 20, 0x5e)) fail(
"snapshot-write", 1, 0);
523 for (uint32_t i = 0; i < (uint32_t)
sizeof(pico9918_t); ++i)
525 if (before[i] == live[i])
continue;
527 if (i != regByte && i != dirty) fail(
"write-reg-touched-offset", regByte, i);
529 if (moved != 2) fail(
"write-reg-moved-count", 2, moved);
534#if PICO9918_BUILD_LAYER_MASK
546 {
"tile1", PICO9918_SUPPRESS_TILE1, sceneLockedGm2},
547 {
"gm2-colour", PICO9918_SUPPRESS_GM2_COLOUR, sceneLockedGm2},
548 {
"gm2-pattern", PICO9918_SUPPRESS_GM2_PATTERN, sceneLockedGm2},
549 {
"blanking", PICO9918_SUPPRESS_BLANKING, sceneBlanked },
550 {
"tile2", PICO9918_SUPPRESS_TILE2, sceneTile2Only},
551 {
"bitmap", PICO9918_SUPPRESS_BITMAP, sceneBmlOnly },
552 {
"sprites", PICO9918_SUPPRESS_SPRITES, sceneSprites },
555 uint8_t spriteStatus[2];
557 for (
unsigned i = 0; i <
sizeof(cases) /
sizeof(cases[0]); ++i)
559 const uint32_t plain = suppressDigest(0, cases[i].scene, 20, NULL);
561 if (suppressDigest(0, cases[i].scene, 20, NULL) != plain)
562 fail(
"suppress-scene-unstable", plain, suppressDigest(0, cases[i].scene, 20, NULL));
563 if (suppressDigest(cases[i].bit, cases[i].scene, 20, NULL) == plain)
564 fail(cases[i].name, 0, plain);
568 gm2[0] = suppressDigest(0, sceneLockedGm2, 20, NULL);
569 gm2[1] = suppressDigest(PICO9918_SUPPRESS_GM2_COLOUR, sceneLockedGm2, 20, NULL);
570 gm2[2] = suppressDigest(PICO9918_SUPPRESS_GM2_PATTERN, sceneLockedGm2, 20, NULL);
571 if (gm2[1] == gm2[2]) fail(
"suppress-gm2-transposed", gm2[1], gm2[2]);
574 if (suppressDigest(PICO9918_SUPPRESS_BLANKING, sceneBlanked, 20, NULL) != gm2[0])
575 fail(
"suppress-blanking-differs", gm2[0],
576 suppressDigest(PICO9918_SUPPRESS_BLANKING, sceneBlanked, 20, NULL));
579 suppressDigest(0, sceneSprites, 20, &spriteStatus[0]);
580 suppressDigest(PICO9918_SUPPRESS_SPRITES, sceneSprites, 20, &spriteStatus[1]);
585 if (spriteStatus[0] != spriteStatus[1])
586 fail(
"suppress-sprites-moved-status", spriteStatus[0], spriteStatus[1]);
590 fail(
"suppress-unknown-bit", 0x80000000u, pico9918_debug_suppress(
PICO9918_INST_ONLY));
604 for (uint8_t sr = 0; sr < 16; ++sr)
606 if (!pico9918_debug_status_write(
PICO9918_INST sr, (uint8_t)(0xa0 + sr)))
607 fail(
"status-write-refused", sr, 0);
609 for (uint8_t sr = 0; sr < 16; ++sr)
617 if (pico9918_debug_status_write(
PICO9918_INST 16, 0x11)) fail(
"status-write-16", 0, 1);
625 tms9918->frameStatus = 0x1f;
638 if (!tms9918->frameInt) fail(
"status-write-int-not-raised", 1, 0);
640 if (tms9918->frameInt) fail(
"status-write-int-not-dropped", 0, 1);
644 if (!tms9918->frameInt) fail(
"status-write-hf-not-raised", 1, 0);
646 if (tms9918->frameInt) fail(
"status-write-hf-not-dropped", 0, 1);
651 uint8_t*
const before = malloc(
sizeof(pico9918_t));
652 const uint8_t*
const live = (
const uint8_t*)tms9918;
656 if (!before)
return fail(
"status-snapshot-alloc", 1, 0), 1;
659 memcpy(before, tms9918,
sizeof(pico9918_t));
663 for (uint32_t i = 0; i < (uint32_t)
sizeof(pico9918_t); ++i)
665 if (before[i] == live[i])
continue;
667 if (i != statusByte) fail(
"status-write-touched-offset", statusByte, i);
669 if (moved != 1) fail(
"status-write-moved-count", 1, moved);
675 printf(
"%s: debugger surface, %d failure(s)\n", failures ?
"FAIL" :
"PASS", failures);
676 return failures ? 1 : 0;
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
see the header.
uint32_t pico9918_gpu_mem_size(void)
see the header.
uint8_t pico9918_gpu_mem_value(pico9918_t *tms9918, uint32_t addr)
see the header.
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
uint8_t pico9918_status_value(pico9918_t *tms9918, pico9918_status_register_t reg)
return a status register value without the side effects of reading it
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
uint8_t pico9918_read_status(pico9918_t *tms9918)
read from the status register
uint8_t pico9918_reg_value(pico9918_t *tms9918, pico9918_register_t reg)
return a register value - see the header for the locked-device aliasing
#define PICO9918_R49_TILE2_ENABLE
register 49 bits: tile layer 2, row count, and the enhanced colour modes
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
#define TMS_R1_MODE_TEXT
40-column text
#define PICO9918_R56_GPU_RUN
register 56 bit: the GPU trigger
#define TMS_R0_MODE_TEXT_80
80-column text, with R1's text mode.
#define PICO9918_INST_ONLY
pass the instance as the only argument
#define PICO9918_R50_TILE1_OFF
stop drawing tile layer 1
#define PICO9918_R50_RESET
register 50 bits: GPU triggers and the remaining layer controls
#define TMS_R1_DISP_ACTIVE
render the active display
#define PICO9918_SR0_INT
status register 0 bits.
pico9918_register_t
the eight TMS9918 registers, by number and by what each one holds
@ PICO9918_REG_BML_BASE
bitmap layer base address, in 64-byte units
@ PICO9918_REG_NAME_TABLE2
tile layer 2 name table base
@ PICO9918_REG_ENHANCED1
tile layer 2, 30-row mode, ECM levels, real Y
@ PICO9918_REG_GPU_PC_MSB
GPU program counter, high byte.
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
@ PICO9918_REG_ENHANCED2
GPU triggers, per-position attributes, layer priority.
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
@ PICO9918_REG_BML_WIDTH
bitmap layer width in pixels
@ PICO9918_REG_MAX_SCAN_SPRITES
sprites drawn per scanline before the limit bites
@ PICO9918_REG_GPU_PC_LSB
GPU program counter, low byte - writing it also starts the GPU.
@ PICO9918_REG_BML_TOP_ROW
bitmap layer top row
@ PICO9918_REG_BML_CONTROL
bitmap layer enable, priority, transparency, fat pixels
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
@ PICO9918_REG_FLASH_CONTROL
PICO9918 only: flash operation control.
@ PICO9918_REG_BML_HEIGHT
bitmap layer height in rows
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
@ PICO9918_SR_SECONDS_LSB
second counter, low byte
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
#define TMS_R0_INT_SCANLINE
assert /INT when the raster reaches the line in R19.
#define PICO9918_SR1_HF
status register 1 bits.
#define PICO9918_R31_BML_ENABLE
register 31 bits: the bitmap layer
#define TMS_R1_INT_ENABLE
assert /INT at end of frame
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
#define PICO9918_INST
pass the instance ahead of other arguments
pico9918-core - debugger access
#define PICO9918_DEBUG_STATUS
the status file, which the library owns
#define PICO9918_DEBUG_WRITABLE
pico9918_debug_write stores here
#define PICO9918_DEBUG_REGISTERS
the register file - use pico9918_debug_reg_write
#define PICO9918_DEBUG_READABLE
pico9918_debug_read returns real bytes here
#define PICO9918_DEBUG_PALETTE
PRAM - a write here republishes the palette.
pico9918-core - the private instance layout
PICO9918_INLINE_HOT uint32_t pico9918_cpu_vram_addr_impl(pico9918_t *tms9918, uint32_t addr)
where a CPU-side VRAM access lands