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 live palette entry, in host byte order
152 *
153 * PRAM as the renderer reads it, with the big-endian storage undone - so the value is
154 * the 0x0rgb an F18A program wrote, not the byte-swapped word underneath. The F18A
155 * defines the low twelve bits; anything above them is whatever is stored there, because
156 * this is the backing state and a debugger that wrote a raw byte should see it back.
157 *
158 * Above index 63 returns 0, PRAM being 64 entries.
159 */
161uint16_t pico9918_debug_palette(PICO9918_INST_ARG uint8_t index);
162
163/**
164 * \brief where the next guest access would land
165 *
166 * The host address latch made EFFECTIVE, which is not the counter it is kept in: that
167 * one is 32 bits and runs past the bus width between accesses, and on a 4K chip with
168 * R1's 16K bit clear the machine permutes the address rather than merely masking it. So
169 * a pane wanting "the byte the next read returns" cannot get there with a mask.
170 */
173
174/**
175 * \brief whether a GPU program is waiting to run
176 *
177 * Armed, not executing: a host pacing the GPU itself asks this to find out whether there
178 * is anything to step. Whether a program is still going after a slice is
179 * pico9918_gpu_step_n()'s return, which is a different question.
180 */
183
184/**
185 * \brief move the GPU's PC without starting it
186 *
187 * Masked even, the way the register path masks it. Leaves the armed state exactly as it
188 * found it, so this redirects a program that was going to run and does not start one
189 * that was not. Writes neither R54/R55 - which would be a second, visible effect on the
190 * register file - nor the status.
191 *
192 * pico9918_gpu_pc() reads it back.
193 */
196
197#ifdef __cplusplus
198}
199#endif
pico9918-core - GPU Interface
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
uint32_t pico9918_debug_region(uint32_t addr, uint32_t *end)
what the byte at addr is, and how far that stays true
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
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
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
uint16_t pico9918_debug_vram_address(pico9918_t *tms9918)
where the next guest access would land