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 /* Unlocked, an F18A reaches enhanced colour in 80 columns, so the wide tier gives it a byte a pixel. */
256
257 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_TEXT80)
258 {
259 printf("an unlocked F18A refused 80-column text\n");
260 return 1;
261 }
262
264 {
265 printf("an unlocked F18A in 80 columns rendered a %u-byte line, expected %u\n",
267 return 1;
268 }
269
270 /* VR57 stays reachable while locked; three address bits would put it in VR1. */
274 {
275 printf("a VR57 write was latched into VR1: VR1 0x%02x\n",
277 return 1;
278 }
279
280 /* R1 bit 7 clear is 4K DRAM addressing, on Classic99's hardware-confirmed vectors.
281 TRAP: its comment block lists >2240 as landing at >2280, which is the one entry its
282 own math disagrees with - bits 6 to 12 rotate up one, so >2240 goes to >2480 like
283 the other six. No formula produces all seven, and the six confirm the rotate. */
284 {
285 static const struct
286 {
287 uint16_t addr; /* what the guest sets */
288 uint16_t real; /* where the byte lands */
289 } fourK[] = {
290 {0x1100, 0x0240}, {0x1810, 0x1050}, {0x2210, 0x2410}, {0x2211, 0x2411},
291 {0x2240, 0x2480}, {0x3210, 0x2450}, {0x3810, 0x3050},
292 };
293
295
296 for (unsigned i = 0; i < sizeof fourK / sizeof fourK[0]; ++i)
297 {
298 const uint8_t marker = (uint8_t)(0xa0 + i);
299
303
304 if (pico9918_vram_value(PICO9918_INST fourK[i].real) != marker)
305 {
306 printf("a 4K TMS9918A did not put >%04x at >%04x: found 0x%02x, and 0x%02x at >%04x\n", fourK[i].addr,
307 fourK[i].real, pico9918_vram_value(PICO9918_INST fourK[i].real),
308 pico9918_vram_value(PICO9918_INST fourK[i].addr), fourK[i].addr);
309 return 1;
310 }
311
312 /* and the bit is what did it, so the same write at 16K lands where it says */
318
319 if (pico9918_vram_value(PICO9918_INST fourK[i].addr) != marker)
320 {
321 printf("a 16K TMS9918A moved >%04x anyway: found 0x%02x\n", fourK[i].addr,
322 pico9918_vram_value(PICO9918_INST fourK[i].addr));
323 return 1;
324 }
325
328 }
329
330 /* An F18A has SRAM and no such bit, so a personality above the base ignores it. */
335
336 if (pico9918_vram_value(PICO9918_INST fourK[0].addr) != 0x5a)
337 {
338 printf("an F18A decoded R1 bit 7: >%04x holds 0x%02x and >%04x holds 0x%02x\n", fourK[0].addr,
339 pico9918_vram_value(PICO9918_INST fourK[0].addr), fourK[0].real,
340 pico9918_vram_value(PICO9918_INST fourK[0].real));
341 return 1;
342 }
343
347 }
348
349 /* The PRO tier's ceiling is the build's, because PICO9918_TEXT80_8BPP is a buffer width
350 rather than a runtime choice: a narrow build clamps the request to PICO9918 instead of
351 half-honouring it. Either way SR1 must read 0xE8 - a PRO is a PICO9918 to anything
352 probing for the chip, and the test that decides it is a >= that a new tier can slip past. */
355 {
356 printf("asking for PRO gave neither PRO nor the ceiling: chip %d\n",
358 return 1;
359 }
360
362 {
363 printf("a PRO does not answer SR1 as a PICO9918: 0x%02x\n",
365 return 1;
366 }
367
369
370 printf("pico9918-core: chip switch honoured, the register file and config port follow it\n");
371
372 /* The F18A's power-on badge. Its geometry macros come from the installed overlay
373 header, so reaching them at all also checks the install rules carry it. */
374 {
375 const PICO9918_PIXEL_T sentinel = 0x0123;
376 PICO9918_PIXEL_T __aligned(4) badge[PICO9918_F18A_BADGE_WIDTH + BADGE_GUARD];
377 uint64_t digest = FNV64_OFFSET;
378
379 for (uint16_t line = 0; line < PICO9918_F18A_BADGE_HEIGHT; ++line)
380 {
381 fillPixels(badge, BADGE_BUFFER, sentinel);
382
383 if (!pico9918_f18a_badge_render(line, 0, badge))
384 {
385 printf("the F18A badge did not draw row %u\n", (unsigned)line);
386 return 1;
387 }
388
389 /* The badge is the left edge of a line the host owns the rest of, so an overrun
390 here is an overrun into the border or the picture. */
391 for (uint32_t g = 0; g < BADGE_GUARD; ++g)
392 {
393 if (badge[PICO9918_F18A_BADGE_WIDTH + g] != sentinel)
394 {
395 printf("the F18A badge drew past column %u on row %u\n",
396 (unsigned)PICO9918_F18A_BADGE_WIDTH, (unsigned)line);
397 return 1;
398 }
399 }
400
401 for (uint32_t x = 0; x < PICO9918_F18A_BADGE_WIDTH; ++x)
402 {
403 digest = fnv1aByte(digest, (uint8_t)(badge[x] & 0xff));
404 digest = fnv1aByte(digest, (uint8_t)((badge[x] >> 8) & 0xff));
405 }
406 }
407
408 /* Pins the artwork, the palette conversion and the 1bpp packing together. The asset
409 carries no bit-depth macro, so a palette grown past two entries would silently
410 become 2bpp and only this catches it. */
411 if (digest != BADGE_DIGEST)
412 {
413 printf("the F18A badge artwork digest is 0x%016llx, expected 0x%016llx\n",
414 (unsigned long long)digest, (unsigned long long)BADGE_DIGEST);
415 return 1;
416 }
417
418 /* Named, so a digest mismatch has somewhere to start: the margin and the box. */
419 pico9918_f18a_badge_render(0, 0, badge);
420 if (badge[0] != PICO9918_PIXEL_FROM_RGB12(0xb202))
421 {
422 printf("the F18A badge margin is 0x%04x, expected dark green\n", (unsigned)badge[0]);
423 return 1;
424 }
425 pico9918_f18a_badge_render(1, 0, badge);
426 if (badge[2] != PICO9918_PIXEL_FROM_RGB12(0xff0f))
427 {
428 printf("the F18A badge box edge is 0x%04x, expected white\n", (unsigned)badge[2]);
429 return 1;
430 }
431
432 /* Shown for frames 0..383 and never again, and a frame outside that window must
433 leave the buffer alone rather than draw something the host then has to undo. */
434 if (!pico9918_f18a_badge_render(0, PICO9918_F18A_BADGE_FRAMES - 1, badge))
435 {
436 printf("the F18A badge stopped one frame early\n");
437 return 1;
438 }
439
440 fillPixels(badge, BADGE_BUFFER, sentinel);
441 if (pico9918_f18a_badge_render(0, PICO9918_F18A_BADGE_FRAMES, badge) ||
442 badge[0] != sentinel)
443 {
444 printf("the F18A badge outlived frame %u\n", (unsigned)PICO9918_F18A_BADGE_FRAMES);
445 return 1;
446 }
447
448 fillPixels(badge, BADGE_BUFFER, sentinel);
449 if (pico9918_f18a_badge_render(PICO9918_F18A_BADGE_HEIGHT, 0, badge) ||
450 badge[0] != sentinel)
451 {
452 printf("the F18A badge drew below row %u\n", (unsigned)PICO9918_F18A_BADGE_HEIGHT);
453 return 1;
454 }
455
456 /* One badge row per OUTPUT line, odd lines included. An odd line re-reads the
457 buffer rather than re-rendering, and what it holds is the row above, so the badge
458 has to be drawn again there and the call has to say the buffer changed. */
459 {
460 PICO9918_PIXEL_T __aligned(4) out[BADGE_LINE_PIXELS];
461 pico9918_scanline_params_t params = {BADGE_LINE_PIXELS, 240, false, 0};
462
467 /* Otherwise an odd line reports a change because it was dimmed, and the badge's
468 own contribution to that answer would go unchecked. */
469 pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_CRT_SCANLINES] = 0;
470
471 for (uint32_t line = 0; line < PICO9918_F18A_BADGE_HEIGHT; ++line)
472 {
473 fillPixels(out, BADGE_LINE_PIXELS, sentinel);
474
475 if (!pico9918_frame_output_line(PICO9918_INST line, &params, out))
476 {
477 printf("output line %u reported no change with the F18A badge on it\n",
478 (unsigned)line);
479 return 1;
480 }
481
482 fillPixels(badge, BADGE_BUFFER, sentinel);
483 pico9918_f18a_badge_render((uint16_t)line, 0, badge);
484
485 for (uint32_t x = 0; x < PICO9918_F18A_BADGE_WIDTH; ++x)
486 {
487 if (out[x] != badge[x])
488 {
489 printf("output line %u pixel %u is 0x%04x, badge row %u has 0x%04x\n",
490 (unsigned)line, (unsigned)x, (unsigned)out[x], (unsigned)line,
491 (unsigned)badge[x]);
492 return 1;
493 }
494 }
495 }
496
497 /* A PICO9918 shows its own splash instead, and that one is not here. */
499 fillPixels(out, BADGE_LINE_PIXELS, sentinel);
501
502 pico9918_f18a_badge_render(1, 0, badge);
503 if (out[2] == badge[2])
504 {
505 printf("a PICO9918 drew the F18A badge\n");
506 return 1;
507 }
508 }
509
510 printf("pico9918-core: the F18A badge is pixel-exact, one row per output line\n");
511 }
512#endif
513
516 pico9918_set_fg_bg_color(PICO9918_INST TMS_WHITE, TMS_DK_BLUE);
518
519 const uint8_t status = pico9918_scan_line(PICO9918_INST 0);
520 const uint32_t bytes = pico9918_line_bytes(PICO9918_INST_ONLY);
521 const uint8_t* line = pico9918_line_source(PICO9918_INST_ONLY);
522
523 if (!line || bytes < TMS9918_PIXELS_X || bytes > PICO9918_SCANLINE_BUFFER_SIZE)
524 {
525 printf("implausible line: %p, %u bytes\n", (const void*)line, (unsigned)bytes);
526 return 1;
527 }
528 if (pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_GRAPHICS_I)
529 {
530 printf("mode is not Graphics I\n");
531 return 1;
532 }
533
534 printf("pico9918-core: %u-byte line, status 0x%02x, palette entry 1 0x%08x\n",
535 (unsigned)bytes, status, pico9918_palette[1]);
536
537 /* The settings block a host loads out of its own storage. Writable through the
538 accessor and read by the library, which is the whole contract - so setting one
539 panel byte and applying must derive the summary the overlay gates on. */
540 uint8_t* config = pico9918_config(PICO9918_INST_ONLY);
541 if (!config)
542 {
543 printf("pico9918_config returned NULL\n");
544 return 1;
545 }
546
547 config[PICO9918_CONF_DIAG] = 0;
548 config[PICO9918_CONF_DIAG_REGISTERS] = 1;
550
551 if (!pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_DIAG])
552 {
553 printf("config applied but the derived DIAG summary stayed clear\n");
554 return 1;
555 }
556
557 config[PICO9918_CONF_DIAG_REGISTERS] = 0;
559
560 if (pico9918_config(PICO9918_INST_ONLY)[PICO9918_CONF_DIAG])
561 {
562 printf("every panel is off and the derived DIAG summary is still set\n");
563 return 1;
564 }
565
566 printf("pico9918-core: config block reachable, %u bytes, applied both ways\n",
567 (unsigned)PICO9918_CONFIG_BYTES);
568
569 /* The status file, read without the side effects of reading it. Two things to prove,
570 because pico9918_read_status has neither: that it INDEXES rather than always
571 answering SR0, and that it leaves the flags alone. */
572 const uint8_t sr1Before = pico9918_status_value(PICO9918_INST PICO9918_SR_IDENT);
574
576 {
577 printf("status_value did not read SR0 back: 0x%02x\n",
579 return 1;
580 }
581
582 /* SR1 must not have moved - if this always answered SR0 it would read 0x45 now */
584 {
585 printf("status_value ignores its register argument\n");
586 return 1;
587 }
588
589 /* twice, unchanged: a destructive read would differ the second time */
591 {
592 printf("status_value cleared what it returned\n");
593 return 1;
594 }
595
596 /* and the destructive read still is destructive, so the two are really different */
599 {
600 printf("read_status left the flags standing\n");
601 return 1;
602 }
603
604 printf("pico9918-core: status file readable without clearing it\n");
605
606#if !PICO9918_SINGLE_INSTANCE
607 /* Two VDPs at once is what this mode is for, and no other test can check it: every
608 harness in the library holds exactly one. Cleared VRAM makes every tile pixel
609 transparent, so each line is its own instance's backdrop and the two must differ. */
610 pico9918_t* second = pico9918_new();
611 if (!second)
612 {
613 printf("second pico9918_new failed\n");
614 return 1;
615 }
616 pico9918_reset(second);
618 pico9918_set_fg_bg_color(second, TMS_WHITE, TMS_DK_RED);
620
621 /* the line is one module-level buffer, so read each instance's out before the other renders */
622 pico9918_scan_line(tms9918, 0);
623 const uint8_t firstPixel = pico9918_line_source(tms9918)[0];
624 pico9918_scan_line(second, 0);
625 const uint8_t secondPixel = pico9918_line_source(second)[0];
626
627 if (firstPixel == secondPixel)
628 {
629 printf("two instances rendered the same line: 0x%02x\n", firstPixel);
630 return 1;
631 }
632 printf("pico9918-core: two instances, lines 0x%02x and 0x%02x\n", firstPixel, secondPixel);
633
634 /* The host callbacks, registered per instance - the reason this mode has them at all.
635 A single shared registration passes the first check and fails the second, because
636 registering on `second` would have overwritten the first instance's. */
637 int firstTag = 0, secondTag = 0;
638 pico9918_config_set_applied_callback(tms9918, appliedCallback, &firstTag);
639 pico9918_config_set_applied_callback(second, appliedCallback, &secondTag);
640
641 pico9918_config_apply_now(second, false);
642 if (appliedSeen.calls != 1 || appliedSeen.inst != second || appliedSeen.userdata != &secondTag)
643 {
644 printf("the second instance's config-applied callback did not fire with its own "
645 "instance and userdata\n");
646 return 1;
647 }
648
649 pico9918_config_apply_now(tms9918, false);
650 if (appliedSeen.calls != 2 || appliedSeen.inst != tms9918 || appliedSeen.userdata != &firstTag)
651 {
652 printf("registering on one instance disturbed the other's callback\n");
653 return 1;
654 }
655
656 /* and a NULL registration is how a host withdraws one */
657 pico9918_config_set_applied_callback(second, NULL, NULL);
658 pico9918_config_apply_now(second, false);
659 if (appliedSeen.calls != 2)
660 {
661 printf("a NULL registration still fired\n");
662 return 1;
663 }
664
665 printf("pico9918-core: host callbacks registered per instance\n");
666
667 pico9918_destroy(second);
669#endif
670 return 0;
671}
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
Definition pico9918.c:3587
void pico9918_destroy(pico9918_t *tms9918)
destroy a TMS9918
Definition pico9918.c:420
void pico9918_set_chip(pico9918_t *tms9918, pico9918_chip_t chip)
select which chip this instance answers as
Definition pico9918.c:330
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
Definition pico9918.c:3617
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:3427
pico9918_chip_t pico9918_chip(pico9918_t *tms9918)
which chip this instance answers as
Definition pico9918.c:370
uint8_t pico9918_vram_value(pico9918_t *tms9918, uint16_t addr)
return a value from vram
Definition pico9918.c:3573
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
Definition pico9918.c:3606
void pico9918_set_status(pico9918_t *tms9918, uint8_t status)
set status flag
Definition pico9918.c:487
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
Definition pico9918.c:3335
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
Definition pico9918.c:378
uint8_t pico9918_read_status(pico9918_t *tms9918)
read from the status register
Definition pico9918.c:439
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:3420
void pico9918_write_data(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
Definition pico9918.c:455
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:600
#define TMS_R1_MODE_TEXT
40-column text
Definition pico9918.h:320
#define TMS_R1_RAM_16K
register 1 bits: VRAM size, blanking, interrupt, mode and sprite size
Definition pico9918.h:311
#define TMS_R0_MODE_TEXT_80
80-column text, with R1's text mode.
Definition pico9918.h:304
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:83
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:86
#define TMS_R1_DISP_ACTIVE
render the active display
Definition pico9918.h:314
#define TMS_R0_MODE_GRAPHICS_II
Graphics II - the only mode R0 selects.
Definition pico9918.h:301
#define TMS_R1_RAM_4K
4KB of VRAM
Definition pico9918.h:312
#define PICO9918_SCANLINE_BYTES_MAX
the widest active line this build renders, in bytes
Definition pico9918.h:591
@ PICO9918_REG_CONFIG_INDEX
PICO9918 only: which configuration byte R59 addresses.
Definition pico9918.h:257
@ PICO9918_REG_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
Definition pico9918.h:258
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
Definition pico9918.h:233
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
Definition pico9918.h:256
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
Definition pico9918.h:270
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
Definition pico9918.h:271
#define PICO9918_CHIP_MAX
the highest personality this build can be, and what a new instance is
Definition pico9918.h:182
#define PICO9918_R57_UNLOCK
the value register 57 takes, twice in a row, to unlock
Definition pico9918.h:382
@ PICO9918_CHIP_PICO9918
an F18A plus the PICO9918's own extensions
Definition pico9918.h:168
@ PICO9918_CHIP_TMS9918
a pre-A TMS9918: a TMS9918A without Graphics II
Definition pico9918.h:165
@ PICO9918_CHIP_PICO9918_PRO
a PICO9918 PRO: 8bpp 80-column text, its own splash
Definition pico9918.h:169
@ PICO9918_CHIP_TMS9918A
a TMS9918A: locked, no GPU, no extensions
Definition pico9918.h:166
@ PICO9918_CHIP_F18A
an F18A: unlock, enhanced renderer, GPU
Definition pico9918.h:167
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:85
uint8_t * pico9918_config(pico9918_t *tms9918)
the instance's PICO9918_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
void pico9918_config_apply_now(pico9918_t *tms9918, bool applyVdpEffects)
apply the block now, and cancel any apply already owed
pico9918-core - config byte layout
#define PICO9918_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