pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
gpu.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - GPU Interface
4 *
5 * Copyright (c) 2021 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Purpose: Library-public interface to the TMS9900 GPU (F18A compatibility layer)
12 *
13 */
14
15#pragma once
16
17/*
18 * Public surface only: out-of-line API needing nothing but the instance type and
19 * the PICO9918_INST_* argument macros, both of which pico9918.h declares. Nothing
20 * under impl/ may be included from here - pico9918_gpu_trigger needs the private
21 * layout, so it lives on the privileged surface in impl/pico9918_gpu_priv.h.
22 */
23#include "pico9918.h"
24/**
25 * Initialize the TMS9900 GPU.
26 * Must be called after pico9918_init() / pico9918_reset().
27 */
30
31#ifdef PICO_BUILD
32/**
33 * The palette guard, as much of it as the host has to see. A GPU palette write
34 * has no other way of announcing itself, so an MPU region faults on it, marks
35 * the palette dirty and takes itself out of the way until the renderer has taken
36 * the flag. Putting it back is the host's to schedule, because the MPU belongs
37 * to the core running the GPU, and the only place that core is reliably idle is
38 * its scanline interrupt: read the flag from there, and call the re-arm.
39 */
40extern volatile uint8_t pico9918_gpu_palette_guard_off;
41/** \brief put the palette guard back, from the core that owns the MPU */
44#endif
45
46/**
47 * GPU main loop - call from a dedicated core/thread.
48 * Runs indefinitely; processes GPU programs, flash requests, and config saves.
49 */
52
53/**
54 * One pass of that loop: run a pending trigger to completion, then dispatch any
55 * flash and config-action requests. Returns.
56 *
57 * It is the loop's body rather than a second copy of it, so a program run this
58 * way is run by the same code the device runs it with, and it is timed into the
59 * same accumulator pico9918_gpu_time reads.
60 *
61 * How long it takes is the program's business: run9900 returns on IDLE or when
62 * the program clears its own run flag (TMS register 0x38 bit 0), and a program
63 * that does neither does not return.
64 *
65 * Which makes this the wrong entry for a host with one thread, however much it
66 * looks like the right one. A program may WAIT on the display - the scanline
67 * being scanned out is readable at >7000 - and the caller that would advance the
68 * raster is the one blocked in here. Use pico9918_gpu_step_n for that, or give
69 * the GPU a thread and render on the one you have.
70 */
73
74/**
75 * Rough GPU throughput, in TMS9900 instructions a second, for pico9918_gpu_set_clock.
76 *
77 * At the top clock preset, not the 252MHz a board boots at. The PRO and F18A figures sit
78 * where a measured comparison puts them - a PRO at 352MHz beats an F18A, which lands at
79 * a PRO's 302MHz preset within a couple of percent - and the RP2040 where cycle-counting
80 * its dispatch does. Read the GPU% row of the diagnostics overlay to do better.
81 */
82#define PICO9918_GPU_IPS_CLASSIC 7000000u /* PICO9918, RP2040 at 352MHz */
83#define PICO9918_GPU_IPS_PRO 10000000u ///< PICO9918 PRO, RP2350 at 352MHz
84#define PICO9918_GPU_IPS_F18A 8500000u ///< the F18A itself, ie. a PRO at 302MHz
85
86/**
87 * Hand GPU execution to the library, at this many instructions a second.
88 *
89 * Zero - the default - leaves the GPU to whoever else drives it: a board's second core,
90 * or a host thread running pico9918_gpu_loop(). Set a rate and the library runs it
91 * instead, from the register write that arms a program and once per scanline after, and
92 * a host that sets one calls no other GPU entry point. Arming matters: software probing
93 * for an F18A reads its result back a few cycles later, so a GPU serviced once a scanline
94 * has not run yet and the probe intermittently sees no F18A at all.
95 *
96 * The rate becomes a per-scanline slice, re-derived each frame, so a mode change needs
97 * nothing from the host. Ignored where pico9918_gpu_step_n's cap is - a hand-written
98 * Thumb core runs to completion - and it charges GPU time to the calling thread.
99 */
101void pico9918_gpu_set_clock(PICO9918_INST_ARG uint32_t instructionsPerSecond);
102
103/**
104 * The same pass, capped at `instructions`, returning true while the program still
105 * has work left. Zero means no cap, which is pico9918_gpu_step().
106 *
107 * This is the entry for a host with one thread. pico9918_gpu_step() cannot come back
108 * until the program stops itself, so a program that waits on the scanline at >7000 -
109 * to page a bitmap in the vertical blank, say - would wait forever: the caller that
110 * would advance the raster is the one blocked inside it. Capped, the caller gets
111 * control back with the PC kept, renders, and calls again:
112 *
113 * while (pico9918_gpu_step_n(PICO9918_INST 20000))
114 * renderOneScanline();
115 *
116 * A host with a thread to spare wants pico9918_gpu_loop() on it instead, which is
117 * what the firmware does. Both shapes are real; this one asks nothing of the host
118 * but a loop.
119 *
120 * Only the portable C core counts instructions. On a board built with the
121 * hand-written Thumb core the cap is ignored and this runs to completion - which
122 * costs that build nothing, because it has a core to give the GPU.
123 */
125bool pico9918_gpu_step_n(PICO9918_INST_ARG uint32_t instructions);
126
127/**
128 * Where the GPU is: the address the next slice resumes from. The arming address before
129 * it first runs, and the point it reached after a capped slice returns true.
130 *
131 * ODD MEANS NOTHING IS ARMED. A reset parks 0xFFFF here and the engine refuses to start
132 * from an odd address, so a caller polling this reads odd as "no program", not as a
133 * position. An even value is a real address whether or not a program is still running -
134 * use pico9918_gpu_step_n()'s return for that.
135 */
138
139/**
140 * \brief a byte of the instance's memory, without disturbing anything
141 *
142 * The BACKING STATE, not the map a GPU program observes. Those differ: a running
143 * personality mirrors 0x4xxx, 0x5xxx, 0x6xxx and 0x7xxx across 4KB each and answers 0
144 * in the holes, where this is every byte exactly once. It is the view a debugger wants,
145 * because it re-lays-out nothing when the chip personality changes, and the decoded one
146 * is derivable from it.
147 *
148 * Not what the host data port sees either: pico9918_vram_value is the guest's view and
149 * stops at 0x3FFF, so it cannot reach GRAM at 0x4000, the palette at 0x5000, the
150 * register and status windows, or the workspace. Disassembly and memory views want this
151 * one, and pico9918_debug.h has the span form of it where a build asks for that.
152 *
153 * The space runs past 0xFFFF. The GPU's workspace pointer is 0xFFFE, so R0 is the last
154 * word of the 64KB map and R1-R15 spill into an overflow above it. Anything beyond the
155 * space reads 0, so a view that walks off the end sees zeroes rather than the instance.
156 */
158uint8_t pico9918_gpu_mem_value(PICO9918_INST_ARG uint32_t addr);
159
160/** \brief the size of that space, so a memory view knows where to stop */
162uint32_t pico9918_gpu_mem_size(void);
163
164/**
165 * \brief where this program's sixteen registers are
166 *
167 * 0xFFFE unless the program moved it with LWPI, which puts R0 at the last word of the
168 * 64KB map and R1-R15 in the overflow above it. A real F18A has no LWPI and no
169 * workspace pointer - its registers are a register file - so on that personality this
170 * only ever answers 0xFFFE.
171 *
172 * TRAP: a build whose GPU runs to completion cannot stop between instructions, so it
173 * keeps no workspace and this answers the starting value whatever the program did.
174 */
177
178/**
179 * \brief a GPU workspace register, R0-R15, without disturbing anything
180 *
181 * A TMS9900 register is a word at the workspace, so this is the two bytes at
182 * pico9918_gpu_wp() + 2n read big-endian. Only the low four bits of \p reg are used.
183 * Reachable through pico9918_gpu_mem_value() as well; this is here because the wrap
184 * past 0xFFFF is the library's business, not a debugger's.
185 */
187uint16_t pico9918_gpu_reg_value(PICO9918_INST_ARG uint8_t reg);
188
189/**
190 * \brief the GPU's status register between instructions
191 *
192 * In the architectural bit positions, which is where STST stores them and where a
193 * disassembler or a flag display expects them - use the PICO9918_GPU_ST_* masks. The
194 * cores keep the flags in the low byte internally; this is the only published view and
195 * it agrees with the instruction.
196 *
197 * TRAP: maintained only where the library paces the GPU itself. The hand-written Thumb
198 * cores a board builds run a program to completion and keep the status in a local, so
199 * there is no point between instructions for this to describe and it reads whatever it
200 * last held. Where pico9918_gpu_step_n() honours its cap - every desktop build - this
201 * is the status at the point the slice stopped, which is what a single step wants.
202 */
205
206/* ST0-ST5 of pico9918_gpu_status(), the TMS9900's own numbering from the MSB */
207#define PICO9918_GPU_ST_LGT 0x8000 /**< ST0, logical greater than */
208#define PICO9918_GPU_ST_AGT 0x4000 /**< ST1, arithmetic greater than */
209#define PICO9918_GPU_ST_EQ 0x2000 /**< ST2, equal */
210#define PICO9918_GPU_ST_C 0x1000 /**< ST3, carry */
211#define PICO9918_GPU_ST_OV 0x0800 /**< ST4, overflow */
212#define PICO9918_GPU_ST_P 0x0400 /**< ST5, odd parity */
213
214/**
215 * Return the GPU's CPU time in microseconds.
216 * If the GPU is still running (hasn't reported back), returns totalTime.
217 *
218 * CROSS-CORE: the accumulator and its reported-back flag are written by
219 * pico9918_gpu_loop - core 0 on Pico - while these two calls are made from the
220 * frame/overlay side on core 1. Both are volatile and neither call is guarded:
221 * the worst case is one sample window's update being lost, which is acceptable
222 * for a statistics readout and cheaper than a critical section per frame.
223 */
225uint32_t pico9918_gpu_time(uint32_t totalTime);
226
227/**
228 * Reset the internal GPU time accumulator to 0.
229 * Cross-core, unguarded - see pico9918_gpu_time.
230 */
232void pico9918_gpu_reset_time(void);
233
234/**
235 * Register a callback that will be invoked when the GPU wants to flash a sector.
236 * Pass NULL to disable.
237 *
238 * Registered per instance in a multi-instance build - see pico9918.h for why the two
239 * builds take different shapes.
240 */
242void pico9918_gpu_set_flash_callback(PICO9918_INST_ARG pico9918_gpu_flash_fn cb, void* userdata);
243
244/**
245 * \brief is a flash request outstanding?
246 *
247 * SR2 bit 7 cannot proxy for this: it is also set for a running GPU program. And under
248 * pico9918_gpu_set_clock(0) nothing dispatches the request until the host calls
249 * pico9918_gpu_step_n(), so a host that owns dispatch owns noticing.
250 */
253
254/** \brief what a flash operation finished as, reported in status register 2 */
255typedef enum
256{
257 PICO9918_FLASH_OK = 0, /**< the operation completed */
258 PICO9918_FLASH_ERR_HEADER = 1, /**< the staged block is not one this build takes */
259 PICO9918_FLASH_ERR_SEQUENCE = 2, /**< a block arrived without the one before it */
260 PICO9918_FLASH_ERR_SIZE = 3, /**< the target is past the region the operation may write */
261 PICO9918_FLASH_ERR_VERIFY = 4, /**< what was read back is not what was written */
262 PICO9918_FLASH_ERR_UNSUPPORTED = 5, /**< no host is listening - see pico9918_gpu_set_flash_callback */
263 PICO9918_FLASH_ERR_FULL = 6, /**< no block is free to allocate */
265
266/**
267 * End the flash operation R63 requested, with the result the guest reads back.
268 *
269 * The busy flag is the engine's and a host has no other way to clear it: the palette
270 * rebuild is forced while SR2 bit 7 is set, so an operation left un-ended rebuilds the
271 * lookup table on every active scanline. Call this once per request, from the callback or
272 * later - a host that hands the work to another thread ends it when that finishes, and
273 * the guest polls SR2 until then.
274 *
275 * The pending request itself is already taken before the callback is entered, so a
276 * request arriving during a long erase re-arms rather than being lost here.
277 *
278 * Bits 6-5 (the retry count) and 1-0 (the progress code) are left as they were found.
279 */
282
283/**
284 * Register a callback that will be invoked when the GPU loop detects a config
285 * action request. The callback receives the config array pointer and the
286 * config key that fired (save / forced save / pending confirm / pending
287 * cancel - semantics are owned by the host). The key is cleared before the
288 * callback is invoked.
289 * Pass NULL to disable.
290 *
291 * Registered per instance in a multi-instance build - see pico9918.h for why the two
292 * builds take different shapes.
293 */
295void pico9918_gpu_set_config_save_callback(PICO9918_INST_ARG pico9918_gpu_config_save_fn cb, void* userdata);
uint16_t pico9918_gpu_status(pico9918_t *tms9918)
the GPU's status register between instructions
Definition gpu.c:587
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
Where the GPU is: the address the next slice resumes from.
Definition gpu.c:540
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
Definition gpu.c:502
void pico9918_gpu_set_flash_callback(pico9918_t *tms9918, pico9918_gpu_flash_fn cb, void *userdata)
Register a callback that will be invoked when the GPU wants to flash a sector.
Definition gpu.c:161
volatile uint8_t pico9918_gpu_palette_guard_off
The palette guard, as much of it as the host has to see.
Definition gpu.c:329
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
Definition gpu.c:466
uint32_t pico9918_gpu_mem_size(void)
the size of that space, so a memory view knows where to stop
Definition gpu.c:560
void pico9918_gpu_step(pico9918_t *tms9918)
One pass of that loop: run a pending trigger to completion, then dispatch any flash and config-action...
Definition gpu.c:511
uint16_t pico9918_gpu_wp(pico9918_t *tms9918)
where this program's sixteen registers are
Definition gpu.c:547
void pico9918_gpu_loop(pico9918_t *tms9918)
GPU main loop - call from a dedicated core/thread.
Definition gpu.c:663
void pico9918_gpu_rearm_palette_guard(pico9918_t *tms9918)
put the palette guard back, from the core that owns the MPU
Definition gpu.c:379
uint8_t pico9918_gpu_mem_value(pico9918_t *tms9918, uint32_t addr)
a byte of the instance's memory, without disturbing anything
Definition gpu.c:567
bool pico9918_gpu_flash_pending(pico9918_t *tms9918)
is a flash request outstanding?
Definition gpu.c:168
pico9918_flash_result_t
what a flash operation finished as, reported in status register 2
Definition gpu.h:256
@ PICO9918_FLASH_ERR_SEQUENCE
a block arrived without the one before it
Definition gpu.h:259
@ PICO9918_FLASH_ERR_FULL
no block is free to allocate
Definition gpu.h:263
@ PICO9918_FLASH_ERR_VERIFY
what was read back is not what was written
Definition gpu.h:261
@ PICO9918_FLASH_OK
the operation completed
Definition gpu.h:257
@ PICO9918_FLASH_ERR_HEADER
the staged block is not one this build takes
Definition gpu.h:258
@ PICO9918_FLASH_ERR_UNSUPPORTED
no host is listening - see pico9918_gpu_set_flash_callback
Definition gpu.h:262
@ PICO9918_FLASH_ERR_SIZE
the target is past the region the operation may write
Definition gpu.h:260
uint16_t pico9918_gpu_reg_value(pico9918_t *tms9918, uint8_t reg)
a GPU workspace register, R0-R15, without disturbing anything
Definition gpu.c:577
void pico9918_gpu_flash_complete(pico9918_t *tms9918, pico9918_flash_result_t result)
End the flash operation R63 requested, with the result the guest reads back.
Definition gpu.c:184
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
Definition gpu.c:493
bool pico9918_gpu_step_n(pico9918_t *tms9918, uint32_t instructions)
The same pass, capped at instructions, returning true while the program still has work left.
Definition gpu.c:600
void pico9918_gpu_set_clock(pico9918_t *tms9918, uint32_t instructionsPerSecond)
Hand GPU execution to the library, at this many instructions a second.
Definition gpu.c:679
void pico9918_gpu_set_config_save_callback(pico9918_t *tms9918, pico9918_gpu_config_save_fn cb, void *userdata)
Register a callback that will be invoked when the GPU loop detects a config action request.
Definition gpu.c:173
pico9918-core - core interface
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:83
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:84
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:50