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. PRAM with the big-endian storage undone. */
144uint16_t pico9918_debug_palette(PICO9918_INST_ARG uint8_t index)
145{
146 if (index > PICO9918_R47_INDEX) return 0;
147
148 return __builtin_bswap16(tms9918->vram.map.pram[index]);
149}
150
151/** \brief see the header. The latch as an access would use it, permutation included. */
153uint16_t pico9918_debug_vram_address(PICO9918_INST_ONLY_ARG)
154{
155 return (uint16_t)pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress);
156}
157
158/** \brief see the header. Whether a program is armed, not whether one is executing. */
160bool pico9918_debug_gpu_armed(PICO9918_INST_ONLY_ARG)
161{
162 return tms9918->restart != 0;
163}
164
165/** \brief see the header. The PC alone - not the registers it is loaded from, not the run. */
167void pico9918_debug_gpu_set_pc(PICO9918_INST_ARG uint16_t pc)
168{
169 tms9918->gpuAddress = pc & 0xFFFE;
170}
uint32_t pico9918_gpu_mem_size(void)
see the header.
Definition gpu.c:469
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
#define PICO9918_R47_INDEX
first palette index to write
Definition pico9918.h:339
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:41
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.
void pico9918_debug_sync_mode_impl(pico9918_t *tms9918)
see impl/pico9918_priv.h.
Definition pico9918.c:3506