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 a GPU workspace register, R0-R15, without disturbing anything
166 *
167 * The workspace is fixed at 0xFFFE and a TMS9900 register is a word there, so this is
168 * the two bytes at 0xFFFE + 2n read big-endian. Only the low four bits of \p reg are
169 * used. Reachable through pico9918_gpu_mem_value() as well; this is here because the
170 * wrap past 0xFFFF is the library's business, not a debugger's.
171 */
173uint16_t pico9918_gpu_reg_value(PICO9918_INST_ARG uint8_t reg);
174
175/**
176 * \brief the GPU's status register between instructions
177 *
178 * In the architectural bit positions, which is where STST stores them and where a
179 * disassembler or a flag display expects them - use the PICO9918_GPU_ST_* masks. The
180 * cores keep the flags in the low byte internally; this is the only published view and
181 * it agrees with the instruction.
182 *
183 * TRAP: maintained only where the library paces the GPU itself. The hand-written Thumb
184 * cores a board builds run a program to completion and keep the status in a local, so
185 * there is no point between instructions for this to describe and it reads whatever it
186 * last held. Where pico9918_gpu_step_n() honours its cap - every desktop build - this
187 * is the status at the point the slice stopped, which is what a single step wants.
188 */
191
192/* ST0-ST5 of pico9918_gpu_status(), the TMS9900's own numbering from the MSB */
193#define PICO9918_GPU_ST_LGT 0x8000 /**< ST0, logical greater than */
194#define PICO9918_GPU_ST_AGT 0x4000 /**< ST1, arithmetic greater than */
195#define PICO9918_GPU_ST_EQ 0x2000 /**< ST2, equal */
196#define PICO9918_GPU_ST_C 0x1000 /**< ST3, carry */
197#define PICO9918_GPU_ST_OV 0x0800 /**< ST4, overflow */
198#define PICO9918_GPU_ST_P 0x0400 /**< ST5, odd parity */
199
200/**
201 * Return the GPU's CPU time in microseconds.
202 * If the GPU is still running (hasn't reported back), returns totalTime.
203 *
204 * CROSS-CORE: the accumulator and its reported-back flag are written by
205 * pico9918_gpu_loop - core 0 on Pico - while these two calls are made from the
206 * frame/overlay side on core 1. Both are volatile and neither call is guarded:
207 * the worst case is one sample window's update being lost, which is acceptable
208 * for a statistics readout and cheaper than a critical section per frame.
209 */
211uint32_t pico9918_gpu_time(uint32_t totalTime);
212
213/**
214 * Reset the internal GPU time accumulator to 0.
215 * Cross-core, unguarded - see pico9918_gpu_time.
216 */
218void pico9918_gpu_reset_time(void);
219
220/**
221 * Register a callback that will be invoked when the GPU wants to flash a sector.
222 * Pass NULL to disable.
223 *
224 * Registered per instance in a multi-instance build - see pico9918.h for why the two
225 * builds take different shapes.
226 */
228void pico9918_gpu_set_flash_callback(PICO9918_INST_ARG pico9918_gpu_flash_fn cb, void* userdata);
229
230/** \brief what a flash operation finished as, reported in status register 2 */
231typedef enum
232{
233 PICO9918_FLASH_OK = 0, /**< the operation completed */
234 PICO9918_FLASH_ERR_HEADER = 1, /**< the staged block is not one this build takes */
235 PICO9918_FLASH_ERR_SEQUENCE = 2, /**< a block arrived without the one before it */
236 PICO9918_FLASH_ERR_SIZE = 3, /**< the target is past the region the operation may write */
237 PICO9918_FLASH_ERR_VERIFY = 4, /**< what was read back is not what was written */
238 PICO9918_FLASH_ERR_UNSUPPORTED = 5, /**< no host is listening - see pico9918_gpu_set_flash_callback */
239 PICO9918_FLASH_ERR_FULL = 6, /**< no block is free to allocate */
241
242/**
243 * End the flash operation R63 requested, with the result the guest reads back.
244 *
245 * The busy flag is the engine's and a host has no other way to clear it: the palette
246 * rebuild is forced while SR2 bit 7 is set, so an operation left un-ended rebuilds the
247 * lookup table on every active scanline. Call this once per request, from the callback or
248 * later - a host that hands the work to another thread ends it when that finishes, and
249 * the guest polls SR2 until then.
250 *
251 * The pending request itself is already taken before the callback is entered, so a
252 * request arriving during a long erase re-arms rather than being lost here.
253 *
254 * Bits 6-5 (the retry count) and 1-0 (the progress code) are left as they were found.
255 */
258
259/**
260 * Register a callback that will be invoked when the GPU loop detects a config
261 * action request. The callback receives the config array pointer and the
262 * config key that fired (save / forced save / pending confirm / pending
263 * cancel - semantics are owned by the host). The key is cleared before the
264 * callback is invoked.
265 * Pass NULL to disable.
266 *
267 * Registered per instance in a multi-instance build - see pico9918.h for why the two
268 * builds take different shapes.
269 */
271void 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:496
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
Where the GPU is: the address the next slice resumes from.
Definition gpu.c:459
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
Definition gpu.c:421
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:95
volatile uint8_t pico9918_gpu_palette_guard_off
The palette guard, as much of it as the host has to see.
Definition gpu.c:256
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
Definition gpu.c:388
uint32_t pico9918_gpu_mem_size(void)
the size of that space, so a memory view knows where to stop
Definition gpu.c:469
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:430
void pico9918_gpu_loop(pico9918_t *tms9918)
GPU main loop - call from a dedicated core/thread.
Definition gpu.c:546
void pico9918_gpu_rearm_palette_guard(pico9918_t *tms9918)
put the palette guard back, from the core that owns the MPU
Definition gpu.c:306
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:476
pico9918_flash_result_t
what a flash operation finished as, reported in status register 2
Definition gpu.h:232
@ PICO9918_FLASH_ERR_SEQUENCE
a block arrived without the one before it
Definition gpu.h:235
@ PICO9918_FLASH_ERR_FULL
no block is free to allocate
Definition gpu.h:239
@ PICO9918_FLASH_ERR_VERIFY
what was read back is not what was written
Definition gpu.h:237
@ PICO9918_FLASH_OK
the operation completed
Definition gpu.h:233
@ PICO9918_FLASH_ERR_HEADER
the staged block is not one this build takes
Definition gpu.h:234
@ PICO9918_FLASH_ERR_UNSUPPORTED
no host is listening - see pico9918_gpu_set_flash_callback
Definition gpu.h:238
@ PICO9918_FLASH_ERR_SIZE
the target is past the region the operation may write
Definition gpu.h:236
uint16_t pico9918_gpu_reg_value(pico9918_t *tms9918, uint8_t reg)
a GPU workspace register, R0-R15, without disturbing anything
Definition gpu.c:486
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:112
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
Definition gpu.c:412
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:509
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:562
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:101
pico9918-core - core interface
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:41