| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
#include <cstdint> |
| 7 |
#include <cstdio> |
| 8 |
#include <vector> |
| 9 |
|
| 10 |
#include "imgui.h" |
| 11 |
#include "debug_ui_panels.h" |
| 12 |
#include "debug_ui_bridge.h" |
| 13 |
#include "debug_ui_widgets.h" |
| 14 |
#include "debug_ui_settings.h" |
| 15 |
|
| 16 |
extern "C" { |
| 17 |
#include "disasm.h" |
| 18 |
} |
| 19 |
|
| 20 |
namespace { |
| 21 |
|
| 22 |
|
| 23 |
const ImGuiInputTextFlags kHexFlags = |
| 24 |
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase; |
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
bool cpu_native() { return regs.is65c816 && !regs.e; } |
| 33 |
bool acc_is16() { return cpu_native() && !(regs.status & FLAG_MEMORY_WIDTH); } |
| 34 |
bool idx_is16() { return cpu_native() && !(regs.status & FLAG_INDEX_WIDTH); } |
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
struct VectorProbe { |
| 50 |
uint16_t native_irq; |
| 51 |
uint16_t emu_irq; |
| 52 |
char native_disasm[64]; |
| 53 |
bool looks_absent; |
| 54 |
}; |
| 55 |
|
| 56 |
uint16_t read_word(uint16_t addr) |
| 57 |
{ |
| 58 |
return (uint16_t)debug_ui_read6502(addr, 0, -1) | |
| 59 |
((uint16_t)debug_ui_read6502((uint16_t)(addr + 1), 0, -1) << 8); |
| 60 |
} |
| 61 |
|
| 62 |
VectorProbe probe_vectors() |
| 63 |
{ |
| 64 |
VectorProbe p{}; |
| 65 |
p.native_irq = read_word(0xFFEE); |
| 66 |
p.emu_irq = read_word(0xFFFE); |
| 67 |
|
| 68 |
p.native_disasm[0] = '\0'; |
| 69 |
|
| 70 |
int32_t eff = -1; |
| 71 |
disasm(p.native_irq, 0, nullptr, p.native_disasm, sizeof p.native_disasm, -1, |
| 72 |
(uint8_t)(FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH), &eff); |
| 73 |
|
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
const bool emu_in_ram = p.emu_irq < 0xC000; |
| 80 |
const bool native_in_rom = p.native_irq >= 0xC000; |
| 81 |
p.looks_absent = (emu_in_ram && native_in_rom) || (p.native_irq == 0x0000) || |
| 82 |
(p.native_irq == 0xFFFF); |
| 83 |
return p; |
| 84 |
} |
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
|
| 90 |
void set_emulation_flag(bool emulation) |
| 91 |
{ |
| 92 |
if (!regs.is65c816 || (regs.e != 0) == emulation) { |
| 93 |
return; |
| 94 |
} |
| 95 |
regs.e = emulation ? 1 : 0; |
| 96 |
if (emulation) { |
| 97 |
regs.status |= FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH; |
| 98 |
regs.xh = 0; |
| 99 |
regs.yh = 0; |
| 100 |
regs.sp = (uint16_t)(0x0100 | (regs.sp & 0x00FF)); |
| 101 |
} |
| 102 |
|
| 103 |
|
| 104 |
} |
| 105 |
|
| 106 |
|
| 107 |
|
| 108 |
|
| 109 |
|
| 110 |
|
| 111 |
|
| 112 |
|
| 113 |
bool s_emu_confirm_open = false; |
| 114 |
bool s_emu_confirm_target = false; |
| 115 |
bool s_emu_skip_confirm = false; |
| 116 |
|
| 117 |
void draw_emulation_toggle(const char *label) |
| 118 |
{ |
| 119 |
if (!regs.is65c816) { |
| 120 |
return; |
| 121 |
} |
| 122 |
const bool paused = DEBUGIsPaused(); |
| 123 |
const bool unlocked = debug_ui_settings().allow_breaking_changes; |
| 124 |
const bool editable = paused && unlocked; |
| 125 |
|
| 126 |
bool e = regs.e != 0; |
| 127 |
ImGui::BeginDisabled(!editable); |
| 128 |
if (ImGui::Checkbox(label, &e)) { |
| 129 |
if (s_emu_skip_confirm) { |
| 130 |
set_emulation_flag(e); |
| 131 |
} else { |
| 132 |
s_emu_confirm_open = true; |
| 133 |
s_emu_confirm_target = e; |
| 134 |
} |
| 135 |
} |
| 136 |
ImGui::EndDisabled(); |
| 137 |
|
| 138 |
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { |
| 139 |
if (editable) { |
| 140 |
ImGui::SetTooltip("E - Emulation flag\n" |
| 141 |
"1 = 6502 emulation mode; 0 = 65C816 native.\n\n" |
| 142 |
"Applies the same state changes as XCE. Editing is unlocked;\n" |
| 143 |
"see the confirmation for what this will do to the ROM."); |
| 144 |
} else if (!unlocked) { |
| 145 |
ImGui::SetTooltip("E - Emulation flag (read-only)\n" |
| 146 |
"1 = 6502 emulation mode; 0 = 65C816 native.\n\n" |
| 147 |
"The running program owns this flag. Changing it moves interrupts\n" |
| 148 |
"from $FFFE to $FFEE, and a 6502-era ROM has no handler there, so\n" |
| 149 |
"the machine crashes at the next IRQ.\n\n" |
| 150 |
"Enable System > Settings > Safety > \"Allow breaking\n" |
| 151 |
"modifications\" if you are hand-driving the CPU."); |
| 152 |
} else { |
| 153 |
ImGui::SetTooltip("E - Emulation flag (pause to edit)\n" |
| 154 |
"Switching CPU mode mid-stream breaks the running program's\n" |
| 155 |
"stack frames and interrupt vectors."); |
| 156 |
} |
| 157 |
} |
| 158 |
} |
| 159 |
|
| 160 |
|
| 161 |
|
| 162 |
void draw_emulation_confirm_popup() |
| 163 |
{ |
| 164 |
static const char *kTitle = "Switch CPU mode?"; |
| 165 |
|
| 166 |
if (s_emu_confirm_open) { |
| 167 |
ImGui::OpenPopup(kTitle); |
| 168 |
s_emu_confirm_open = false; |
| 169 |
} |
| 170 |
|
| 171 |
ImVec2 center = ImGui::GetMainViewport()->GetCenter(); |
| 172 |
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); |
| 173 |
if (!ImGui::BeginPopupModal(kTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { |
| 174 |
return; |
| 175 |
} |
| 176 |
|
| 177 |
const bool to_emulation = s_emu_confirm_target; |
| 178 |
ImGui::TextColored(ImVec4(0.95f, 0.70f, 0.30f, 1.0f), |
| 179 |
"Switch to %s mode?", to_emulation ? "6502 emulation" : "65C816 native"); |
| 180 |
ImGui::Separator(); |
| 181 |
|
| 182 |
|
| 183 |
|
| 184 |
const VectorProbe vp = probe_vectors(); |
| 185 |
if (!to_emulation) { |
| 186 |
ImGui::TextUnformatted("Interrupts vector by mode. On this machine, right now:"); |
| 187 |
ImGui::Spacing(); |
| 188 |
if (ImGui::BeginTable("vec_tbl", 3, |
| 189 |
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | |
| 190 |
ImGuiTableFlags_SizingFixedFit)) { |
| 191 |
ImGui::TableSetupColumn("Mode"); |
| 192 |
ImGui::TableSetupColumn("IRQ vector"); |
| 193 |
ImGui::TableSetupColumn("Points to"); |
| 194 |
ImGui::TableHeadersRow(); |
| 195 |
|
| 196 |
ImGui::TableNextRow(); |
| 197 |
ImGui::TableNextColumn(); ImGui::TextUnformatted("Emulation (now)"); |
| 198 |
ImGui::TableNextColumn(); ImGui::TextUnformatted("$FFFE"); |
| 199 |
ImGui::TableNextColumn(); ImGui::Text("$%04X", vp.emu_irq); |
| 200 |
|
| 201 |
ImGui::TableNextRow(); |
| 202 |
ImGui::TableNextColumn(); |
| 203 |
ImGui::TextColored(ImVec4(1.00f, 0.45f, 0.45f, 1.0f), "Native (after)"); |
| 204 |
ImGui::TableNextColumn(); |
| 205 |
ImGui::TextColored(ImVec4(1.00f, 0.45f, 0.45f, 1.0f), "$FFEE"); |
| 206 |
ImGui::TableNextColumn(); |
| 207 |
ImGui::TextColored(ImVec4(1.00f, 0.45f, 0.45f, 1.0f), "$%04X %s", |
| 208 |
vp.native_irq, vp.native_disasm); |
| 209 |
ImGui::EndTable(); |
| 210 |
} |
| 211 |
ImGui::Spacing(); |
| 212 |
if (vp.looks_absent) { |
| 213 |
ImGui::TextColored(ImVec4(1.00f, 0.45f, 0.45f, 1.0f), |
| 214 |
"This ROM does not appear to provide a native IRQ handler."); |
| 215 |
ImGui::TextUnformatted( |
| 216 |
"$FFEE holds ROM code, not a vector, so the next IRQ - 60 times a\n" |
| 217 |
"second - will execute whatever is at the address above. Expect the\n" |
| 218 |
"machine to crash to the monitor almost immediately after resuming."); |
| 219 |
ImGui::TextDisabled("(Judged by the emulation vector pointing at RAM while the native\n" |
| 220 |
"one points into ROM. Check the addresses yourself above.)"); |
| 221 |
} else { |
| 222 |
ImGui::TextUnformatted("The native vector looks populated, but verify it is really a\n" |
| 223 |
"handler before resuming."); |
| 224 |
} |
| 225 |
ImGui::Spacing(); |
| 226 |
ImGui::TextUnformatted("Also note:"); |
| 227 |
} else { |
| 228 |
ImGui::TextUnformatted( |
| 229 |
"This changes the CPU out from under the running program. Unless it was\n" |
| 230 |
"about to make the same switch itself, expect it to break:"); |
| 231 |
} |
| 232 |
ImGui::Bullet(); |
| 233 |
ImGui::TextUnformatted("Native interrupts also push the program bank, so an RTI that\n" |
| 234 |
"doesn't match its entry mode returns to a garbage address."); |
| 235 |
ImGui::Bullet(); |
| 236 |
ImGui::TextUnformatted("Emulation confines the stack to page 1; native does not."); |
| 237 |
ImGui::Bullet(); |
| 238 |
ImGui::TextUnformatted("In native, clearing M changes instruction lengths, so the PC\n" |
| 239 |
"stream desyncs from the code that was assembled for 8-bit."); |
| 240 |
ImGui::Separator(); |
| 241 |
ImGui::TextDisabled("Fine for experiments and for driving the CPU by hand; not something\n" |
| 242 |
"a live program will survive."); |
| 243 |
ImGui::Spacing(); |
| 244 |
ImGui::Checkbox("Don't ask again this session", &s_emu_skip_confirm); |
| 245 |
ImGui::Spacing(); |
| 246 |
|
| 247 |
if (ImGui::Button("Switch anyway", ImVec2(130, 0))) { |
| 248 |
set_emulation_flag(to_emulation); |
| 249 |
ImGui::CloseCurrentPopup(); |
| 250 |
} |
| 251 |
ImGui::SameLine(); |
| 252 |
if (ImGui::Button("Cancel", ImVec2(90, 0))) { |
| 253 |
s_emu_skip_confirm = false; |
| 254 |
ImGui::CloseCurrentPopup(); |
| 255 |
} |
| 256 |
ImGui::EndPopup(); |
| 257 |
} |
| 258 |
|
| 259 |
|
| 260 |
|
| 261 |
|
| 262 |
uint16_t dp_add(uint16_t offset) |
| 263 |
{ |
| 264 |
if (regs.e && (regs.dp & 0x00FF) == 0) |
| 265 |
return (uint16_t)((regs.dp & 0xFF00) | (uint8_t)((uint8_t)(regs.dp & 0xFF) + (offset & 0xFF))); |
| 266 |
return (uint16_t)(regs.dp + offset); |
| 267 |
} |
| 268 |
|
| 269 |
char ascii_of(uint8_t b) { return (b >= 0x20 && b < 0x7f) ? (char)b : '.'; } |
| 270 |
|
| 271 |
|
| 272 |
int hex_digit(char c) |
| 273 |
{ |
| 274 |
if (c >= '0' && c <= '9') return c - '0'; |
| 275 |
if (c >= 'a' && c <= 'f') return c - 'a' + 10; |
| 276 |
if (c >= 'A' && c <= 'F') return c - 'A' + 10; |
| 277 |
return -1; |
| 278 |
} |
| 279 |
|
| 280 |
|
| 281 |
float hex_width(int digits) |
| 282 |
{ |
| 283 |
return ImGui::CalcTextSize("F").x * (float)(digits + 1) + |
| 284 |
ImGui::GetStyle().FramePadding.x * 2.0f + 4.0f; |
| 285 |
} |
| 286 |
|
| 287 |
bool reg_u8(const char *label, uint8_t *p, const char *desc = nullptr) |
| 288 |
{ |
| 289 |
ImGui::SetNextItemWidth(hex_width(2)); |
| 290 |
bool changed = ImGui::InputScalar(label, ImGuiDataType_U8, p, nullptr, nullptr, "%02X", kHexFlags); |
| 291 |
dbgui_hover_value_tooltip(desc, *p, 1); |
| 292 |
return changed; |
| 293 |
} |
| 294 |
|
| 295 |
bool reg_u16(const char *label, uint16_t *p, const char *desc = nullptr) |
| 296 |
{ |
| 297 |
ImGui::SetNextItemWidth(hex_width(4)); |
| 298 |
bool changed = ImGui::InputScalar(label, ImGuiDataType_U16, p, nullptr, nullptr, "%04X", kHexFlags); |
| 299 |
dbgui_hover_value_tooltip(desc, *p, 2); |
| 300 |
return changed; |
| 301 |
} |
| 302 |
|
| 303 |
|
| 304 |
|
| 305 |
|
| 306 |
void flag_checkbox(const char *label, uint8_t mask, const char *name, const char *desc) |
| 307 |
{ |
| 308 |
bool v = (regs.status & mask) != 0; |
| 309 |
if (ImGui::Checkbox(label, &v)) { |
| 310 |
if (v) { |
| 311 |
regs.status |= mask; |
| 312 |
} else { |
| 313 |
regs.status = (uint8_t)(regs.status & ~mask); |
| 314 |
} |
| 315 |
} |
| 316 |
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { |
| 317 |
int bit = 0; |
| 318 |
for (uint8_t m = mask; m > 1; m >>= 1) { |
| 319 |
bit++; |
| 320 |
} |
| 321 |
ImGui::BeginTooltip(); |
| 322 |
ImGui::Text("%s (bit %d, mask $%02X)", name, bit, mask); |
| 323 |
ImGui::Separator(); |
| 324 |
ImGui::TextUnformatted(desc); |
| 325 |
ImGui::TextDisabled("current value: %d", (regs.status & mask) ? 1 : 0); |
| 326 |
ImGui::EndTooltip(); |
| 327 |
} |
| 328 |
} |
| 329 |
|
| 330 |
|
| 331 |
|
| 332 |
|
| 333 |
void draw_registers() |
| 334 |
{ |
| 335 |
if (!ImGui::CollapsingHeader("Registers", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 336 |
return; |
| 337 |
} |
| 338 |
ImGui::PushID("registers"); |
| 339 |
|
| 340 |
const bool is816 = regs.is65c816; |
| 341 |
const bool native = cpu_native(); |
| 342 |
|
| 343 |
const char *mode = !is816 ? "65C02 (6502)" |
| 344 |
: (regs.e ? "65C816 (emulation)" : "65C816 (native)"); |
| 345 |
ImGui::Text("Mode: %s", mode); |
| 346 |
|
| 347 |
|
| 348 |
if (is816) { |
| 349 |
ImGui::SetItemTooltip( |
| 350 |
"M=%d X=%d E=%d\n\n" |
| 351 |
"The register set follows the CPU mode.\n" |
| 352 |
"%s\n\n" |
| 353 |
"M (P bit 5) picks the accumulator width, X (P bit 4) the index\n" |
| 354 |
"width; 1 = 8-bit. DP, DB and K are live in both modes.", |
| 355 |
(regs.status >> 5) & 1, (regs.status >> 4) & 1, regs.e ? 1 : 0, |
| 356 |
regs.e ? "Emulation mode re-asserts M and X before every instruction, so\n" |
| 357 |
"A/X/Y are 8-bit and cannot be widened while E is set. The program\n" |
| 358 |
"leaves emulation mode with XCE." |
| 359 |
: "Native mode does not widen the registers by itself - exactly as on\n" |
| 360 |
"hardware, the program selects the widths with REP/SEP."); |
| 361 |
} else { |
| 362 |
ImGui::SetItemTooltip("A/X/Y are 8-bit. The 65C816-only registers (C, B, DP, DB, K, E)\n" |
| 363 |
"do not exist on the 65C02 and are not shown."); |
| 364 |
} |
| 365 |
ImGui::Separator(); |
| 366 |
|
| 367 |
reg_u16("PC##rPC", ®s.pc, "PC — Program Counter\nAddress of the next instruction to execute."); |
| 368 |
|
| 369 |
|
| 370 |
ImGui::SameLine(); |
| 371 |
{ |
| 372 |
|
| 373 |
|
| 374 |
|
| 375 |
|
| 376 |
|
| 377 |
uint16_t sp_disp = (uint16_t)(regs.sp | (is816 ? 0 : 0x0100)); |
| 378 |
if (reg_u16("SP##rSP", &sp_disp, |
| 379 |
native ? "SP - Stack Pointer (16-bit)\n65C816 native: the stack can live anywhere." |
| 380 |
: "SP - Stack Pointer\nConfined to page 1 ($01xx): pushes and pulls wrap inside\n" |
| 381 |
"the page. The high byte is held at $01 for you.")) { |
| 382 |
regs.sp = native ? sp_disp : (uint16_t)(0x0100 | (sp_disp & 0x00FF)); |
| 383 |
} |
| 384 |
} |
| 385 |
|
| 386 |
|
| 387 |
|
| 388 |
|
| 389 |
|
| 390 |
|
| 391 |
|
| 392 |
|
| 393 |
|
| 394 |
if (is816) { |
| 395 |
reg_u8("A##rA", ®s.a, |
| 396 |
"A - accumulator, low 8 bits of C.\n" |
| 397 |
"The whole accumulator when M selects 8-bit."); |
| 398 |
ImGui::SameLine(); |
| 399 |
reg_u8("B##rB", ®s.b, |
| 400 |
"B - accumulator high byte.\n" |
| 401 |
"XBA swaps it with A - which works in emulation mode too, so B is\n" |
| 402 |
"real state whatever the accumulator width is."); |
| 403 |
ImGui::SameLine(); |
| 404 |
char cdesc[288]; |
| 405 |
snprintf(cdesc, sizeof cdesc, |
| 406 |
"C - the full 16-bit accumulator, B:A = $%02X%02X\n\n" |
| 407 |
"%s\n\n" |
| 408 |
"Not native-only: XBA, TCD, TDC, TCS and TSC move all 16 bits\n" |
| 409 |
"regardless of M, and work in emulation mode.", |
| 410 |
regs.b, regs.a, |
| 411 |
acc_is16() ? "M selects 16-bit, so C is the arithmetic accumulator." |
| 412 |
: "16-bit arithmetic (LDA, ADC, ...) needs M=0, which needs\n" |
| 413 |
"native mode, so right now arithmetic uses A alone."); |
| 414 |
reg_u16("C##rC", ®s.c, cdesc); |
| 415 |
} else { |
| 416 |
reg_u8("A##rA", ®s.a, "A - accumulator (8-bit)"); |
| 417 |
} |
| 418 |
|
| 419 |
|
| 420 |
if (idx_is16()) { |
| 421 |
reg_u16("X##rX", ®s.x, "X — index register (16-bit)"); |
| 422 |
ImGui::SameLine(); |
| 423 |
reg_u16("Y##rY", ®s.y, "Y — index register (16-bit)"); |
| 424 |
} else { |
| 425 |
reg_u8("X##rX", ®s.xl, "X — index register (8-bit)"); |
| 426 |
ImGui::SameLine(); |
| 427 |
reg_u8("Y##rY", ®s.yl, "Y — index register (8-bit)"); |
| 428 |
} |
| 429 |
|
| 430 |
|
| 431 |
|
| 432 |
|
| 433 |
if (is816 && native && (!acc_is16() || !idx_is16()) && |
| 434 |
debug_ui_settings().allow_breaking_changes) { |
| 435 |
if (ImGui::SmallButton("Widen now (REP #$30)")) { |
| 436 |
regs.status &= (uint8_t)~(FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH); |
| 437 |
} |
| 438 |
ImGui::SetItemTooltip( |
| 439 |
"Clear M and X, the same thing REP #$30 does, to give 16-bit\n" |
| 440 |
"A/X/Y. Real hardware only widens when the program asks;\n" |
| 441 |
"this is a manual poke, so the running code may not expect it.\n\n" |
| 442 |
"It also only sticks while E stays clear: setting E re-asserts\n" |
| 443 |
"M and X before every instruction."); |
| 444 |
} |
| 445 |
|
| 446 |
|
| 447 |
|
| 448 |
|
| 449 |
if (is816) { |
| 450 |
ImGui::Separator(); |
| 451 |
|
| 452 |
|
| 453 |
|
| 454 |
|
| 455 |
reg_u16("DP##rDP", ®s.dp, |
| 456 |
native ? "DP - Direct Page register\nBase of the direct (zero) page." |
| 457 |
: "DP - Direct Page register\nBase of the direct (zero) page. Still live in emulation\n" |
| 458 |
"mode: when its low byte is 0 the offset wraps inside the page\n" |
| 459 |
"(6502 behaviour), otherwise it adds normally."); |
| 460 |
ImGui::SameLine(); |
| 461 |
reg_u8("DB##rDB", ®s.db, |
| 462 |
"DB - Data Bank register\nHigh byte (bank) for data memory accesses."); |
| 463 |
ImGui::SameLine(); |
| 464 |
reg_u8("K##rK", ®s.k, |
| 465 |
"K - Program Bank register\nHigh byte (bank) of the 24-bit program counter."); |
| 466 |
} |
| 467 |
|
| 468 |
ImGui::PopID(); |
| 469 |
} |
| 470 |
|
| 471 |
|
| 472 |
|
| 473 |
|
| 474 |
|
| 475 |
|
| 476 |
void draw_vregs() |
| 477 |
{ |
| 478 |
if (!ImGui::CollapsingHeader("Virtual Regs (R0-R15)")) { |
| 479 |
return; |
| 480 |
} |
| 481 |
ImGui::PushID("vregs"); |
| 482 |
ImGui::TextDisabled("X16 16-bit pseudo-registers (direct-page +$02-$21, little-endian)"); |
| 483 |
|
| 484 |
for (int n = 0; n < 16; n++) { |
| 485 |
uint16_t addr = dp_add((uint16_t)(0x02 + 2 * n)); |
| 486 |
uint8_t lo = debug_ui_read6502(addr, 0, DEBUG_UI_CURRENT_BANK); |
| 487 |
uint8_t hi = debug_ui_read6502((uint16_t)(addr + 1), 0, DEBUG_UI_CURRENT_BANK); |
| 488 |
uint16_t val = (uint16_t)(lo | (hi << 8)); |
| 489 |
|
| 490 |
char label[16]; |
| 491 |
snprintf(label, sizeof label, "R%-2d##vr%d", n, n); |
| 492 |
ImGui::SetNextItemWidth(hex_width(4)); |
| 493 |
if (ImGui::InputScalar(label, ImGuiDataType_U16, &val, nullptr, nullptr, "%04X", kHexFlags)) { |
| 494 |
debug_ui_write6502(addr, (uint8_t)(val & 0xFF), 0, DEBUG_UI_CURRENT_BANK); |
| 495 |
debug_ui_write6502((uint16_t)(addr + 1), (uint8_t)(val >> 8), 0, DEBUG_UI_CURRENT_BANK); |
| 496 |
} |
| 497 |
char desc[112]; |
| 498 |
snprintf(desc, sizeof desc, |
| 499 |
"R%d - X16 virtual register\n@ $%04X/$%04X (direct-page relative, little-endian)", |
| 500 |
n, addr, (uint16_t)(addr + 1)); |
| 501 |
dbgui_hover_value_tooltip(desc, val, 2); |
| 502 |
|
| 503 |
if (n % 2 == 0) { |
| 504 |
ImGui::SameLine(0.0f, 24.0f); |
| 505 |
} |
| 506 |
} |
| 507 |
ImGui::PopID(); |
| 508 |
} |
| 509 |
|
| 510 |
|
| 511 |
|
| 512 |
|
| 513 |
void draw_flags() |
| 514 |
{ |
| 515 |
if (!ImGui::CollapsingHeader("Status Flags", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 516 |
return; |
| 517 |
} |
| 518 |
ImGui::PushID("flags"); |
| 519 |
|
| 520 |
ImGui::Text("P = $%02X", regs.status); |
| 521 |
{ |
| 522 |
char bits[48]; |
| 523 |
dbgui_format_binary(bits, sizeof bits, regs.status, 8); |
| 524 |
ImGui::SetItemTooltip( |
| 525 |
"P - Processor status register\n" |
| 526 |
"$%02X = %s\n" |
| 527 |
"Bit 7..0: %s\n\n" |
| 528 |
"Each bit is editable as a checkbox below. On the 65C816 bits 5 and 4\n" |
| 529 |
"are the M and X width selects; on the 65C02 they are the unused and\n" |
| 530 |
"Break bits, which is why the layout changes with the CPU.", |
| 531 |
regs.status, bits, |
| 532 |
regs.is65c816 ? "N V M X D I Z C" : "N V - B D I Z C"); |
| 533 |
} |
| 534 |
|
| 535 |
flag_checkbox("N##fN", FLAG_SIGN, "N — Negative", |
| 536 |
"Set when bit 7 of the result is set (result looks negative)."); |
| 537 |
ImGui::SameLine(); |
| 538 |
flag_checkbox("V##fV", FLAG_OVERFLOW, "V — Overflow", |
| 539 |
"Set on signed arithmetic overflow (ADC/SBC), or by BIT/CLV."); |
| 540 |
ImGui::SameLine(); |
| 541 |
if (regs.is65c816) { |
| 542 |
|
| 543 |
|
| 544 |
|
| 545 |
const bool widths_locked = regs.e != 0; |
| 546 |
ImGui::BeginDisabled(widths_locked); |
| 547 |
flag_checkbox("M##fM", FLAG_MEMORY_WIDTH, "M — Memory/Accumulator width", |
| 548 |
"65C816 native only. 0 = 16-bit accumulator/memory, 1 = 8-bit."); |
| 549 |
ImGui::SameLine(); |
| 550 |
flag_checkbox("X##fX", FLAG_INDEX_WIDTH, "X — Index register width", |
| 551 |
"65C816 native only. 0 = 16-bit X/Y, 1 = 8-bit. Forced 1 in emulation."); |
| 552 |
ImGui::EndDisabled(); |
| 553 |
if (widths_locked && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { |
| 554 |
ImGui::SetTooltip("Forced to 1 while E (emulation) is set — the CPU rewrites\n" |
| 555 |
"both flags before every instruction. Clear E to change them."); |
| 556 |
} |
| 557 |
ImGui::SameLine(); |
| 558 |
} else { |
| 559 |
flag_checkbox("-##fConst", FLAG_CONSTANT, "- — Unused", |
| 560 |
"Unused on the 65C02; conventionally reads as 1."); |
| 561 |
ImGui::SameLine(); |
| 562 |
flag_checkbox("B##fBrk", FLAG_BREAK, "B — Break", |
| 563 |
"Distinguishes a BRK/PHP push from an IRQ/NMI push on the stack."); |
| 564 |
ImGui::SameLine(); |
| 565 |
} |
| 566 |
flag_checkbox("D##fD", FLAG_DECIMAL, "D — Decimal", |
| 567 |
"Enables BCD (binary-coded decimal) mode for ADC/SBC."); |
| 568 |
ImGui::SameLine(); |
| 569 |
flag_checkbox("I##fI", FLAG_INTERRUPT, "I — IRQ disable", |
| 570 |
"1 masks maskable IRQs; NMIs are unaffected."); |
| 571 |
ImGui::SameLine(); |
| 572 |
flag_checkbox("Z##fZ", FLAG_ZERO, "Z — Zero", |
| 573 |
"Set when the result of an operation is zero."); |
| 574 |
ImGui::SameLine(); |
| 575 |
flag_checkbox("C##fC", FLAG_CARRY, "C — Carry", |
| 576 |
"Carry/borrow out of bit 7 (bit 15 in 16-bit), and the bit shifted by shifts/rotates."); |
| 577 |
|
| 578 |
if (regs.is65c816) { |
| 579 |
draw_emulation_toggle("E (Emulation)##fE"); |
| 580 |
} |
| 581 |
|
| 582 |
ImGui::PopID(); |
| 583 |
} |
| 584 |
|
| 585 |
|
| 586 |
|
| 587 |
|
| 588 |
void draw_stack() |
| 589 |
{ |
| 590 |
if (!ImGui::CollapsingHeader("Stack", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 591 |
return; |
| 592 |
} |
| 593 |
ImGui::PushID("stack"); |
| 594 |
|
| 595 |
|
| 596 |
|
| 597 |
uint16_t sp_actual = (uint16_t)(regs.sp | (regs.is65c816 ? 0 : 0x100)); |
| 598 |
uint16_t sp_top = (uint16_t)(sp_actual + 1); |
| 599 |
bool page1_stack = !cpu_native(); |
| 600 |
|
| 601 |
ImGui::Text("SP = $%04X top-of-stack = $%04X", sp_actual, sp_top); |
| 602 |
|
| 603 |
static int rows = 16; |
| 604 |
ImGui::SetNextItemWidth(160.0f); |
| 605 |
ImGui::SliderInt("rows", &rows, 4, 64); |
| 606 |
|
| 607 |
const ImVec4 hi(1.0f, 0.85f, 0.35f, 1.0f); |
| 608 |
float line = ImGui::GetTextLineHeightWithSpacing(); |
| 609 |
int visible = rows < 16 ? rows : 16; |
| 610 |
|
| 611 |
if (ImGui::BeginChild("stack_scroll", ImVec2(0.0f, line * (visible + 1.5f)), true)) { |
| 612 |
if (ImGui::BeginTable("stack_tbl", 3, |
| 613 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | |
| 614 |
DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 615 |
ImGui::TableSetupColumn("Addr"); |
| 616 |
ImGui::TableSetupColumn("Byte"); |
| 617 |
ImGui::TableSetupColumn("ASCII"); |
| 618 |
ImGui::TableHeadersRow(); |
| 619 |
|
| 620 |
for (int i = 0; i < rows; i++) { |
| 621 |
uint32_t addr = (uint32_t)sp_top + (uint32_t)i; |
| 622 |
if (page1_stack && addr > 0x01FF) { |
| 623 |
break; |
| 624 |
} |
| 625 |
uint16_t a16 = (uint16_t)addr; |
| 626 |
uint8_t byte = debug_ui_read6502(a16, 0, DEBUG_UI_CURRENT_BANK); |
| 627 |
bool top = (i == 0); |
| 628 |
|
| 629 |
ImGui::TableNextRow(); |
| 630 |
ImGui::TableSetColumnIndex(0); |
| 631 |
if (top) { |
| 632 |
ImGui::TextColored(hi, "> $%04X", a16); |
| 633 |
} else { |
| 634 |
ImGui::Text(" $%04X", a16); |
| 635 |
} |
| 636 |
ImGui::TableSetColumnIndex(1); |
| 637 |
if (top) { |
| 638 |
ImGui::TextColored(hi, "%02X", byte); |
| 639 |
} else { |
| 640 |
ImGui::Text("%02X", byte); |
| 641 |
} |
| 642 |
ImGui::TableSetColumnIndex(2); |
| 643 |
if (top) { |
| 644 |
ImGui::TextColored(hi, "%c", ascii_of(byte)); |
| 645 |
} else { |
| 646 |
ImGui::Text("%c", ascii_of(byte)); |
| 647 |
} |
| 648 |
} |
| 649 |
ImGui::EndTable(); |
| 650 |
} |
| 651 |
} |
| 652 |
ImGui::EndChild(); |
| 653 |
|
| 654 |
ImGui::PopID(); |
| 655 |
} |
| 656 |
|
| 657 |
|
| 658 |
|
| 659 |
|
| 660 |
|
| 661 |
|
| 662 |
void draw_interrupts() |
| 663 |
{ |
| 664 |
if (!ImGui::CollapsingHeader("Interrupts", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 665 |
return; |
| 666 |
} |
| 667 |
ImGui::PushID("irq"); |
| 668 |
|
| 669 |
if (cpu_in_interrupt()) { |
| 670 |
const int vec = cpu_irq_last_vector(); |
| 671 |
const char *what = vec == 0x0A ? "NMI" : (vec == 0x06 ? "BRK" : (vec == 0x04 ? "COP" : "IRQ")); |
| 672 |
ImGui::TextColored(ImVec4(1.00f, 0.45f, 0.45f, 1.0f), |
| 673 |
"In %s handler (depth %d)", what, cpu_irq_depth()); |
| 674 |
ImGui::SameLine(); |
| 675 |
ImGui::TextDisabled("returns to $%04X", (unsigned)cpu_irq_return_pc()); |
| 676 |
} else { |
| 677 |
ImGui::TextDisabled("Not in an interrupt"); |
| 678 |
} |
| 679 |
|
| 680 |
|
| 681 |
|
| 682 |
uint8_t ien = 0, isr = 0; |
| 683 |
uint16_t irqline = 0; |
| 684 |
video_get_irq_state(&ien, &isr, &irqline); |
| 685 |
|
| 686 |
ImGui::Text("VERA IEN $%02X", ien); |
| 687 |
ImGui::SameLine(); |
| 688 |
ImGui::TextDisabled("[%s%s%s%s]", |
| 689 |
(ien & 1) ? "VSYNC " : "", (ien & 2) ? "LINE " : "", |
| 690 |
(ien & 4) ? "SPRCOL " : "", (ien & 8) ? "AFLOW" : ""); |
| 691 |
ImGui::SameLine(); |
| 692 |
ImGui::Text("ISR $%02X", isr); |
| 693 |
if (ien & 2) { |
| 694 |
ImGui::SameLine(); |
| 695 |
ImGui::TextDisabled("line=%u", (unsigned)irqline); |
| 696 |
} |
| 697 |
|
| 698 |
uint32_t cycles = 0; |
| 699 |
uint8_t source = 0; |
| 700 |
if (video_next_irq((float)MHZ, &cycles, &source)) { |
| 701 |
|
| 702 |
|
| 703 |
|
| 704 |
const unsigned insns = (unsigned)(cycles / 4); |
| 705 |
ImGui::Text("Next: %s in %u cyc", source == 1 ? "VSYNC" : "LINE", (unsigned)cycles); |
| 706 |
ImGui::SameLine(); |
| 707 |
ImGui::TextDisabled("(~%u instructions)", insns); |
| 708 |
if (ImGui::IsItemHovered()) { |
| 709 |
ImGui::SetTooltip("Cycles until the next enabled VERA interrupt, from the\n" |
| 710 |
"current raster position. The instruction figure is a rough\n" |
| 711 |
"estimate at ~4 cycles each."); |
| 712 |
} |
| 713 |
} else if (regs.status & FLAG_INTERRUPT) { |
| 714 |
ImGui::TextDisabled("No VERA interrupt armed (and I flag masks IRQs)"); |
| 715 |
} else { |
| 716 |
ImGui::TextDisabled("No VERA interrupt armed"); |
| 717 |
} |
| 718 |
|
| 719 |
if (regs.status & FLAG_INTERRUPT) { |
| 720 |
ImGui::SameLine(); |
| 721 |
ImGui::TextColored(ImVec4(0.95f, 0.70f, 0.30f, 1.0f), "I"); |
| 722 |
if (ImGui::IsItemHovered()) { |
| 723 |
ImGui::SetTooltip("The I flag is set: maskable IRQs are blocked right now."); |
| 724 |
} |
| 725 |
} |
| 726 |
|
| 727 |
ImGui::TextDisabled("%u interrupts taken", (unsigned)cpu_irq_count()); |
| 728 |
|
| 729 |
ImGui::PopID(); |
| 730 |
} |
| 731 |
|
| 732 |
|
| 733 |
|
| 734 |
|
| 735 |
|
| 736 |
struct WatchEntry { |
| 737 |
uint16_t addr = 0x0000; |
| 738 |
int len = 1; |
| 739 |
int bank = -1; |
| 740 |
}; |
| 741 |
|
| 742 |
|
| 743 |
|
| 744 |
std::vector<WatchEntry> g_watch_entries; |
| 745 |
|
| 746 |
void draw_watch() |
| 747 |
{ |
| 748 |
|
| 749 |
bool added = false; |
| 750 |
{ |
| 751 |
uint16_t waddr = 0; |
| 752 |
int16_t wbank = -1; |
| 753 |
uint16_t wlen = 1; |
| 754 |
if (debug_ui_take_watch_request(&waddr, &wbank, &wlen)) { |
| 755 |
|
| 756 |
|
| 757 |
uint16_t left = wlen ? wlen : 1; |
| 758 |
uint16_t at = waddr; |
| 759 |
while (left > 0 && g_watch_entries.size() < 64) { |
| 760 |
WatchEntry w; |
| 761 |
w.addr = at; |
| 762 |
w.len = left > 16 ? 16 : (int)left; |
| 763 |
w.bank = wbank; |
| 764 |
g_watch_entries.push_back(w); |
| 765 |
at = (uint16_t)(at + w.len); |
| 766 |
left = (uint16_t)(left - w.len); |
| 767 |
} |
| 768 |
added = true; |
| 769 |
} |
| 770 |
} |
| 771 |
if (added) { |
| 772 |
ImGui::SetNextItemOpen(true); |
| 773 |
} |
| 774 |
|
| 775 |
if (!ImGui::CollapsingHeader("Watch", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 776 |
return; |
| 777 |
} |
| 778 |
ImGui::PushID("watch"); |
| 779 |
|
| 780 |
std::vector<WatchEntry> &entries = g_watch_entries; |
| 781 |
|
| 782 |
if (ImGui::Button("+ Add")) { |
| 783 |
entries.push_back(WatchEntry()); |
| 784 |
} |
| 785 |
ImGui::SameLine(); |
| 786 |
ImGui::TextDisabled("address-based watches (bank -1 = current)"); |
| 787 |
|
| 788 |
if (ImGui::BeginTable("watch_tbl", 6, |
| 789 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | |
| 790 |
ImGuiTableFlags_SizingFixedFit | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 791 |
ImGui::TableSetupColumn(""); |
| 792 |
ImGui::TableSetupColumn("Addr"); |
| 793 |
ImGui::TableSetupColumn("Len"); |
| 794 |
ImGui::TableSetupColumn("Bank"); |
| 795 |
ImGui::TableSetupColumn("Hex"); |
| 796 |
ImGui::TableSetupColumn("Dec / ASCII"); |
| 797 |
ImGui::TableHeadersRow(); |
| 798 |
|
| 799 |
int remove = -1; |
| 800 |
for (int i = 0; i < (int)entries.size(); i++) { |
| 801 |
ImGui::PushID(i); |
| 802 |
WatchEntry &w = entries[i]; |
| 803 |
|
| 804 |
ImGui::TableNextRow(); |
| 805 |
|
| 806 |
ImGui::TableSetColumnIndex(0); |
| 807 |
if (ImGui::SmallButton("x")) { |
| 808 |
remove = i; |
| 809 |
} |
| 810 |
|
| 811 |
ImGui::TableSetColumnIndex(1); |
| 812 |
ImGui::SetNextItemWidth(hex_width(4)); |
| 813 |
ImGui::InputScalar("##addr", ImGuiDataType_U16, &w.addr, nullptr, nullptr, |
| 814 |
"%04X", kHexFlags); |
| 815 |
|
| 816 |
ImGui::TableSetColumnIndex(2); |
| 817 |
ImGui::SetNextItemWidth(hex_width(3)); |
| 818 |
ImGui::InputInt("##len", &w.len, 0); |
| 819 |
if (w.len < 1) w.len = 1; |
| 820 |
if (w.len > 16) w.len = 16; |
| 821 |
|
| 822 |
ImGui::TableSetColumnIndex(3); |
| 823 |
ImGui::SetNextItemWidth(hex_width(3)); |
| 824 |
ImGui::InputInt("##bank", &w.bank, 0); |
| 825 |
if (w.bank < -1) w.bank = -1; |
| 826 |
if (w.bank > 255) w.bank = 255; |
| 827 |
|
| 828 |
uint8_t bytes[16]; |
| 829 |
for (int j = 0; j < w.len; j++) { |
| 830 |
bytes[j] = debug_ui_read6502((uint16_t)(w.addr + j), 0, (int16_t)w.bank); |
| 831 |
} |
| 832 |
|
| 833 |
ImGui::TableSetColumnIndex(4); |
| 834 |
|
| 835 |
|
| 836 |
|
| 837 |
|
| 838 |
char hexbuf[16 * 3 + 1]; |
| 839 |
int hp = 0; |
| 840 |
for (int j = 0; j < w.len; j++) { |
| 841 |
hp += snprintf(hexbuf + hp, sizeof(hexbuf) - hp, "%02X ", bytes[j]); |
| 842 |
} |
| 843 |
if (hp > 0 && hexbuf[hp - 1] == ' ') { |
| 844 |
hexbuf[hp - 1] = '\0'; |
| 845 |
} |
| 846 |
ImGui::SetNextItemWidth(hex_width(w.len * 3 + 1)); |
| 847 |
if (ImGui::InputText("##hex", hexbuf, sizeof(hexbuf), |
| 848 |
kHexFlags | ImGuiInputTextFlags_EnterReturnsTrue | |
| 849 |
ImGuiInputTextFlags_AutoSelectAll)) { |
| 850 |
|
| 851 |
|
| 852 |
const char *p = hexbuf; |
| 853 |
for (int j = 0; j < w.len; j++) { |
| 854 |
while (*p == ' ' || *p == '\t') |
| 855 |
p++; |
| 856 |
if (!*p) |
| 857 |
break; |
| 858 |
int hi = hex_digit(*p++); |
| 859 |
if (hi < 0) |
| 860 |
break; |
| 861 |
int lo = hex_digit(*p); |
| 862 |
uint8_t v; |
| 863 |
if (lo < 0) { |
| 864 |
v = (uint8_t)hi; |
| 865 |
} else { |
| 866 |
v = (uint8_t)((hi << 4) | lo); |
| 867 |
p++; |
| 868 |
} |
| 869 |
debug_ui_write6502((uint16_t)(w.addr + j), v, 0, (int16_t)w.bank); |
| 870 |
} |
| 871 |
} |
| 872 |
if (ImGui::IsItemHovered() && !ImGui::IsItemActive()) { |
| 873 |
uint32_t hv = 0; |
| 874 |
int hb = w.len < 4 ? w.len : 4; |
| 875 |
for (int j = 0; j < hb; j++) |
| 876 |
hv |= (uint32_t)bytes[j] << (8 * j); |
| 877 |
dbgui_hover_value_tooltip("Watch value (little-endian) - editable", hv, hb); |
| 878 |
} |
| 879 |
|
| 880 |
ImGui::TableSetColumnIndex(5); |
| 881 |
uint32_t val = 0; |
| 882 |
for (int j = 0; j < w.len && j < 4; j++) { |
| 883 |
val |= (uint32_t)bytes[j] << (8 * j); |
| 884 |
} |
| 885 |
char abuf[17]; |
| 886 |
for (int j = 0; j < w.len; j++) { |
| 887 |
abuf[j] = ascii_of(bytes[j]); |
| 888 |
} |
| 889 |
abuf[w.len] = '\0'; |
| 890 |
ImGui::Text("%u \"%s\"", val, abuf); |
| 891 |
|
| 892 |
ImGui::PopID(); |
| 893 |
} |
| 894 |
ImGui::EndTable(); |
| 895 |
|
| 896 |
if (remove >= 0) { |
| 897 |
entries.erase(entries.begin() + remove); |
| 898 |
} |
| 899 |
} |
| 900 |
|
| 901 |
ImGui::PopID(); |
| 902 |
} |
| 903 |
|
| 904 |
void |
| 905 |
cpu_panel_render(bool *p_open) |
| 906 |
{ |
| 907 |
if (ImGui::Begin("CPU", p_open)) { |
| 908 |
dbgui_window_zoom("cpu"); |
| 909 |
draw_registers(); |
| 910 |
draw_vregs(); |
| 911 |
draw_flags(); |
| 912 |
draw_interrupts(); |
| 913 |
draw_stack(); |
| 914 |
draw_watch(); |
| 915 |
draw_emulation_confirm_popup(); |
| 916 |
} |
| 917 |
dbgui_window_end(); |
| 918 |
} |
| 919 |
|
| 920 |
} |
| 921 |
|
| 922 |
static DebugPanelRegistration s_reg("CPU", cpu_panel_render, true); |