pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
consumer.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - a consumer of the installed package
4 *
5 * Copyright (c) 2026 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Drives the API the way a host does - create, reset, set a mode up, render a
12 * line, read it back - so the exported package is checked for the things an
13 * archive cannot show: a header the install rules missed, an include path that
14 * only resolved in the build tree, a PUBLIC definition lost on the way out.
15 *
16 * Written for either instance mode, since which one the library was built with
17 * is a property of the installed package rather than of this file. Same for the
18 * runtime chip switch, which only a PICO9918_RUNTIME_CHIP=ON package exports.
19 */
20
21#include "overlay/splash.h"
22#include "pico9918.h"
23#include "pico9918_config.h"
24#include "pico9918_frame.h"
25#include "pico9918_util.h"
26
27#include <stdio.h>
28
29#if !PICO9918_SINGLE_INSTANCE
30/* What the config-applied callback was handed. One recorder for both instances on
31 purpose: the registration is what has to be per instance, not the function. */
32static struct
33{
34 pico9918_t* inst;
35 void* userdata;
36 int calls;
37} appliedSeen;
38
39static void appliedCallback(pico9918_t* tms9918, void* userdata)
40{
41 appliedSeen.inst = tms9918;
42 appliedSeen.userdata = userdata;
43 ++appliedSeen.calls;
44}
45#endif
46
47#if PICO9918_BUILD_RUNTIME_CHIP
48/* VR15 selects which status register a read returns, so it is how a host reads any of
49 them. Above R7, so it needs an unlocked device. */
50static uint8_t statusReg(PICO9918_INST_ARG uint8_t index)
51{
54}
55
56/* The F18A badge's expected artwork, as a 64-bit FNV-1a over every pixel of rows 0..13,
57 low byte then high byte so the constant does not depend on this host's endianness.
58 Computed from the ROM art and PICO9918_PIXEL_FROM_RGB12 independently of the library,
59 which is what makes it a check rather than a restatement. */
60#define FNV64_OFFSET 0xcbf29ce484222325ull
61#define FNV64_PRIME 0x00000100000001b3ull
62#define BADGE_DIGEST 0xc0ce60a2a13c9a2dull
63
64#define BADGE_GUARD 2 /* columns past the badge that must stay untouched */
65#define BADGE_BUFFER (PICO9918_F18A_BADGE_WIDTH + BADGE_GUARD)
66#define BADGE_LINE_PIXELS 640
67
68static uint64_t fnv1aByte(uint64_t h, uint8_t b)
69{
70 return (h ^ b) * FNV64_PRIME;
71}
72
73static void fillPixels(PICO9918_PIXEL_T* pixels, uint32_t count, PICO9918_PIXEL_T value)
74{
75 for (uint32_t i = 0; i < count; ++i) pixels[i] = value;
76}
77#endif
78
79int main(void)
80{
81#if PICO9918_SINGLE_INSTANCE
82 pico9918_init();
83#else
84 pico9918_t* tms9918 = pico9918_new();
85 if (!tms9918)
86 {
87 printf("pico9918_new failed\n");
88 return 1;
89 }
90#endif
91
92#if PICO9918_BUILD_RUNTIME_CHIP
93 /* The entry points exist only where the generated header says the library was built
94 with them, so reaching them at all is half of what this proves. The other half is
95 that the choice does something: a TMS9918A has no answer to the F18A unlock write, so
96 its register file stays eight wide. Runs before the setup below, which then leaves
97 the instance in the state the rest of the file expects. */
99 {
100 printf("a new instance is not PICO9918_CHIP_MAX\n");
101 return 1;
102 }
103
106 {
107 printf("the chip did not step down\n");
108 return 1;
109 }
110
111 /* Locked, a write above R7 is ignored and a read masks to the low three bits, so VR33
112 both fails to take and reads back as the VR1 it aliases. Unlocked it is itself. That
113 pair is the register file's width, which is what the unlock actually buys. */
118 {
119 printf("a TMS9918A honoured the unlock\n");
120 return 1;
121 }
122
127 if (pico9918_reg_value(PICO9918_INST 0x21) != 0x5a)
128 {
129 printf("a PICO9918 refused the unlock: VR33 0x%02x\n",
131 return 1;
132 }
133
134 /* VR58 selects a config option and VR59 writes it, with SR12 reading back what landed.
135 A real F18A has no such port, so neither register may reach it - and VR59 is the one
136 that writes, so gating VR58 alone would leave the board's config open. */
140 if (statusReg(PICO9918_INST 12) == 0x5a)
141 {
142 printf("an F18A reached the config port\n");
143 return 1;
144 }
145
149 if (statusReg(PICO9918_INST 12) != 0x5a)
150 {
151 printf("a PICO9918 could not reach the config port\n");
152 return 1;
153 }
154
155 /* M4 is F18A-only and honoured there even while locked, so the gate is the personality.
156 The mode is cached at render time, hence the scan_line before each read. */
160
163 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_TEXT)
164 {
165 printf("a TMS9918A with M4 and the text bit is not in 40-column text: mode %d\n",
167 return 1;
168 }
169
170 /* Mode comes from M1/M2/M3 alone, so the bit selects nothing. */
173 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_GRAPHICS_I)
174 {
175 printf("a TMS9918A with R0 bit 2 set is not in Graphics I: mode %d\n",
177 return 1;
178 }
179
180 /* The A in TMS9918A: M3 selects Graphics II on the base and nothing on the pre-A part,
181 where M1/M2 still do. The bottom of the ladder is zero, so the round trip below also
182 catches a step down being read back as the enum's default rather than as itself. */
185 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_GRAPHICS_II)
186 {
187 printf("a TMS9918A with M3 is not in Graphics II: mode %d\n",
189 return 1;
190 }
191
194 {
195 printf("the pre-A personality did not take: chip %d\n", (int)pico9918_chip(PICO9918_INST_ONLY));
196 return 1;
197 }
198
200 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_GRAPHICS_I)
201 {
202 printf("a pre-A TMS9918 decoded M3: mode %d\n", (int)pico9918_display_mode(PICO9918_INST_ONLY));
203 return 1;
204 }
205
208
209 /* Three address bits, so VR8 is VR0 and the write lands. */
213 {
214 printf("a TMS9918A dropped a VR8 write instead of masking it into VR0: VR0 0x%02x\n",
216 return 1;
217 }
219
224 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_TEXT80)
225 {
226 printf("a locked F18A refused 80-column text, which TurboForth needs\n");
227 return 1;
228 }
229
230 /* With M4 set it ignores them instead, so VR0-15 setup writes cannot reach VR0-7. */
232 if (pico9918_reg_value(PICO9918_INST 0) != 0x04)
233 {
234 printf("a locked F18A in 80 columns masked a VR8 write into VR0: VR0 0x%02x\n",
236 return 1;
237 }
238
239 /* M4 clear and it latches three bits like a 9918A again. */
243 {
244 printf("a locked F18A with M4 clear dropped a VR8 write: VR0 0x%02x\n",
246 return 1;
247 }
248
249 /* VR57 stays reachable while locked; three address bits would put it in VR1. */
253 {
254 printf("a VR57 write was latched into VR1: VR1 0x%02x\n",
256 return 1;
257 }
258
259 /* R1 bit 7 clear is 4K DRAM addressing, on Classic99's hardware-confirmed vectors.
260 TRAP: its comment block lists >2240 as landing at >2280, which is the one entry its
261 own math disagrees with - bits 6 to 12 rotate up one, so >2240 goes to >2480 like
262 the other six. No formula produces all seven, and the six confirm the rotate. */
263 {
264 static const struct
265 {
266 uint16_t addr; /* what the guest sets */
267 uint16_t real; /* where the byte lands */
268 } fourK[] = {
269 {0x1100, 0x0240}, {0x1810, 0x1050}, {0x2210, 0x2410}, {0x2211, 0x2411},
270 {0x2240, 0x2480}, {0x3210, 0x2450}, {0x3810, 0x3050},
271 };
272
274
275 for (unsigned i = 0; i < sizeof fourK / sizeof fourK[0]; ++i)
276 {
277 const uint8_t marker = (uint8_t)(0xa0 + i);
278
282
283 if (pico9918_vram_value(PICO9918_INST fourK[i].real) != marker)
284 {
285 printf("a 4K TMS9918A did not put >%04x at >%04x: found 0x%02x, and 0x%02x at >%04x\n", fourK[i].addr,
286 fourK[i].real, pico9918_vram_value(PICO9918_INST fourK[i].real),
287 pico9918_vram_value(PICO9918_INST fourK[i].addr), fourK[i].addr);
288 return 1;
289 }
290
291 /* and the bit is what did it, so the same write at 16K lands where it says */
297
298 if (pico9918_vram_value(PICO9918_INST fourK[i].addr) != marker)
299 {
300 printf("a 16K TMS9918A moved >%04x anyway: found 0x%02x\n", fourK[i].addr,
301 pico9918_vram_value(PICO9918_INST fourK[i].addr));
302 return 1;
303 }
304
307 }
308
309 /* An F18A has SRAM and no such bit, so a personality above the base ignores it. */
314
315 if (pico9918_vram_value(PICO9918_INST fourK[0].addr) != 0x5a)
316 {
317 printf("an F18A decoded R1 bit 7: >%04x holds 0x%02x and >%04x holds 0x%02x\n", fourK[0].addr,
318 pico9918_vram_value(PICO9918_INST fourK[0].addr), fourK[0].real,
319 pico9918_vram_value(PICO9918_INST fourK[0].real));
320 return 1;
321 }
322
326 }
327
328 /* The PRO tier's ceiling is the build's, because PICO9918_TEXT80_8BPP is a buffer width
329 rather than a runtime choice: a narrow build clamps the request to PICO9918 instead of
330 half-honouring it. Either way SR1 must read 0xE8 - a PRO is a PICO9918 to anything
331 probing for the chip, and the test that decides it is a >= that a new tier can slip past. */
334 {
335 printf("asking for PRO gave neither PRO nor the ceiling: chip %d\n",
337 return 1;
338 }
339
341 {
342 printf("a PRO does not answer SR1 as a PICO9918: 0x%02x\n",
344 return 1;
345 }
346
348
349 printf("pico9918-core: chip switch honoured, the register file and config port follow it\n");
350
351 /* The F18A's power-on badge. Its geometry macros come from the installed overlay
352 header, so reaching them at all also checks the install rules carry it. */
353 {
354 const PICO9918_PIXEL_T sentinel = 0x0123;
355 PICO9918_PIXEL_T __aligned(4) badge[PICO9918_F18A_BADGE_WIDTH + BADGE_GUARD];
356 uint64_t digest = FNV64_OFFSET;
357
358 for (uint16_t line = 0; line < PICO9918_F18A_BADGE_HEIGHT; ++line)
359 {
360 fillPixels(badge, BADGE_BUFFER, sentinel);
361
362 if (!pico9918_f18a_badge_render(line, 0, badge))
363 {
364 printf("the F18A badge did not draw row %u\n", (unsigned)line);
365 return 1;
366 }
367
368 /* The badge is the left edge of a line the host owns the rest of, so an overrun
369 here is an overrun into the border or the picture. */
370 for (uint32_t g = 0; g < BADGE_GUARD; ++g)
371 {
372 if (badge[PICO9918_F18A_BADGE_WIDTH + g] != sentinel)
373 {
374 printf("the F18A badge drew past column %u on row %u\n",
375 (unsigned)PICO9918_F18A_BADGE_WIDTH, (unsigned)line);
376 return 1;
377 }
378 }
379
380 for (uint32_t x = 0; x < PICO9918_F18A_BADGE_WIDTH; ++x)
381 {
382 digest = fnv1aByte(digest, (uint8_t)(badge[x] & 0xff));
383 digest = fnv1aByte(digest, (uint8_t)((badge[x] >> 8) & 0xff));
384 }
385 }
386
387 /* Pins the artwork, the palette conversion and the 1bpp packing together. The asset
388 carries no bit-depth macro, so a palette grown past two entries would silently
389 become 2bpp and only this catches it. */
390 if (digest != BADGE_DIGEST)
391 {
392 printf("the F18A badge artwork digest is 0x%016llx, expected 0x%016llx\n",
393 (unsigned long long)digest, (unsigned long long)BADGE_DIGEST);
394 return 1;
395 }
396
397 /* Named, so a digest mismatch has somewhere to start: the margin and the box. */
398 pico9918_f18a_badge_render(0, 0, badge);
399 if (badge[0] != PICO9918_PIXEL_FROM_RGB12(0xb202))
400 {
401 printf("the F18A badge margin is 0x%04x, expected dark green\n", (unsigned)badge[0]);
402 return 1;
403 }
404 pico9918_f18a_badge_render(1, 0, badge);
405 if (badge[2] != PICO9918_PIXEL_FROM_RGB12(0xff0f))
406 {
407 printf("the F18A badge box edge is 0x%04x, expected white\n", (unsigned)badge[2]);
408 return 1;
409 }
410
411 /* Shown for frames 0..383 and never again, and a frame outside that window must
412 leave the buffer alone rather than draw something the host then has to undo. */
413 if (!pico9918_f18a_badge_render(0, PICO9918_F18A_BADGE_FRAMES - 1, badge))
414 {
415 printf("the F18A badge stopped one frame early\n");
416 return 1;
417 }
418
419 fillPixels(badge, BADGE_BUFFER, sentinel);
420 if (pico9918_f18a_badge_render(0, PICO9918_F18A_BADGE_FRAMES, badge) ||
421 badge[0] != sentinel)
422 {
423 printf("the F18A badge outlived frame %u\n", (unsigned)PICO9918_F18A_BADGE_FRAMES);
424 return 1;
425 }
426
427 fillPixels(badge, BADGE_BUFFER, sentinel);
428 if (pico9918_f18a_badge_render(PICO9918_F18A_BADGE_HEIGHT, 0, badge) ||
429 badge[0] != sentinel)
430 {
431 printf("the F18A badge drew below row %u\n", (unsigned)PICO9918_F18A_BADGE_HEIGHT);
432 return 1;
433 }
434
435 /* One badge row per OUTPUT line, odd lines included. An odd line re-reads the
436 buffer rather than re-rendering, and what it holds is the row above, so the badge
437 has to be drawn again there and the call has to say the buffer changed. */
438 {
439 PICO9918_PIXEL_T __aligned(4) out[BADGE_LINE_PIXELS];
440 pico9918_scanline_params_t params = {BADGE_LINE_PIXELS, 240, false, 0};
441
446 /* Otherwise an odd line reports a change because it was dimmed, and the badge's
447 own contribution to that answer would go unchecked. */
448 pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_CRT_SCANLINES] = 0;
449
450 for (uint32_t line = 0; line < PICO9918_F18A_BADGE_HEIGHT; ++line)
451 {
452 fillPixels(out, BADGE_LINE_PIXELS, sentinel);
453
454 if (!pico9918_frame_output_line(PICO9918_INST line, &params, out))
455 {
456 printf("output line %u reported no change with the F18A badge on it\n",
457 (unsigned)line);
458 return 1;
459 }
460
461 fillPixels(badge, BADGE_BUFFER, sentinel);
462 pico9918_f18a_badge_render((uint16_t)line, 0, badge);
463
464 for (uint32_t x = 0; x < PICO9918_F18A_BADGE_WIDTH; ++x)
465 {
466 if (out[x] != badge[x])
467 {
468 printf("output line %u pixel %u is 0x%04x, badge row %u has 0x%04x\n",
469 (unsigned)line, (unsigned)x, (unsigned)out[x], (unsigned)line,
470 (unsigned)badge[x]);
471 return 1;
472 }
473 }
474 }
475
476 /* A PICO9918 shows its own splash instead, and that one is not here. */
478 fillPixels(out, BADGE_LINE_PIXELS, sentinel);
480
481 pico9918_f18a_badge_render(1, 0, badge);
482 if (out[2] == badge[2])
483 {
484 printf("a PICO9918 drew the F18A badge\n");
485 return 1;
486 }
487 }
488
489 printf("pico9918-core: the F18A badge is pixel-exact, one row per output line\n");
490 }
491#endif
492
495 pico9918_set_fg_bg_color(PICO9918_INST TMS_WHITE, TMS_DK_BLUE);
497
498 const uint8_t status = pico9918_scan_line(PICO9918_INST 0);
499 const uint32_t bytes = pico9918_line_bytes(PICO9918_INST_ONLY);
500 const uint8_t* line = pico9918_line_source(PICO9918_INST_ONLY);
501
502 if (!line || bytes < TMS9918_PIXELS_X || bytes > PICO9918_SCANLINE_BUFFER_SIZE)
503 {
504 printf("implausible line: %p, %u bytes\n", (const void*)line, (unsigned)bytes);
505 return 1;
506 }
507 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_GRAPHICS_I)
508 {
509 printf("mode is not Graphics I\n");
510 return 1;
511 }
512
513 printf("pico9918-core: %u-byte line, status 0x%02x, palette entry 1 0x%08x\n",
514 (unsigned)bytes, status, pico9918_palette[1]);
515
516 /* The settings block a host loads out of its own storage. Writable through the
517 accessor and read by the library, which is the whole contract - so setting one
518 panel byte and applying must derive the summary the overlay gates on. */
519 uint8_t* config = pico9918_config(PICO9918_INST_ONLY);
520 if (!config)
521 {
522 printf("pico9918_config returned NULL\n");
523 return 1;
524 }
525
526 config[PICO9918_CONF_DIAG] = 0;
527 config[PICO9918_CONF_DIAG_REGISTERS] = 1;
529
530 if (!pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_DIAG])
531 {
532 printf("config applied but the derived DIAG summary stayed clear\n");
533 return 1;
534 }
535
536 config[PICO9918_CONF_DIAG_REGISTERS] = 0;
538
539 if (pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_DIAG])
540 {
541 printf("every panel is off and the derived DIAG summary is still set\n");
542 return 1;
543 }
544
545 printf("pico9918-core: config block reachable, %u bytes, applied both ways\n",
546 (unsigned)CONFIG_BYTES);
547
548 /* The status file, read without the side effects of reading it. Two things to prove,
549 because pico9918_read_status has neither: that it INDEXES rather than always
550 answering SR0, and that it leaves the flags alone. */
551 const uint8_t sr1Before = pico9918_status_value(PICO9918_INST PICO9918_SR_IDENT);
553
555 {
556 printf("status_value did not read SR0 back: 0x%02x\n",
558 return 1;
559 }
560
561 /* SR1 must not have moved - if this always answered SR0 it would read 0x45 now */
563 {
564 printf("status_value ignores its register argument\n");
565 return 1;
566 }
567
568 /* twice, unchanged: a destructive read would differ the second time */
570 {
571 printf("status_value cleared what it returned\n");
572 return 1;
573 }
574
575 /* and the destructive read still is destructive, so the two are really different */
578 {
579 printf("read_status left the flags standing\n");
580 return 1;
581 }
582
583 printf("pico9918-core: status file readable without clearing it\n");
584
585#if !PICO9918_SINGLE_INSTANCE
586 /* Two VDPs at once is what this mode is for, and no other test can check it: every
587 harness in the library holds exactly one. Cleared VRAM makes every tile pixel
588 transparent, so each line is its own instance's backdrop and the two must differ. */
589 pico9918_t* second = pico9918_new();
590 if (!second)
591 {
592 printf("second pico9918_new failed\n");
593 return 1;
594 }
595 pico9918_reset(second);
597 pico9918_set_fg_bg_color(second, TMS_WHITE, TMS_DK_RED);
599
600 /* the line is one module-level buffer, so read each instance's out before the other renders */
601 pico9918_scan_line(tms9918, 0);
602 const uint8_t firstPixel = pico9918_line_source(tms9918)[0];
603 pico9918_scan_line(second, 0);
604 const uint8_t secondPixel = pico9918_line_source(second)[0];
605
606 if (firstPixel == secondPixel)
607 {
608 printf("two instances rendered the same line: 0x%02x\n", firstPixel);
609 return 1;
610 }
611 printf("pico9918-core: two instances, lines 0x%02x and 0x%02x\n", firstPixel, secondPixel);
612
613 /* The host callbacks, registered per instance - the reason this mode has them at all.
614 A single shared registration passes the first check and fails the second, because
615 registering on `second` would have overwritten the first instance's. */
616 int firstTag = 0, secondTag = 0;
617 pico9918_config_set_applied_callback(tms9918, appliedCallback, &firstTag);
618 pico9918_config_set_applied_callback(second, appliedCallback, &secondTag);
619
620 pico9918_config_apply(second);
621 if (appliedSeen.calls != 1 || appliedSeen.inst != second || appliedSeen.userdata != &secondTag)
622 {
623 printf("the second instance's config-applied callback did not fire with its own "
624 "instance and userdata\n");
625 return 1;
626 }
627
628 pico9918_config_apply(tms9918);
629 if (appliedSeen.calls != 2 || appliedSeen.inst != tms9918 || appliedSeen.userdata != &firstTag)
630 {
631 printf("registering on one instance disturbed the other's callback\n");
632 return 1;
633 }
634
635 /* and a NULL registration is how a host withdraws one */
636 pico9918_config_set_applied_callback(second, NULL, NULL);
637 pico9918_config_apply(second);
638 if (appliedSeen.calls != 2)
639 {
640 printf("a NULL registration still fired\n");
641 return 1;
642 }
643
644 printf("pico9918-core: host callbacks registered per instance\n");
645
646 pico9918_destroy(second);
648#endif
649 return 0;
650}
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
Definition pico9918.c:3499
void pico9918_destroy(pico9918_t *tms9918)
destroy a TMS9918
Definition pico9918.c:360
void pico9918_set_chip(pico9918_t *tms9918, pico9918_chip_t chip)
select which chip this instance answers as
Definition pico9918.c:276
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
Definition pico9918.c:3529
uint8_t pico9918_status_value(pico9918_t *tms9918, pico9918_status_register_t reg)
return a status register value without the side effects of reading it
Definition pico9918.c:3336
pico9918_chip_t pico9918_chip(pico9918_t *tms9918)
which chip this instance answers as
Definition pico9918.c:310
uint8_t pico9918_vram_value(pico9918_t *tms9918, uint16_t addr)
return a value from vram
Definition pico9918.c:3485
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
Definition pico9918.c:3518
void pico9918_set_status(pico9918_t *tms9918, uint8_t status)
set status flag
Definition pico9918.c:427
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
Definition pico9918.c:3256
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
Definition pico9918.c:318
uint8_t pico9918_read_status(pico9918_t *tms9918)
read from the status register
Definition pico9918.c:379
uint8_t pico9918_reg_value(pico9918_t *tms9918, pico9918_register_t reg)
return a register value - see the header for the locked-device aliasing
Definition pico9918.c:3329
void pico9918_write_data(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
Definition pico9918.c:395
pico9918-core - core interface
pico9918_t * pico9918_new(void)
create a new TMS9918
#define PICO9918_SCANLINE_BUFFER_SIZE
the library's line buffer size - the active pixels plus the eight bytes past them that a fine-h-scrol...
Definition pico9918.h:517
#define TMS_R1_MODE_TEXT
40-column text
Definition pico9918.h:306
#define TMS_R1_RAM_16K
register 1 bits: VRAM size, blanking, interrupt, mode and sprite size
Definition pico9918.h:297
#define TMS_R0_MODE_TEXT_80
80-column text, with R1's text mode.
Definition pico9918.h:290
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
#define TMS_R1_DISP_ACTIVE
render the active display
Definition pico9918.h:300
#define TMS_R0_MODE_GRAPHICS_II
Graphics II - the only mode R0 selects.
Definition pico9918.h:287
#define TMS_R1_RAM_4K
4KB of VRAM
Definition pico9918.h:298
@ PICO9918_REG_CONFIG_INDEX
PICO9918 only: which configuration byte R59 addresses.
Definition pico9918.h:243
@ PICO9918_REG_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
Definition pico9918.h:244
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
Definition pico9918.h:219
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
Definition pico9918.h:242
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
Definition pico9918.h:256
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
Definition pico9918.h:257
#define PICO9918_CHIP_MAX
the highest personality this build can be, and what a new instance is
Definition pico9918.h:168
#define PICO9918_R57_UNLOCK
the value register 57 takes, twice in a row, to unlock
Definition pico9918.h:368
@ PICO9918_CHIP_PICO9918
an F18A plus the PICO9918's own extensions
Definition pico9918.h:154
@ PICO9918_CHIP_TMS9918
a pre-A TMS9918: a TMS9918A without Graphics II
Definition pico9918.h:151
@ PICO9918_CHIP_PICO9918_PRO
a PICO9918 PRO: 8bpp 80-column text, its own splash
Definition pico9918.h:155
@ PICO9918_CHIP_TMS9918A
a TMS9918A: locked, no GPU, no extensions
Definition pico9918.h:152
@ PICO9918_CHIP_F18A
an F18A: unlock, enhanced renderer, GPU
Definition pico9918.h:153
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
void pico9918_config_apply(pico9918_t *tms9918)
apply the config block's VDP-side effects: registers 50 and 30, the palette unpack,...
uint8_t * pico9918_config(pico9918_t *tms9918)
the instance's CONFIG_BYTES settings block
void pico9918_config_set_applied_callback(pico9918_t *tms9918, pico9918_config_applied_fn cb, void *userdata)
register the host's config-applied hook
pico9918-core - config byte layout
#define CONFIG_BYTES
size of the config block, in bytes
bool pico9918_frame_output_line(pico9918_t *tms9918, uint32_t outputLine, pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
see the header.
pico9918-core - frame module
PICO9918_DLLEXPORT_CONST uint32_t pico9918_palette[]
the sixteen TMS9918 colours, each packed as 0xrrggbbaa
PICO9918_DLLEXPORT void pico9918_initialise_gfx_i(pico9918_t *tms9918)
program Graphics I, the default table addresses and black on cyan, then clear VRAM
pico9918-core - utility / helper functions
static void pico9918_set_address_write(pico9918_t *tms9918, uint16_t addr)
point the VRAM address register at addr for writing, ie.
static void pico9918_set_fg_bg_color(pico9918_t *tms9918, pico9918_color_t fg, pico9918_color_t bg)
set register 7, the text-mode foreground colour and the backdrop
static void pico9918_write_register_value(pico9918_t *tms9918, pico9918_register_t reg, uint8_t value)
write a VDP register as a host would, value byte first
pico9918-core - Splash overlay
#define PICO9918_F18A_BADGE_WIDTH
badge columns - must stay a multiple of 8, see splash.c
Definition splash.h:82
#define PICO9918_F18A_BADGE_HEIGHT
badge rows, each one OUTPUT line rather than one display line
Definition splash.h:84
#define PICO9918_F18A_BADGE_FRAMES
frames the badge is shown for, counting from reset
Definition splash.h:86
the host's per-call display parameters, as the scanline sees them