pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_debug.c
1/*
2 * pico9918-core - debugger access
3 *
4 * Copyright (c) 2026 Troy Schrapel
5 *
6 * This code is licensed under the MIT license
7 *
8 * https://github.com/visrealm/pico9918-core
9 *
10 * Compiled only when PICO9918_DEBUG_API is on, which a board never sets.
11 */
12
13#include "pico9918_debug.h"
14
15#include "impl/pico9918_priv.h"
16
17/*
18 * The map, as runs of equal flags rather than as the fields it is made of - adjacent
19 * fields a debugger cannot tell apart are one region, which is what "how far that stays
20 * true" means. Each row ENDS where the next begins, so row i covers [row i-1 end, end).
21 *
22 * 0x00000 base VRAM and GRAM below the palette read, write
23 * 0x05000 PRAM read, write, palette
24 * 0x06000 the 64 registers read
25 * 0x06040 GRAM, the scanline and blanking bytes read, write
26 * 0x0B000 the 16 status bytes read
27 * 0x0B010 GRAM and the GPU workspace overflow read, write
28 *
29 * Derived with offsetof rather than transcribed, so a change to the union moves the
30 * regions with it instead of silently disagreeing.
31 */
32#define MAP_AT(FIELD) ((uint32_t)offsetof(pico9918_mem_map_t, FIELD))
33
34#define DEBUG_RW (PICO9918_DEBUG_READABLE | PICO9918_DEBUG_WRITABLE)
35
36typedef struct
37{
38 uint32_t end;
39 uint32_t flags;
41
42static const pico9918_debug_region_t debugMap[] = {
43 {MAP_AT(pram), DEBUG_RW},
44 {MAP_AT(registers), DEBUG_RW | PICO9918_DEBUG_PALETTE},
46 {MAP_AT(status), DEBUG_RW},
48 {(uint32_t)sizeof(((pico9918_t*)0)->vram), DEBUG_RW},
49};
50
51/** \brief see the header. The flags at addr, and where they stop being true. */
53uint32_t pico9918_debug_region(uint32_t addr, uint32_t* end)
54{
55 for (unsigned i = 0; i < sizeof(debugMap) / sizeof(debugMap[0]); ++i)
56 {
57 if (addr < debugMap[i].end)
58 {
59 if (end) *end = debugMap[i].end;
60 return debugMap[i].flags;
61 }
62 }
63
64 /* past the end: 0 flags and an end of addr, which stops a walk rather than looping */
65 if (end) *end = addr;
66 return 0;
67}
68
69/** \brief see the header. A span of the backing state, disturbing nothing. */
71size_t pico9918_debug_read(PICO9918_INST_ARG uint32_t addr, uint8_t* out, size_t len)
72{
73 const uint32_t size = pico9918_gpu_mem_size();
74
75 if (!out || len == 0 || addr >= size) return 0;
76
77 size_t count = size - addr;
78 if (count > len) count = len;
79
80 const uint8_t* const from = (const uint8_t*)&tms9918->vram + addr;
81 for (size_t i = 0; i < count; ++i) out[i] = from[i];
82
83 return count;
84}
85
86/** \brief see the header. The same span, writing, stopping at the first byte it will not. */
88size_t pico9918_debug_write(PICO9918_INST_ARG uint32_t addr, const uint8_t* in, size_t len)
89{
90 size_t done = 0;
91
92 if (!in) return 0;
93
94 /* region at a time: a run may span several, and it stops at the first it may not write */
95 while (done < len)
96 {
97 uint32_t end = 0;
98 const uint32_t flags = pico9918_debug_region(addr, &end);
99
100 if (!(flags & PICO9918_DEBUG_WRITABLE)) break;
101
102 size_t run = (size_t)(end - addr);
103 if (run > len - done) run = len - done;
104
105 uint8_t* const to = (uint8_t*)&tms9918->vram + addr;
106 for (size_t i = 0; i < run; ++i) to[i] = in[done + i];
107
108 /* the converted copy owes PRAM now, or the edit takes and the picture does not change */
109 if (flags & PICO9918_DEBUG_PALETTE) tms9918->palDirty = 1;
110
111 done += run;
112 addr += (uint32_t)run;
113 }
114
115 return done;
116}
117
118/** \brief see the header. The register file's own byte, not the guest's folded read. */
120uint8_t pico9918_debug_reg(PICO9918_INST_ARG uint8_t reg)
121{
122 if (reg >= TMS_REGISTERS) return 0;
123
124 return TMS_REGISTER(tms9918, reg);
125}
126
127/** \brief see the header. The store itself, plus the four things it owes and nothing else. */
129bool pico9918_debug_reg_write(PICO9918_INST_ARG uint8_t reg, uint8_t value)
130{
131 if (reg >= TMS_REGISTERS) return false;
132
133 TMS_REGISTER(tms9918, reg) = value;
134
135 tms9918->palDirty = 1;
138
139 return true;
140}
141
142/** \brief see the header. The store, the shadow SR0 also lives in, and the pin. */
144bool pico9918_debug_status_write(PICO9918_INST_ARG uint8_t reg, uint8_t value)
145{
146 if (reg >= TMS_STATUS_REGISTERS) return false;
147
148 if (reg == PICO9918_SR_STATUS)
149 {
151 }
152 else
153 {
154 TMS_STATUS(tms9918, reg) = value;
155 }
156
158
159 return true;
160}
161
162/** \brief see the header. PRAM with the big-endian storage undone. */
164uint16_t pico9918_debug_palette(PICO9918_INST_ARG uint8_t index)
165{
166 if (index > PICO9918_R47_INDEX) return 0;
167
168 return __builtin_bswap16(tms9918->vram.map.pram[index]);
169}
170
171/** \brief see the header. The latch as an access would use it, permutation included. */
173uint16_t pico9918_debug_vram_address(PICO9918_INST_ONLY_ARG)
174{
175 return (uint16_t)pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress);
176}
177
178/** \brief see the header. Whether a program is armed, not whether one is executing. */
180bool pico9918_debug_gpu_armed(PICO9918_INST_ONLY_ARG)
181{
182 return tms9918->restart != 0;
183}
184
185#if PICO9918_BUILD_LAYER_MASK
186
187/** \brief see the header. A view over the renderer, stored whole so a host can read it back. */
189void pico9918_debug_set_suppress(PICO9918_INST_ARG uint32_t mask)
190{
191 tms9918->suppress = mask;
192}
193
194/** \brief see the header. What was stored, including bits this build does not act on. */
196uint32_t pico9918_debug_suppress(PICO9918_INST_ONLY_ARG)
197{
198 return tms9918->suppress;
199}
200
201#endif
202
203/** \brief see the header. The PC alone - not the registers it is loaded from, not the run. */
205void pico9918_debug_gpu_set_pc(PICO9918_INST_ARG uint16_t pc)
206{
207 tms9918->gpuAddress = pc & 0xFFFE;
208}
209
210/** \brief see the header. The word pico9918_gpu_reg_value() reads, at the live workspace. */
212void pico9918_debug_gpu_set_reg_value(PICO9918_INST_ARG uint8_t reg, uint16_t value)
213{
214 uint8_t* at = (uint8_t*)&tms9918->vram + pico9918_gpu_wp(PICO9918_INST_ONLY) + ((uint32_t)(reg & 0x0f) << 1);
215
216 at[0] = (uint8_t)(value >> 8);
217 at[1] = (uint8_t)value;
218}
219
220/** \brief see the header. The inverse of what pico9918_gpu_status() publishes. */
222void pico9918_debug_gpu_set_status(PICO9918_INST_ARG uint16_t st)
223{
224 tms9918->gpuStatus = (uint16_t)(st >> 8);
225}
226
227/** \brief see the header. The workspace, plus the start reset it has to call off. */
229bool pico9918_debug_gpu_set_wp(PICO9918_INST_ARG uint16_t wp)
230{
231#if PICO9918_GPU_BUDGETED
232 tms9918->gpuWp = wp;
233
234 /* TRAP: without this the slice puts >FFFE back on the way in and the value never runs */
235 if (tms9918->restart) tms9918->restart = PICO9918_GPU_RESUMING;
236
237 return true;
238#else
239 (void)wp;
240 return false;
241#endif
242}
243
244#if PICO9918_BUILD_STEP_CALLBACK
245
246/** \brief see the header. Armed on the instance, so a library-paced slice sees it too. */
248void pico9918_debug_set_step_callback(PICO9918_INST_ARG pico9918_gpu_step_fn cb, void* userdata)
249{
250 tms9918->stepFn = cb;
251 tms9918->stepUserdata = userdata;
252}
253
254/** \brief see the header. The armed pair, so a caller can put it back and not just count it. */
256pico9918_gpu_step_fn pico9918_debug_step_callback(PICO9918_INST_ARG void** userdata)
257{
258 if (userdata) *userdata = tms9918->stepUserdata;
259
260 return tms9918->stepFn;
261}
262
263#endif
uint32_t pico9918_gpu_mem_size(void)
see the header.
Definition gpu.c:560
uint16_t pico9918_gpu_wp(pico9918_t *tms9918)
see the header.
Definition gpu.c:547
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:83
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:86
#define PICO9918_R47_INDEX
first palette index to write
Definition pico9918.h:353
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
Definition pico9918.h:270
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:84
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:85
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:50
pico9918-core - debugger access
#define PICO9918_DEBUG_STATUS
the status file, which the library owns
#define PICO9918_DEBUG_WRITABLE
pico9918_debug_write stores here
#define PICO9918_DEBUG_REGISTERS
the register file - use pico9918_debug_reg_write
#define PICO9918_DEBUG_READABLE
pico9918_debug_read returns real bytes here
#define PICO9918_DEBUG_PALETTE
PRAM - a write here republishes the palette.
pico9918-core - the private instance layout
PICO9918_INLINE_HOT uint32_t pico9918_cpu_vram_addr_impl(pico9918_t *tms9918, uint32_t addr)
where a CPU-side VRAM access lands
PICO9918_INLINE void pico9918_write_reconcile_int_impl(pico9918_t *tms9918)
bring /INT into agreement after a write that can change the predicate.
PICO9918_INLINE_HOT void pico9918_set_status_impl(pico9918_t *tms9918, uint8_t status)
set status flag
void pico9918_debug_sync_mode_impl(pico9918_t *tms9918)
see impl/pico9918_priv.h.
Definition pico9918.c:3594