pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_debug.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - debugger access
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 * Purpose: what a host's memory pane, register editor and disassembler need, so that
12 * none of them has to include impl/.
13 *
14 * These read and write the library's BACKING STATE - the instance's memory as it is
15 * stored, every byte appearing exactly once, the GPU's workspace overflow included.
16 * That is not the map a GPU program observes: a running personality mirrors 0x4xxx,
17 * 0x5xxx, 0x6xxx and 0x7xxx across 4KB and answers 0 in the holes. Backing state is
18 * what an emulator's debugger wants, because it re-lays-out nothing when the user
19 * switches chip, and the decoded view is derivable from it.
20 *
21 * Nothing here disturbs the machine. No address latch moves, no read-ahead is
22 * consumed, no status is cleared, no interrupt is acknowledged, and the guest cannot
23 * tell that any of it happened - which is the entire difference between this and
24 * driving the host bus.
25 *
26 * The scalar half of the read side is already published: pico9918_gpu_mem_size() is
27 * the size of the map these address, and pico9918_gpu_mem_value() is one byte of it.
28 * They are in gpu/gpu.h for historical reasons and are part of this surface.
29 */
30
31#pragma once
32
33#include <stddef.h>
34
35#include "gpu/gpu.h"
36#include "pico9918.h"
37#include "pico9918_build_config.h"
38
39/* Not in every archive, so including this without it would fail at link time with
40 nothing to say why. */
41#if !PICO9918_BUILD_DEBUG_API
42#error "this library was built without PICO9918_DEBUG_API"
43#endif
44
45#ifdef __cplusplus
46extern "C"
47{
48#endif
49
50/* what pico9918_debug_region() reports about a span */
51#define PICO9918_DEBUG_READABLE 0x01 /**< pico9918_debug_read returns real bytes here */
52#define PICO9918_DEBUG_WRITABLE 0x02 /**< pico9918_debug_write stores here */
53#define PICO9918_DEBUG_REGISTERS 0x04 /**< the register file - use pico9918_debug_reg_write */
54#define PICO9918_DEBUG_STATUS 0x08 /**< the status file, which the library owns */
55#define PICO9918_DEBUG_PALETTE 0x10 /**< PRAM - a write here republishes the palette */
56
57/**
58 * \brief what the byte at \p addr is, and how far that stays true
59 *
60 * The map itself, so a pane can colour protected spans and split bulk work without
61 * carrying its own copy of the layout. Returns the PICO9918_DEBUG_* flags for the
62 * region containing \p addr, and through \p end, which may be null, the EXCLUSIVE
63 * address the region stops at - so a walk is `addr = end` until the flags come back 0.
64 *
65 * Past the end of the map returns 0 with `*end = addr`, which terminates such a walk.
66 *
67 * No instance: the layout is the build's, not the selected personality's, the same
68 * reason pico9918_gpu_mem_size() takes none.
69 */
71uint32_t pico9918_debug_region(uint32_t addr, uint32_t* end);
72
73/**
74 * \brief a span of the map, without disturbing anything
75 *
76 * Copies up to \p len bytes from \p addr into \p out and returns how many, which is
77 * short at the end of the map and 0 past it. A null \p out, or a \p len of 0, copies
78 * nothing and returns 0 rather than faulting, so a caller may probe with either.
79 *
80 * Every readable byte here is the byte pico9918_gpu_mem_value() returns for the same
81 * address. This exists because a 64KB pane one call at a time across a shared-library
82 * boundary is 65572 calls.
83 */
85size_t pico9918_debug_read(PICO9918_INST_ARG uint32_t addr, uint8_t* out, size_t len);
86
87/**
88 * \brief a span of the map, written without the machine noticing
89 *
90 * Copies up to \p len bytes from \p in to \p addr and returns how many landed. A null
91 * \p in, or a \p len of 0, writes nothing and returns 0.
92 *
93 * SHORT AT THE FIRST BYTE IT WILL NOT WRITE, which is the end of the map, the register
94 * window and the status window - the two PICO9918_DEBUG_REGISTERS and
95 * PICO9918_DEBUG_STATUS report. So a bulk loader scrubbing memory cannot start a GPU
96 * program or strand a firmware update, and a caller that wants a register has
97 * pico9918_debug_reg_write, which is a different operation with a different contract.
98 * A run that stops immediately returns 0, which is how a caller tells a refused window
99 * from an accepted one.
100 *
101 * A span landing in PRAM republishes the palette, because the write contract is "no
102 * host-bus side effects" rather than "no effects" - without it a debugger edits the
103 * palette successfully and the picture does not change.
104 */
106size_t pico9918_debug_write(PICO9918_INST_ARG uint32_t addr, const uint8_t* in, size_t len);
107
108/**
109 * \brief a register, as the register file actually holds it
110 *
111 * NOT what pico9918_reg_value() answers, which is the guest's read and folds the number
112 * to three bits on a locked device - so a pane showing R30 there is showing R6. This is
113 * the byte at \p reg. Above 63 returns 0, the file being 64 entries.
114 */
116uint8_t pico9918_debug_reg(PICO9918_INST_ARG uint8_t reg);
117
118/**
119 * \brief a register, stored where its number says, with no device behaviour
120 *
121 * NOT the device's write. pico9918_write_register_value() is a protocol: it folds the
122 * number to three bits on a locked device, so asking it for R30 stores R6; it drops the
123 * write entirely on a locked M4; and R55, R56, R50, R63 and R15 each set something in
124 * motion. A register editor wants none of that - it wants R30 to mean R30.
125 *
126 * So this is the physical store, and its contract is a list rather than a principle.
127 * For \p reg 0-63 it does EXACTLY four things:
128 *
129 * 1. stores \p value at register \p reg - not reg & lockedMask, not reg & 7
130 * 2. marks the palette as owing a republish
131 * 3. synchronizes the cached display mode, which R0 and R1 change
132 * 4. reconciles /INT, because R1's interrupt enable must take effect at once
133 *
134 * Everything else is untouched: the unlock latch, the GPU's address and armed state, the
135 * flash and config-dirty flags, the host address latch, every other register, every
136 * status byte, every config byte. No GPU program starts, no firmware update begins, no
137 * register file resets, no timer snaps.
138 *
139 * The unlock latch is PRESERVED rather than recomputed, and that is deliberate: the
140 * device unlocks on the value arriving TWICE, so the byte stored in R57 does not
141 * determine the state - after one write and after two it is the same byte and the same
142 * count, differing only in the latch. Recomputing it would have to guess. Typing into a
143 * register pane is not performing the handshake, so it does not move it.
144 *
145 * Returns false, changing nothing, for \p reg above 63.
146 */
148bool pico9918_debug_reg_write(PICO9918_INST_ARG uint8_t reg, uint8_t value);
149
150/**
151 * \brief a status byte, stored where its number says, with no device behaviour
152 *
153 * The write side of pico9918_status_value(), which is the whole file read without
154 * clearing anything. Nothing else can reach SR1-SR15: the span write refuses the status
155 * window by contract, pico9918_debug_reg_write() is the other file, and the device's own
156 * paths set these as consequences rather than on request.
157 *
158 * For \p reg 0-15 it does EXACTLY three things:
159 *
160 * 1. stores \p value at status register \p reg
161 * 2. keeps SR0's shadow in step, SR0 being latched in two places
162 * 3. reconciles /INT, SR0 and SR1 being two of the four terms that decide it
163 *
164 * The shadow is not an implementation detail a caller could skip: the frame path merges
165 * into it and publishes the result, so a write that moved only the published byte would
166 * be undone by the next frame with the old flags coming back with it.
167 *
168 * Nothing is cleared, no sprite number is restored and no read is simulated - a status
169 * editor is not the guest's destructive read. What it cannot do is make a derived byte
170 * stay put: the machine rewrites SR1's blanking bits, SR2, SR3, the SR4-SR11 counters and
171 * SR13 as it runs, so an edit to one of those lasts until the next line draws.
172 *
173 * Returns false, changing nothing, for \p reg above 15.
174 */
176bool pico9918_debug_status_write(PICO9918_INST_ARG uint8_t reg, uint8_t value);
177
178/**
179 * \brief a live palette entry, in host byte order
180 *
181 * PRAM as the renderer reads it, with the big-endian storage undone - so the value is
182 * the 0x0rgb an F18A program wrote, not the byte-swapped word underneath. The F18A
183 * defines the low twelve bits; anything above them is whatever is stored there, because
184 * this is the backing state and a debugger that wrote a raw byte should see it back.
185 *
186 * Above index 63 returns 0, PRAM being 64 entries.
187 */
189uint16_t pico9918_debug_palette(PICO9918_INST_ARG uint8_t index);
190
191/**
192 * \brief where the next guest access would land
193 *
194 * The host address latch made EFFECTIVE, which is not the counter it is kept in: that
195 * one is 32 bits and runs past the bus width between accesses, and on a 4K chip with
196 * R1's 16K bit clear the machine permutes the address rather than merely masking it. So
197 * a pane wanting "the byte the next read returns" cannot get there with a mask.
198 */
201
202/**
203 * \brief whether a GPU program is waiting to run
204 *
205 * Armed, not executing: a host pacing the GPU itself asks this to find out whether there
206 * is anything to step. Whether a program is still going after a slice is
207 * pico9918_gpu_step_n()'s return, which is a different question.
208 */
211
212#if PICO9918_BUILD_LAYER_MASK
213
214/**
215 * \brief keep layers off the picture without touching the registers that drew them
216 *
217 * The PICO9918_SUPPRESS_* bits are in pico9918.h, beside the register bits they override,
218 * because the renderer reads them and it does not include this header.
219 *
220 * A view, not a device state: nothing a guest can read changes, and the same frame comes
221 * back the moment the mask is cleared. That extends to the status file, which is the
222 * whole difficulty with suppressing sprites - SR0's collision and fifth-sprite bits are
223 * still reported for a sprite whose pixels never reach the line, because a user looking
224 * behind the sprite layer must not change what the program sees.
225 *
226 * The two Graphics II bits mean nothing on an unlocked device. ECM attributes are per
227 * tile and per position, so there is no colour table to leave out; ask
228 * pico9918_unlocked() and grey them.
229 *
230 * Bits this build does not define are stored and returned unchanged, so a host written
231 * against a later header can write a mask and read it back to find out what took.
232 */
234void pico9918_debug_set_suppress(PICO9918_INST_ARG uint32_t mask);
235
236/** \brief the mask pico9918_debug_set_suppress() last stored */
238uint32_t pico9918_debug_suppress(PICO9918_INST_ONLY_ARG);
239
240#endif // PICO9918_BUILD_LAYER_MASK
241
242/**
243 * \brief move the GPU's PC without starting it
244 *
245 * Masked even, the way the register path masks it. Leaves the armed state exactly as it
246 * found it, so this redirects a program that was going to run and does not start one
247 * that was not. Writes neither R54/R55 - which would be a second, visible effect on the
248 * register file - nor the status.
249 *
250 * pico9918_gpu_pc() reads it back.
251 */
254
255/**
256 * \brief type into the GPU's R0-R15
257 *
258 * The write pico9918_gpu_reg_value() reads back: a big-endian word at
259 * pico9918_gpu_wp() + 2n, with only the low four bits of \p reg used. No workspace can
260 * put a register out of reach, the space carrying enough overflow above 0xFFFF for R15
261 * of the highest one, so there is nothing here to refuse.
262 */
264void pico9918_debug_gpu_set_reg_value(PICO9918_INST_ARG uint8_t reg, uint16_t value);
265
266/**
267 * \brief type into the GPU's status register
268 *
269 * Takes the architectural positions pico9918_gpu_status() publishes and the
270 * PICO9918_GPU_ST_* masks name, so a flag display can write back what it showed. The
271 * cores keep the six flags in a byte, so the low half of \p st has nowhere to go and is
272 * dropped - which is what pico9918_gpu_status() already says by never setting it.
273 *
274 * Nothing re-derives the flags, so an edit stands until the next instruction that writes
275 * one. Meaningful only where the library paces the GPU; see pico9918_gpu_status().
276 */
279
280/**
281 * \brief move the GPU's workspace, the way an LWPI would
282 *
283 * Takes the program's registers with it: pico9918_gpu_reg_value() and
284 * pico9918_debug_gpu_set_reg_value() both answer at the new place immediately, and a
285 * slice that resumes carries it.
286 *
287 * ALSO SUPPRESSES THE START RESET. A program that has been armed but has not run yet is
288 * about to have its workspace put back to 0xFFFE, which would discard this. Setting it
289 * says the host owns the starting state, so the reset is skipped - for this run only.
290 * Arming another program restores it. Nothing else about the armed state moves, exactly
291 * as with pico9918_debug_gpu_set_pc().
292 *
293 * Returns false, changing nothing, on a build whose GPU runs to completion and therefore
294 * keeps no workspace between instructions - the TRAP in pico9918_gpu_wp(). A desktop
295 * build is never that build.
296 */
299
300/**
301 * \brief pico9918_gpu_step_n() with a look at every instruction before it runs
302 *
303 * \p cb is called with the PC the next instruction will be fetched from, before the
304 * fetch, and returning false stops the slice there. That is the same stop an exhausted
305 * budget makes: the PC is kept, this returns true, and the next call carries on from the
306 * instruction that was not run. Null \p cb is exactly pico9918_gpu_step_n().
307 *
308 * WHAT IT CANNOT SERVE IS A READ OR WRITE BREAKPOINT. Between instructions is too early
309 * to know what the next one will touch and too late to catch what the last one did, so a
310 * host wanting those has to decode the instruction itself. Reporting them is a separate
311 * change inside the interpreter that has not been made.
312 *
313 * The callback is this call's, not the instance's, so two debuggers or two panes do not
314 * have to agree on one. It must not re-enter the library: it is called from inside the
315 * interpreter, with the GPU's registers and status in a CPU context that is only written
316 * back when the run returns. Read the machine through the accessors after the slice.
317 *
318 * On a build whose GPU runs to completion the callback is never called, this being the
319 * build whose cap pico9918_gpu_step_n() also cannot honour. No desktop build is that one.
320 */
322bool pico9918_debug_gpu_step_n(PICO9918_INST_ARG uint32_t instructions, pico9918_gpu_step_fn cb,
323 void* userdata);
324
325#if PICO9918_BUILD_STEP_CALLBACK
326
327/**
328 * \brief the same look at every instruction, for slices the host does not pace
329 *
330 * An emulator that leaves the GPU's pacing to the library - pico9918_gpu_step_n() from
331 * its own frame loop, or a scanline handler that runs it - has no call of its own to
332 * hang a breakpoint list from, and would have to take over pacing to get one, which is
333 * the one thing a debugger must not change about the machine it is watching. This arms
334 * the callback on the INSTANCE instead, and every slice consults it, whichever entry
335 * drove it.
336 *
337 * Same contract as pico9918_debug_gpu_step_n()'s \p cb in every other respect: called
338 * before the fetch with the PC it will come from, false stops the slice with the PC kept,
339 * and it must not re-enter the library. Null disarms.
340 *
341 * A callback passed to pico9918_debug_gpu_step_n() wins for that call, so a pane that
342 * paces its own slice is not fighting whatever the main debugger armed.
343 *
344 * A reset does not clear it. The host armed it, not the guest, and a program resetting
345 * the VDP is often the thing being debugged.
346 */
348void pico9918_debug_set_step_callback(PICO9918_INST_ARG pico9918_gpu_step_fn cb, void* userdata);
349
350/**
351 * \brief the callback pico9918_debug_set_step_callback() last armed, or null
352 *
353 * Both halves of it: \p userdata, where it is not null, receives what was armed beside
354 * the function, so a caller can put the pair back afterwards rather than only ask whether
355 * there is one.
356 */
358pico9918_gpu_step_fn pico9918_debug_step_callback(PICO9918_INST_ARG void** userdata);
359
360#endif // PICO9918_BUILD_STEP_CALLBACK
361
362#ifdef __cplusplus
363}
364#endif
pico9918-core - GPU Interface
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
bool pico9918_debug_status_write(pico9918_t *tms9918, uint8_t reg, uint8_t value)
a status byte, stored where its number says, with no device behaviour
uint32_t pico9918_debug_region(uint32_t addr, uint32_t *end)
what the byte at addr is, and how far that stays true
bool pico9918_debug_gpu_step_n(pico9918_t *tms9918, uint32_t instructions, pico9918_gpu_step_fn cb, void *userdata)
pico9918_gpu_step_n() with a look at every instruction before it runs
Definition gpu.c:638
uint8_t pico9918_debug_reg(pico9918_t *tms9918, uint8_t reg)
a register, as the register file actually holds it
bool pico9918_debug_gpu_armed(pico9918_t *tms9918)
whether a GPU program is waiting to run
size_t pico9918_debug_write(pico9918_t *tms9918, uint32_t addr, const uint8_t *in, size_t len)
a span of the map, written without the machine noticing
bool pico9918_debug_gpu_set_wp(pico9918_t *tms9918, uint16_t wp)
move the GPU's workspace, the way an LWPI would
uint16_t pico9918_debug_palette(pico9918_t *tms9918, uint8_t index)
a live palette entry, in host byte order
void pico9918_debug_gpu_set_pc(pico9918_t *tms9918, uint16_t pc)
move the GPU's PC without starting it
void pico9918_debug_gpu_set_status(pico9918_t *tms9918, uint16_t st)
type into the GPU's status register
size_t pico9918_debug_read(pico9918_t *tms9918, uint32_t addr, uint8_t *out, size_t len)
a span of the map, without disturbing anything
bool pico9918_debug_reg_write(pico9918_t *tms9918, uint8_t reg, uint8_t value)
a register, stored where its number says, with no device behaviour
void pico9918_debug_gpu_set_reg_value(pico9918_t *tms9918, uint8_t reg, uint16_t value)
type into the GPU's R0-R15
uint16_t pico9918_debug_vram_address(pico9918_t *tms9918)
where the next guest access would land