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;
72static uint32_t lcgState;
74static void lcgSeed(uint32_t seed)
79static uint8_t lcgByte(
void)
81 lcgState = lcgState * 1664525u + 1013904223u;
82 return (uint8_t)(lcgState >> 24);
89static void regWrite(uint8_t reg, uint8_t value)
95static void vramSetWriteAddr(uint16_t addr)
103static void vramSetReadAddr(uint16_t addr)
109static void vramFillLcg(uint16_t addr,
int count)
111 vramSetWriteAddr(addr);
115static void vramFillByte(uint16_t addr, uint8_t value,
int count)
117 vramSetWriteAddr(addr);
121static void vramWriteBytes(uint16_t addr,
const uint8_t* bytes,
int count)
123 vramSetWriteAddr(addr);
128static void unlockF18a(
void)
137static void paletteWriteLcg(
int count)
151static void sceneBegin(
void)
181static void dumpPalette(uint8_t out[GOLDEN_PAL_BYTES])
197 for (
int line = 0; line < 2; ++line)
201 uint8_t* dst = out + line * 64;
202 for (
int i = 0; i < 64; ++i)
204 const uint8_t* p = probe + i * 4;
205 dst[i] = (uint8_t)((p[0] << 6) | (p[1] << 4) | (p[2] << 2) | p[3]);
247#define FNV64_OFFSET 0xcbf29ce484222325ull
248#define FNV64_PRIME 0x00000100000001b3ull
250static uint64_t fnv1a(
const void* data,
size_t len)
252 const uint8_t* p = (
const uint8_t*)data;
253 uint64_t h = FNV64_OFFSET;
281static uint16_t refPixel(uint16_t pram)
283 const unsigned g = (pram >> 12) & 0x0f;
284 const unsigned b = (pram >> 8) & 0x0f;
285 const unsigned r = (pram ) & 0x0f;
286 return (uint16_t)((g << 12) | (b << 8) | (g << 4) | r);
290#define POST_PAL_PIXELS (GOLDEN_BYTES_PER_LINE * 2)
292static PICO9918_PIXEL_T libPixels[POST_PAL_PIXELS];
293static uint16_t refPixels[POST_PAL_PIXELS];
300_Static_assert(
sizeof(PICO9918_PIXEL_T) ==
sizeof(uint16_t),
301 "golden surfaces require the 16-bit Pico pixel policy");
302_Static_assert(
sizeof(PICO9918_PALETTE_LUT_T) ==
sizeof(uint32_t),
303 "golden expansion requires a 32-bit packed-pair LUT");
317static uint32_t refLut[256];
331static void refRegenerate(
bool paired)
333 const uint16_t* pram = tms9918->vram.map.pram;
337 for (
int i = 0; i < 64; ++i)
339 const uint32_t px = refPixel(pram[i]);
340 refLut[i] = px | (px << 16);
346 for (
int i = 0; i < 16; ++i)
348 pal[i] = refPixel(pram[i]);
349 refLut[i] = (uint32_t)pal[i] | ((uint32_t)pal[i] << 16);
351 for (
int j = 16; j < 256; ++j)
353 refLut[j] = ((uint32_t)pal[j & 0x0f] << 16) | pal[j >> 4];
361static void refExpand(uint16_t* dst,
const uint8_t* src,
int n)
363 for (
int i = 0; i < n; ++i)
365 const uint32_t entry = refLut[src[i]];
366 dst[i * 2 + 0] = (uint16_t)entry;
367 dst[i * 2 + 1] = (uint16_t)(entry >> 16);
372static void expandLine(
const uint8_t* indexed, uint64_t digests[GOLDEN_DIGESTS_PER_LINE])
374 PICO9918_EXPAND_INDEXED(libPixels, indexed, GOLDEN_BYTES_PER_LINE, pico9918_palette_lut);
375 refExpand(refPixels, indexed, GOLDEN_BYTES_PER_LINE);
377 digests[0] = fnv1a(libPixels,
sizeof(libPixels));
378 digests[1] = fnv1a(refPixels,
sizeof(refPixels));
384static int refDivergence(
void)
386 for (
int i = 0; i < POST_PAL_PIXELS; ++i)
388 if (libPixels[i] != refPixels[i])
return i;
398static void sceneGraphicsI(
void)
401 vramFillLcg(0x0000, 0x4000);
414static void sceneGraphicsII(
void)
417 vramFillLcg(0x0000, 0x4000);
429static void sceneText(
void)
432 vramFillLcg(0x0000, 0x4000);
441static void sceneMulticolor(
void)
444 vramFillLcg(0x0000, 0x4000);
456static void sceneText80(
void)
459 vramFillLcg(0x0000, 0x4000);
469static void sceneSpritesMax(
void)
471 static const uint8_t sat[] = {
473 0x27, 0x00, 0x00, 0x02,
474 0x27, 0x28, 0x00, 0x03,
475 0x27, 0x50, 0x00, 0x04,
476 0x27, 0x78, 0x00, 0x05,
477 0x27, 0xa0, 0x00, 0x06,
478 0x63, 0x64, 0x00, 0x08,
479 0x63, 0x68, 0x01, 0x09,
480 0xd0, 0x00, 0x00, 0x00
483 vramFillByte(0x0000, 0x00, 0x4000);
484 vramFillByte(0x1800, 0xff, 8);
485 vramFillByte(0x1808, 0xf0, 8);
486 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
500static void sceneF18aUnlocked(
void)
503 vramFillLcg(0x0000, 0x4000);
529static void sceneF18aEcm3Bml(
void)
532 vramFillLcg(0x0000, 0x4000);
556static void sceneF18aBmlWrap(
void)
559 vramFillLcg(0x0000, 0x4000);
583static void sceneF18aBmlStride(
void)
586 vramFillLcg(0x0000, 0x4000);
608static void sceneF18aText80Attrs(
void)
611 vramFillLcg(0x0000, 0x4000);
631static void sceneF18aVramSnapshot(
void)
634 vramFillLcg(0x0000, 0x4000);
668static void sceneF18aBmlPriority(
void)
670 static const uint8_t sat[] = {
672 0x10, 0x20, 0x01, 0x06,
673 0x2c, 0x10, 0x00, 0x03,
674 0x50, 0x58, 0x04, 0x05,
675 0x6c, 0xa0, 0x08, 0x07,
676 0xd0, 0x00, 0x00, 0x00
680 vramFillLcg(0x0000, 0x4000);
681 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
706static void sceneF18aEcm0(
void)
709 vramFillLcg(0x0000, 0x4000);
716 vramSetReadAddr(0x2600);
721 for (
int i = 0; i < 4; ++i)
723 sat[i * 4 + 0] = (uint8_t)(0x08 + i * 0x28);
724 sat[i * 4 + 1] = rd[i * 3];
725 sat[i * 4 + 2] = rd[i * 3 + 1];
726 sat[i * 4 + 3] = rd[i * 3 + 2] & 0x0f;
732 static const uint8_t satTail[] = {
733 0x50, 0x40, 0x00, 0x05,
734 0x50, 0x44, 0x01, 0x00,
735 0x78, 0x80, 0x00, 0x0b,
736 0xd0, 0x00, 0x00, 0x00
738 memcpy(sat + 16, satTail,
sizeof(satTail));
741 vramFillByte(0x1800, 0xff, 16);
742 vramFillByte(0x0000, 0x53, 32);
745 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
762static void sceneF18aGfx2(
void)
764 static const uint8_t sat[] = {
766 0x20, 0x30, 0x00, 0x07,
767 0x50, 0x80, 0x04, 0x0b,
768 0x60, 0x40, 0x0c, 0x04,
769 0x85, 0xc8, 0x08, 0x0d,
770 0xd0, 0x00, 0x00, 0x00
774 vramFillLcg(0x0000, 0x4000);
775 vramWriteBytes(0x3b00, sat, (
int)
sizeof(sat));
802static void sceneRawReset(
void)
805 vramFillLcg(0x0000, 0x4000);
824static const Scene scenes[] = {
825 {
"graphics-i", 192, sceneGraphicsI },
826 {
"graphics-ii", 192, sceneGraphicsII },
827 {
"text", 192, sceneText },
828 {
"multicolor", 192, sceneMulticolor },
829 {
"text80", 192, sceneText80 },
830 {
"sprites-max", 192, sceneSpritesMax },
831 {
"f18a-unlocked", 240, sceneF18aUnlocked },
832 {
"f18a-ecm3-bml", 192, sceneF18aEcm3Bml },
833 {
"f18a-text80-attrs", 192, sceneF18aText80Attrs },
834 {
"f18a-vram-snapshot", 192, sceneF18aVramSnapshot},
835 {
"f18a-bml-priority", 192, sceneF18aBmlPriority },
836 {
"f18a-bml-wrap", 192, sceneF18aBmlWrap },
837 {
"f18a-bml-stride", 192, sceneF18aBmlStride },
838 {
"f18a-ecm0", 192, sceneF18aEcm0 },
839 {
"f18a-gfx2", 192, sceneF18aGfx2 },
840 {
"raw-reset", 8, sceneRawReset },
843#define SCENE_COUNT ((int)(sizeof(scenes) / sizeof(scenes[0])))
854static uint8_t frame[MAX_LINES][GOLDEN_BYTES_PER_LINE + 1];
857static uint8_t palDump[GOLDEN_PAL_BYTES];
860static uint64_t digests[MAX_LINES][GOLDEN_DIGESTS_PER_LINE];
865static int refFailLine;
866static int refFailPixel;
867static uint16_t refFailLib;
868static uint16_t refFailRef;
870static void renderScene(
const Scene* scene,
int* lines5s,
int* linesCol)
880 for (
int y = 0; y < scene->lines; ++y)
882 uint8_t* pixels = frame[y];
892 if (pico9918_palette_dirty())
896 pico9918_palette_regenerate();
897 refRegenerate(paired);
902 pixels[GOLDEN_BYTES_PER_LINE] = status;
906 expandLine(pixels, digests[y]);
910 const int bad = refDivergence();
915 refFailLib = (uint16_t)libPixels[bad];
916 refFailRef = refPixels[bad];
923 dumpPalette(palDump);
947static void putU32(FILE* f, uint32_t v)
949 uint8_t b[4] = { (uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24) };
953static void putU64(FILE* f, uint64_t v)
955 putU32(f, (uint32_t)v);
956 putU32(f, (uint32_t)(v >> 32));
959static bool getU32(FILE* f, uint32_t* v)
962 if (fread(b, 1, 4, f) != 4)
return false;
963 *v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
967static bool getU64(FILE* f, uint64_t* v)
970 if (!getU32(f, &lo))
return false;
971 if (!getU32(f, &hi))
return false;
972 *v = (uint64_t)lo | ((uint64_t)hi << 32);
976static void scenePath(
char* buf,
size_t bufLen,
const char* dataDir,
const char* name)
978 snprintf(buf, bufLen,
"%s/%s.golden", dataDir, name);
981static bool captureScene(
const Scene* scene,
const char* dataDir)
984 scenePath(path,
sizeof(path), dataDir, scene->name);
986 int lines5s, linesCol;
987 renderScene(scene, &lines5s, &linesCol);
991 if (refFailLine >= 0)
993 printf(
"[ERROR] %-19s library expansion diverges from the reference at "
994 "line %d, pixel %d (lib 0x%04x, ref 0x%04x) - NOT captured\n",
995 scene->name, refFailLine, refFailPixel, refFailLib, refFailRef);
999 FILE* f = fopen(path,
"wb");
1002 printf(
"[ERROR] %s: cannot open %s for writing\n", scene->name, path);
1006 char name[GOLDEN_NAME_LEN] = { 0 };
1007 strncpy(name, scene->name, GOLDEN_NAME_LEN - 1);
1009 fwrite(GOLDEN_MAGIC, 1, 4, f);
1010 putU32(f, GOLDEN_VERSION);
1011 fwrite(name, 1, GOLDEN_NAME_LEN, f);
1012 putU32(f, (uint32_t)scene->lines);
1013 putU32(f, GOLDEN_BYTES_PER_LINE);
1015 putU32(f, GOLDEN_PAL_ENTRIES);
1016 putU32(f, GOLDEN_DIGESTS_PER_LINE);
1019 for (
int y = 0; y < scene->lines; ++y)
1021 fwrite(frame[y], 1, GOLDEN_BYTES_PER_LINE + 1, f);
1023 for (
int y = 0; y < scene->lines; ++y)
1025 for (
int d = 0; d < GOLDEN_DIGESTS_PER_LINE; ++d) putU64(f, digests[y][d]);
1027 fwrite(palDump, 1, GOLDEN_PAL_BYTES, f);
1030 printf(
"[CAPTURED] %-19s %3d lines (5S on %d lines, COL on %d lines) -> %s\n",
1031 scene->name, scene->lines, lines5s, linesCol, path);
1035static bool compareScene(
const Scene* scene,
const char* dataDir)
1038 scenePath(path,
sizeof(path), dataDir, scene->name);
1040 FILE* f = fopen(path,
"rb");
1043 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
1049 uint32_t version = 0, lines = 0, bytesPerLine = 0, statusPerLine = 0, palEntries = 0;
1050 uint32_t digestsPerLine = 0;
1051 char name[GOLDEN_NAME_LEN];
1054 fread(magic, 1, 4, f) == 4 && memcmp(magic, GOLDEN_MAGIC, 4) == 0 &&
1055 getU32(f, &version) && version == GOLDEN_VERSION &&
1056 fread(name, 1, GOLDEN_NAME_LEN, f) == GOLDEN_NAME_LEN &&
1057 getU32(f, &lines) &&
1058 getU32(f, &bytesPerLine) && bytesPerLine == GOLDEN_BYTES_PER_LINE &&
1059 getU32(f, &statusPerLine) && statusPerLine == 1 &&
1060 getU32(f, &palEntries) && palEntries == GOLDEN_PAL_ENTRIES &&
1061 getU32(f, &digestsPerLine) && digestsPerLine == GOLDEN_DIGESTS_PER_LINE;
1063 if (!headerOk || lines != (uint32_t)scene->lines)
1065 printf(
"[FAIL] %-19s bad golden header in %s\n", scene->name, path);
1070 int lines5s, linesCol;
1071 renderScene(scene, &lines5s, &linesCol);
1073 static uint8_t expected[GOLDEN_BYTES_PER_LINE + 1];
1074 for (
int y = 0; y < scene->lines; ++y)
1076 if (fread(expected, 1,
sizeof(expected), f) !=
sizeof(expected))
1078 printf(
"[FAIL] %-19s truncated golden file at line %d\n", scene->name, y);
1082 if (memcmp(expected, frame[y],
sizeof(expected)) != 0)
1085 while (expected[i] == frame[y][i]) ++i;
1086 if (i == GOLDEN_BYTES_PER_LINE)
1088 printf(
"[FAIL] %-19s first divergence: line %d status byte (expected 0x%02x, got 0x%02x)\n",
1089 scene->name, y, expected[i], frame[y][i]);
1093 printf(
"[FAIL] %-19s first divergence: line %d, byte %d (expected 0x%02x, got 0x%02x)\n",
1094 scene->name, y, i, expected[i], frame[y][i]);
1103 static const char*
const surfaceName[GOLDEN_DIGESTS_PER_LINE] = {
"library",
"reference" };
1104 for (
int y = 0; y < scene->lines; ++y)
1106 for (
int d = 0; d < GOLDEN_DIGESTS_PER_LINE; ++d)
1108 uint64_t expectedDigest;
1109 if (!getU64(f, &expectedDigest))
1111 printf(
"[FAIL] %-19s truncated golden file in digest block at line %d\n",
1116 if (expectedDigest != digests[y][d])
1118 printf(
"[FAIL] %-19s first divergence: line %d %s post-palette digest "
1119 "(expected 0x%016llx, got 0x%016llx)\n",
1120 scene->name, y, surfaceName[d],
1121 (
unsigned long long)expectedDigest, (
unsigned long long)digests[y][d]);
1130 if (refFailLine >= 0)
1132 printf(
"[FAIL] %-19s library expansion diverges from the reference at "
1133 "line %d, pixel %d (lib 0x%04x, ref 0x%04x)\n",
1134 scene->name, refFailLine, refFailPixel, refFailLib, refFailRef);
1139 static uint8_t expectedPal[GOLDEN_PAL_BYTES];
1140 if (fread(expectedPal, 1,
sizeof(expectedPal), f) !=
sizeof(expectedPal))
1142 printf(
"[FAIL] %-19s truncated golden file in palette block\n", scene->name);
1146 if (memcmp(expectedPal, palDump,
sizeof(expectedPal)) != 0)
1149 while (expectedPal[i] == palDump[i]) ++i;
1150 const int entry = i >> 1;
1151 printf(
"[FAIL] %-19s first divergence: palette entry %d (expected 0x%04x, got 0x%04x)\n",
1153 expectedPal[entry * 2] | (expectedPal[entry * 2 + 1] << 8),
1154 palDump[entry * 2] | (palDump[entry * 2 + 1] << 8));
1160 printf(
"[PASS] %-19s %3d lines\n", scene->name, scene->lines);
1196#define OVERLAY_MAGIC "TMSO"
1197#define OVERLAY_VERSION 1
1215extern uint8_t font[];
1216extern uint8_t splash[];
1217extern PICO9918_PIXEL_T splash_pal[];
1219#define OVERLAY_FONT_WIDTH 768
1220#define OVERLAY_FONT_HEIGHT 6
1221#define OVERLAY_SPLASH_WIDTH 176
1222#define OVERLAY_SPLASH_HEIGHT 10
1228#define fontWidth goldenUnusedFontWidth
1229#define fontHeight goldenUnusedFontHeight
1230#define splashWidth goldenUnusedSplashWidth
1231#define splashHeight goldenUnusedSplashHeight
1232#include "overlay/bmp_font.h"
1233#include "overlay/bmp_splash.h"
1239_Static_assert(OVERLAY_FONT_WIDTH == FONT_WIDTH &&
1240 OVERLAY_FONT_HEIGHT == FONT_HEIGHT,
1241 "overlay font geometry has drifted from the generated asset");
1242_Static_assert(OVERLAY_SPLASH_WIDTH == SPLASH_WIDTH &&
1243 OVERLAY_SPLASH_HEIGHT == SPLASH_HEIGHT,
1244 "overlay splash geometry has drifted from the generated asset");
1257#define OVERLAY_PIXELS_X 642
1259static PICO9918_PIXEL_T ovLib[OVERLAY_PIXELS_X];
1260static PICO9918_PIXEL_T ovRef[OVERLAY_PIXELS_X];
1272#define OVERLAY_PREFILL_SEED 0xfedc
1274static void overlayPrefill(
void)
1276 uint16_t v = OVERLAY_PREFILL_SEED;
1277 for (
int i = 0; i < OVERLAY_PIXELS_X; ++i)
1279 ovLib[i] = (PICO9918_PIXEL_T)v;
1280 ovRef[i] = (PICO9918_PIXEL_T)v;
1281 v = (uint16_t)(v * 2053u + 13849u);
1288static void overlayRestorePrefill(PICO9918_PIXEL_T* dst,
int from,
int to)
1290 uint16_t v = OVERLAY_PREFILL_SEED;
1291 for (
int i = 0; i < to; ++i)
1293 if (i >= from) dst[i] = (PICO9918_PIXEL_T)v;
1294 v = (uint16_t)(v * 2053u + 13849u);
1315static int refDarken(
int x, PICO9918_PIXEL_T* pixels)
1317 const unsigned in = pixels[x];
1318 pixels[x] = (PICO9918_PIXEL_T)((in >> 2) & 0x333);
1339#define OVERLAY_FONT_ROW_BYTES (OVERLAY_FONT_WIDTH / 8)
1341static uint8_t refGlyphRowBits(
char ch,
int fy)
1343 const int col = (
unsigned char)ch - 32;
1344 return font[fy * OVERLAY_FONT_ROW_BYTES + col];
1360static int refRenderText(uint16_t scanline,
const char* text, uint16_t x, uint16_t y,
1361 PICO9918_PIXEL_T fg, PICO9918_PIXEL_T* pixels)
1363 const int fontY = (int)scanline - (
int)y;
1367 for (
const char* p = text; *p; ++p)
1369 const uint8_t bits = refGlyphRowBits(*p, fontY);
1373 else xPos = refDarken(xPos, pixels);
1393static int refLogoOffset;
1394static bool refCanHide;
1398#define OVERLAY_SPLASH_ENTER_FRAMES 60
1399#define OVERLAY_SPLASH_HOLD_FRAMES 180
1400#define OVERLAY_SPLASH_START_POS \
1401 (OVERLAY_SPLASH_ENTER_FRAMES + OVERLAY_SPLASH_HEIGHT + 2)
1403static void refSplashReset(
void)
1405 refLogoOffset = OVERLAY_SPLASH_START_POS;
1408static void refSplashRender(uint16_t y, uint32_t frameCount, uint32_t vBorder,
1409 uint32_t vPixels, uint32_t vVirtualPixels,
1410 PICO9918_PIXEL_T* pixels)
1414 if (frameCount < OVERLAY_SPLASH_ENTER_FRAMES) --refLogoOffset;
1415 else if (refCanHide &&
1416 frameCount > (OVERLAY_SPLASH_ENTER_FRAMES + OVERLAY_SPLASH_HOLD_FRAMES))
1420 if (y > vVirtualPixels)
return;
1423 const uint16_t row =
1424 (uint16_t)(y - (uint16_t)(vBorder + vPixels + (uint32_t)refLogoOffset));
1425 if (row >= OVERLAY_SPLASH_HEIGHT)
return;
1427 const int leftBorderPx = 4;
1428 const uint8_t* src = splash + (row * OVERLAY_SPLASH_WIDTH / 4);
1429 for (
int x = 0; x < OVERLAY_SPLASH_WIDTH; x += 4)
1431 const uint8_t c = *src++;
1432 for (
int px = 0; px < 4; ++px)
1434 const uint8_t palIndex = (uint8_t)((c >> (6 - px * 2)) & 0x03);
1435 if (palIndex) pixels[leftBorderPx + x + px] = splash_pal[palIndex];
1450#define OVERLAY_MAX_ROWS 4096
1452static uint64_t overlayDigests[OVERLAY_MAX_ROWS][2];
1453static int overlayRowCount;
1456static int ovFailRow;
1457static int ovFailPixel;
1458static uint16_t ovFailLib;
1459static uint16_t ovFailRef;
1463static const char* ovRowLabel[OVERLAY_MAX_ROWS];
1464static const char* ovCurrentLabel =
"";
1466static void overlayEmitRow(
void)
1468 if (overlayRowCount >= OVERLAY_MAX_ROWS)
1473 printf(
"[ERROR] overlay: row budget %d exhausted - raise OVERLAY_MAX_ROWS\n",
1478 const int row = overlayRowCount++;
1479 ovRowLabel[row] = ovCurrentLabel;
1480 overlayDigests[row][0] = fnv1a(ovLib,
sizeof(ovLib));
1481 overlayDigests[row][1] = fnv1a(ovRef,
sizeof(ovRef));
1485 for (
int i = 0; i < OVERLAY_PIXELS_X; ++i)
1487 if (ovLib[i] != ovRef[i])
1491 ovFailLib = (uint16_t)ovLib[i];
1492 ovFailRef = (uint16_t)ovRef[i];
1516#define OVERLAY_CENTRE_X(text) \
1517 ((uint16_t)((OVERLAY_PIXELS_X - (sizeof(text) - 1) * PICO9918_DIAG_CHAR_WIDTH) / 2))
1520#define OVERLAY_BANNER_FG ((PICO9918_PIXEL_T)(PICO9918_PIXEL_FROM_RGB12(0xff0f) & 0x0fff))
1526extern const PICO9918_PIXEL_T labelColor;
1527extern const PICO9918_PIXEL_T valueColor;
1528extern const PICO9918_PIXEL_T unitsColor;
1538static void overlayTextCase(
const char* label,
const char* text, uint16_t x,
1539 uint16_t y, PICO9918_PIXEL_T fg)
1541 ovCurrentLabel = label;
1547 const int refX = refRenderText((uint16_t)scanline, text, x, y, fg, ovRef);
1549 ovLib[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)libX;
1550 ovRef[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)refX;
1556static void overlayTextGroup(
void)
1559 overlayTextCase(
"banner-await-pc",
"POWER CYCLE TO TEST NEW CONFIGURATION",
1560 OVERLAY_CENTRE_X(
"POWER CYCLE TO TEST NEW CONFIGURATION"), 8, OVERLAY_BANNER_FG);
1561 overlayTextCase(
"banner-await-ok",
"OPEN CONFIGURATOR TO CONFIRM NEW SETTINGS",
1562 OVERLAY_CENTRE_X(
"OPEN CONFIGURATOR TO CONFIRM NEW SETTINGS"), 8, OVERLAY_BANNER_FG);
1565 overlayTextCase(
"label-color",
"HWVER : ", 2, 0, labelColor);
1566 overlayTextCase(
"value-color",
"1.0.2", 50, 0, valueColor);
1567 overlayTextCase(
"units-color",
"MHZ", 80, 0, unitsColor);
1572 overlayTextCase(
"font-row0",
" !\"#$%&'()*+,-./", 4, 0, labelColor);
1573 overlayTextCase(
"font-row1",
"0123456789:;<=>?", 4, 0, labelColor);
1574 overlayTextCase(
"font-row2",
"@ABCDEFGHIJKLMNO", 4, 0, labelColor);
1575 overlayTextCase(
"font-row3",
"PQRSTUVWXYZ[\\]^_", 4, 0, labelColor);
1579 overlayTextCase(
"reg-glyphs",
"R49:))((", 8, 0, valueColor);
1583 ovCurrentLabel =
"chained-run";
1591 rx = refRenderText((uint16_t)scanline,
"TEMP : ", (uint16_t)rx, 0, labelColor, ovRef);
1592 rx = refRenderText((uint16_t)scanline,
"42.75", (uint16_t)rx, 0, valueColor, ovRef);
1593 rx = refRenderText((uint16_t)scanline,
"^C", (uint16_t)rx, 0, unitsColor, ovRef);
1594 ovLib[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)lx;
1595 ovRef[OVERLAY_PIXELS_X - 1] = (PICO9918_PIXEL_T)rx;
1600 overlayTextCase(
"y-offset",
"OUTPUT: 480P @60", 2, 200, valueColor);
1604 overlayTextCase(
"x-zero",
"8", 0, 0, valueColor);
1626#define OVERLAY_SPLASH_VVIRT 240u
1627#define OVERLAY_SPLASH_VPIXELS 192u
1628#define OVERLAY_SPLASH_VBORDER ((OVERLAY_SPLASH_VVIRT - OVERLAY_SPLASH_VPIXELS) / 2u)
1630static void overlaySplashFrame(uint32_t frameCount,
bool sample)
1636 OVERLAY_SPLASH_VVIRT, ovLib);
1637 refSplashRender(0, frameCount, OVERLAY_SPLASH_VBORDER, OVERLAY_SPLASH_VPIXELS,
1638 OVERLAY_SPLASH_VVIRT, ovRef);
1639 if (sample) overlayEmitRow();
1641 if (!sample)
return;
1646 for (uint16_t y = (uint16_t)(OVERLAY_SPLASH_VBORDER + OVERLAY_SPLASH_VPIXELS);
1647 y <= OVERLAY_SPLASH_VVIRT; ++y)
1651 OVERLAY_SPLASH_VVIRT, ovLib);
1652 refSplashRender(y, frameCount, OVERLAY_SPLASH_VBORDER, OVERLAY_SPLASH_VPIXELS,
1653 OVERLAY_SPLASH_VVIRT, ovRef);
1658static void overlaySplashGroup(
void)
1667 ovCurrentLabel =
"splash-enter";
1668 for (uint32_t f = 0; f < OVERLAY_SPLASH_ENTER_FRAMES; ++f)
1670 overlaySplashFrame(f, f >= 46 && f <= 50);
1675 ovCurrentLabel =
"splash-hold";
1676 for (uint32_t f = OVERLAY_SPLASH_ENTER_FRAMES; f <= 199; ++f)
1678 overlaySplashFrame(f, f == 60 || f == 199);
1689 for (uint32_t f = 200; f <= 245; ++f)
1691 overlaySplashFrame(f, f == 239 || f == 240 || f == 241 || f == 242 || f == 245);
1697 ovCurrentLabel =
"splash-exit";
1702 for (uint32_t f = 246; f <= 275; ++f)
1704 overlaySplashFrame(f, (f >= 246 && f <= 249) || (f >= 257 && f <= 261));
1711 ovCurrentLabel =
"splash-reset";
1712 overlaySplashFrame(276,
true);
1747static PICO9918_PIXEL_T refSwatch(uint16_t pram)
1749 const unsigned g = (pram >> 12) & 0x0f;
1750 const unsigned b = (pram >> 8) & 0x0f;
1751 const unsigned r = pram & 0x0f;
1752 return (PICO9918_PIXEL_T)((b << 8) | (g << 4) | r);
1758static void refPalettePanel(
int y, uint32_t vVirtualPixels, PICO9918_PIXEL_T* pixels)
1760 const int row = y % 6;
1761 const uint8_t palette = (uint8_t)((y - ((
int)vVirtualPixels - 24)) / 6);
1762 if (palette >= 4)
return;
1764 char buf[] =
"PALETTE 0:";
1765 buf[8] = (char)(
'0' + palette);
1766 refRenderText((uint16_t)row, buf, 2, 0, labelColor, pixels);
1768 if (row >= 5)
return;
1771 for (
int c = 0; c < 16; ++c)
1773 const PICO9918_PIXEL_T sw = refSwatch(tms9918->vram.map.pram[palette * 16 + c]);
1774 for (
int i = 0; i < 30; ++i) pixels[pair * 2 + i] = sw;
1783static void refRegisterPanel(
int diagRow,
int row, PICO9918_PIXEL_T* pixels)
1785 static const char*
const bins[] = {
1786 "((((",
"((()",
"(()(",
"(())",
1787 "()((",
"()()",
"())(",
"()))",
1788 ")(((",
")(()",
")()(",
")())",
1789 "))((",
"))()",
")))(",
"))))",
1791 const unsigned reg = (unsigned)diagRow;
1792 char buf[] =
"R00:";
1793 buf[1] = (char)(
'0' + reg / 10u);
1794 buf[2] = (char)(
'0' + reg % 10u);
1797 xPos = refRenderText((uint16_t)row, buf, (uint16_t)xPos, 0, labelColor, pixels);
1800 xPos = refDarken(xPos, pixels);
1801 xPos = refDarken(xPos, pixels);
1802 xPos = refRenderText((uint16_t)row, bins[value >> 4], (uint16_t)xPos, 0, valueColor, pixels);
1803 xPos = refDarken(xPos, pixels);
1804 xPos = refDarken(xPos, pixels);
1805 refRenderText((uint16_t)row, bins[value & 0x0f], (uint16_t)xPos, 0, valueColor, pixels);
1810static void overlaySetConfig(uint8_t index, uint8_t value)
1812 tms9918->config[index] = value;
1847static void overlayPrimeDiag(
void)
1884 pico9918_frame_count = 7;
1885 pico9918_dropped_frames_count = 1;
1886#if PICO9918_DIAG_GPU_FRAME_COUNTER
1887 pico9918_gpu_frame_count = 12345;
1908static void overlayPanelCase(
const char* label,
bool registers,
bool address,
1909 bool palette,
bool unlocked,
bool gfx2,
1916 vramFillLcg(0x0000, 0x4000);
1920 paletteWriteLcg(64);
1938 tms9918->vram.map.pram[0] = 0x3f70;
1939 tms9918->vram.map.pram[5] = 0xc59a;
1940 tms9918->vram.map.pram[17] = 0x08f3;
1941 tms9918->vram.map.pram[33] = 0xfae1;
1942 tms9918->vram.map.pram[49] = 0x714c;
1943 tms9918->palDirty = 1;
1955 regWrite(0, gfx2 ? 0x02 : 0x00);
1988 overlaySetConfig(PICO9918_CONF_DIAG, 1);
1989 overlaySetConfig(PICO9918_CONF_DIAG_REGISTERS, registers);
1990 overlaySetConfig(PICO9918_CONF_DIAG_PERFORMANCE, performance);
1991 overlaySetConfig(PICO9918_CONF_DIAG_PALETTE, palette);
1992 overlaySetConfig(PICO9918_CONF_DIAG_ADDRESS, address);
2013 ovCurrentLabel = label;
2026 for (uint16_t y = 1; y <= OVERLAY_SPLASH_VVIRT; ++y)
2045 memcpy(ovRef, ovLib,
sizeof(ovRef));
2047 const int panelY = (int)y - 1;
2048 const int diagRow = panelY / 6;
2049 const int row = panelY % 6;
2055 const bool refStrip = palette && panelY > (int)OVERLAY_SPLASH_VVIRT - 27;
2058 overlayRestorePrefill(ovRef, 0, OVERLAY_PIXELS_X);
2059 refPalettePanel(panelY + 2, OVERLAY_SPLASH_VVIRT, ovRef);
2065 if (unlocked) maxReg += 30;
2066 if (diagRow < maxReg)
2068 static const int extRegRef[] = { 10, 11, 15, 19, 24, 25, 26, 27,
2069 28, 29, 30, 31, 32, 33, 34, 35,
2070 36, 37, 38, 48, 49, 50, 51, 54, 55,
2071 56, 57, 58, 59, 63 };
2072 const int reg = (diagRow >= 8) ? extRegRef[diagRow - 8] : diagRow;
2075 if (!refStrip) overlayRestorePrefill(ovRef, regPanelX, OVERLAY_PIXELS_X);
2076 refRegisterPanel(reg, row, ovRef);
2084static void overlayPanelGroup(
void)
2086 overlayPanelCase(
"panel-registers",
true,
false,
false,
false,
false,
false);
2087 overlayPanelCase(
"panel-registers-unlocked",
true,
false,
false,
true,
false,
false);
2088 overlayPanelCase(
"panel-address",
false,
true,
false,
false,
false,
false);
2089 overlayPanelCase(
"panel-palette",
false,
false,
true,
true,
false,
false);
2090 overlayPanelCase(
"panel-all",
true,
true,
true,
true,
false,
false);
2093 overlayPanelCase(
"panel-address-gfx2",
false,
true,
false,
false,
true,
false);
2108 overlayPanelCase(
"panel-performance",
false,
false,
false,
false,
false,
true);
2109 overlayPanelCase(
"panel-performance-address",
false,
true,
false,
true,
false,
true);
2113static void overlayRender(
void)
2115 overlayRowCount = 0;
2124 overlaySplashGroup();
2125 overlayPanelGroup();
2128static bool overlayCapture(
const char* dataDir)
2134 printf(
"[ERROR] overlay library diverges from the reference at "
2135 "row %d (%s), pixel %d (lib 0x%04x, ref 0x%04x) - NOT captured\n",
2136 ovFailRow, ovRowLabel[ovFailRow], ovFailPixel, ovFailLib, ovFailRef);
2141 snprintf(path,
sizeof(path),
"%s/overlay.golden", dataDir);
2142 FILE* f = fopen(path,
"wb");
2145 printf(
"[ERROR] overlay: cannot open %s for writing\n", path);
2149 fwrite(OVERLAY_MAGIC, 1, 4, f);
2150 putU32(f, OVERLAY_VERSION);
2151 putU32(f, (uint32_t)overlayRowCount);
2152 putU32(f, OVERLAY_PIXELS_X);
2154 for (
int r = 0; r < overlayRowCount; ++r)
2156 putU64(f, overlayDigests[r][0]);
2157 putU64(f, overlayDigests[r][1]);
2161 printf(
"[CAPTURED] %-19s %3d rows -> %s\n",
"overlay", overlayRowCount, path);
2165static bool overlayCompare(
const char* dataDir)
2168 snprintf(path,
sizeof(path),
"%s/overlay.golden", dataDir);
2169 FILE* f = fopen(path,
"rb");
2172 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
2178 uint32_t version = 0, rows = 0, width = 0, perRow = 0;
2179 const bool headerOk =
2180 fread(magic, 1, 4, f) == 4 && memcmp(magic, OVERLAY_MAGIC, 4) == 0 &&
2181 getU32(f, &version) && version == OVERLAY_VERSION &&
2183 getU32(f, &width) && width == OVERLAY_PIXELS_X &&
2184 getU32(f, &perRow) && perRow == 2;
2187 printf(
"[FAIL] %-19s bad golden header in %s\n",
"overlay", path);
2194 if (rows != (uint32_t)overlayRowCount)
2196 printf(
"[FAIL] %-19s row count changed (golden %u, got %d)\n",
2197 "overlay", rows, overlayRowCount);
2202 static const char*
const surface[2] = {
"library",
"reference" };
2203 for (
int r = 0; r < overlayRowCount; ++r)
2205 for (
int d = 0; d < 2; ++d)
2208 if (!getU64(f, &expected))
2210 printf(
"[FAIL] %-19s truncated golden file at row %d\n",
"overlay", r);
2214 if (expected != overlayDigests[r][d])
2216 printf(
"[FAIL] %-19s first divergence: row %d (%s) %s digest "
2217 "(expected 0x%016llx, got 0x%016llx)\n",
2218 "overlay", r, ovRowLabel[r], surface[d],
2219 (
unsigned long long)expected, (
unsigned long long)overlayDigests[r][d]);
2228 printf(
"[FAIL] %-19s library diverges from the reference at row %d (%s), "
2229 "pixel %d (lib 0x%04x, ref 0x%04x)\n",
2230 "overlay", ovFailRow, ovRowLabel[ovFailRow], ovFailPixel, ovFailLib, ovFailRef);
2236 printf(
"[PASS] %-19s %3d rows\n",
"overlay", overlayRowCount);
2279#define FRAME_MAGIC "TMSF"
2282#define FRAME_VERSION 2
2286#define FRAME_R0_DOUBLE_ROWS 0x08
2289#define FRAME_R31_ROW30 0x40
2297#define FRAME_DISPLAY_VGA 480
2298#define FRAME_DISPLAY_SCART_PAL 268
2299#define FRAME_DISPLAY_SCART_NTSC 220
2304#define FRAME_INTERLACED_PIXEL_SCALE 1
2325 uint8_t vPixelScale;
2326 uint16_t vVirtualPixels;
2332 uint8_t vPixelScale;
2333 uint16_t vVirtualPixels;
2336 uint32_t triggerScanline;
2367static uint16_t frameMapLine(uint16_t yRaw,
const FrameParams* params, uint8_t reg0,
2368 uint8_t interlacedFieldOrder)
2370 TMS_REGISTER(tms9918, 0) = reg0;
2372 return pico9918_frame_map_line_impl(
PICO9918_INST (uint16_t)(yRaw & 0x0fff),
2373 (uint8_t)((yRaw >> 12) & 1),
2374 params->interlaced, interlacedFieldOrder);
2407 params->vPixelScale, params->vVirtualPixels };
2409 TMS_REGISTER(tms9918, 0) = reg0;
2410 TMS_REGISTER(tms9918, 0x31) = reg31;
2449static uint16_t refMapLine(uint16_t yRaw,
const FrameParams* params, uint8_t reg0,
2450 uint8_t interlacedFieldOrder)
2452 const int field = (yRaw >> 12) & 1;
2453 const int lineInField = yRaw & 0x0fff;
2455 if (!params->interlaced)
return (uint16_t)lineInField;
2456 if (!(reg0 & FRAME_R0_DOUBLE_ROWS))
return (uint16_t)lineInField;
2459 const int evenVdpLine = lineInField + lineInField;
2460 const int oddVdpLine = evenVdpLine + 1;
2469 const bool takesEven = (field != 0) == (interlacedFieldOrder != 0);
2470 return (uint16_t)(takesEven ? evenVdpLine : oddVdpLine);
2491 const bool doubleRows = (reg0 & FRAME_R0_DOUBLE_ROWS) != 0;
2492 const int baseRows = (reg31 & FRAME_R31_ROW30) ? 30 : 24;
2495 g.vPixels = baseRows * 8;
2497 if (params->interlaced)
2500 g.vPixelScale = params->vPixelScale;
2501 g.vVirtualPixels = params->vVirtualPixels;
2508 g.vVirtualPixels = params->displayPixels;
2509 g.vPixels = g.vPixels * 2;
2514 g.vVirtualPixels = params->displayPixels / 2;
2516 params->vPixelScale = g.vPixelScale;
2517 params->vVirtualPixels = g.vVirtualPixels;
2533 const int spareLines = (int)g.vVirtualPixels - g.vPixels;
2534 g.vBorder = (uint32_t)(spareLines / 2);
2536 g.triggerScanline = g.vBorder + (uint32_t)g.vPixels;
2562static int32_t frameActiveLines(
const FrameGeometry* g,
int displayPixels)
2565 for (uint32_t y = 0; y < (uint32_t)displayPixels; ++y)
2566 if (!(y < g->vBorder || y >= (g->vBorder + (uint32_t)g->vPixels)))
2574static int32_t refActiveLines(
const FrameGeometry* g,
int displayPixels)
2576 const uint32_t total = (uint32_t)displayPixels;
2577 const uint32_t firstActive = g->vBorder;
2578 const uint32_t lastActive = g->vBorder + (uint32_t)g->vPixels - 1u;
2581 for (uint32_t y = 0; y < total; ++y)
2582 if (y < firstActive || y > lastActive)
2584 return (int32_t)total - border;
2601#define FRAME_MAX_ROWS 1024
2603static uint64_t frameDigests[FRAME_MAX_ROWS][2];
2604static int frameRowCount;
2607static int frFailRow;
2608static const char* frFailField;
2609static long frFailCand;
2610static long frFailRef;
2612static const char* frRowLabel[FRAME_MAX_ROWS];
2613static const char* frCurrentLabel =
"";
2621#define FRAME_ROW_VALUES 13
2625 int32_t v[FRAME_ROW_VALUES];
2628static const char*
const frameFieldName[FRAME_ROW_VALUES] = {
2629 "mappedLine",
"vPixelScale",
"vVirtualPixels",
"vPixels",
"vBorder",
"triggerScanline",
"paramsVPixelScale",
2630 "paramsVVirtualPixels",
"activeLines",
2632 "frameStatusShadow",
"sr0Register",
"intPin",
"sr1Register"};
2636 if (frameRowCount >= FRAME_MAX_ROWS)
2639 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
2644 const int row = frameRowCount++;
2645 frRowLabel[row] = frCurrentLabel;
2646 frameDigests[row][0] = fnv1a(cand,
sizeof(*cand));
2647 frameDigests[row][1] = fnv1a(ref,
sizeof(*ref));
2651 for (
int i = 0; i < FRAME_ROW_VALUES; ++i)
2653 if (cand->v[i] != ref->v[i])
2656 frFailField = frameFieldName[i];
2657 frFailCand = cand->v[i];
2658 frFailRef = ref->v[i];
2678static void frameMapCase(
const char* label, uint16_t yRaw,
bool interlaced,
2679 uint8_t reg0, uint8_t order)
2681 frCurrentLabel = label;
2683 FrameParams cp = { FRAME_DISPLAY_SCART_PAL, interlaced,
2684 FRAME_INTERLACED_PIXEL_SCALE, FRAME_DISPLAY_SCART_PAL };
2687 FrameRow cand = { { 0 } }, ref = { { 0 } };
2688 cand.v[0] = frameMapLine(yRaw, &cp, reg0, order);
2689 ref.v[0] = refMapLine(yRaw, &rp, reg0, order);
2691 frameEmitRow(&cand, &ref);
2700static char frMapLabels[FRAME_MAX_ROWS][32];
2702static void frameMapQuad(
const char* label, uint16_t lineInField,
bool interlaced,
2705 for (
int field = 0; field < 2; ++field)
2707 for (
int order = 0; order < 2; ++order)
2711 if (frameRowCount >= FRAME_MAX_ROWS)
2713 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
2717 char* stored = frMapLabels[frameRowCount];
2718 snprintf(stored,
sizeof(frMapLabels[0]),
"%s-f%d-o%d", label, field, order);
2719 frameMapCase(stored, (uint16_t)((field << 12) | lineInField),
2720 interlaced, reg0, (uint8_t)order);
2725static void frameMapGroup(
void)
2729 frameMapQuad(
"il-dbl-line0", 0,
true, FRAME_R0_DOUBLE_ROWS);
2730 frameMapQuad(
"il-dbl-line1", 1,
true, FRAME_R0_DOUBLE_ROWS);
2731 frameMapQuad(
"il-dbl-line95", 95,
true, FRAME_R0_DOUBLE_ROWS);
2734 frameMapQuad(
"il-dbl-line119", 119,
true, FRAME_R0_DOUBLE_ROWS);
2736 frameMapQuad(
"il-dbl-line149", 149,
true, FRAME_R0_DOUBLE_ROWS);
2739 frameMapQuad(
"il-nodbl-line0", 0,
true, 0x00);
2740 frameMapQuad(
"il-nodbl-line119", 119,
true, 0x00);
2745 frameMapQuad(
"prog-dbl-line0", 0,
false, FRAME_R0_DOUBLE_ROWS);
2746 frameMapQuad(
"prog-dbl-line191", 191,
false, FRAME_R0_DOUBLE_ROWS);
2747 frameMapQuad(
"prog-nodbl-line0", 0,
false, 0x00);
2748 frameMapQuad(
"prog-nodbl-line191", 191,
false, 0x00);
2763 frameMapQuad(
"il-dbl-line2000", 2000,
true, FRAME_R0_DOUBLE_ROWS);
2764 frameMapQuad(
"il-dbl-line3000", 3000,
true, FRAME_R0_DOUBLE_ROWS);
2765 frameMapQuad(
"il-nodbl-line3000", 3000,
true, 0x00);
2794static void frameGeomCase(
const char* label,
int displayPixels,
bool interlaced,
2795 uint8_t reg0, uint8_t reg31)
2797 frCurrentLabel = label;
2803 FRAME_INTERLACED_PIXEL_SCALE, displayPixels };
2809 FrameRow cand = { { 0 } }, ref = { { 0 } };
2810 cand.v[1] = cg.vPixelScale;
2811 cand.v[2] = cg.vVirtualPixels;
2812 cand.v[3] = cg.vPixels;
2813 cand.v[4] = cg.vBorder;
2814 cand.v[5] = cg.triggerScanline;
2815 cand.v[6] = cp.vPixelScale;
2816 cand.v[7] = cp.vVirtualPixels;
2817 cand.v[8] = frameActiveLines(&cg, displayPixels);
2819 ref.v[1] = rg.vPixelScale;
2820 ref.v[2] = rg.vVirtualPixels;
2821 ref.v[3] = rg.vPixels;
2822 ref.v[4] = rg.vBorder;
2823 ref.v[5] = rg.triggerScanline;
2824 ref.v[6] = rp.vPixelScale;
2825 ref.v[7] = rp.vVirtualPixels;
2826 ref.v[8] = refActiveLines(&rg, displayPixels);
2828 frameEmitRow(&cand, &ref);
2831static void frameGeomGroup(
void)
2834 frameGeomCase(
"geom-vga", FRAME_DISPLAY_VGA,
false, 0x00, 0x00);
2835 frameGeomCase(
"geom-vga-row30", FRAME_DISPLAY_VGA,
false, 0x00, FRAME_R31_ROW30);
2836 frameGeomCase(
"geom-vga-dbl", FRAME_DISPLAY_VGA,
false, FRAME_R0_DOUBLE_ROWS, 0x00);
2837 frameGeomCase(
"geom-vga-dbl-row30", FRAME_DISPLAY_VGA,
false,
2838 FRAME_R0_DOUBLE_ROWS, FRAME_R31_ROW30);
2842 frameGeomCase(
"geom-scart-pal", FRAME_DISPLAY_SCART_PAL,
true, 0x00, 0x00);
2843 frameGeomCase(
"geom-scart-pal-dbl", FRAME_DISPLAY_SCART_PAL,
true,
2844 FRAME_R0_DOUBLE_ROWS, 0x00);
2845 frameGeomCase(
"geom-scart-ntsc", FRAME_DISPLAY_SCART_NTSC,
true, 0x00, 0x00);
2846 frameGeomCase(
"geom-scart-ntsc-dbl", FRAME_DISPLAY_SCART_NTSC,
true,
2847 FRAME_R0_DOUBLE_ROWS, 0x00);
2853 frameGeomCase(
"geom-scart-pal-row30", FRAME_DISPLAY_SCART_PAL,
true,
2854 0x00, FRAME_R31_ROW30);
2855 frameGeomCase(
"geom-scart-ntsc-row30", FRAME_DISPLAY_SCART_NTSC,
true,
2856 0x00, FRAME_R31_ROW30);
2897#define FRAME_R1_INT_ENABLE 0x20
2901#define FRAME_SR0_F 0x80
2902#define FRAME_SR0_5S 0x40
2903#define FRAME_SR0_COL 0x20
2904#define FRAME_SR0_ID 0x1f
2908#define FRAME_R0_INT_SCANLINE 0x10
2909#define FRAME_SR1_HF 0x01
2922static uint8_t refMergeStatus(uint8_t currentStatus, uint8_t tempStatus)
2924 const bool haveF = (currentStatus & FRAME_SR0_F) != 0;
2925 const bool have5S = (currentStatus & FRAME_SR0_5S) != 0;
2929 bool out5S = have5S;
2930 bool outCol = (currentStatus & FRAME_SR0_COL) != 0;
2936 if (tempStatus & FRAME_SR0_COL) outCol =
true;
2941 if (tempStatus & FRAME_SR0_F) outF =
true;
2942 if (tempStatus & FRAME_SR0_5S) out5S =
true;
2943 if (tempStatus & FRAME_SR0_COL) outCol =
true;
2948 uint8_t
id = currentStatus & FRAME_SR0_ID;
2949 if (!haveF && !have5S)
id = tempStatus & FRAME_SR0_ID;
2952 if (outF) out |= FRAME_SR0_F;
2953 if (out5S) out |= FRAME_SR0_5S;
2954 if (outCol) out |= FRAME_SR0_COL;
2966static bool refIntPin(uint8_t mergedStatus, uint8_t reg1, uint8_t reg0, uint8_t sr1,
bool unlocked)
2968 const bool frameEnabled = (reg1 & FRAME_R1_INT_ENABLE) != 0;
2969 const bool frameFlagLatched = (mergedStatus & FRAME_SR0_F) != 0;
2971 const bool scanlineEnabled = (reg0 & FRAME_R0_INT_SCANLINE) != 0;
2972 const bool scanlineFlagLatched = (sr1 & FRAME_SR1_HF) != 0;
2975 return (frameEnabled && frameFlagLatched) || (unlocked && scanlineEnabled && scanlineFlagLatched);
2987static void frameIntSetup(
bool want)
2990 TMS_REGISTER(tms9918, TMS_REG_0) &= (uint8_t)~FRAME_R0_INT_SCANLINE;
2991 TMS_REGISTER(tms9918, TMS_REG_1) = want ? FRAME_R1_INT_ENABLE : 0x00;
3002static void frameIntCase(
const char* label, uint8_t currentStatus, uint8_t tempStatus, uint8_t reg1,
3003 bool intPinPre, uint8_t reg0, uint8_t sr1,
bool unlocked)
3005 frCurrentLabel = label;
3008 frameIntSetup(intPinPre);
3009 TMS_REGISTER(tms9918, TMS_REG_1) = reg1;
3010 TMS_REGISTER(tms9918, TMS_REG_0) = reg0;
3012 tms9918->isUnlocked = unlocked;
3019 FrameRow cand = { { 0 } }, ref = { { 0 } };
3020 cand.v[9] = pico9918_frame_status_impl();
3021 cand.v[10] = TMS_STATUS(tms9918, 0);
3022 cand.v[11] = pico9918_frame_int_impl();
3025 const uint8_t expected = refMergeStatus(currentStatus, tempStatus);
3026 ref.v[9] = expected;
3027 ref.v[10] = expected;
3028 ref.v[11] = refIntPin(expected, reg1, reg0, sr1, unlocked);
3033 frameEmitRow(&cand, &ref);
3048static char frIntLabels[FRAME_MAX_ROWS][40];
3050static void frameIntQuad(
const char* label, uint8_t currentStatus, uint8_t tempStatus)
3052 for (
int r1 = 0; r1 < 2; ++r1)
3054 for (
int pre = 0; pre < 2; ++pre)
3058 if (frameRowCount >= FRAME_MAX_ROWS)
3060 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n",
3064 char* stored = frIntLabels[frameRowCount];
3065 snprintf(stored,
sizeof(frIntLabels[0]),
"%s-r1%d-pin%d", label, r1, pre);
3069 frameIntCase(stored, currentStatus, tempStatus, (uint8_t)(r1 ? FRAME_R1_INT_ENABLE : 0x00), pre != 0,
3070 FRAME_R0_INT_SCANLINE, 0x00, true);
3081static void frameHIntPair(
const char* label, uint8_t currentStatus, uint8_t tempStatus, uint8_t reg1,
3082 uint8_t reg0, uint8_t sr1,
bool unlocked)
3084 for (
int pre = 0; pre < 2; ++pre)
3086 if (frameRowCount >= FRAME_MAX_ROWS)
3088 printf(
"[ERROR] frame: row budget %d exhausted - raise FRAME_MAX_ROWS\n", FRAME_MAX_ROWS);
3092 char* stored = frIntLabels[frameRowCount];
3093 snprintf(stored,
sizeof(frIntLabels[0]),
"%s-pin%d", label, pre);
3094 frameIntCase(stored, currentStatus, tempStatus, reg1, pre != 0, reg0, sr1, unlocked);
3109static void frameHIntReadCase(
const char* label, uint8_t currentStatus, uint8_t reg1)
3111 frCurrentLabel = label;
3114 frameIntSetup(
true);
3115 TMS_REGISTER(tms9918, TMS_REG_1) = reg1;
3116 TMS_REGISTER(tms9918, TMS_REG_0) = FRAME_R0_INT_SCANLINE;
3119 tms9918->isUnlocked =
true;
3126 FrameRow cand = {{0}}, ref = {{0}};
3128 cand.v[10] = TMS_STATUS(tms9918, 0);
3129 cand.v[11] = pico9918_frame_int_impl();
3133 ref.v[9] = FRAME_SR1_HF;
3134 ref.v[10] = currentStatus;
3136 const uint8_t sr1After = (uint8_t)(FRAME_SR1_HF & ~FRAME_SR1_HF);
3137 ref.v[11] = refIntPin(currentStatus, reg1, FRAME_R0_INT_SCANLINE, sr1After,
true);
3138 ref.v[12] = sr1After;
3140 frameEmitRow(&cand, &ref);
3143static void frameIntGroup(
void)
3152 frameIntQuad(
"int-a-5s-id5", 0x45, 0x9f);
3154 frameIntQuad(
"int-a-5s-id3", 0x43, 0xe8);
3156 frameIntQuad(
"int-a-5s-col", 0x40, 0x20);
3163 frameIntQuad(
"int-b-vblank", 0x1f, 0x80);
3165 frameIntQuad(
"int-b-empty", 0x00, 0xc5);
3169 frameIntQuad(
"int-b-keep-col", 0x20, 0x1f);
3177 frameIntQuad(
"int-c-block-5s", 0x85, 0x40);
3180 frameIntQuad(
"int-c-pass-col", 0x9f, 0xe5);
3183 frameIntQuad(
"int-c-col-again", 0xa0, 0x20);
3192 frameIntQuad(
"int-a-noop", 0x47, 0x00);
3196 frameIntQuad(
"int-c-f-again", 0x80, 0x80);
3202 frameIntQuad(
"int-b-full-id", 0x00, 0x7f);
3207 frameIntQuad(
"int-pin-only", 0x80, 0x00);
3217 frameHIntPair(
"hint-alone", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
true);
3221 frameHIntPair(
"hint-locked", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
false);
3222 frameHIntPair(
"hint-disarmed", 0x00, 0x00, 0x00, 0x00, FRAME_SR1_HF,
true);
3223 frameHIntPair(
"hint-noflag", 0x00, 0x00, 0x00, FRAME_R0_INT_SCANLINE, 0x00,
true);
3227 frameHIntPair(
"hint-f-latched-r1off", FRAME_SR0_F, 0x00, 0x00, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
true);
3231 frameHIntPair(
"hint-both", FRAME_SR0_F, 0xc5, FRAME_R1_INT_ENABLE, FRAME_R0_INT_SCANLINE, FRAME_SR1_HF,
3236 frameHIntPair(
"hint-frame-only", 0x00, FRAME_SR0_F, FRAME_R1_INT_ENABLE, FRAME_R0_INT_SCANLINE, 0x00,
true);
3244 frameHIntReadCase(
"hread-releases", 0x00, 0x00);
3245 frameHIntReadCase(
"hread-frame-holds", FRAME_SR0_F, FRAME_R1_INT_ENABLE);
3249static void frameRender(
void)
3273static bool frameCapture(
const char* dataDir)
3279 printf(
"[ERROR] frame candidate diverges from the reference at "
3280 "row %d (%s), field %s (cand %ld, ref %ld) - NOT captured\n",
3281 frFailRow, frRowLabel[frFailRow], frFailField, frFailCand, frFailRef);
3286 snprintf(path,
sizeof(path),
"%s/frame.golden", dataDir);
3287 FILE* f = fopen(path,
"wb");
3290 printf(
"[ERROR] frame: cannot open %s for writing\n", path);
3294 fwrite(FRAME_MAGIC, 1, 4, f);
3295 putU32(f, FRAME_VERSION);
3296 putU32(f, (uint32_t)frameRowCount);
3298 putU32(f, FRAME_ROW_VALUES);
3300 for (
int r = 0; r < frameRowCount; ++r)
3302 putU64(f, frameDigests[r][0]);
3303 putU64(f, frameDigests[r][1]);
3307 printf(
"[CAPTURED] %-19s %3d rows -> %s\n",
"frame", frameRowCount, path);
3311static bool frameCompare(
const char* dataDir)
3314 snprintf(path,
sizeof(path),
"%s/frame.golden", dataDir);
3315 FILE* f = fopen(path,
"rb");
3318 printf(
"[FAIL] %-19s missing golden file %s (run with --capture first)\n",
3324 uint32_t version = 0, rows = 0, values = 0, perRow = 0;
3325 const bool headerOk =
3326 fread(magic, 1, 4, f) == 4 && memcmp(magic, FRAME_MAGIC, 4) == 0 &&
3327 getU32(f, &version) && version == FRAME_VERSION &&
3329 getU32(f, &values) && values == FRAME_ROW_VALUES &&
3330 getU32(f, &perRow) && perRow == 2;
3333 printf(
"[FAIL] %-19s bad golden header in %s\n",
"frame", path);
3340 if (rows != (uint32_t)frameRowCount)
3342 printf(
"[FAIL] %-19s row count changed (golden %u, got %d)\n",
3343 "frame", rows, frameRowCount);
3348 static const char*
const surface[2] = {
"candidate",
"reference" };
3349 for (
int r = 0; r < frameRowCount; ++r)
3351 for (
int d = 0; d < 2; ++d)
3354 if (!getU64(f, &expected))
3356 printf(
"[FAIL] %-19s truncated golden file at row %d\n",
"frame", r);
3360 if (expected != frameDigests[r][d])
3362 printf(
"[FAIL] %-19s first divergence: row %d (%s) %s digest "
3363 "(expected 0x%016llx, got 0x%016llx)\n",
3364 "frame", r, frRowLabel[r], surface[d],
3365 (
unsigned long long)expected, (
unsigned long long)frameDigests[r][d]);
3374 printf(
"[FAIL] %-19s candidate diverges from the reference at row %d (%s), "
3375 "field %s (cand %ld, ref %ld)\n",
3376 "frame", frFailRow, frRowLabel[frFailRow], frFailField, frFailCand, frFailRef);
3382 printf(
"[PASS] %-19s %3d rows\n",
"frame", frameRowCount);
3390static bool resetCheck(
void)
3392 if (pico9918_frame_count_impl() == 0)
3394 printf(
"[FAIL] %-19s frame count was already zero, so this proves nothing\n",
"reset");
3400 if (pico9918_frame_count_impl() != 0)
3402 printf(
"[FAIL] %-19s pico9918_reset left the frame count at %d, so the splash gate stays shut\n",
3403 "reset", pico9918_frame_count_impl());
3407 printf(
"[PASS] %-19s splash gate reopens\n",
"reset");
3416static bool unlockCheck(
void)
3425 {57, 0x00,
false,
"a lock write leaves it locked" },
3426 {57, 0x1c,
false,
"one 0x1c is not enough" },
3427 {57, 0x1c,
true,
"two consecutive 0x1c unlock" },
3428 {57, 0x1c,
true,
"a redundant unlock is a no-op" },
3429 {57, 0x00,
false,
"any other value locks on the spot" },
3430 {57, 0x1c,
false,
"one 0x1c is not enough" },
3431 { 1, 0x00,
false,
"any other register write clears the run"},
3432 {57, 0x1c,
false,
"so that pair is not a consecutive one" },
3433 {57, 0x00,
false,
"back to a known locked state" },
3434 {57, 0x1f,
false,
"the low two bits are ignored" },
3435 {57, 0x1f,
true,
"so 0x1f unlocks exactly as 0x1c does" },
3438 for (
int i = 0; i < (int)(
sizeof(steps) /
sizeof(steps[0])); ++i)
3440 regWrite(steps[i].reg, steps[i].value);
3441 if (PICO9918_UNLOCKED(tms9918) != steps[i].unlocked)
3443 printf(
"[FAIL] %-19s step %d, R%d = %02x: %s\n",
"unlock", i + 1, steps[i].reg, steps[i].value,
3449 printf(
"[PASS] %-19s %d VR57 latch steps\n",
"unlock", (
int)(
sizeof(steps) /
sizeof(steps[0])));
3456int main(
int argc,
char* argv[])
3458 bool capture =
false;
3459 const char* dataDir = GOLDEN_DATA_DIR;
3461 for (
int i = 1; i < argc; ++i)
3463 if (strcmp(argv[i],
"--capture") == 0)
3467 else if (strcmp(argv[i],
"--data") == 0 && i + 1 < argc)
3469 dataDir = argv[++i];
3473 printf(
"usage: golden_runner [--capture] [--data DIR]\n");
3481 for (
int i = 0; i < SCENE_COUNT; ++i)
3483 bool ok = capture ? captureScene(&scenes[i], dataDir)
3484 : compareScene(&scenes[i], dataDir);
3485 if (!ok) ++failures;
3493 const int artifacts = SCENE_COUNT + 2;
3494 if (!(capture ? overlayCapture(dataDir) : overlayCompare(dataDir))) ++failures;
3502 if (!(capture ? frameCapture(dataDir) : frameCompare(dataDir))) ++failures;
3505 if (!resetCheck()) ++failures;
3508 if (!unlockCheck()) ++failures;
3512 printf(
"%d artifact(s) captured to %s\n", artifacts - failures, dataDir);
3516 printf(
"%d of %d artifact(s) FAILED\n", failures, artifacts);
3520 printf(
"all %d artifact(s) passed\n", artifacts);
3523 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
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_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)
recompute the interrupt state and, only if it changed, drive the pin.
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