OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / src / debug_ui / debug_ui_bridge.h
20.7 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — bridge for C++ ImGui panels to read C emulator
2
// state.
3
//
4
// The emulator core is C. C++ panels must reach C globals/functions through a
5
// single, documented, C++-safe surface so that linkage is correct.
6
//
7
// FLEET PANEL AUTHORS: include THIS header (not the raw C core headers) to read
8
// emulator state. If an accessor you need is missing, add the *include* of the
9
// real emulator header here rather than a declaration of the function -- see
10
// the note below on why re-declaring is how this file went wrong.
11
//
12
// Why it is shaped this way:
13
//   * Struct/type *definitions* (POD; identical layout in C and C++) are
14
//     included normally — structs have no language linkage, so `struct regs`
15
//     is the same type in both languages. (registers.h below.)
16
//   * Function and variable *declarations* go inside `extern "C"` so the C++
17
//     compiler references the C symbol names the core actually exports.
18
#ifndef DEBUG_UI_BRIDGE_H
19
#define DEBUG_UI_BRIDGE_H
20
21
#include <stdint.h>
22
#include <stdbool.h>
23
24
// struct regs — the CPU register file. POD; members include:
25
//   .pc (uint16)  .a/.xl/.yl (uint8 low bytes)  .c/.x/.y (uint16 aliases)
26
//   .sp .dp (uint16)  .status .k .db .e (uint8)  .is65c816 (bool)
27
// See src/cpu/registers.h. Resolved via the `src` include dir.
28
#include "cpu/registers.h"
29
30
// Disassembly: the anchored disassembler / code map (its own extern "C" guards).
31
#include "code_map.h"
32
33
// The real emulator headers, rather than copies of their declarations.
34
//
35
// This file used to re-declare ~20 core functions and re-define struct
36
// breakpoint and struct watchpoint, on the reasoning that debugger.h drags in
37
// SDL. The cost of that was silent drift: the copies stopped matching the core
38
// and nothing said so, because a duplicate declaration compiles and links
39
// perfectly whether or not it is true. Three examples that were live here:
40
//
41
//   * dbg_info_label_to_addr / _symbol_at / _addr_to_label were declared with
42
//     uint16_t* address arguments. The core writes a 32-bit dbg_addr_t through
43
//     that pointer, so every symbol and source lookup wrote two bytes past the
44
//     caller's variable.
45
//   * struct watchpoint was missing the core's x16Bank field, while
46
//     watchPoints[] was declared extern and indexed directly -- wrong field
47
//     offsets and a 12-byte stride over a 16-byte array.
48
//   * memory_get_num_rom_banks() was declared but has never existed.
49
//   * cpu_irq_return_pc() was declared returning uint16_t against the core's
50
//     uint32_t, silently dropping the program bank of a 65C816 return address.
51
//
52
// So the rule is now the opposite one: include the real header, and where the
53
// panels genuinely want a different shape, convert in a documented shim below
54
// where the compiler can check it.
55
#include "debug_core.h"   // breakpoints, watchpoints, conditions, bank rules
56
#include "debugger.h"     // execution control + run state (pulls SDL, which is
57
                          // fine: the ImGui backends are SDL2 already)
58
#include "dbg_info.h"     // symbols and source lines; owns dbg_addr_t
59
#include "memory.h"       // real_read6502, write6502, bank mapping
60
#include "glue.h"         // is_gen2, num_banks, num_ram_banks, MHZ, warp_mode
61
#include "smc.h"          // smc_requested_reset
62
#include "timing.h"       // absolute kHz speed control
63
#include "perf_budget.h"  // guest cycle budget accounting
64
#include "vera_bandwidth.h" // VERA VRAM fetch and data-port bandwidth
65
#include "sprite_trace.h" // sprite multiplexing and per-line render time
66
#include "video.h"        // video_read/video_space_* and the VERA debug views
67
#include "cpu/irq_ctx.h"  // interrupt context, incl. the 24-bit return PC
68
#include "cpu/fake6502.h" // irq6502()
69
70
// I/O panel: the event ring plus each device's side-effect-free debug snapshot.
71
// Same rule as above -- the real headers, so a struct that changes shape breaks
72
// the build here rather than silently misreading device state in the panel.
73
#include "io_trace.h"   // the I/O event ring buffer
74
#include "sdcard.h"     // sdcard_debug_get_state, sdcard_get_path
75
#include "sdcard_fat.h" // the FAT index that names SD blocks
76
#include "vera_spi.h"   // vera_spi_debug_get_state -- the SD data path
77
#include "joystick.h"   // joystick_debug_get_state and the JOY_BIT_* layout
78
#include "via.h"        // via_debug_get_state
79
#include "i2c.h"        // i2c_debug_get_state
80
#include "rtc.h"        // rtc_debug_get_state
81
#include "ieee.h"       // ieee_debug_get_state -- host-FS files, by name
82
#include "serial.h"     // serial_port, the IEC line states
83
84
#ifdef __cplusplus
85
extern "C" {
86
#endif
87
88
// The global CPU register file (defined in the C core).
89
extern struct regs regs;
90
91
// Non-intrusive read of VERA video memory / register space.
92
93
// RAM banking is sized at runtime by -ram, so num_ram_banks (glue.h) is a real
94
// variable. ROM is not: it is a fixed ROM[NUM_ROM_BANKS * 16384] array and has
95
// no accessor. The panels asked for memory_get_num_rom_banks(), which linked
96
// only because this header declared a function nobody defined.
97
static inline int
98
memory_get_num_rom_banks(void)
99
{
100
	return NUM_ROM_BANKS;
101
}
102
103
// ─── Breakpoints ───────────────────────────────────────────────────────────
104
// struct breakpoint, breakPoints[], numBreakpoints and the debug_bp_* API all
105
// come from debug_core.h. The panels were written against the older DEBUG*
106
// spelling and a two-argument (pc, bank) identity that predates bank
107
// awareness. A breakpoint is now identified by (pc, bank, x16Bank), because the
108
// same $A000 in two RAM banks is two different breakpoints.
109
//
110
// These shims therefore take x16Bank and pass it through, rather than
111
// supplying DEBUG_BANK_ANY on the panels' behalf. ANY is a wildcard when a
112
// breakpoint is *tested* (bank_selector_matches), but debug_bp_find() compares
113
// x16Bank for equality, so passing ANY as a lookup key finds only breakpoints
114
// that were themselves stored as ANY. Since an add records a concrete bank for
115
// every address at $A000 and above, keying on ANY silently matched nothing for
116
// any breakpoint in banked RAM or ROM -- so they could be set, but never
117
// removed, disabled, cleared, or have their hits or condition read.
118
//
119
// Everything the panels create is owned by DEBUG_OWNER_UI. The core records who
120
// asked for each entry, so a breakpoint this window sets at an address a DAP
121
// client also wants survives that client disconnecting, and vice versa.
122
static inline bool
123
DEBUGAddBreakPoint(struct breakpoint bp)
124
{
125
	return debug_bp_add_for(bp, DEBUG_OWNER_UI) != DEBUG_ADD_FULL;
126
}
127
128
// The user asking for a breakpoint to go means gone, whoever else also wanted
129
// it. Dropping only this window's claim would leave it armed and the button
130
// looking broken.
131
static inline bool
132
DEBUGRemoveBreakPoint(int pc, uint8_t bank, int x16Bank)
133
{
134
	return debug_bp_delete(pc, bank, x16Bank);
135
}
136
137
// Disabling keeps the entry, its owners, its condition and its hit count, so
138
// the breakpoint still shows in every panel's gutter while it is off.
139
static inline bool DEBUGSetBreakpointEnabled(int pc, uint8_t bank, int x16Bank, bool on) { return debug_bp_set_enabled(pc, bank, x16Bank, on); }
140
static inline bool DEBUGIsBreakpointEnabled(int pc, uint8_t bank, int x16Bank) { return debug_bp_is_enabled(pc, bank, x16Bank); }
141
142
static inline void DEBUGForgetBreakpoint(int pc, uint8_t bank, int x16Bank) { debug_bp_forget(pc, bank, x16Bank); }
143
static inline void DEBUGClearBreakpointCondition(int pc, uint8_t bank, int x16Bank) { debug_bp_clear_condition(pc, bank, x16Bank); }
144
static inline uint32_t DEBUGGetBreakpointHits(int pc, uint8_t bank, int x16Bank) { return debug_bp_get_hits(pc, bank, x16Bank); }
145
static inline void DEBUGResetBreakpointHits(int pc, uint8_t bank, int x16Bank) { debug_bp_reset_hits(pc, bank, x16Bank); }
146
147
static inline void
148
DEBUGSetBreakpointCondition(int pc, uint8_t bank, int x16Bank, int operand,
149
                            uint16_t operand_addr, int op, uint32_t value)
150
{
151
	debug_bp_set_condition(pc, bank, x16Bank, operand, operand_addr, op, value);
152
}
153
154
static inline void
155
DEBUGSetBreakpointIgnore(int pc, uint8_t bank, int x16Bank, uint32_t ignore)
156
{
157
	debug_bp_set_ignore(pc, bank, x16Bank, ignore);
158
}
159
160
static inline bool
161
DEBUGGetBreakpointCondition(int pc, uint8_t bank, int x16Bank, int *has_cond,
162
                            int *operand, uint16_t *operand_addr, int *op,
163
                            uint32_t *value, uint32_t *ignore)
164
{
165
	return debug_bp_get_condition(pc, bank, x16Bank, has_cond, operand,
166
	                             operand_addr, op, value, ignore);
167
}
168
169
// The panels' operand/comparison names, as aliases of the core's codes rather
170
// than a second enum with its own numbering. debug_core.h states these values
171
// are external and must not be renumbered; two independent copies is exactly
172
// how that promise gets broken.
173
enum { BP_OPND_A = BPOPERAND_A, BP_OPND_X = BPOPERAND_X, BP_OPND_Y = BPOPERAND_Y,
174
       BP_OPND_SP = BPOPERAND_SP, BP_OPND_P = BPOPERAND_P,
175
       BP_OPND_BYTE = BPOPERAND_BYTE, BP_OPND_WORD = BPOPERAND_WORD };
176
enum { BP_CMP_EQ = BPCMP_EQ, BP_CMP_NE = BPCMP_NE, BP_CMP_LT = BPCMP_LT,
177
       BP_CMP_LE = BPCMP_LE, BP_CMP_GT = BPCMP_GT, BP_CMP_GE = BPCMP_GE };
178
179
// ─── VERA: accessors ───────────────────────────────────────────────────────
180
// Non-intrusive read of a VERA I/O register (0x00..0x1F). Pass debugOn=true so
181
// the read has no side effects (no address auto-increment, no FIFO changes).
182
// Used by the VERA panel to read the layer registers ($0D..$1A) that describe
183
// map/tile bases, color depth, scroll, and bitmap width.
184
//
185
// The per-scanline layer-register history, the active layer size and the VERA
186
// interrupt prediction all come from video.h, included above.
187
188
// ─── Memory: accessors  (Memory panel — keep minimal & shared) ─────────────
189
190
// Explicit, intended debug write into VERA video memory (0..0x1FFFF). (video.h)
191
192
// Banked-memory mapping controls (memory.c, declared in memory.h above). The
193
// Memory panel drives its RAM/ROM bank selectors with these.
194
195
// Explicit debug write into the CPU address space, mirroring the bank
196
// semantics of real_read6502(): for banked RAM ($A000-$BFFF) / ROM
197
// ($C000-$FFFF), x16Bank>=0 selects the bank (-1 = current mapping). Wraps
198
// write6502() (save/set/restore bank for banked regions).
199
// NOTE: the definition currently lives in panels/memory_panel.cpp; integration
200
// may relocate it into the core.
201
void debug_ui_write6502(uint16_t address, uint8_t value, uint8_t bank, int16_t x16Bank);
202
203
// ─── Memory write watchpoints ──────────────────────────────────────────────
204
// struct watchpoint, MAX_WATCHPOINTS and the debug_wp_* API come from
205
// debug_core.h. This header used to carry its own copy of the struct, and that
206
// copy was missing the core's x16Bank field while watchPoints[] was declared
207
// extern and indexed directly -- so the panel read every field after `len` from
208
// the wrong offset, with a 12-byte stride over a 16-byte array. The table is
209
// static in debug_core.c and is now read through debug_wp_at().
210
//
211
// Owned by DEBUG_OWNER_UI, like the breakpoints above: -wp on the command line
212
// and a DAP client's data breakpoints can name the same address, and none of
213
// them should be able to disarm another's.
214
static inline bool DEBUGAddWatchPoint(uint16_t addr, uint16_t len) { return debug_wp_add_for(addr, len, DEBUG_BANK_ANY, DEBUG_OWNER_UI) != DEBUG_ADD_FULL; }
215
static inline bool DEBUGRemoveWatchPoint(uint16_t addr) { return debug_wp_delete(addr, DEBUG_BANK_ANY); }
216
static inline bool DEBUGCheckWatchPoint(uint16_t addr) { return debug_wp_covers(addr); }
217
218
// ─── Machine model (glue.h) ────────────────────────────────────────────────
219
// is_gen2 is true for the X16 GS / Gen-2 machine (-gs): a 65C816 with 24-bit
220
// addressable RAM. In gen2, the 65C816 bank byte selects a flat 64K RAM page:
221
// bank 0 is the classic X16 map (I/O + windowed $A000-$BFFF/$C000-$FFFF banks);
222
// banks 1..num_banks-1 are flat RAM. num_banks is the total number of 64K CPU
223
// banks (256 in gen2, 1 otherwise) — bounds the Memory panel's CPU-bank selector.
224
extern bool     is_gen2;
225
extern uint16_t num_banks;
226
227
// ─── Execution control (debugger.c) ────────────────────────────────────────
228
// Drive the CPU from a panel exactly like the debugger control bar does. These
229
// mirror the classic F5/F10/F11 handlers and the DAP commands (including the
230
// timing re-base on resume). Handy for e.g. a "Run to cursor" in the disasm
231
// panel or a "Continue" shortcut in any panel.
232
233
// Why execution last stopped. This lived in the DAP server before, which meant
234
// it did not exist unless that was built; it belongs to the debugger, which is
235
// what actually stops. Kept under the old name so the panels read the same.
236
static inline const char *debug_server_last_stop_reason(void) { return DEBUGGetStopReason(); }
237
238
// Interrupt following (debugger.c). With follow-interrupts on, an interrupt
239
// taken while stepping stops at the handler's first instruction instead of
240
// being run through invisibly; break-on-interrupt stops on every entry.
241
242
// Publish the "run to cursor" target — the disassembly instruction row under// the mouse — for the Ctrl+F10 shortcut (consumed by debug_ui_run_to_cursor()).
243
// The Disassembly panel calls this every frame: valid=true for the hovered row,
244
// valid=false when nothing is hovered. Defined in debug_ui.cpp.
245
void debug_ui_set_cursor(uint16_t addr, uint8_t bank, bool valid);
246
247
// Cross-panel navigation. A panel (e.g. Symbols / Call Stack) calls
248
// debug_ui_request_goto() to jump the Disassembly + Memory panels to an address;
249
// those panels call debug_ui_peek_goto() each frame to pick it up. Defined in
250
// debug_ui.cpp. (One-frame latency; visible to all panels regardless of order.)
251
void debug_ui_request_goto(uint16_t addr, uint8_t bank);
252
bool debug_ui_peek_goto(uint16_t *addr, uint8_t *bank);
253
254
// "Add to watch": the watch list lives in the CPU panel, but the natural place
255
// to ask for a watch is wherever you found the address (the memory view's
256
// context menu, including over a drag-selected range). The CPU panel drains
257
// this once per frame and splits anything longer than one watch row. bank is an
258
// x16 RAM/ROM bank, or -1 for "whatever is currently mapped".
259
void debug_ui_request_watch(uint16_t addr, int16_t bank, uint16_t len);
260
bool debug_ui_take_watch_request(uint16_t *addr, int16_t *bank, uint16_t *len);
261
262
// ─── System control (main.c / smc.c / cpu) ─────────────────────────────────
263
// Reset is deferred to the emulator-loop boundary via the SMC reset flag (the
264
// same path Ctrl-R uses) so it never fires mid-instruction; machine_reset()
265
// also calls code_map_reset() so the disassembly re-aligns. IRQ/NMI are
266
// edge-triggered exactly like the RESTORE-key NMI already in video.c.
267
extern bool smc_requested_reset;  // set true to request a machine reset
268
269
// ─── Symbols / labels ──────────────────────────────────────────────────────
270
// dbg_info.h (included above) declares these. The panels want 16-bit addresses
271
// while the core deals in 24-bit dbg_addr_t, so the narrowing happens here,
272
// once, in shims the compiler can type-check -- rather than through a
273
// re-declaration with a uint16_t* parameter, which is what previously made the
274
// core write four bytes into the caller's two-byte variable.
275
276
// ─── Counters (status line) ─────────────────────────────────────────────────
277
// clockticks6502 comes from cpu/fake6502.h and instruction_counter from
278
// glue.h, both included above.
279
280
// ─── Emulation speed / warp (timing.h above, main.c, glue.h) ────────────────
281
// Speed is an absolute target clock in kHz. The machine's own clock comes from
282
// -mhz at startup, so timing_native_khz() is what "normal speed" means for this
283
// run. Warp bypasses the throttle entirely and ignores the target.
284
285
// ─── Interrupt context (cpu/fake6502.c) ─────────────────────────────────────
286
// The CPU core records interrupt entry/exit so the debugger can show when
287
// execution is inside a handler. Depth counts nesting.
288
289
// ─── VERA interrupt prediction (video.c) ────────────────────────────────────
290
// Cycles until the next enabled VERA interrupt and the ISR bit that will cause
291
// it (1 = VSYNC, 2 = LINE); false when neither is enabled.
292
293
#ifdef __cplusplus
294
} // extern "C"
295
#endif
296
297
// ─── Audio: accessors ───────────────────────────────────────────────────────
298
// The three audio sources each expose a small, side-effect-free debug surface
299
// for the PSG / YM2151 / PCM panels. Unlike debugger.h (which drags in SDL),
300
// these headers are plain and carry their own extern "C" guards, so panels get
301
// the POD debug structs *and* the correctly-linked declarations by including
302
// them here — hence they are included rather than re-declared.
303
//
304
// Threading: psg_render(), pcm_render() and YM_stream_update() all run from
305
// audio_render() on the emulator main thread, which is the same thread that
306
// calls debug_ui_render(). The scope ring buffers are therefore
307
// single-producer/single-consumer on one thread and need no locking.
308
//
309
// Scope capture is SELF-ARMING: each *_scope_read() call re-arms capture for
310
// about half a second, and the audio cores stop capturing on their own once
311
// nothing reads. Panels just call the read every frame while their Scope tab is
312
// visible — there is no enable/disable to balance, and no cost when hidden.
313
#include "vera_psg.h" // PSG voices, registers, scope   (psg_debug_*)
314
#include "vera_pcm.h" // VERA PCM FIFO + scope          (pcm_debug_*)
315
#include "ymglue.h"   // YM2151 registers, EG, scope    (YM_debug_*)
316
317
// Follow the current X16 bank (mirrors USE_CURRENT_X16_BANK from memory.h).
318
#define DEBUG_UI_CURRENT_BANK (-1)
319
320
// Convenience wrapper mirroring memory.h's debug_read6502() macro, for use from
321
// C++ panels: read one CPU byte for display with no side effects.
322
static inline uint8_t
323
debug_ui_read6502(uint16_t address, uint8_t bank, int16_t x16Bank)
324
{
325
    return real_read6502(address, bank, true, x16Bank);
326
}
327
328
// VERA: accessors — bulk copy a run of VERA video memory into `dest`. Wraps the
329
// 17-bit VRAM space ($00000..$1FFFF) so a viewer can never read out of bounds.
330
// Convenient for decoding tiles/sprites/bitmaps a whole tile-row at a time.
331
static inline void
332
debug_ui_vram_read_range(uint8_t *dest, uint32_t address, uint32_t size)
333
{
334
    for (uint32_t i = 0; i < size; ++i) {
335
        dest[i] = video_space_read((address + i) & 0x1FFFF);
336
    }
337
}
338
339
// ─── Symbol / source lookups, narrowed ─────────────────────────────────────
340
// The core addresses code with a 24-bit dbg_addr_t; the panels work in the
341
// 16-bit CPU address space. Each shim narrows in one place, under the
342
// compiler's supervision. Passing a uint16_t* straight to the core -- which is
343
// what this header used to declare -- silently corrupted two bytes of the
344
// caller's stack on every call.
345
346
static inline bool
347
debug_ui_label_to_addr(const char *name, uint16_t *addr)
348
{
349
	dbg_addr_t wide = 0;
350
	if (!dbg_info_label_to_addr(name, &wide)) return false;
351
	*addr = (uint16_t)(wide & 0xFFFF);
352
	return true;
353
}
354
355
static inline bool
356
debug_ui_source_to_addr(const char *file, int line, uint16_t *addr)
357
{
358
	dbg_addr_t wide = 0;
359
	if (!dbg_info_source_to_addr(file, line, &wide)) return false;
360
	*addr = (uint16_t)(wide & 0xFFFF);
361
	return true;
362
}
363
364
static inline bool
365
debug_ui_symbol_at(int index, const char **name, uint16_t *addr)
366
{
367
	dbg_addr_t wide = 0;
368
	if (!dbg_info_symbol_at(index, name, &wide)) return false;
369
	*addr = (uint16_t)(wide & 0xFFFF);
370
	return true;
371
}
372
373
static inline bool
374
debug_ui_addr_to_label_nearest(uint16_t addr, const char **name, uint16_t *sym_addr)
375
{
376
	dbg_addr_t wide = 0;
377
	if (!dbg_info_addr_to_label_nearest(addr, name, &wide)) return false;
378
	*sym_addr = (uint16_t)(wide & 0xFFFF);
379
	return true;
380
}
381
382
static inline bool
383
debug_ui_name_to_value(const char *name, uint16_t *addr, int *out_kind)
384
{
385
	dbg_addr_t wide = 0;
386
	if (!dbg_info_name_to_value(name, &wide, out_kind)) return false;
387
	*addr = (uint16_t)(wide & 0xFFFF);
388
	return true;
389
}
390
391
#endif // DEBUG_UI_BRIDGE_H