pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
goldenClock.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - the golden harness's deterministic clock
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 * Force-included (-include) into every TU of the golden build - the library AND
12 * the harness - through the documented host-override mechanism (see
13 * impl/platform.h).
14 *
15 * WHY THIS EXISTS
16 *
17 * The library's one wall-clock read is PICO9918_HOST_TIME_US(), which defaults to
18 * time_us_32() - QueryPerformanceCounter / clock_gettime off-target, so neither of
19 * the two library surfaces that read it is repeatable without this:
20 *
21 * - the diagnostics panel's GPU% row, and the flt2Str / uint2Str plumbing behind
22 * it, which cannot be covered at all against a wall clock, and
23 * - the F18A reset/snap timer registers, which are device behaviour a host can
24 * read back rather than diagnostics.
25 *
26 * The counter below replaces the clock for both, which is the point of putting
27 * the op on the whole library rather than in the overlay: a clock injected for
28 * the panel alone would leave the timer registers on the wall clock and the
29 * resulting flakiness would read as a harness bug.
30 *
31 * WHY A FIXED STEP AND NOT A CONSTANT
32 *
33 * A constant clock would make totalTime (currentTime - lastUpdateTime) ZERO on
34 * the second and every later update, and the GPU% row divides by it - a
35 * divide-by-zero producing inf/nan and, worse, a row whose glyphs would no
36 * longer respond to the arithmetic being tested. A fixed nonzero STEP keeps the
37 * elapsed interval both deterministic and representative.
38 *
39 * The step is a per-call increment, not a per-frame one, deliberately: the
40 * library must not be able to tell how many times it read the clock, so no
41 * call count is baked into a golden. Any read order still yields the same
42 * sequence because the sequence is the only state.
43 *
44 * goldenClockReset() lets a scenario start the sequence from a known point, so
45 * cases stay independent of each other and of scene order - the same property
46 * overlayPrimeDiag exists to give the value strings.
47 */
48
49#pragma once
50
51#include <stdint.h>
52
53/*
54 * 1000us per read. Chosen so the derived numbers are stable, nonzero and land
55 * with digits in the positions the panel actually renders:
56 * - the GPU% row's divisor totalTime is a whole number of milliseconds, and
57 * - gpuTimeUs is 0 in the harness (pico9918_gpu_loop is never run here, so the
58 * accumulator is never fed), which makes the GPU% row a stable 0.000.
59 * Not a power of two: a shift-vs-divide mutation must not be absorbed.
60 */
61#define GOLDEN_CLOCK_STEP_US 1000u
62
63extern uint32_t goldenClockNow;
64
65static inline uint32_t goldenClockTick(void)
66{
67 const uint32_t now = goldenClockNow;
68 goldenClockNow = now + GOLDEN_CLOCK_STEP_US;
69 return now;
70}
71
72static inline void goldenClockReset(void)
73{
74 goldenClockNow = 0;
75}
76
77#define PICO9918_HOST_TIME_US() goldenClockTick()