43#ifndef GOLDEN_DATA_DIR
44#define GOLDEN_DATA_DIR "data"
47#define GOLDEN_MAGIC "TMSG"
48#define GOLDEN_VERSION 3
49#define GOLDEN_NAME_LEN 32
50#define GOLDEN_BYTES_PER_LINE TMS9918_PIXELS_X
51#define GOLDEN_PAL_ENTRIES 64
52#define GOLDEN_PAL_BYTES (GOLDEN_PAL_ENTRIES * 2)
55#define GOLDEN_DIGESTS_PER_LINE 2
56#define GOLDEN_DIGEST_BYTES (GOLDEN_DIGESTS_PER_LINE * 8)
67uint32_t goldenClockNow = 0;
70uint8_t goldenPublishedStatus = 0;
71uint32_t goldenPublishCount = 0;
76static uint32_t lcgState;
78static void lcgSeed(uint32_t seed)
83static uint8_t lcgByte(
void)
85 lcgState = lcgState * 1664525u + 1013904223u;
86 return (uint8_t)(lcgState >> 24);
93static void regWrite(uint8_t reg, uint8_t value)
99static void vramSetWriteAddr(uint16_t addr)
107static void vramSetReadAddr(uint16_t addr)
113static void vramFillLcg(uint16_t addr,
int count)
115 vramSetWriteAddr(addr);
119static void vramFillByte(uint16_t addr, uint8_t value,
int count)
121 vramSetWriteAddr(addr);
125static void vramWriteBytes(uint16_t addr,
const uint8_t* bytes,
int count)
127 vramSetWriteAddr(addr);
132static void unlockF18a(
void)
141static void paletteWriteLcg(
int count)
155static void sceneBegin(
void)
185static void dumpPalette(uint8_t out[GOLDEN_PAL_BYTES])
201 for (
int line = 0; line < 2; ++line)
205 uint8_t* dst = out + line * 64;
206 for (
int i = 0; i < 64; ++i)
208 const uint8_t* p = probe + i * 4;
209 dst[i] = (uint8_t)((p[0] << 6) | (p[1] << 4) | (p[2] << 2) | p[3]);
251#define FNV64_OFFSET 0xcbf29ce484222325ull
252#define FNV64_PRIME 0x00000100000001b3ull
254static uint64_t fnv1a(
const void* data,
size_t len)
256 const uint8_t* p = (
const uint8_t*)data;
257 uint64_t h = FNV64_OFFSET;
285static uint16_t refPixel(uint16_t pram)
287 const unsigned g = (pram >> 12) & 0x0f;
288 const unsigned b = (pram >> 8) & 0x0f;
289 const unsigned r = (pram ) & 0x0f;
290 return (uint16_t)((g << 12) | (b << 8) | (g << 4) | r);
294#define POST_PAL_PIXELS (GOLDEN_BYTES_PER_LINE * 2)
296static PICO9918_PIXEL_T libPixels[POST_PAL_PIXELS];
297static uint16_t refPixels[POST_PAL_PIXELS];
304_Static_assert(
sizeof(PICO9918_PIXEL_T) ==
sizeof(uint16_t),
305 "golden surfaces require the 16-bit Pico pixel policy");
306_Static_assert(
sizeof(PICO9918_PALETTE_LUT_T) ==
sizeof(uint32_t),
307 "golden expansion requires a 32-bit packed-pair LUT");
321static uint32_t refLut[256];
335static void refRegenerate(
bool paired)
337 const uint16_t* pram = tms9918->vram.map.pram;
341 for (
int i = 0; i < 64; ++i)
343 const uint32_t px = refPixel(pram[i]);
344 refLut[i] = px | (px << 16);
350 for (
int i = 0; i < 16; ++i)
352 pal[i] = refPixel(pram[i]);
353 refLut[i] = (uint32_t)pal[i] | ((uint32_t)pal[i] << 16);
355 for (
int j = 16; j < 256; ++j)
357 refLut[j] = ((uint32_t)pal[j & 0x0f] << 16) | pal[j >> 4];
365static void refExpand(uint16_t* dst,
const uint8_t* src,
int n)
367 for (
int i = 0; i < n; ++i)
369 const uint32_t entry = refLut[src[i]];
370 dst[i * 2 + 0] = (uint16_t)entry;
371 dst[i * 2 + 1] = (uint16_t)(entry >> 16);
376static void expandLine(
const uint8_t* indexed, uint64_t digests[GOLDEN_DIGESTS_PER_LINE])
378 PICO9918_EXPAND_INDEXED(libPixels, indexed, GOLDEN_BYTES_PER_LINE, pico9918_palette_lut);
379 refExpand(refPixels, indexed, GOLDEN_BYTES_PER_LINE);
381 digests[0] = fnv1a(libPixels,
sizeof(libPixels));
382 digests[1] = fnv1a(refPixels,
sizeof(refPixels));
388static int refDivergence(
void)
390 for (
int i = 0; i < POST_PAL_PIXELS; ++i)
392 if (libPixels[i] != refPixels[i])
return i;
402static void sceneGraphicsI(
void)
405 vramFillLcg(0x0000, 0x4000);
418static void sceneGraphicsII(
void)
421 vramFillLcg(0x0000, 0x4000);
433static void sceneText(
void)
436 vramFillLcg(0x0000, 0x4000);
445static void sceneMulticolor(
void)
448 vramFillLcg(0x0000, 0x4000);
460static void sceneText80(
void)
463 vramFillLcg(0x0000, 0x4000);
473static void sceneSpritesMax(
void)
475 static const uint8_t sat[] = {
477 0x27, 0x00, 0x00, 0x02,
478 0x27, 0x28, 0x00, 0x03,
479 0x27, 0x50, 0x00, 0x04,
480 0x27, 0x78, 0x00, 0x05,
481 0x27, 0xa0, 0x00, 0x06,
482 0x63, 0x64, 0x00, 0x08,
483 0x63, 0x68, 0x01, 0x09,
484 0xd0, 0x00, 0x00, 0x00
487 vramFillByte(0x0000, 0x00, 0x4000);
488 vramFillByte(0x1800, 0xff, 8);
489 vramFillByte(0x1808, 0xf0, 8);
490 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
504static void sceneF18aUnlocked(
void)
507 vramFillLcg(0x0000, 0x4000);
533static void sceneF18aEcm3Bml(
void)
536 vramFillLcg(0x0000, 0x4000);
560static void sceneF18aBmlWrap(
void)
563 vramFillLcg(0x0000, 0x4000);
587static void sceneF18aBmlStride(
void)
590 vramFillLcg(0x0000, 0x4000);
612static void sceneF18aText80Attrs(
void)
615 vramFillLcg(0x0000, 0x4000);
635static void sceneF18aVramSnapshot(
void)
638 vramFillLcg(0x0000, 0x4000);
672static void sceneF18aBmlPriority(
void)
674 static const uint8_t sat[] = {
676 0x10, 0x20, 0x01, 0x06,
677 0x2c, 0x10, 0x00, 0x03,
678 0x50, 0x58, 0x04, 0x05,
679 0x6c, 0xa0, 0x08, 0x07,
680 0xd0, 0x00, 0x00, 0x00
684 vramFillLcg(0x0000, 0x4000);
685 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
710static void sceneF18aEcm0(
void)
713 vramFillLcg(0x0000, 0x4000);
720 vramSetReadAddr(0x2600);
725 for (
int i = 0; i < 4; ++i)
727 sat[i * 4 + 0] = (uint8_t)(0x08 + i * 0x28);
728 sat[i * 4 + 1] = rd[i * 3];
729 sat[i * 4 + 2] = rd[i * 3 + 1];
730 sat[i * 4 + 3] = rd[i * 3 + 2] & 0x0f;
736 static const uint8_t satTail[] = {
737 0x50, 0x40, 0x00, 0x05,
738 0x50, 0x44, 0x01, 0x00,
739 0x78, 0x80, 0x00, 0x0b,
740 0xd0, 0x00, 0x00, 0x00
742 memcpy(sat + 16, satTail,
sizeof(satTail));
745 vramFillByte(0x1800, 0xff, 16);
746 vramFillByte(0x0000, 0x53, 32);
749 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
766static void sceneF18aGfx2(
void)
768 static const uint8_t sat[] = {
770 0x20, 0x30, 0x00, 0x07,
771 0x50, 0x80, 0x04, 0x0b,
772 0x60, 0x40, 0x0c, 0x04,
773 0x85, 0xc8, 0x08, 0x0d,
774 0xd0, 0x00, 0x00, 0x00
778 vramFillLcg(0x0000, 0x4000);
779 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
806static void sceneRawReset(
void)
809 vramFillLcg(0x0000, 0x4000);
828static const Scene scenes[] = {
829 {
"graphics-i", 192, sceneGraphicsI },
830 {
"graphics-ii", 192, sceneGraphicsII },
831 {
"text", 192, sceneText },
832 {
"multicolor", 192, sceneMulticolor },
833 {
"text80", 192, sceneText80 },
834 {
"sprites-max", 192, sceneSpritesMax },
835 {
"f18a-unlocked", 240, sceneF18aUnlocked },
836 {
"f18a-ecm3-bml", 192, sceneF18aEcm3Bml },
837 {
"f18a-text80-attrs", 192, sceneF18aText80Attrs },
838 {
"f18a-vram-snapshot", 192, sceneF18aVramSnapshot},
839 {
"f18a-bml-priority", 192, sceneF18aBmlPriority },
840 {
"f18a-bml-wrap", 192, sceneF18aBmlWrap },
841 {
"f18a-bml-stride", 192, sceneF18aBmlStride },
842 {
"f18a-ecm0", 192, sceneF18aEcm0 },
843 {
"f18a-gfx2", 192, sceneF18aGfx2 },
844 {
"raw-reset", 8, sceneRawReset },
847#define SCENE_COUNT ((int)(sizeof(scenes) / sizeof(scenes[0])))
858static uint8_t frame[MAX_LINES][GOLDEN_BYTES_PER_LINE + 1];
861static uint8_t palDump[GOLDEN_PAL_BYTES];
864static uint64_t digests[MAX_LINES][GOLDEN_DIGESTS_PER_LINE];
869static int refFailLine;
870static int refFailPixel;
871static uint16_t refFailLib;
872static uint16_t refFailRef;
874static void renderScene(
const Scene* scene,
int* lines5s,
int* linesCol)
884 for (
int y = 0; y < scene->lines; ++y)
886 uint8_t* pixels = frame[y];
896 if (pico9918_palette_dirty())
900 pico9918_palette_regenerate();
901 refRegenerate(paired);
906 pixels[GOLDEN_BYTES_PER_LINE] = status;
910 expandLine(pixels, digests[y]);
914 const int bad = refDivergence();
919 refFailLib = (uint16_t)libPixels[bad];
920 refFailRef = refPixels[bad];
927 dumpPalette(palDump);
951static void putU32(FILE* f, uint32_t v)
953 uint8_t b[4] = { (uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24) };
957static void putU64(FILE* f, uint64_t v)
959 putU32(f, (uint32_t)v);
960 putU32(f, (uint32_t)(v >> 32));
963static bool getU32(FILE* f, uint32_t* v)
966 if (fread(b, 1, 4, f) != 4)
return false;
967 *v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
971static bool getU64(FILE* f, uint64_t* v)
974 if (!getU32(f, &lo))
return false;
975 if (!getU32(f, &hi))
return false;
976 *v = (uint64_t)lo | ((uint64_t)hi << 32);
980static void scenePath(
char* buf,
size_t bufLen,
const char* dataDir,
const char* name)
982 snprintf(buf, bufLen,
"%s/%s.golden", dataDir, name);
985static bool captureScene(
const Scene* scene,
const char* dataDir)
988 scenePath(path,
sizeof(path), dataDir, scene->name);
990 int lines5s, linesCol;
991 renderScene(scene, &lines5s, &linesCol);
995 if (refFailLine >= 0)
997 printf(
"[ERROR] %-19s library expansion diverges from the reference at "
998 "line %d, pixel %d (lib 0x%04x, ref 0x%04x) - NOT captured\n",
999 scene->name, refFailLine, refFailPixel, refFailLib, refFailRef);
1003 FILE* f = fopen(path,
"wb");
1006 printf(
"[ERROR] %s: cannot open %s for writing\n", scene->name, path);
1010 char name[GOLDEN_NAME_LEN] = { 0 };
1011 strncpy(name, scene->name, GOLDEN_NAME_LEN - 1);
1013 fwrite(GOLDEN_MAGIC, 1, 4, f);
1014 putU32(f, GOLDEN_VERSION);
1015 fwrite(name, 1, GOLDEN_NAME_LEN, f);
1016 putU32(f, (uint32_t)scene->lines);
1017 putU32(f, GOLDEN_BYTES_PER_LINE);
1019 putU32(f, GOLDEN_PAL_ENTRIES);
1020 putU32(f, GOLDEN_DIGESTS_PER_LINE);
1023 for (
int y = 0; y < scene->lines; ++y)
1025 fwrite(frame[y], 1, GOLDEN_BYTES_PER_LINE + 1, f);
1027 for (
int y = 0; y < scene->lines; ++y)
1029 for (
int d = 0; d < GOLDEN_DIGESTS_PER_LINE; ++d) putU64(f, digests[y][d]);
1031 fwrite(palDump, 1, GOLDEN_PAL_BYTES, f);
1034 printf(
"[CAPTURED] %-19s %3d lines (5S on %d lines, COL on %d lines) -> %s\n",
1035 scene->name, scene->lines, lines5s, linesCol, path);
1039static bool compareScene(
const Scene* scene,
const char* dataDir)
1042 scenePath(path,
sizeof(path), dataDir, scene->name);
1044 FILE* f = fopen(path,
"rb");
1047 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
1053 uint32_t version = 0, lines = 0, bytesPerLine = 0, statusPerLine = 0, palEntries = 0;
1054 uint32_t digestsPerLine = 0;
1055 char name[GOLDEN_NAME_LEN];
1058 fread(magic, 1, 4, f) == 4 && memcmp(magic, GOLDEN_MAGIC, 4) == 0 &&
1059 getU32(f, &version) && version == GOLDEN_VERSION &&
1060 fread(name, 1, GOLDEN_NAME_LEN, f) == GOLDEN_NAME_LEN &&
1061 getU32(f, &lines) &&
1062 getU32(f, &bytesPerLine) && bytesPerLine == GOLDEN_BYTES_PER_LINE &&
1063 getU32(f, &statusPerLine) && statusPerLine == 1 &&
1064 getU32(f, &palEntries) && palEntries == GOLDEN_PAL_ENTRIES &&
1065 getU32(f, &digestsPerLine) && digestsPerLine == GOLDEN_DIGESTS_PER_LINE;
1067 if (!headerOk || lines != (uint32_t)scene->lines)
1069 printf(
"[FAIL] %-19s bad golden header in %s\n", scene->name, path);
1074 int lines5s, linesCol;
1075 renderScene(scene, &lines5s, &linesCol);
1077 static uint8_t expected[GOLDEN_BYTES_PER_LINE + 1];
1078 for (
int y = 0; y < scene->lines; ++y)
1080 if (fread(expected, 1,
sizeof(expected), f) !=
sizeof(expected))
1082 printf(
"[FAIL] %-19s truncated golden file at line %d\n", scene->name, y);
1086 if (memcmp(expected, frame[y],
sizeof(expected)) != 0)
1089 while (expected[i] == frame[y][i]) ++i;
1090 if (i == GOLDEN_BYTES_PER_LINE)
1092 printf(
"[FAIL] %-19s first divergence: line %d status byte (expected 0x%02x, got 0x%02x)\n",
1093 scene->name, y, expected[i], frame[y][i]);
1097 printf(
"[FAIL] %-19s first divergence: line %d, byte %d (expected 0x%02x, got 0x%02x)\n",
1098 scene->name, y, i, expected[i], frame[y][i]);
1107 static const char*
const surfaceName[GOLDEN_DIGESTS_PER_LINE] = {
"library",
"reference" };
1108 for (
int y = 0; y < scene->lines; ++y)
1110 for (
int d = 0; d < GOLDEN_DIGESTS_PER_LINE; ++d)
1112 uint64_t expectedDigest;
1113 if (!getU64(f, &expectedDigest))
1115 printf(
"[FAIL] %-19s truncated golden file in digest block at line %d\n",
1120 if (expectedDigest != digests[y][d])
1122 printf(
"[FAIL] %-19s first divergence: line %d %s post-palette digest "
1123 "(expected 0x%016llx, got 0x%016llx)\n",
1124 scene->name, y, surfaceName[d],
1125 (
unsigned long long)expectedDigest, (
unsigned long long)digests[y][d]);
1134 if (refFailLine >= 0)
1136 printf(
"[FAIL] %-19s library expansion diverges from the reference at "
1137 "line %d, pixel %d (lib 0x%04x, ref 0x%04x)\n",
1138 scene->name, refFailLine, refFailPixel, refFailLib, refFailRef);
1143 static uint8_t expectedPal[GOLDEN_PAL_BYTES];
1144 if (fread(expectedPal, 1,
sizeof(expectedPal), f) !=
sizeof(expectedPal))
1146 printf(
"[FAIL] %-19s truncated golden file in palette block\n", scene->name);
1150 if (memcmp(expectedPal, palDump,
sizeof(expectedPal)) != 0)
1153 while (expectedPal[i] == palDump[i]) ++i;
1154 const int entry = i >> 1;
1155 printf(
"[FAIL] %-19s first divergence: palette entry %d (expected 0x%04x, got 0x%04x)\n",
1157 expectedPal[entry * 2] | (expectedPal[entry * 2 + 1] << 8),
1158 palDump[entry * 2] | (palDump[entry * 2 + 1] << 8));
1164 printf(
"[PASS] %-19s %3d lines\n", scene->name, scene->lines);
1200#define OVERLAY_MAGIC "TMSO"
1201#define OVERLAY_VERSION 1
1219extern uint8_t font[];
1220extern uint8_t splash[];
1221extern PICO9918_PIXEL_T splash_pal[];
1223#define OVERLAY_FONT_WIDTH 768
1224#define OVERLAY_FONT_HEIGHT 6
1225#define OVERLAY_SPLASH_WIDTH 176
1226#define OVERLAY_SPLASH_HEIGHT 10
1232#define fontWidth goldenUnusedFontWidth
1233#define fontHeight goldenUnusedFontHeight
1234#define splashWidth goldenUnusedSplashWidth
1235#define splashHeight goldenUnusedSplashHeight
1236#include "overlay/bmp_font.h"
1237#include "overlay/bmp_splash.h"
1243_Static_assert(OVERLAY_FONT_WIDTH == FONT_WIDTH &&
1244 OVERLAY_FONT_HEIGHT == FONT_HEIGHT,
1245 "overlay font geometry has drifted from the generated asset");
1246_Static_assert(OVERLAY_SPLASH_WIDTH == SPLASH_WIDTH &&
1247 OVERLAY_SPLASH_HEIGHT == SPLASH_HEIGHT,
1248 "overlay splash geometry has drifted from the generated asset");
1261#define OVERLAY_PIXELS_X 642
1263static PICO9918_PIXEL_T ovLib[OVERLAY_PIXELS_X];
1264static PICO9918_PIXEL_T ovRef[OVERLAY_PIXELS_X];
1276#define OVERLAY_PREFILL_SEED 0xfedc
1278static void overlayPrefill(
void)
1280 uint16_t v = OVERLAY_PREFILL_SEED;
1281 for (
int i = 0; i < OVERLAY_PIXELS_X; ++i)
1283 ovLib[i] = (PICO9918_PIXEL_T)v;
1284 ovRef[i] = (PICO9918_PIXEL_T)v;
1285 v = (uint16_t)(v * 2053u + 13849u);
1292static void overlayRestorePrefill(PICO9918_PIXEL_T* dst,
int from,
int to)
1294 uint16_t v = OVERLAY_PREFILL_SEED;
1295 for (
int i = 0; i < to; ++i)
1297 if (i >= from) dst[i] = (PICO9918_PIXEL_T)v;
1298 v = (uint16_t)(v * 2053u + 13849u);
1319static int refDarken(
int x, PICO9918_PIXEL_T* pixels)
1321 const unsigned in = pixels[x];
1322 pixels[x] = (PICO9918_PIXEL_T)((in >> 2) & 0x333);
1343#define OVERLAY_FONT_ROW_BYTES (OVERLAY_FONT_WIDTH / 8)
1345static uint8_t refGlyphRowBits(
char ch,
int fy)
1347 const int col = (
unsigned char)ch - 32;
1348 return font[fy * OVERLAY_FONT_ROW_BYTES + col];
1364static int refRenderText(uint16_t scanline,
const char* text, uint16_t x, uint16_t y,
1365 PICO9918_PIXEL_T fg, PICO9918_PIXEL_T* pixels)
1367 const int fontY = (int)scanline - (
int)y;
1371 for (
const char* p = text; *p; ++p)
1373 const uint8_t bits = refGlyphRowBits(*p, fontY);
1377 else xPos = refDarken(xPos, pixels);
1397static int refLogoOffset;
1398static bool refCanHide;
1402#define OVERLAY_SPLASH_ENTER_FRAMES 60
1403#define OVERLAY_SPLASH_HOLD_FRAMES 180
1404#define OVERLAY_SPLASH_START_POS \
1405 (OVERLAY_SPLASH_ENTER_FRAMES + OVERLAY_SPLASH_HEIGHT + 2)
1407static void refSplashReset(
void)
1409 refLogoOffset = OVERLAY_SPLASH_START_POS;
1412static void refSplashRender(uint16_t y, uint32_t frameCount, uint32_t vBorder,
1413 uint32_t vPixels, uint32_t vVirtualPixels,
1414 PICO9918_PIXEL_T* pixels)
1418 if (frameCount < OVERLAY_SPLASH_ENTER_FRAMES) --refLogoOffset;
1419 else if (refCanHide &&
1420 frameCount > (OVERLAY_SPLASH_ENTER_FRAMES + OVERLAY_SPLASH_HOLD_FRAMES))
1424 if (y > vVirtualPixels)
return;
1427 const uint16_t row =
1428 (uint16_t)(y - (uint16_t)(vBorder + vPixels + (uint32_t)refLogoOffset));
1429 if (row >= OVERLAY_SPLASH_HEIGHT)
return;
1431 const int leftBorderPx = 4;
1432 const uint8_t* src = splash + (row * OVERLAY_SPLASH_WIDTH / 4);
1433 for (
int x = 0; x < OVERLAY_SPLASH_WIDTH; x += 4)
1435 const uint8_t c = *src++;
1436 for (
int px = 0; px < 4; ++px)
1438 const uint8_t palIndex = (uint8_t)((c >> (6 - px * 2)) & 0x03);
1439 if (palIndex) pixels[leftBorderPx + x + px] = splash_pal[palIndex];
1454#define OVERLAY_MAX_ROWS 4096
1456static uint64_t overlayDigests[OVERLAY_MAX_ROWS][2];
1457static int overlayRowCount;
1460static int ovFailRow;
1461static int ovFailPixel;
1462static uint16_t ovFailLib;
1463static uint16_t ovFailRef;
1467static const char* ovRowLabel[OVERLAY_MAX_ROWS];
1468static const char* ovCurrentLabel =
"";
1470static void overlayEmitRow(
void)
1472 if (overlayRowCount >= OVERLAY_MAX_ROWS)
1477 printf(
"[ERROR] overlay: row budget %d exhausted - raise OVERLAY_MAX_ROWS\n",
1482 const int row = overlayRowCount++;
1483 ovRowLabel[row] = ovCurrentLabel;
1484 overlayDigests[row][0] = fnv1a(ovLib,
sizeof(ovLib));
1485 overlayDigests[row][1] = fnv1a(ovRef,
sizeof(ovRef));
1489 for (
int i = 0; i < OVERLAY_PIXELS_X; ++i)
1491 if (ovLib[i] != ovRef[i])
1495 ovFailLib = (uint16_t)ovLib[i];
1496 ovFailRef = (uint16_t)ovRef[i];
1520#define OVERLAY_CENTRE_X(text) \
1521 ((uint16_t)((OVERLAY_PIXELS_X - (sizeof(text) - 1) * PICO9918_DIAG_CHAR_WIDTH) / 2))
1524#define OVERLAY_BANNER_FG ((PICO9918_PIXEL_T)(PICO9918_PIXEL_FROM_RGB12(0xff0f) & 0x0fff))
1530extern const PICO9918_PIXEL_T labelColor;
1531extern const PICO9918_PIXEL_T valueColor;
1532extern const PICO9918_PIXEL_T unitsColor;
1542static void overlayTextCase(
const char* label,
const char* text, uint16_t x,
1543 uint16_t y, PICO9918_PIXEL_T fg)
1545 ovCurrentLabel = label;
1551 const int refX = refRenderText((uint16_t)scanline, text, x, y, fg, ovRef);
1553 ovLib[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)libX;
1554 ovRef[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)refX;
1560static void overlayTextGroup(
void)
1563 overlayTextCase(
"banner-await-pc",
"POWER CYCLE TO TEST NEW CONFIGURATION",
1564 OVERLAY_CENTRE_X(
"POWER CYCLE TO TEST NEW CONFIGURATION"), 8, OVERLAY_BANNER_FG);
1565 overlayTextCase(
"banner-await-ok",
"OPEN CONFIGURATOR TO CONFIRM NEW SETTINGS",
1566 OVERLAY_CENTRE_X(
"OPEN CONFIGURATOR TO CONFIRM NEW SETTINGS"), 8, OVERLAY_BANNER_FG);
1569 overlayTextCase(
"label-color",
"HWVER : ", 2, 0, labelColor);
1570 overlayTextCase(
"value-color",
"1.0.2", 50, 0, valueColor);
1571 overlayTextCase(
"units-color",
"MHZ", 80, 0, unitsColor);
1576 overlayTextCase(
"font-row0",
" !\"#$%&'()*+,-./", 4, 0, labelColor);
1577 overlayTextCase(
"font-row1",
"0123456789:;<=>?", 4, 0, labelColor);
1578 overlayTextCase(
"font-row2",
"@ABCDEFGHIJKLMNO", 4, 0, labelColor);
1579 overlayTextCase(
"font-row3",
"PQRSTUVWXYZ[\\]^_", 4, 0, labelColor);
1583 overlayTextCase(
"reg-glyphs",
"R49:))((", 8, 0, valueColor);
1587 ovCurrentLabel =
"chained-run";
1595 rx = refRenderText((uint16_t)scanline,
"TEMP : ", (uint16_t)rx, 0, labelColor, ovRef);
1596 rx = refRenderText((uint16_t)scanline,
"42.75", (uint16_t)rx, 0, valueColor, ovRef);
1597 rx = refRenderText((uint16_t)scanline,
"^C", (uint16_t)rx, 0, unitsColor, ovRef);
1598 ovLib[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)lx;
1599 ovRef[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)rx;
1604 overlayTextCase(
"y-offset",
"OUTPUT: 480P @60", 2, 200, valueColor);
1608 overlayTextCase(
"x-zero",
"8", 0, 0, valueColor);
1630#define OVERLAY_SPLASH_VVIRT 240u
1631#define OVERLAY_SPLASH_VPIXELS 192u
1632#define OVERLAY_SPLASH_VBORDER ((OVERLAY_SPLASH_VVIRT - OVERLAY_SPLASH_VPIXELS) / 2u)
1634static void overlaySplashFrame(uint32_t frameCount,
bool sample)
1640 OVERLAY_SPLASH_VVIRT, ovLib);
1641 refSplashRender(0, frameCount, OVERLAY_SPLASH_VBORDER, OVERLAY_SPLASH_VPIXELS,
1642 OVERLAY_SPLASH_VVIRT, ovRef);
1643 if (sample) overlayEmitRow();
1645 if (!sample)
return;
1650 for (uint16_t y = (uint16_t)(OVERLAY_SPLASH_VBORDER + OVERLAY_SPLASH_VPIXELS);
1651 y <= OVERLAY_SPLASH_VVIRT; ++y)
1655 OVERLAY_SPLASH_VVIRT, ovLib);
1656 refSplashRender(y, frameCount, OVERLAY_SPLASH_VBORDER, OVERLAY_SPLASH_VPIXELS,
1657 OVERLAY_SPLASH_VVIRT, ovRef);
1662static void overlaySplashGroup(
void)
1671 ovCurrentLabel =
"splash-enter";
1672 for (uint32_t f = 0; f < OVERLAY_SPLASH_ENTER_FRAMES; ++f)
1674 overlaySplashFrame(f, f >= 46 && f <= 50);
1679 ovCurrentLabel =
"splash-hold";
1680 for (uint32_t f = OVERLAY_SPLASH_ENTER_FRAMES; f <= 199; ++f)
1682 overlaySplashFrame(f, f == 60 || f == 199);
1693 for (uint32_t f = 200; f <= 245; ++f)
1695 overlaySplashFrame(f, f == 239 || f == 240 || f == 241 || f == 242 || f == 245);
1701 ovCurrentLabel =
"splash-exit";
1706 for (uint32_t f = 246; f <= 275; ++f)
1708 overlaySplashFrame(f, (f >= 246 && f <= 249) || (f >= 257 && f <= 261));
1715 ovCurrentLabel =
"splash-reset";
1716 overlaySplashFrame(276,
true);
1751static PICO9918_PIXEL_T refSwatch(uint16_t pram)
1753 const unsigned g = (pram >> 12) & 0x0f;
1754 const unsigned b = (pram >> 8) & 0x0f;
1755 const unsigned r = pram & 0x0f;
1756 return (PICO9918_PIXEL_T)((b << 8) | (g << 4) | r);
1762static void refPalettePanel(
int y, uint32_t vVirtualPixels, PICO9918_PIXEL_T* pixels)
1764 const int row = y % 6;
1765 const uint8_t palette = (uint8_t)((y - ((
int)vVirtualPixels - 24)) / 6);
1766 if (palette >= 4)
return;
1768 char buf[] =
"PALETTE 0:";
1769 buf[8] = (char)(
'0' + palette);
1770 refRenderText((uint16_t)row, buf, 2, 0, labelColor, pixels);
1772 if (row >= 5)
return;
1775 for (
int c = 0; c < 16; ++c)
1777 const PICO9918_PIXEL_T sw = refSwatch(tms9918->vram.map.pram[palette * 16 + c]);
1778 for (
int i = 0; i < 30; ++i) pixels[pair * 2 + i] = sw;
1787static void refRegisterPanel(
int diagRow,
int row, PICO9918_PIXEL_T* pixels)
1789 static const char*
const bins[] = {
1790 "((((",
"((()",
"(()(",
"(())",
1791 "()((",
"()()",
"())(",
"()))",
1792 ")(((",
")(()",
")()(",
")())",
1793 "))((",
"))()",
")))(",
"))))",
1795 const unsigned reg = (unsigned)diagRow;
1796 char buf[] =
"R00:";
1797 buf[1] = (char)(
'0' + reg / 10u);
1798 buf[2] = (char)(
'0' + reg % 10u);
1801 xPos = refRenderText((uint16_t)row, buf, (uint16_t)xPos, 0, labelColor, pixels);
1804 xPos = refDarken(xPos, pixels);
1805 xPos = refDarken(xPos, pixels);
1806 xPos = refRenderText((uint16_t)row, bins[value >> 4], (uint16_t)xPos, 0, valueColor, pixels);
1807 xPos = refDarken(xPos, pixels);
1808 xPos = refDarken(xPos, pixels);
1809 refRenderText((uint16_t)row, bins[value & 0x0f], (uint16_t)xPos, 0, valueColor, pixels);
1814static void overlaySetConfig(uint8_t index, uint8_t value)
1816 tms9918->config[index] = value;
1851static void overlayPrimeDiag(
void)
1888 pico9918_frame_count = 7;
1889 pico9918_dropped_frames_count = 1;
1890#if PICO9918_DIAG_GPU_FRAME_COUNTER
1891 pico9918_gpu_frame_count = 12345;
1912static void overlayPanelCase(
const char* label,
bool registers,
bool address,
1913 bool palette,
bool unlocked,
bool gfx2,
1920 vramFillLcg(0x0000, 0x4000);
1924 paletteWriteLcg(64);
1942 tms9918->vram.map.pram[0] = 0x3f70;
1943 tms9918->vram.map.pram[5] = 0xc59a;
1944 tms9918->vram.map.pram[17] = 0x08f3;
1945 tms9918->vram.map.pram[33] = 0xfae1;
1946 tms9918->vram.map.pram[49] = 0x714c;
1947 tms9918->palDirty = 1;
1959 regWrite(0, gfx2 ? 0x02 : 0x00);
1992 overlaySetConfig(PICO9918_CONF_DIAG, 1);
1993 overlaySetConfig(PICO9918_CONF_DIAG_REGISTERS, registers);
1994 overlaySetConfig(PICO9918_CONF_DIAG_PERFORMANCE, performance);
1995 overlaySetConfig(PICO9918_CONF_DIAG_PALETTE, palette);
1996 overlaySetConfig(PICO9918_CONF_DIAG_ADDRESS, address);
2017 ovCurrentLabel = label;
2030 for (uint16_t y = 1; y <= OVERLAY_SPLASH_VVIRT; ++y)
2049 memcpy(ovRef, ovLib,
sizeof(ovRef));
2051 const int panelY = (int)y - 1;
2052 const int diagRow = panelY / 6;
2053 const int row = panelY % 6;
2059 const bool refStrip = palette && panelY > (int)OVERLAY_SPLASH_VVIRT - 27;
2062 overlayRestorePrefill(ovRef, 0, OVERLAY_PIXELS_X);
2063 refPalettePanel(panelY + 2, OVERLAY_SPLASH_VVIRT, ovRef);
2069 if (unlocked) maxReg += 30;
2070 if (diagRow < maxReg)
2072 static const int extRegRef[] = { 10, 11, 15, 19, 24, 25, 26, 27,
2073 28, 29, 30, 31, 32, 33, 34, 35,
2074 36, 37, 38, 48, 49, 50, 51, 54, 55,
2075 56, 57, 58, 59, 63 };
2076 const int reg = (diagRow >= 8) ? extRegRef[diagRow - 8] : diagRow;
2079 if (!refStrip) overlayRestorePrefill(ovRef, regPanelX, OVERLAY_PIXELS_X);
2080 refRegisterPanel(reg, row, ovRef);
2088static void overlayPanelGroup(
void)
2090 overlayPanelCase(
"panel-registers",
true,
false,
false,
false,
false,
false);
2091 overlayPanelCase(
"panel-registers-unlocked",
true,
false,
false,
true,
false,
false);
2092 overlayPanelCase(
"panel-address",
false,
true,
false,
false,
false,
false);
2093 overlayPanelCase(
"panel-palette",
false,
false,
true,
true,
false,
false);
2094 overlayPanelCase(
"panel-all",
true,
true,
true,
true,
false,
false);
2097 overlayPanelCase(
"panel-address-gfx2",
false,
true,
false,
false,
true,
false);
2112 overlayPanelCase(
"panel-performance",
false,
false,
false,
false,
false,
true);
2113 overlayPanelCase(
"panel-performance-address",
false,
true,
false,
true,
false,
true);
2117static void overlayRender(
void)
2119 overlayRowCount = 0;
2128 overlaySplashGroup();
2129 overlayPanelGroup();
2132static bool overlayCapture(
const char* dataDir)
2138 printf(
"[ERROR] overlay library diverges from the reference at "
2139 "row %d (%s), pixel %d (lib 0x%04x, ref 0x%04x) - NOT captured\n",
2140 ovFailRow, ovRowLabel[ovFailRow], ovFailPixel, ovFailLib, ovFailRef);
2145 snprintf(path,
sizeof(path),
"%s/overlay.golden", dataDir);
2146 FILE* f = fopen(path,
"wb");
2149 printf(
"[ERROR] overlay: cannot open %s for writing\n", path);
2153 fwrite(OVERLAY_MAGIC, 1, 4, f);
2154 putU32(f, OVERLAY_VERSION);
2155 putU32(f, (uint32_t)overlayRowCount);
2156 putU32(f, OVERLAY_PIXELS_X);
2158 for (
int r = 0; r < overlayRowCount; ++r)
2160 putU64(f, overlayDigests[r][0]);
2161 putU64(f, overlayDigests[r][1]);
2165 printf(
"[CAPTURED] %-19s %3d rows -> %s\n",
"overlay", overlayRowCount, path);
2169static bool overlayCompare(
const char* dataDir)
2172 snprintf(path,
sizeof(path),
"%s/overlay.golden", dataDir);
2173 FILE* f = fopen(path,
"rb");
2176 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
2182 uint32_t version = 0, rows = 0, width = 0, perRow = 0;
2183 const bool headerOk =
2184 fread(magic, 1, 4, f) == 4 && memcmp(magic, OVERLAY_MAGIC, 4) == 0 &&
2185 getU32(f, &version) && version == OVERLAY_VERSION &&
2187 getU32(f, &width) && width == OVERLAY_PIXELS_X &&
2188 getU32(f, &perRow) && perRow == 2;
2191 printf(
"[FAIL] %-19s bad golden header in %s\n",
"overlay", path);
2198 if (rows != (uint32_t)overlayRowCount)
2200 printf(
"[FAIL] %-19s row count changed (golden %u, got %d)\n",
2201 "overlay", rows, overlayRowCount);
2206 static const char*
const surface[2] = {
"library",
"reference" };
2207 for (
int r = 0; r < overlayRowCount; ++r)
2209 for (
int d = 0; d < 2; ++d)
2212 if (!getU64(f, &expected))
2214 printf(
"[FAIL] %-19s truncated golden file at row %d\n",
"overlay", r);
2218 if (expected != overlayDigests[r][d])
2220 printf(
"[FAIL] %-19s first divergence: row %d (%s) %s digest "
2221 "(expected 0x%016llx, got 0x%016llx)\n",
2222 "overlay", r, ovRowLabel[r], surface[d],
2223 (
unsigned long long)expected, (
unsigned long long)overlayDigests[r][d]);
2232 printf(
"[FAIL] %-19s library diverges from the reference at row %d (%s), "
2233 "pixel %d (lib 0x%04x, ref 0x%04x)\n",
2234 "overlay", ovFailRow, ovRowLabel[ovFailRow], ovFailPixel, ovFailLib, ovFailRef);
2240 printf(
"[PASS] %-19s %3d rows\n",
"overlay", overlayRowCount);
2283#define FRAME_MAGIC "TMSF"
2286#define FRAME_VERSION 3
2290#define FRAME_R0_DOUBLE_ROWS 0x08
2293#define FRAME_R31_ROW30 0x40
2301#define FRAME_DISPLAY_VGA 480
2302#define FRAME_DISPLAY_SCART_PAL 268
2303#define FRAME_DISPLAY_SCART_NTSC 220
2308#define FRAME_INTERLACED_PIXEL_SCALE 1
2329 uint8_t vPixelScale;
2330 uint16_t vVirtualPixels;
2336 uint8_t vPixelScale;
2337 uint16_t vVirtualPixels;
2340 uint32_t triggerScanline;
2371static uint16_t frameMapLine(uint16_t yRaw,
const FrameParams* params, uint8_t reg0,
2372 uint8_t interlacedFieldOrder)
2374 TMS_REGISTER(tms9918, 0) = reg0;
2376 return pico9918_frame_map_line_impl(
PICO9918_INST (uint16_t)(yRaw & 0x0fff),
2377 (uint8_t)((yRaw >> 12) & 1),
2378 params->interlaced, interlacedFieldOrder);
2411 params->vPixelScale, params->vVirtualPixels };
2413 TMS_REGISTER(tms9918, 0) = reg0;
2414 TMS_REGISTER(tms9918, 0x31) = reg31;
2453static uint16_t refMapLine(uint16_t yRaw,
const FrameParams* params, uint8_t reg0,
2454 uint8_t interlacedFieldOrder)
2456 const int field = (yRaw >> 12) & 1;
2457 const int lineInField = yRaw & 0x0fff;
2459 if (!params->interlaced)
return (uint16_t)lineInField;
2460 if (!(reg0 & FRAME_R0_DOUBLE_ROWS))
return (uint16_t)lineInField;
2463 const int evenVdpLine = lineInField + lineInField;
2464 const int oddVdpLine = evenVdpLine + 1;
2473 const bool takesEven = (field != 0) == (interlacedFieldOrder != 0);
2474 return (uint16_t)(takesEven ? evenVdpLine : oddVdpLine);
2495 const bool doubleRows = (reg0 & FRAME_R0_DOUBLE_ROWS) != 0;
2496 const int baseRows = (reg31 & FRAME_R31_ROW30) ? 30 : 24;
2499 g.vPixels = baseRows * 8;
2501 if (params->interlaced)
2504 g.vPixelScale = params->vPixelScale;
2505 g.vVirtualPixels = params->vVirtualPixels;
2512 g.vVirtualPixels = params->displayPixels;
2513 g.vPixels = g.vPixels * 2;
2518 g.vVirtualPixels = params->displayPixels / 2;
2520 params->vPixelScale = g.vPixelScale;
2521 params->vVirtualPixels = g.vVirtualPixels;
2537 const int spareLines = (int)g.vVirtualPixels - g.vPixels;
2538 g.vBorder = (uint32_t)(spareLines / 2);
2540 g.triggerScanline = g.vBorder + (uint32_t)g.vPixels;
2566static int32_t frameActiveLines(
const FrameGeometry* g,
int displayPixels)
2569 for (uint32_t y = 0; y < (uint32_t)displayPixels; ++y)
2570 if (!(y < g->vBorder || y >= (g->vBorder + (uint32_t)g->vPixels)))
2578static int32_t refActiveLines(
const FrameGeometry* g,
int displayPixels)
2580 const uint32_t total = (uint32_t)displayPixels;
2581 const uint32_t firstActive = g->vBorder;
2582 const uint32_t lastActive = g->vBorder + (uint32_t)g->vPixels - 1u;
2585 for (uint32_t y = 0; y < total; ++y)
2586 if (y < firstActive || y > lastActive)
2588 return (int32_t)total - border;
2605#define FRAME_MAX_ROWS 1024
2607static uint64_t frameDigests[FRAME_MAX_ROWS][2];
2608static int frameRowCount;
2611static int frFailRow;
2612static const char* frFailField;
2613static long frFailCand;
2614static long frFailRef;
2616static const char* frRowLabel[FRAME_MAX_ROWS];
2617static const char* frCurrentLabel =
"";
2625#define FRAME_ROW_VALUES 15
2629 int32_t v[FRAME_ROW_VALUES];
2632static const char*
const frameFieldName[FRAME_ROW_VALUES] = {
2633 "mappedLine",
"vPixelScale",
"vVirtualPixels",
"vPixels",
"vBorder",
"triggerScanline",
"paramsVPixelScale",
2634 "paramsVVirtualPixels",
"activeLines",
2636 "frameStatusShadow",
"sr0Register",
"intPin",
"sr1Register",
"lockLatch",
"publishedStatus"};
2640 if (frameRowCount >= FRAME_MAX_ROWS)
2643 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
2648 const int row = frameRowCount++;
2649 frRowLabel[row] = frCurrentLabel;
2650 frameDigests[row][0] = fnv1a(cand,
sizeof(*cand));
2651 frameDigests[row][1] = fnv1a(ref,
sizeof(*ref));
2655 for (
int i = 0; i < FRAME_ROW_VALUES; ++i)
2657 if (cand->v[i] != ref->v[i])
2660 frFailField = frameFieldName[i];
2661 frFailCand = cand->v[i];
2662 frFailRef = ref->v[i];
2682static void frameMapCase(
const char* label, uint16_t yRaw,
bool interlaced,
2683 uint8_t reg0, uint8_t order)
2685 frCurrentLabel = label;
2687 FrameParams cp = { FRAME_DISPLAY_SCART_PAL, interlaced,
2688 FRAME_INTERLACED_PIXEL_SCALE, FRAME_DISPLAY_SCART_PAL };
2691 FrameRow cand = { { 0 } }, ref = { { 0 } };
2692 cand.v[0] = frameMapLine(yRaw, &cp, reg0, order);
2693 ref.v[0] = refMapLine(yRaw, &rp, reg0, order);
2695 frameEmitRow(&cand, &ref);
2704static char frMapLabels[FRAME_MAX_ROWS][32];
2706static void frameMapQuad(
const char* label, uint16_t lineInField,
bool interlaced,
2709 for (
int field = 0; field < 2; ++field)
2711 for (
int order = 0; order < 2; ++order)
2715 if (frameRowCount >= FRAME_MAX_ROWS)
2717 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
2721 char* stored = frMapLabels[frameRowCount];
2722 snprintf(stored,
sizeof(frMapLabels[0]),
"%s-f%d-o%d", label, field, order);
2723 frameMapCase(stored, (uint16_t)((field << 12) | lineInField),
2724 interlaced, reg0, (uint8_t)order);
2729static void frameMapGroup(
void)
2733 frameMapQuad(
"il-dbl-line0", 0,
true, FRAME_R0_DOUBLE_ROWS);
2734 frameMapQuad(
"il-dbl-line1", 1,
true, FRAME_R0_DOUBLE_ROWS);
2735 frameMapQuad(
"il-dbl-line95", 95,
true, FRAME_R0_DOUBLE_ROWS);
2738 frameMapQuad(
"il-dbl-line119", 119,
true, FRAME_R0_DOUBLE_ROWS);
2740 frameMapQuad(
"il-dbl-line149", 149,
true, FRAME_R0_DOUBLE_ROWS);
2743 frameMapQuad(
"il-nodbl-line0", 0,
true, 0x00);
2744 frameMapQuad(
"il-nodbl-line119", 119,
true, 0x00);
2749 frameMapQuad(
"prog-dbl-line0", 0,
false, FRAME_R0_DOUBLE_ROWS);
2750 frameMapQuad(
"prog-dbl-line191", 191,
false, FRAME_R0_DOUBLE_ROWS);
2751 frameMapQuad(
"prog-nodbl-line0", 0,
false, 0x00);
2752 frameMapQuad(
"prog-nodbl-line191", 191,
false, 0x00);
2767 frameMapQuad(
"il-dbl-line2000", 2000,
true, FRAME_R0_DOUBLE_ROWS);
2768 frameMapQuad(
"il-dbl-line3000", 3000,
true, FRAME_R0_DOUBLE_ROWS);
2769 frameMapQuad(
"il-nodbl-line3000", 3000,
true, 0x00);
2798static void frameGeomCase(
const char* label,
int displayPixels,
bool interlaced,
2799 uint8_t reg0, uint8_t reg31)
2801 frCurrentLabel = label;
2807 FRAME_INTERLACED_PIXEL_SCALE, displayPixels };
2813 FrameRow cand = { { 0 } }, ref = { { 0 } };
2814 cand.v[1] = cg.vPixelScale;
2815 cand.v[2] = cg.vVirtualPixels;
2816 cand.v[3] = cg.vPixels;
2817 cand.v[4] = cg.vBorder;
2818 cand.v[5] = cg.triggerScanline;
2819 cand.v[6] = cp.vPixelScale;
2820 cand.v[7] = cp.vVirtualPixels;
2821 cand.v[8] = frameActiveLines(&cg, displayPixels);
2823 ref.v[1] = rg.vPixelScale;
2824 ref.v[2] = rg.vVirtualPixels;
2825 ref.v[3] = rg.vPixels;
2826 ref.v[4] = rg.vBorder;
2827 ref.v[5] = rg.triggerScanline;
2828 ref.v[6] = rp.vPixelScale;
2829 ref.v[7] = rp.vVirtualPixels;
2830 ref.v[8] = refActiveLines(&rg, displayPixels);
2832 frameEmitRow(&cand, &ref);
2835static void frameGeomGroup(
void)
2838 frameGeomCase(
"geom-vga", FRAME_DISPLAY_VGA,
false, 0x00, 0x00);
2839 frameGeomCase(
"geom-vga-row30", FRAME_DISPLAY_VGA,
false, 0x00, FRAME_R31_ROW30);
2840 frameGeomCase(
"geom-vga-dbl", FRAME_DISPLAY_VGA,
false, FRAME_R0_DOUBLE_ROWS, 0x00);
2841 frameGeomCase(
"geom-vga-dbl-row30", FRAME_DISPLAY_VGA,
false,
2842 FRAME_R0_DOUBLE_ROWS, FRAME_R31_ROW30);
2846 frameGeomCase(
"geom-scart-pal", FRAME_DISPLAY_SCART_PAL,
true, 0x00, 0x00);
2847 frameGeomCase(
"geom-scart-pal-dbl", FRAME_DISPLAY_SCART_PAL,
true,
2848 FRAME_R0_DOUBLE_ROWS, 0x00);
2849 frameGeomCase(
"geom-scart-ntsc", FRAME_DISPLAY_SCART_NTSC,
true, 0x00, 0x00);
2850 frameGeomCase(
"geom-scart-ntsc-dbl", FRAME_DISPLAY_SCART_NTSC,
true,
2851 FRAME_R0_DOUBLE_ROWS, 0x00);
2857 frameGeomCase(
"geom-scart-pal-row30", FRAME_DISPLAY_SCART_PAL,
true,
2858 0x00, FRAME_R31_ROW30);
2859 frameGeomCase(
"geom-scart-ntsc-row30", FRAME_DISPLAY_SCART_NTSC,
true,
2860 0x00, FRAME_R31_ROW30);
2901#define FRAME_R1_INT_ENABLE 0x20
2905#define FRAME_SR0_F 0x80
2906#define FRAME_SR0_5S 0x40
2907#define FRAME_SR0_COL 0x20
2908#define FRAME_SR0_ID 0x1f
2912#define FRAME_R0_INT_SCANLINE 0x10
2913#define FRAME_SR1_HF 0x01
2926static uint8_t refMergeStatus(uint8_t currentStatus, uint8_t tempStatus)
2928 const bool haveF = (currentStatus & FRAME_SR0_F) != 0;
2929 const bool have5S = (currentStatus & FRAME_SR0_5S) != 0;
2933 bool out5S = have5S;
2934 bool outCol = (currentStatus & FRAME_SR0_COL) != 0;
2940 if (tempStatus & FRAME_SR0_COL) outCol =
true;
2945 if (tempStatus & FRAME_SR0_F) outF =
true;
2946 if (tempStatus & FRAME_SR0_5S) out5S =
true;
2947 if (tempStatus & FRAME_SR0_COL) outCol =
true;
2952 uint8_t
id = currentStatus & FRAME_SR0_ID;
2953 if (!haveF && !have5S)
id = tempStatus & FRAME_SR0_ID;
2956 if (outF) out |= FRAME_SR0_F;
2957 if (out5S) out |= FRAME_SR0_5S;
2958 if (outCol) out |= FRAME_SR0_COL;
2976static bool refIntPin(uint8_t mergedStatus, uint8_t reg1, uint8_t reg0, uint8_t sr1)
2978 const bool frameEnabled = (reg1 & FRAME_R1_INT_ENABLE) != 0;
2979 const bool frameFlagLatched = (mergedStatus & FRAME_SR0_F) != 0;
2981 const bool scanlineEnabled = (reg0 & FRAME_R0_INT_SCANLINE) != 0;
2982 const bool scanlineFlagLatched = (sr1 & FRAME_SR1_HF) != 0;
2984 return (frameEnabled && frameFlagLatched) || (scanlineEnabled && scanlineFlagLatched);
2996static void frameIntSetup(
bool want)
2999 TMS_REGISTER(tms9918, TMS_REG_0) &= (uint8_t)~FRAME_R0_INT_SCANLINE;
3000 TMS_REGISTER(tms9918, TMS_REG_1) = want ? FRAME_R1_INT_ENABLE : 0x00;
3011static void frameIntCase(
const char* label, uint8_t currentStatus, uint8_t tempStatus, uint8_t reg1,
3012 bool intPinPre, uint8_t reg0, uint8_t sr1,
bool unlocked)
3014 frCurrentLabel = label;
3017 frameIntSetup(intPinPre);
3018 TMS_REGISTER(tms9918, TMS_REG_1) = reg1;
3019 TMS_REGISTER(tms9918, TMS_REG_0) = reg0;
3021 tms9918->isUnlocked = unlocked;
3023 goldenPublishReset(currentStatus);
3029 FrameRow cand = { { 0 } }, ref = { { 0 } };
3030 cand.v[9] = pico9918_frame_status_impl();
3031 cand.v[10] = TMS_STATUS(tms9918, 0);
3032 cand.v[11] = pico9918_frame_int_impl();
3035 cand.v[14] = goldenPublishedStatus;
3037 const uint8_t expected = refMergeStatus(currentStatus, tempStatus);
3038 ref.v[9] = expected;
3039 ref.v[10] = expected;
3040 ref.v[11] = refIntPin(expected, reg1, reg0, sr1);
3046 ref.v[14] = expected;
3053 ref.v[13] = unlocked;
3055 frameEmitRow(&cand, &ref);
3070static char frIntLabels[FRAME_MAX_ROWS][40];
3072static void frameIntQuad(
const char* label, uint8_t currentStatus, uint8_t tempStatus)
3074 for (
int r1 = 0; r1 < 2; ++r1)
3076 for (
int pre = 0; pre < 2; ++pre)
3080 if (frameRowCount >= FRAME_MAX_ROWS)
3082 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
3086 char* stored = frIntLabels[frameRowCount];
3087 snprintf(stored,
sizeof(frIntLabels[0]),
"%s-r1%d-pin%d", label, r1, pre);
3091 frameIntCase(stored, currentStatus, tempStatus, (uint8_t)(r1 ? FRAME_R1_INT_ENABLE : 0x00), pre != 0,
3092 FRAME_R0_INT_SCANLINE, 0x00, true);
3103static void frameHIntPair(
const char* label, uint8_t currentStatus, uint8_t tempStatus, uint8_t reg1,
3104 uint8_t reg0, uint8_t sr1,
bool unlocked)
3106 for (
int pre = 0; pre < 2; ++pre)
3108 if (frameRowCount >= FRAME_MAX_ROWS)
3110 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n", FRAME_MAX_ROWS);
3114 char* stored = frIntLabels[frameRowCount];
3115 snprintf(stored,
sizeof(frIntLabels[0]),
"%s-pin%d", label, pre);
3116 frameIntCase(stored, currentStatus, tempStatus, reg1, pre != 0, reg0, sr1, unlocked);
3131static void frameHIntReadCase(
const char* label, uint8_t currentStatus, uint8_t reg1)
3133 frCurrentLabel = label;
3136 frameIntSetup(
true);
3137 TMS_REGISTER(tms9918, TMS_REG_1) = reg1;
3138 TMS_REGISTER(tms9918, TMS_REG_0) = FRAME_R0_INT_SCANLINE;
3141 tms9918->isUnlocked =
true;
3148 FrameRow cand = {{0}}, ref = {{0}};
3150 cand.v[10] = TMS_STATUS(tms9918, 0);
3151 cand.v[11] = pico9918_frame_int_impl();
3156 ref.v[9] = FRAME_SR1_HF;
3157 ref.v[10] = currentStatus;
3159 const uint8_t sr1After = (uint8_t)(FRAME_SR1_HF & ~FRAME_SR1_HF);
3160 ref.v[11] = refIntPin(currentStatus, reg1, FRAME_R0_INT_SCANLINE, sr1After);
3161 ref.v[12] = sr1After;
3164 frameEmitRow(&cand, &ref);
3176static void frameSr0ReadCase(
const char* label, uint8_t reg0, uint8_t sr1,
bool unlocked)
3178 frCurrentLabel = label;
3181 frameIntSetup(
true);
3182 TMS_REGISTER(tms9918, TMS_REG_1) = FRAME_R1_INT_ENABLE;
3183 TMS_REGISTER(tms9918, TMS_REG_0) = reg0;
3186 tms9918->isUnlocked = unlocked;
3193 FrameRow cand = {{0}}, ref = {{0}};
3195 cand.v[10] = TMS_STATUS(tms9918, 0);
3196 cand.v[11] = pico9918_frame_int_impl();
3202 ref.v[9] = FRAME_SR0_F;
3205 ref.v[11] = refIntPin(0x00, FRAME_R1_INT_ENABLE, reg0, sr1);
3209 ref.v[13] = unlocked;
3211 frameEmitRow(&cand, &ref);
3217static void frameSr0ThenSr1(
void)
3219 frameSr0ReadCase(
"sread-scanline-holds", FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
true);
3221 frCurrentLabel =
"sread-then-hread";
3226 FrameRow cand = {{0}}, ref = {{0}};
3228 cand.v[10] = TMS_STATUS(tms9918, 0);
3229 cand.v[11] = pico9918_frame_int_impl();
3233 ref.v[9] = FRAME_SR1_HF;
3235 ref.v[11] = refIntPin(0x00, FRAME_R1_INT_ENABLE, FRAME_R0_INT_SCANLINE, 0x00);
3239 frameEmitRow(&cand, &ref);
3248static void frameRelockCase(
void)
3250 frCurrentLabel =
"hint-relock-holds";
3253 frameIntSetup(
false);
3254 TMS_REGISTER(tms9918, TMS_REG_1) = 0x00;
3255 TMS_REGISTER(tms9918, TMS_REG_0) = FRAME_R0_INT_SCANLINE;
3257 tms9918->isUnlocked =
true;
3266 FrameRow cand = {{0}}, ref = {{0}};
3267 cand.v[9] = pico9918_frame_status_impl();
3268 cand.v[10] = TMS_STATUS(tms9918, 0);
3269 cand.v[11] = pico9918_frame_int_impl();
3275 ref.v[11] = refIntPin(0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF);
3276 ref.v[12] = FRAME_SR1_HF;
3281 frameEmitRow(&cand, &ref);
3284static void frameIntGroup(
void)
3293 frameIntQuad(
"int-a-5s-id5", 0x45, 0x9f);
3295 frameIntQuad(
"int-a-5s-id3", 0x43, 0xe8);
3297 frameIntQuad(
"int-a-5s-col", 0x40, 0x20);
3304 frameIntQuad(
"int-b-vblank", 0x1f, 0x80);
3306 frameIntQuad(
"int-b-empty", 0x00, 0xc5);
3310 frameIntQuad(
"int-b-keep-col", 0x20, 0x1f);
3318 frameIntQuad(
"int-c-block-5s", 0x85, 0x40);
3321 frameIntQuad(
"int-c-pass-col", 0x9f, 0xe5);
3324 frameIntQuad(
"int-c-col-again", 0xa0, 0x20);
3333 frameIntQuad(
"int-a-noop", 0x47, 0x00);
3337 frameIntQuad(
"int-c-f-again", 0x80, 0x80);
3343 frameIntQuad(
"int-b-full-id", 0x00, 0x7f);
3348 frameIntQuad(
"int-pin-only", 0x80, 0x00);
3355 frameIntQuad(
"int-b-quiet", 0x00, 0x00);
3360 frameIntQuad(
"int-b-drop-id", 0x1f, 0x00);
3370 frameHIntPair(
"hint-alone", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
true);
3374 frameHIntPair(
"hint-disarmed", 0x00, 0x00, 0x00, 0x00, FRAME_SR1_HF,
true);
3375 frameHIntPair(
"hint-noflag", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, 0x00,
true);
3381 frameHIntPair(
"hint-locked", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
false);
3385 frameHIntPair(
"hint-f-latched-r1off", FRAME_SR0_F, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
true);
3389 frameHIntPair(
"hint-both", FRAME_SR0_F, 0xc5, FRAME_R1_INT_ENABLE, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
3394 frameHIntPair(
"hint-frame-only", 0x00, FRAME_SR0_F, FRAME_R1_INT_ENABLE, FRAME_R0_INT_SCANLINE, 0x00,
true);
3402 frameHIntReadCase(
"hread-releases", 0x00, 0x00);
3403 frameHIntReadCase(
"hread-frame-holds", FRAME_SR0_F, FRAME_R1_INT_ENABLE);
3411 frameSr0ReadCase(
"sread-releases", FRAME_R0_INT_SCANLINE, 0x00,
true);
3412 frameSr0ReadCase(
"sread-disarmed", 0x00, FRAME_SR1_HF,
true);
3414 frameSr0ReadCase(
"sread-locked", FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
false);
3422static void frameRender(
void)
3446static bool frameCapture(
const char* dataDir)
3452 printf(
"[ERROR] frame candidate diverges from the reference at "
3453 "row %d (%s), field %s (cand %ld, ref %ld) - NOT captured\n",
3454 frFailRow, frRowLabel[frFailRow], frFailField, frFailCand, frFailRef);
3459 snprintf(path,
sizeof(path),
"%s/frame.golden", dataDir);
3460 FILE* f = fopen(path,
"wb");
3463 printf(
"[ERROR] frame: cannot open %s for writing\n", path);
3467 fwrite(FRAME_MAGIC, 1, 4, f);
3468 putU32(f, FRAME_VERSION);
3469 putU32(f, (uint32_t)frameRowCount);
3471 putU32(f, FRAME_ROW_VALUES);
3473 for (
int r = 0; r < frameRowCount; ++r)
3475 putU64(f, frameDigests[r][0]);
3476 putU64(f, frameDigests[r][1]);
3480 printf(
"[CAPTURED] %-19s %3d rows -> %s\n",
"frame", frameRowCount, path);
3484static bool frameCompare(
const char* dataDir)
3487 snprintf(path,
sizeof(path),
"%s/frame.golden", dataDir);
3488 FILE* f = fopen(path,
"rb");
3491 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
3497 uint32_t version = 0, rows = 0, values = 0, perRow = 0;
3498 const bool headerOk =
3499 fread(magic, 1, 4, f) == 4 && memcmp(magic, FRAME_MAGIC, 4) == 0 &&
3500 getU32(f, &version) && version == FRAME_VERSION &&
3502 getU32(f, &values) && values == FRAME_ROW_VALUES &&
3503 getU32(f, &perRow) && perRow == 2;
3506 printf(
"[FAIL] %-19s bad golden header in %s\n",
"frame", path);
3513 if (rows != (uint32_t)frameRowCount)
3515 printf(
"[FAIL] %-19s row count changed (golden %u, got %d)\n",
3516 "frame", rows, frameRowCount);
3521 static const char*
const surface[2] = {
"candidate",
"reference" };
3522 for (
int r = 0; r < frameRowCount; ++r)
3524 for (
int d = 0; d < 2; ++d)
3527 if (!getU64(f, &expected))
3529 printf(
"[FAIL] %-19s truncated golden file at row %d\n",
"frame", r);
3533 if (expected != frameDigests[r][d])
3535 printf(
"[FAIL] %-19s first divergence: row %d (%s) %s digest "
3536 "(expected 0x%016llx, got 0x%016llx)\n",
3537 "frame", r, frRowLabel[r], surface[d],
3538 (
unsigned long long)expected, (
unsigned long long)frameDigests[r][d]);
3547 printf(
"[FAIL] %-19s candidate diverges from the reference at row %d (%s), "
3548 "field %s (cand %ld, ref %ld)\n",
3549 "frame", frFailRow, frRowLabel[frFailRow], frFailField, frFailCand, frFailRef);
3555 printf(
"[PASS] %-19s %3d rows\n",
"frame", frameRowCount);
3563static bool resetCheck(
void)
3565 if (pico9918_frame_count_impl() == 0)
3567 printf(
"[FAIL] %-19s frame count was already zero, so this proves nothing\n",
"reset");
3573 if (pico9918_frame_count_impl() != 0)
3575 printf(
"[FAIL] %-19s pico9918_reset left the frame count at %d, so the splash gate stays shut\n",
3576 "reset", pico9918_frame_count_impl());
3580 printf(
"[PASS] %-19s splash gate reopens\n",
"reset");
3589static bool unlockCheck(
void)
3598 {57, 0x00,
false,
"a lock write leaves it locked" },
3599 {57, 0x1c,
false,
"one 0x1c is not enough" },
3600 {57, 0x1c,
true,
"two consecutive 0x1c unlock" },
3601 {57, 0x1c,
true,
"a redundant unlock is a no-op" },
3602 {57, 0x00,
false,
"any other value locks on the spot" },
3603 {57, 0x1c,
false,
"one 0x1c is not enough" },
3604 { 1, 0x00,
false,
"any other register write clears the run"},
3605 {57, 0x1c,
false,
"so that pair is not a consecutive one" },
3606 {57, 0x00,
false,
"back to a known locked state" },
3607 {57, 0x1f,
false,
"the low two bits are ignored" },
3608 {57, 0x1f,
true,
"so 0x1f unlocks exactly as 0x1c does" },
3611 for (
int i = 0; i < (int)(
sizeof(steps) /
sizeof(steps[0])); ++i)
3613 regWrite(steps[i].reg, steps[i].value);
3614 if (PICO9918_UNLOCKED(tms9918) != steps[i].unlocked)
3616 printf(
"[FAIL] %-19s step %d, R%d = %02x: %s\n",
"unlock", i + 1, steps[i].reg, steps[i].value,
3622 printf(
"[PASS] %-19s %d VR57 latch steps\n",
"unlock", (
int)(
sizeof(steps) /
sizeof(steps[0])));
3629int main(
int argc,
char* argv[])
3631 bool capture =
false;
3632 const char* dataDir = GOLDEN_DATA_DIR;
3634 for (
int i = 1; i < argc; ++i)
3636 if (strcmp(argv[i],
"--capture") == 0)
3640 else if (strcmp(argv[i],
"--data") == 0 && i + 1 < argc)
3642 dataDir = argv[++i];
3646 printf(
"usage: golden_runner [--capture] [--data DIR]\n");
3654 for (
int i = 0; i < SCENE_COUNT; ++i)
3656 bool ok = capture ? captureScene(&scenes[i], dataDir)
3657 : compareScene(&scenes[i], dataDir);
3658 if (!ok) ++failures;
3666 const int artifacts = SCENE_COUNT + 2;
3667 if (!(capture ? overlayCapture(dataDir) : overlayCompare(dataDir))) ++failures;
3675 if (!(capture ? frameCapture(dataDir) : frameCompare(dataDir))) ++failures;
3678 if (!resetCheck()) ++failures;
3681 if (!unlockCheck()) ++failures;
3685 printf(
"%d artifact(s) captured to %s\n", artifacts - failures, dataDir);
3689 printf(
"%d of %d artifact(s) FAILED\n", failures, artifacts);
3693 printf(
"all %d artifact(s) passed\n", artifacts);
3696 return failures ? 1 : 0;
void pico9918_diag_set_clock_hz(float clockHz)
system clock, Hz
void pico9918_diag_init(void)
one-time initialisation of the panel value strings
void pico9918_diag_set_temperature(float tempC)
core temperature, degrees C
void pico9918_diag_set_version_info(const char *hwVersion, const char *fwVersion)
Version identity for the HWVER / FWVER rows.
void pico9918_diag_update(pico9918_t *tms9918, uint32_t frameCount)
recompute the panel values - call once per frame
void pico9918_diag_config_updated(pico9918_t *tms9918)
rebuild the panel row table - call whenever the PICO9918_CONF_DIAG* bytes change
int pico9918_diag_render_text(uint16_t scanline, const char *text, uint16_t x, uint16_t y, PICO9918_PIXEL_T fg, PICO9918_PIXEL_T *pixels)
render text into the scanline buffer, if row scanline falls in the glyph band starting at y.
void pico9918_diag_render(pico9918_t *tms9918, uint16_t y, uint32_t vVirtualPixels, PICO9918_PIXEL_T *pixels)
render the diagnostics panels for border row y
void pico9918_diag_set_output_name(const char *name, const char *units)
Display-mode label for the OUTPUT row, e.g.
void pico9918_diag_set_frame_rate(float frameRateHz)
Host display timing, Hz.
void pico9918_diag_update_render_time(uint32_t renderTime, uint32_t frameTime)
accumulate one scanline's render and total time, in microseconds
pico9918-core - Diagnostics overlay
#define PICO9918_DIAG_CHAR_WIDTH
glyph cell width, pixels
#define PICO9918_DIAG_CHAR_HEIGHT
glyph cell height, pixels
bool pico9918_unlocked(pico9918_t *tms9918)
see the header.
uint8_t pico9918_read_data(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
void pico9918_write_addr(pico9918_t *tms9918, uint8_t data)
write an address (mode = 1) to the tms9918
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
uint8_t pico9918_read_data_no_inc(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
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
void pico9918_write_data(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
pico9918-core - core interface
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
#define PICO9918_INST_ONLY
pass the instance as the only argument
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
#define TMS9918_PIXELS_X
active display width, every mode
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
#define PICO9918_INST
pass the instance ahead of other arguments
void pico9918_frame_update_interrupts(pico9918_t *tms9918, uint8_t tempStatus)
merge newly raised status flags into the SR0 latch, publish it, and bring the /INT pin into agreement
pico9918_frame_geometry_t pico9918_frame_geometry(pico9918_t *tms9918, pico9918_frame_display_t *display)
see the header.
pico9918-core - frame module
pico9918-core - the private instance layout
PICO9918_INLINE void pico9918_frame_sync_int_impl(pico9918_t *tms9918)
THE single implementation of "a status register was just read" - shared by the public read (pico9918_...
PICO9918_INLINE_HOT void pico9918_set_status_impl(pico9918_t *tms9918, uint8_t status)
set status flag
void pico9918_splash_allow_hide(void)
allow the splash to animate back out - the host calls this once the display has been enabled
void pico9918_splash_render(uint16_t y, uint32_t frameCount, uint32_t vBorder, uint32_t vPixels, uint32_t vVirtualPixels, PICO9918_PIXEL_T *pixels)
render the splash logo into the scanline buffer, if row y falls in the logo band.
void pico9918_splash_reset(void)
restart the splash animation (after... reset)
pico9918-core - Splash overlay
the host's mutable vertical display parameters, as the end-of-frame geometry sees them
uint16_t vVirtualPixels
(in, and out when yScale > 1)
uint8_t vPixelScale
(in, and out when yScale > 1)
the vertical geometry the end of frame derives
uint32_t vBorder
top border offset, in virtual lines
uint32_t triggerScanline
vBorder + vPixels
int vPixels
active VDP display lines