| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
#include "imgui.h" |
| 22 |
#include "debug_ui_panels.h" |
| 23 |
#include "debug_ui_bridge.h" |
| 24 |
#include "debug_ui_settings.h" |
| 25 |
#include "debug_ui_widgets.h" |
| 26 |
#include "io_reg_info.h" |
| 27 |
|
| 28 |
#include <stdarg.h> |
| 29 |
#include <stdint.h> |
| 30 |
#include <stdio.h> |
| 31 |
#include <string.h> |
| 32 |
|
| 33 |
namespace { |
| 34 |
|
| 35 |
const ImVec4 COL_READ = ImVec4(0.55f, 0.78f, 1.00f, 1.0f); |
| 36 |
const ImVec4 COL_WRITE = ImVec4(1.00f, 0.72f, 0.42f, 1.0f); |
| 37 |
const ImVec4 COL_DECODED = ImVec4(0.62f, 0.90f, 0.62f, 1.0f); |
| 38 |
const ImVec4 COL_DIM = ImVec4(0.70f, 0.70f, 0.78f, 1.0f); |
| 39 |
const ImVec4 COL_BAD = ImVec4(0.95f, 0.55f, 0.45f, 1.0f); |
| 40 |
const ImVec4 COL_ON = ImVec4(0.45f, 0.95f, 0.50f, 1.0f); |
| 41 |
|
| 42 |
|
| 43 |
void |
| 44 |
tip(const char *text) |
| 45 |
{ |
| 46 |
if (ImGui::IsItemHovered()) { |
| 47 |
ImGui::BeginTooltip(); |
| 48 |
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 32.0f); |
| 49 |
ImGui::TextUnformatted(text); |
| 50 |
ImGui::PopTextWrapPos(); |
| 51 |
ImGui::EndTooltip(); |
| 52 |
} |
| 53 |
} |
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
void |
| 63 |
help(const char *text) |
| 64 |
{ |
| 65 |
tip(text); |
| 66 |
} |
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
void |
| 71 |
heading(const char *text, const char *tip_text) |
| 72 |
{ |
| 73 |
ImGui::TextUnformatted(text); |
| 74 |
tip(tip_text); |
| 75 |
} |
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
const char *g_row_tip = nullptr; |
| 80 |
|
| 81 |
|
| 82 |
|
| 83 |
void |
| 84 |
row_begin(const char *label, const char *tip_text) |
| 85 |
{ |
| 86 |
ImGui::TableNextRow(); |
| 87 |
ImGui::TableSetColumnIndex(0); |
| 88 |
ImGui::TextColored(COL_DIM, "%s", label); |
| 89 |
tip(tip_text); |
| 90 |
ImGui::TableSetColumnIndex(1); |
| 91 |
g_row_tip = tip_text; |
| 92 |
} |
| 93 |
|
| 94 |
void |
| 95 |
row_end() |
| 96 |
{ |
| 97 |
tip(g_row_tip); |
| 98 |
} |
| 99 |
|
| 100 |
|
| 101 |
void |
| 102 |
row_kv(const char *label, const char *tip_text, const char *fmt, ...) |
| 103 |
{ |
| 104 |
row_begin(label, tip_text); |
| 105 |
va_list ap; |
| 106 |
va_start(ap, fmt); |
| 107 |
ImGui::TextV(fmt, ap); |
| 108 |
va_end(ap); |
| 109 |
row_end(); |
| 110 |
} |
| 111 |
|
| 112 |
|
| 113 |
void |
| 114 |
lamp(bool on, const char *label) |
| 115 |
{ |
| 116 |
ImGui::TextColored(on ? COL_ON : COL_DIM, "%s %s", on ? "\xe2\x97\x8f" : "\xe2\x97\x8b", label); |
| 117 |
} |
| 118 |
|
| 119 |
|
| 120 |
|
| 121 |
|
| 122 |
|
| 123 |
|
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
void |
| 128 |
apply_capture_settings() |
| 129 |
{ |
| 130 |
const DebugUiSettings &s = debug_ui_settings(); |
| 131 |
|
| 132 |
uint32_t mask = 0; |
| 133 |
if (s.io_cap_via) { |
| 134 |
mask |= IO_TRACE_BIT(IO_DEV_VIA1) | IO_TRACE_BIT(IO_DEV_VIA2); |
| 135 |
} |
| 136 |
if (s.io_cap_vera) { |
| 137 |
mask |= IO_TRACE_BIT(IO_DEV_VERA); |
| 138 |
} |
| 139 |
if (s.io_cap_spi) { |
| 140 |
mask |= IO_TRACE_BIT(IO_DEV_SPI); |
| 141 |
} |
| 142 |
if (s.io_cap_ym) { |
| 143 |
mask |= IO_TRACE_BIT(IO_DEV_YM); |
| 144 |
} |
| 145 |
if (s.io_cap_emu) { |
| 146 |
mask |= IO_TRACE_BIT(IO_DEV_EMU); |
| 147 |
} |
| 148 |
if (s.io_cap_midi) { |
| 149 |
mask |= IO_TRACE_BIT(IO_DEV_MIDI); |
| 150 |
} |
| 151 |
if (s.io_cap_openbus) { |
| 152 |
mask |= IO_TRACE_BIT(IO_DEV_OPENBUS); |
| 153 |
} |
| 154 |
if (s.io_cap_sd) { |
| 155 |
mask |= IO_TRACE_BIT(IO_DEV_SDCARD); |
| 156 |
} |
| 157 |
if (s.io_cap_files) { |
| 158 |
mask |= IO_TRACE_BIT(IO_DEV_IEEE); |
| 159 |
} |
| 160 |
if (s.io_cap_i2c) { |
| 161 |
mask |= IO_TRACE_BIT(IO_DEV_I2C); |
| 162 |
} |
| 163 |
if (s.io_cap_joy) { |
| 164 |
mask |= IO_TRACE_BIT(IO_DEV_JOYSTICK); |
| 165 |
} |
| 166 |
mask |= IO_TRACE_BIT(IO_DEV_SERIAL); |
| 167 |
|
| 168 |
io_trace_device_mask = mask; |
| 169 |
io_trace_enabled = s.io_trace_capture; |
| 170 |
|
| 171 |
|
| 172 |
|
| 173 |
sdcard_fat_autoindex = s.io_fat_autoindex; |
| 174 |
|
| 175 |
if (io_trace_capacity() != 0 && io_trace_capacity() != s.io_trace_capacity) { |
| 176 |
io_trace_init(s.io_trace_capacity); |
| 177 |
|
| 178 |
|
| 179 |
debug_ui_settings().io_trace_capacity = io_trace_capacity(); |
| 180 |
} |
| 181 |
} |
| 182 |
|
| 183 |
|
| 184 |
|
| 185 |
|
| 186 |
|
| 187 |
char s_filter[64] = ""; |
| 188 |
bool s_autoscroll = true; |
| 189 |
bool s_show_reads = true; |
| 190 |
bool s_show_writes = true; |
| 191 |
|
| 192 |
bool |
| 193 |
event_matches(const io_event_t &ev) |
| 194 |
{ |
| 195 |
if (ev.kind == IO_EVENT_ACCESS) { |
| 196 |
if (ev.is_write && !s_show_writes) { |
| 197 |
return false; |
| 198 |
} |
| 199 |
if (!ev.is_write && !s_show_reads) { |
| 200 |
return false; |
| 201 |
} |
| 202 |
} |
| 203 |
if (s_filter[0] == '\0') { |
| 204 |
return true; |
| 205 |
} |
| 206 |
|
| 207 |
|
| 208 |
|
| 209 |
char addr[16]; |
| 210 |
snprintf(addr, sizeof(addr), "$%04X", ev.addr); |
| 211 |
|
| 212 |
return (strstr(io_trace_device_name((io_device_t)ev.device), s_filter) != nullptr) || |
| 213 |
(ev.text[0] != '\0' && strstr(ev.text, s_filter) != nullptr) || |
| 214 |
(ev.has_addr && strstr(addr, s_filter) != nullptr) || |
| 215 |
(ev.has_addr && strstr(io_reg_name(ev.addr), s_filter) != nullptr); |
| 216 |
} |
| 217 |
|
| 218 |
struct DeviceToggle { |
| 219 |
const char *label; |
| 220 |
bool DebugUiSettings::*field; |
| 221 |
const char *tip; |
| 222 |
}; |
| 223 |
|
| 224 |
|
| 225 |
|
| 226 |
|
| 227 |
const DeviceToggle RAW_TOGGLES[] = { |
| 228 |
{"VIA", &DebugUiSettings::io_cap_via, |
| 229 |
"$9F00-$9F1F. Both VIAs: the joystick latch and clock, the I2C lines to the " |
| 230 |
"SMC and RTC, the IEC serial lines, and the two timers.\n\n" |
| 231 |
"Adds over the VIA tab: the order and timing of accesses. The tab shows the " |
| 232 |
"registers as they are now; this shows the polling loop that got them there.\n\n" |
| 233 |
"Rate: moderate. The KERNAL touches the VIA every frame."}, |
| 234 |
{"VERA", &DebugUiSettings::io_cap_vera, |
| 235 |
"$9F20-$9F3F. All 32 VERA registers.\n\n" |
| 236 |
"Adds over the VERA tab: that tab decodes current state, but says nothing " |
| 237 |
"about the sequence that produced it. Only the log shows the address-then-data " |
| 238 |
"handshake, a mid-frame register change, or a raster split as it happens.\n\n" |
| 239 |
"Rate: VERY HIGH. DATA0/DATA1 auto-increment, so a screen fill is one access " |
| 240 |
"per byte -- thousands per frame. Expect this alone to fill the ring."}, |
| 241 |
{"SPI", &DebugUiSettings::io_cap_spi, |
| 242 |
"$9F3E/$9F3F only. These are VERA registers, but they are also the only path " |
| 243 |
"to the SD card, so they are captured separately -- ticking SPI does not drag " |
| 244 |
"in VERA's data ports.\n\n" |
| 245 |
"Adds over the SD Card tab: the raw byte stream, for when you need the SPI " |
| 246 |
"conversation itself rather than the decoded command.\n\n" |
| 247 |
"Rate: high while the card is busy -- one access per byte, 512 per block."}, |
| 248 |
{"YM", &DebugUiSettings::io_cap_ym, |
| 249 |
"$9F40-$9F5F. The YM2151's register-select latch and data port.\n\n" |
| 250 |
"Adds over the YM2151 tab: which registers a music driver writes and in what " |
| 251 |
"order, per frame. The tab shows the resulting channel state.\n\n" |
| 252 |
"Rate: moderate; a music driver typically writes a burst every frame."}, |
| 253 |
{"Emu", &DebugUiSettings::io_cap_emu, |
| 254 |
"$9FB0-$9FBF. The emulator's own control registers: debug toggles, the cycle " |
| 255 |
"counter, character output, and the \"16\" detection bytes.\n\n" |
| 256 |
"Nothing else shows these. Useful for watching a program probe for the " |
| 257 |
"emulator or drive its recording controls.\n\n" |
| 258 |
"Rate: very low."}, |
| 259 |
{"MIDI", &DebugUiSettings::io_cap_midi, |
| 260 |
"The MIDI card, if one is fitted. Its base address is configurable, so this " |
| 261 |
"follows wherever it was placed.\n\n" |
| 262 |
"Rate: low, and zero when no card is fitted."}, |
| 263 |
{"Open bus", &DebugUiSettings::io_cap_openbus, |
| 264 |
"Addresses in the I/O page that no device answers -- reads return $9F.\n\n" |
| 265 |
"Almost always noise, but it is the one way to catch a program reading a " |
| 266 |
"device it thinks exists and silently getting garbage back.\n\n" |
| 267 |
"Rate: normally zero."}, |
| 268 |
}; |
| 269 |
|
| 270 |
const DeviceToggle DECODED_TOGGLES[] = { |
| 271 |
{"SD", &DebugUiSettings::io_cap_sd, |
| 272 |
"Decoded SD card activity: each command by name (CMD17 READ_SINGLE_BLOCK), and " |
| 273 |
"every 512-byte block read or written -- resolved to a filename and offset when " |
| 274 |
"the image has been indexed.\n\n" |
| 275 |
"This is the one to leave on when you care about disk access.\n\n" |
| 276 |
"Rate: low. A handful per command, one per block."}, |
| 277 |
{"Files", &DebugUiSettings::io_cap_files, |
| 278 |
"Host-filesystem file access with real filenames: opens, closes, directory " |
| 279 |
"listings, DOS commands and status codes.\n\n" |
| 280 |
"The Files tab keeps its own always-on history of these, so you do not need " |
| 281 |
"capture on to see them there. This only adds them to this log, in sequence " |
| 282 |
"with everything else.\n\n" |
| 283 |
"Rate: low -- bounded by real file operations."}, |
| 284 |
{"I2C", &DebugUiSettings::io_cap_i2c, |
| 285 |
"One event per completed I2C transaction: which device, direction, and how many " |
| 286 |
"bytes moved.\n\n" |
| 287 |
"Rate: about 60/second and CONSTANT. The KERNAL polls the SMC for keyboard input " |
| 288 |
"every frame whether or not a key is pressed, so this will slowly push everything " |
| 289 |
"else out of the ring even on an idle machine."}, |
| 290 |
{"Joystick", &DebugUiSettings::io_cap_joy, |
| 291 |
"One event each time the controllers are latched, with the sampled button masks.\n\n" |
| 292 |
"Rate: about 60/second and CONSTANT -- a game reads the pad every frame whether " |
| 293 |
"or not anything is pressed."}, |
| 294 |
}; |
| 295 |
|
| 296 |
void |
| 297 |
draw_capture_controls() |
| 298 |
{ |
| 299 |
DebugUiSettings &s = debug_ui_settings(); |
| 300 |
bool changed = false; |
| 301 |
|
| 302 |
changed |= ImGui::Checkbox("Capture", &s.io_trace_capture); |
| 303 |
tip("Record I/O events into the log below.\n\n" |
| 304 |
"Off by default. This is the only debugger feature the running machine pays " |
| 305 |
"for -- a test and a branch on every I/O access, and a copy on every captured " |
| 306 |
"one -- and the I/O page is the busiest address range in the system. Every " |
| 307 |
"other tab in this panel works without it."); |
| 308 |
|
| 309 |
ImGui::SameLine(); |
| 310 |
if (ImGui::SmallButton("Clear")) { |
| 311 |
io_trace_clear(); |
| 312 |
} |
| 313 |
tip("Discard everything currently in the log."); |
| 314 |
|
| 315 |
ImGui::SameLine(); |
| 316 |
ImGui::Checkbox("Auto-scroll", &s_autoscroll); |
| 317 |
tip("Follow the newest event. Scroll up to stop following; scroll back to the " |
| 318 |
"bottom to resume."); |
| 319 |
|
| 320 |
ImGui::SameLine(0, 16); |
| 321 |
ImGui::SetNextItemWidth(dbgui_field_width("wwwwwwwwwwww")); |
| 322 |
ImGui::InputTextWithHint("##iofilter", "filter", s_filter, sizeof(s_filter)); |
| 323 |
tip("Show only rows whose device, address, register name or text contains this. " |
| 324 |
"Case-sensitive substring match, e.g. \"DATA0\", \"$9F23\", \"SD\"."); |
| 325 |
|
| 326 |
ImGui::SameLine(0, 16); |
| 327 |
ImGui::Checkbox("R", &s_show_reads); |
| 328 |
tip("Show reads."); |
| 329 |
ImGui::SameLine(); |
| 330 |
ImGui::Checkbox("W", &s_show_writes); |
| 331 |
tip("Show writes."); |
| 332 |
|
| 333 |
ImGui::TextUnformatted("Registers:"); |
| 334 |
help("Raw register accesses, by address range. These overlap the dedicated tabs " |
| 335 |
"only in subject, not in content: a tab shows you state, this log shows you " |
| 336 |
"sequence and timing -- what was written, in what order, and from which PC."); |
| 337 |
for (const DeviceToggle &t : RAW_TOGGLES) { |
| 338 |
ImGui::SameLine(); |
| 339 |
changed |= ImGui::Checkbox(t.label, &(s.*(t.field))); |
| 340 |
tip(t.tip); |
| 341 |
} |
| 342 |
|
| 343 |
ImGui::TextUnformatted("Decoded: "); |
| 344 |
help("Device-level events rather than register accesses -- an SD command by name, " |
| 345 |
"a file being opened, an I2C transaction. These are what the raw bytes mean."); |
| 346 |
for (const DeviceToggle &t : DECODED_TOGGLES) { |
| 347 |
ImGui::SameLine(); |
| 348 |
changed |= ImGui::Checkbox(t.label, &(s.*(t.field))); |
| 349 |
tip(t.tip); |
| 350 |
} |
| 351 |
|
| 352 |
if (changed) { |
| 353 |
debug_ui_settings_mark_dirty(); |
| 354 |
} |
| 355 |
|
| 356 |
if (!s.io_trace_capture) { |
| 357 |
ImGui::TextColored(COL_DIM, "Capture is off. Tick Capture, then pick at least one device."); |
| 358 |
} else if (io_trace_device_mask == IO_TRACE_BIT(IO_DEV_SERIAL)) { |
| 359 |
ImGui::TextColored(COL_DIM, "No devices selected, so nothing is being recorded."); |
| 360 |
} else { |
| 361 |
ImGui::TextColored(COL_DIM, "%d/%d events", io_trace_count(), io_trace_capacity()); |
| 362 |
const uint32_t dropped = io_trace_dropped(); |
| 363 |
if (dropped > 0) { |
| 364 |
ImGui::SameLine(); |
| 365 |
ImGui::TextColored(COL_DIM, "\xc2\xb7 %u overwritten", dropped); |
| 366 |
tip("The ring wrapped and older events were discarded. Raise the capacity in " |
| 367 |
"System > Settings, or capture fewer devices."); |
| 368 |
} |
| 369 |
} |
| 370 |
} |
| 371 |
|
| 372 |
void |
| 373 |
draw_activity_tab() |
| 374 |
{ |
| 375 |
draw_capture_controls(); |
| 376 |
ImGui::Separator(); |
| 377 |
|
| 378 |
if (ImGui::BeginTable("io_activity", 7, |
| 379 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | |
| 380 |
ImGuiTableFlags_BordersInnerV | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 381 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 382 |
ImGui::TableSetupColumn("Seq", ImGuiTableColumnFlags_WidthFixed, |
| 383 |
dbgui_col_width("Seq", "8888888")); |
| 384 |
ImGui::TableSetupColumn("PC", ImGuiTableColumnFlags_WidthFixed, |
| 385 |
dbgui_col_width("PC", "88:8888")); |
| 386 |
ImGui::TableSetupColumn("Device", ImGuiTableColumnFlags_WidthFixed, |
| 387 |
dbgui_col_width("Device", "Joystick")); |
| 388 |
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed, |
| 389 |
dbgui_col_width("Addr", "$FFFF")); |
| 390 |
ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, |
| 391 |
dbgui_col_width("Register", "L0_HSCROLL_L")); |
| 392 |
ImGui::TableSetupColumn("R/W", ImGuiTableColumnFlags_WidthFixed, |
| 393 |
dbgui_col_width("R/W", "W")); |
| 394 |
ImGui::TableSetupColumn("Value / event", ImGuiTableColumnFlags_WidthStretch); |
| 395 |
ImGui::TableHeadersRow(); |
| 396 |
|
| 397 |
|
| 398 |
|
| 399 |
const int total = io_trace_count(); |
| 400 |
for (int i = 0; i < total; i++) { |
| 401 |
const io_event_t *evp = io_trace_at(i); |
| 402 |
if (evp == nullptr) { |
| 403 |
continue; |
| 404 |
} |
| 405 |
const io_event_t &ev = *evp; |
| 406 |
if (!event_matches(ev)) { |
| 407 |
continue; |
| 408 |
} |
| 409 |
const bool decoded = (ev.kind == IO_EVENT_DECODED); |
| 410 |
|
| 411 |
ImGui::TableNextRow(); |
| 412 |
ImGui::TableSetColumnIndex(0); |
| 413 |
ImGui::TextColored(COL_DIM, "%u", ev.seq); |
| 414 |
if (ImGui::IsItemHovered()) { |
| 415 |
ImGui::SetTooltip("cycle %u", ev.cycles); |
| 416 |
} |
| 417 |
|
| 418 |
ImGui::TableSetColumnIndex(1); |
| 419 |
ImGui::TextColored(COL_DIM, "%02X:%04X", ev.pc_bank, ev.pc); |
| 420 |
tip("Where the CPU was when this happened."); |
| 421 |
|
| 422 |
ImGui::TableSetColumnIndex(2); |
| 423 |
ImGui::TextUnformatted(io_trace_device_name((io_device_t)ev.device)); |
| 424 |
|
| 425 |
ImGui::TableSetColumnIndex(3); |
| 426 |
if (ev.has_addr) { |
| 427 |
ImGui::Text("$%04X", ev.addr); |
| 428 |
tip(io_reg_purpose(ev.addr)); |
| 429 |
} else { |
| 430 |
ImGui::TextUnformatted("-"); |
| 431 |
} |
| 432 |
|
| 433 |
ImGui::TableSetColumnIndex(4); |
| 434 |
if (ev.has_addr) { |
| 435 |
ImGui::TextColored(COL_DIM, "%s", io_reg_name(ev.addr)); |
| 436 |
tip(io_reg_purpose(ev.addr)); |
| 437 |
} else { |
| 438 |
ImGui::TextUnformatted(" "); |
| 439 |
} |
| 440 |
|
| 441 |
ImGui::TableSetColumnIndex(5); |
| 442 |
if (decoded && !ev.has_addr) { |
| 443 |
ImGui::TextUnformatted(" "); |
| 444 |
} else { |
| 445 |
ImGui::TextColored(ev.is_write ? COL_WRITE : COL_READ, "%s", |
| 446 |
ev.is_write ? "W" : "R"); |
| 447 |
} |
| 448 |
|
| 449 |
ImGui::TableSetColumnIndex(6); |
| 450 |
if (decoded) { |
| 451 |
ImGui::TextColored(COL_DECODED, "%s", ev.text); |
| 452 |
} else { |
| 453 |
ImGui::Text("$%02X", ev.value); |
| 454 |
dbgui_hover_value_tooltip(io_reg_name(ev.addr), ev.value, 1); |
| 455 |
} |
| 456 |
} |
| 457 |
|
| 458 |
if (s_autoscroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 1.0f) { |
| 459 |
ImGui::SetScrollHereY(1.0f); |
| 460 |
} |
| 461 |
ImGui::EndTable(); |
| 462 |
} |
| 463 |
} |
| 464 |
|
| 465 |
|
| 466 |
|
| 467 |
|
| 468 |
|
| 469 |
const char * |
| 470 |
sd_command_name(uint8_t cmd, bool is_acmd) |
| 471 |
{ |
| 472 |
if (is_acmd) { |
| 473 |
return (cmd == 41) ? "ACMD41 SD_SEND_OP_COND" : nullptr; |
| 474 |
} |
| 475 |
switch (cmd) { |
| 476 |
case 0: return "CMD0 GO_IDLE_STATE"; |
| 477 |
case 1: return "CMD1 SEND_OP_COND"; |
| 478 |
case 8: return "CMD8 SEND_IF_COND"; |
| 479 |
case 9: return "CMD9 SEND_CSD"; |
| 480 |
case 10: return "CMD10 SEND_CID"; |
| 481 |
case 12: return "CMD12 STOP_TRANSMISSION"; |
| 482 |
case 16: return "CMD16 SET_BLOCKLEN"; |
| 483 |
case 17: return "CMD17 READ_SINGLE_BLOCK"; |
| 484 |
case 18: return "CMD18 READ_MULTIPLE_BLOCK"; |
| 485 |
case 24: return "CMD24 WRITE_BLOCK"; |
| 486 |
case 25: return "CMD25 WRITE_MULTIPLE_BLOCK"; |
| 487 |
case 55: return "CMD55 APP_CMD"; |
| 488 |
case 58: return "CMD58 READ_OCR"; |
| 489 |
default: return nullptr; |
| 490 |
} |
| 491 |
} |
| 492 |
|
| 493 |
|
| 494 |
|
| 495 |
void |
| 496 |
explain_no_sdcard() |
| 497 |
{ |
| 498 |
ImGui::TextColored(COL_DIM, "No SD card image is attached."); |
| 499 |
ImGui::Spacing(); |
| 500 |
ImGui::TextUnformatted("Attach one with:"); |
| 501 |
ImGui::Indent(); |
| 502 |
ImGui::TextColored(COL_DECODED, "x16emu -sdcard mycard.img"); |
| 503 |
ImGui::Unindent(); |
| 504 |
ImGui::Spacing(); |
| 505 |
ImGui::TextWrapped( |
| 506 |
"-fsroot does NOT attach a card. It sets the host directory the machine sees " |
| 507 |
"when it is using host-filesystem access, which is a different path entirely -- " |
| 508 |
"see the Files tab."); |
| 509 |
ImGui::Spacing(); |
| 510 |
ImGui::TextWrapped( |
| 511 |
"The two are mutually exclusive by default: passing -sdcard turns host-filesystem " |
| 512 |
"access off unless you also pass -hostfsdev to keep it. So a machine either has a " |
| 513 |
"card and talks to it in 512-byte blocks, or has no card and gets its files by " |
| 514 |
"name from the host."); |
| 515 |
} |
| 516 |
|
| 517 |
void |
| 518 |
draw_sdcard_tab() |
| 519 |
{ |
| 520 |
sdcard_debug_state_t sd; |
| 521 |
sdcard_debug_get_state(&sd); |
| 522 |
|
| 523 |
if (!sd.attached) { |
| 524 |
explain_no_sdcard(); |
| 525 |
return; |
| 526 |
} |
| 527 |
|
| 528 |
if (ImGui::BeginTable("sd_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 529 |
ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed, |
| 530 |
dbgui_text_width("Multiblock read ")); |
| 531 |
ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch); |
| 532 |
|
| 533 |
row_kv("Image", "The image file backing the emulated card.", "%s", sd.image_path); |
| 534 |
row_kv("Size", "Size of the image file on disk.", "%lld bytes", |
| 535 |
(long long)sd.image_size); |
| 536 |
|
| 537 |
row_kv("Chip select", |
| 538 |
"SPI slave select. The card only listens while this is asserted, so an idle " |
| 539 |
"line means the machine is not talking to it at all.", |
| 540 |
"%s", sd.selected ? "asserted" : "idle"); |
| 541 |
|
| 542 |
{ |
| 543 |
const char *name = sd_command_name(sd.last_cmd, sd.last_cmd_is_acmd); |
| 544 |
const char *ctip = "The most recent command decoded from the SPI stream. It " |
| 545 |
"persists after the command completes, so this is the last " |
| 546 |
"thing asked of the card, not necessarily something in " |
| 547 |
"progress."; |
| 548 |
if (name != nullptr) { |
| 549 |
row_kv("Last command", ctip, "%s", name); |
| 550 |
} else { |
| 551 |
row_kv("Last command", ctip, "%sCMD%u", sd.last_cmd_is_acmd ? "A" : "", |
| 552 |
sd.last_cmd); |
| 553 |
} |
| 554 |
} |
| 555 |
|
| 556 |
row_begin("Block (LBA)", |
| 557 |
"Logical block address of the last block command. Blocks are 512 bytes, so " |
| 558 |
"the byte offset into the image is this times 512."); |
| 559 |
ImGui::Text("%u", sd.last_lba); |
| 560 |
dbgui_hover_value_tooltip("byte offset", sd.last_lba * 512u, 4); |
| 561 |
row_end(); |
| 562 |
|
| 563 |
row_kv("R1 status", |
| 564 |
"The two R1 response bits this emulator models. \"idle\" means the card is in " |
| 565 |
"its power-up idle state and will refuse data commands; the ROM clears it " |
| 566 |
"during start-up with ACMD41.", |
| 567 |
"idle=%d initialised=%d", sd.is_idle ? 1 : 0, sd.is_initialized ? 1 : 0); |
| 568 |
|
| 569 |
row_kv("Multiblock read", |
| 570 |
"CMD18 streams blocks until CMD12 stops it. While this is in progress the card " |
| 571 |
"keeps sending without being asked again.", |
| 572 |
"%s", sd.ongoing_multiblock_read ? "in progress" : "no"); |
| 573 |
|
| 574 |
row_kv("Response", |
| 575 |
"How far through its reply the card is. A stalled transfer usually shows as a " |
| 576 |
"partial count here.", |
| 577 |
"%d / %d bytes sent", sd.response_counter, sd.response_length); |
| 578 |
|
| 579 |
row_kv("Commands", "Commands decoded since the card was attached.", "%u", sd.commands); |
| 580 |
row_kv("Blocks read", "512-byte blocks read since the card was attached.", |
| 581 |
"%llu (%llu bytes)", (unsigned long long)sd.blocks_read, |
| 582 |
(unsigned long long)sd.bytes_read); |
| 583 |
row_kv("Blocks written", "512-byte blocks written since the card was attached.", |
| 584 |
"%llu (%llu bytes)", (unsigned long long)sd.blocks_written, |
| 585 |
(unsigned long long)sd.bytes_written); |
| 586 |
|
| 587 |
ImGui::EndTable(); |
| 588 |
} |
| 589 |
|
| 590 |
ImGui::Separator(); |
| 591 |
|
| 592 |
heading("Last block resolves to:", |
| 593 |
"What the last block access was, in filesystem terms. Needs the image to have " |
| 594 |
"been indexed -- the button is on Files > SD image."); |
| 595 |
ImGui::SameLine(); |
| 596 |
if (!sdcard_fat_ready()) { |
| 597 |
ImGui::TextColored(COL_DIM, "(image not indexed)"); |
| 598 |
} else { |
| 599 |
const sdcard_fat_file_t *file = nullptr; |
| 600 |
uint64_t offset = 0; |
| 601 |
switch (sdcard_fat_lookup(sd.last_lba, &file, &offset)) { |
| 602 |
case SDCARD_FAT_REGION_FILE: |
| 603 |
case SDCARD_FAT_REGION_DIR: |
| 604 |
if (file != nullptr) { |
| 605 |
ImGui::TextColored(COL_DECODED, "%s + %llu", file->path, |
| 606 |
(unsigned long long)offset); |
| 607 |
} else { |
| 608 |
ImGui::TextColored(COL_DIM, "(unnamed)"); |
| 609 |
} |
| 610 |
break; |
| 611 |
case SDCARD_FAT_REGION_MBR: ImGui::TextColored(COL_DIM, "<MBR>"); break; |
| 612 |
case SDCARD_FAT_REGION_RESERVED: ImGui::TextColored(COL_DIM, "<boot/reserved>"); break; |
| 613 |
case SDCARD_FAT_REGION_FAT: ImGui::TextColored(COL_DIM, "<FAT>"); break; |
| 614 |
case SDCARD_FAT_REGION_ROOTDIR: ImGui::TextColored(COL_DIM, "<root directory>"); break; |
| 615 |
case SDCARD_FAT_REGION_FREE: ImGui::TextColored(COL_DIM, "<unallocated>"); break; |
| 616 |
default: ImGui::TextColored(COL_DIM, "<unknown>"); break; |
| 617 |
} |
| 618 |
} |
| 619 |
|
| 620 |
ImGui::Separator(); |
| 621 |
vera_spi_debug_state_t spi; |
| 622 |
vera_spi_debug_get_state(&spi); |
| 623 |
heading("SPI", |
| 624 |
"The VERA SPI port at $9F3E/$9F3F -- the only wire to the card. \"out\" is the " |
| 625 |
"byte being clocked to the card, \"in\" the byte that came back."); |
| 626 |
ImGui::SameLine(); |
| 627 |
ImGui::Text("select=%d busy=%d auto-tx=%d out=$%02X in=$%02X", spi.ss ? 1 : 0, |
| 628 |
spi.busy ? 1 : 0, spi.autotx ? 1 : 0, spi.sending_byte, spi.received_byte); |
| 629 |
tip("The VERA SPI port at $9F3E/$9F3F -- the only wire to the card."); |
| 630 |
|
| 631 |
ImGui::Spacing(); |
| 632 |
ImGui::TextColored(COL_DIM, "This tab is the card as a device -- commands, blocks and " |
| 633 |
"status. For the files inside the image, see Files > SD image."); |
| 634 |
} |
| 635 |
|
| 636 |
|
| 637 |
|
| 638 |
|
| 639 |
|
| 640 |
char s_file_filter[64] = ""; |
| 641 |
|
| 642 |
const char * |
| 643 |
ieee_op_label(uint8_t kind) |
| 644 |
{ |
| 645 |
switch (kind) { |
| 646 |
case IEEE_OP_OPEN: return "open"; |
| 647 |
case IEEE_OP_OPEN_FAILED: return "open FAILED"; |
| 648 |
case IEEE_OP_CLOSE: return "close"; |
| 649 |
case IEEE_OP_DIR: return "dir"; |
| 650 |
case IEEE_OP_COMMAND: return "command"; |
| 651 |
case IEEE_OP_STATUS: return "status"; |
| 652 |
default: return "?"; |
| 653 |
} |
| 654 |
} |
| 655 |
|
| 656 |
const char * |
| 657 |
ieee_op_tip(uint8_t kind) |
| 658 |
{ |
| 659 |
switch (kind) { |
| 660 |
case IEEE_OP_OPEN: |
| 661 |
return "A file was opened successfully on this channel."; |
| 662 |
case IEEE_OP_OPEN_FAILED: |
| 663 |
return "An open was attempted and refused. The Status column says why."; |
| 664 |
case IEEE_OP_CLOSE: |
| 665 |
return "A channel was closed. The byte counts are the totals moved while it " |
| 666 |
"was open."; |
| 667 |
case IEEE_OP_DIR: |
| 668 |
return "A directory listing was opened. \"$\" is the pseudo-file the drive " |
| 669 |
"serves a directory from -- this is what LOAD\"$\",8 does."; |
| 670 |
case IEEE_OP_COMMAND: |
| 671 |
return "A DOS command was sent to the command channel (15). The Meaning column " |
| 672 |
"says what it asks for."; |
| 673 |
case IEEE_OP_STATUS: |
| 674 |
return "A DOS status that no single operation accounted for -- something set an " |
| 675 |
"error outside an open or a command."; |
| 676 |
default: |
| 677 |
return ""; |
| 678 |
} |
| 679 |
} |
| 680 |
|
| 681 |
|
| 682 |
|
| 683 |
|
| 684 |
void |
| 685 |
ieee_meaning(const ieee_history_entry_t &h, char *out, size_t outsz) |
| 686 |
{ |
| 687 |
out[0] = '\0'; |
| 688 |
switch (h.kind) { |
| 689 |
case IEEE_OP_COMMAND: |
| 690 |
snprintf(out, outsz, "%s", io_dos_command_meaning(h.name)); |
| 691 |
break; |
| 692 |
case IEEE_OP_OPEN: |
| 693 |
case IEEE_OP_OPEN_FAILED: { |
| 694 |
const char *mode = h.read && h.write ? "read and write" |
| 695 |
: h.write ? "write" |
| 696 |
: "read"; |
| 697 |
|
| 698 |
|
| 699 |
snprintf(out, outsz, "%sopen for %s", (h.name[0] == '@') ? "overwrite, " : "", |
| 700 |
mode); |
| 701 |
break; |
| 702 |
} |
| 703 |
case IEEE_OP_DIR: |
| 704 |
snprintf(out, outsz, "read the directory"); |
| 705 |
break; |
| 706 |
case IEEE_OP_STATUS: |
| 707 |
snprintf(out, outsz, "not tied to one operation"); |
| 708 |
break; |
| 709 |
default: |
| 710 |
break; |
| 711 |
} |
| 712 |
} |
| 713 |
|
| 714 |
void |
| 715 |
draw_host_history(const ieee_debug_state_t &ie) |
| 716 |
{ |
| 717 |
if (ie.history_count == 0) { |
| 718 |
ImGui::TextColored(COL_DIM, "Nothing yet. Try LOAD\"$\",8,1 or a DOS command such as @$."); |
| 719 |
return; |
| 720 |
} |
| 721 |
|
| 722 |
if (!ImGui::BeginTable("host_hist", 7, |
| 723 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | |
| 724 |
ImGuiTableFlags_BordersInnerV | DBGUI_TABLE_FLAGS_RESIZABLE, |
| 725 |
ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 11))) { |
| 726 |
return; |
| 727 |
} |
| 728 |
|
| 729 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 730 |
ImGui::TableSetupColumn("What", ImGuiTableColumnFlags_WidthFixed, |
| 731 |
dbgui_col_width("What", "open FAILED")); |
| 732 |
ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed, dbgui_col_width("Ch", "88")); |
| 733 |
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); |
| 734 |
ImGui::TableSetupColumn("Meaning", ImGuiTableColumnFlags_WidthFixed, |
| 735 |
dbgui_col_width("Meaning", "overwrite, open for read and write")); |
| 736 |
ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed, |
| 737 |
dbgui_col_width("Read", "8888888")); |
| 738 |
ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed, |
| 739 |
dbgui_col_width("Written", "8888888")); |
| 740 |
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, |
| 741 |
dbgui_col_width("Status", "62,FILE NOT FOUND,00,00")); |
| 742 |
ImGui::TableHeadersRow(); |
| 743 |
|
| 744 |
|
| 745 |
|
| 746 |
|
| 747 |
for (int i = ie.history_count - 1; i >= 0; i--) { |
| 748 |
const ieee_history_entry_t &h = ie.history[i]; |
| 749 |
const bool failed = (h.kind == IEEE_OP_OPEN_FAILED) || (h.kind == IEEE_OP_STATUS); |
| 750 |
|
| 751 |
ImGui::PushID(i); |
| 752 |
ImGui::TableNextRow(); |
| 753 |
|
| 754 |
ImGui::TableSetColumnIndex(0); |
| 755 |
ImGui::TextColored(failed ? COL_BAD : COL_DECODED, "%s", ieee_op_label(h.kind)); |
| 756 |
tip(ieee_op_tip(h.kind)); |
| 757 |
|
| 758 |
ImGui::TableSetColumnIndex(1); |
| 759 |
if (h.channel >= 0) { |
| 760 |
ImGui::Text("%d", h.channel); |
| 761 |
tip(h.channel == 15 ? "Channel 15 is the command and status channel -- commands " |
| 762 |
"go in and DOS status comes back." |
| 763 |
: "The logical channel the machine used, as in OPEN 2,8,2."); |
| 764 |
} else { |
| 765 |
ImGui::TextDisabled("-"); |
| 766 |
tip("Not tied to one channel."); |
| 767 |
} |
| 768 |
|
| 769 |
ImGui::TableSetColumnIndex(2); |
| 770 |
ImGui::TextUnformatted(h.name); |
| 771 |
if (h.name[0] != '\0') { |
| 772 |
tip(h.name[0] == '@' |
| 773 |
? "The name as the machine gave it. A leading @ asks to replace an " |
| 774 |
"existing file; a leading : separates the path from the filename." |
| 775 |
: "The name as the machine gave it, before the emulator resolved it " |
| 776 |
"against the host directory."); |
| 777 |
} |
| 778 |
|
| 779 |
ImGui::TableSetColumnIndex(3); |
| 780 |
{ |
| 781 |
char meaning[64]; |
| 782 |
ieee_meaning(h, meaning, sizeof(meaning)); |
| 783 |
if (meaning[0] != '\0') { |
| 784 |
ImGui::TextColored(COL_DIM, "%s", meaning); |
| 785 |
tip(ieee_op_tip(h.kind)); |
| 786 |
} |
| 787 |
} |
| 788 |
|
| 789 |
ImGui::TableSetColumnIndex(4); |
| 790 |
if (h.kind == IEEE_OP_CLOSE) { |
| 791 |
ImGui::Text("%u", h.bytes_read); |
| 792 |
tip("Bytes the machine read from this file while it was open."); |
| 793 |
} |
| 794 |
|
| 795 |
ImGui::TableSetColumnIndex(5); |
| 796 |
if (h.kind == IEEE_OP_CLOSE) { |
| 797 |
ImGui::Text("%u", h.bytes_written); |
| 798 |
tip("Bytes the machine wrote to this file while it was open."); |
| 799 |
} |
| 800 |
|
| 801 |
ImGui::TableSetColumnIndex(6); |
| 802 |
if (h.status[0] != '\0') { |
| 803 |
const bool ok = (h.status[0] == '0' && h.status[1] == '0'); |
| 804 |
ImGui::TextColored(ok ? COL_DIM : COL_BAD, "%s", h.status); |
| 805 |
tip("The DOS status this operation ended with, as code,text,track,sector. " |
| 806 |
"\"00,OK\" is success; 62 is FILE NOT FOUND, 63 FILE EXISTS, 26 WRITE " |
| 807 |
"PROTECT ON."); |
| 808 |
} else { |
| 809 |
ImGui::TextDisabled("-"); |
| 810 |
tip("This operation does not report a status of its own. A close always " |
| 811 |
"succeeds once the file is open, so DOS has nothing to say about it."); |
| 812 |
} |
| 813 |
|
| 814 |
ImGui::PopID(); |
| 815 |
} |
| 816 |
ImGui::EndTable(); |
| 817 |
} |
| 818 |
|
| 819 |
void |
| 820 |
draw_host_files() |
| 821 |
{ |
| 822 |
ieee_debug_state_t ie; |
| 823 |
ieee_debug_get_state(&ie); |
| 824 |
|
| 825 |
if (!ie.using_hostfs) { |
| 826 |
ImGui::TextColored(COL_DIM, "Host-filesystem access is off."); |
| 827 |
ImGui::Spacing(); |
| 828 |
ImGui::TextWrapped( |
| 829 |
"The machine is using the attached SD card image instead -- passing -sdcard " |
| 830 |
"turns host access off by default. Its file activity is block traffic rather " |
| 831 |
"than named files; see the SD image sub-tab."); |
| 832 |
ImGui::Spacing(); |
| 833 |
ImGui::TextWrapped("Pass -hostfsdev <unit> alongside -sdcard to keep both available."); |
| 834 |
return; |
| 835 |
} |
| 836 |
|
| 837 |
ImGui::Text("Unit %d", ie.ieee_unit); |
| 838 |
tip("The IEC device number the host filesystem answers as. LOAD\"X\",8 talks to unit 8. " |
| 839 |
"Change it with -hostfsdev."); |
| 840 |
ImGui::SameLine(); |
| 841 |
ImGui::TextColored(COL_DIM, "\xc2\xb7 %s", |
| 842 |
ie.hostfscwd[0] != '\0' ? ie.hostfscwd : "(no root set)"); |
| 843 |
tip("The host directory the machine currently sees as its own. Set the starting point " |
| 844 |
"with -fsroot."); |
| 845 |
|
| 846 |
ImGui::Text("Bus: %s%s%s", ie.listening ? "LISTENING " : "", ie.talking ? "TALKING " : "", |
| 847 |
(!ie.listening && !ie.talking) ? "idle" : ""); |
| 848 |
tip("IEC protocol state. LISTENING means the machine is sending to the drive, TALKING " |
| 849 |
"that it is receiving. Both are transient -- an idle bus between operations is " |
| 850 |
"normal, not a fault."); |
| 851 |
|
| 852 |
if (ie.error_str[0] != '\0') { |
| 853 |
ImGui::TextUnformatted("Status:"); |
| 854 |
tip("The drive's current status line -- what reading channel 15 would return now."); |
| 855 |
ImGui::SameLine(); |
| 856 |
|
| 857 |
|
| 858 |
const bool ok = (ie.error_str[0] == '0' && ie.error_str[1] == '0'); |
| 859 |
ImGui::TextColored(ok ? COL_DECODED : COL_BAD, "%s", ie.error_str); |
| 860 |
tip("Format is code,text,track,sector. \"00,OK\" means the last operation " |
| 861 |
"succeeded. This is the CURRENT status; the history below keeps the status " |
| 862 |
"each individual operation ended with."); |
| 863 |
} |
| 864 |
|
| 865 |
ImGui::Separator(); |
| 866 |
heading("Recent activity", |
| 867 |
"Completed file operations, newest first. Kept whether or not I/O capture is on " |
| 868 |
"-- file operations are rare enough to record for free.\n\n" |
| 869 |
"This exists because a file operation erases itself when it finishes: the " |
| 870 |
"channel list below only shows what is open RIGHT NOW, and a directory listing " |
| 871 |
"or a failed open is over within a millisecond. Without this history, doing @$ " |
| 872 |
"or opening a missing file appeared to do nothing at all."); |
| 873 |
draw_host_history(ie); |
| 874 |
|
| 875 |
ImGui::Separator(); |
| 876 |
heading("Open channels", |
| 877 |
"What is open right now. Usually empty -- the machine opens a file, reads it and " |
| 878 |
"closes it faster than anyone can look. The history above is the useful view."); |
| 879 |
|
| 880 |
bool any = false; |
| 881 |
for (int i = 0; i < 16; i++) { |
| 882 |
if (ie.channels[i].is_open || ie.channels[i].name[0] != '\0') { |
| 883 |
any = true; |
| 884 |
break; |
| 885 |
} |
| 886 |
} |
| 887 |
if (!any) { |
| 888 |
ImGui::TextColored(COL_DIM, "None open."); |
| 889 |
return; |
| 890 |
} |
| 891 |
|
| 892 |
if (ImGui::BeginTable("host_files", 5, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 893 |
ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed, |
| 894 |
dbgui_col_width("Ch", "88")); |
| 895 |
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); |
| 896 |
ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed, |
| 897 |
dbgui_col_width("Mode", "read/write")); |
| 898 |
ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed, |
| 899 |
dbgui_col_width("Read", "888888888")); |
| 900 |
ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed, |
| 901 |
dbgui_col_width("Written", "888888888")); |
| 902 |
ImGui::TableHeadersRow(); |
| 903 |
|
| 904 |
for (int i = 0; i < 16; i++) { |
| 905 |
const ieee_channel_debug_t &c = ie.channels[i]; |
| 906 |
if (!c.is_open && c.name[0] == '\0') { |
| 907 |
continue; |
| 908 |
} |
| 909 |
ImGui::TableNextRow(); |
| 910 |
ImGui::TableSetColumnIndex(0); |
| 911 |
ImGui::Text("%d", i); |
| 912 |
ImGui::TableSetColumnIndex(1); |
| 913 |
ImGui::TextColored(c.is_open ? COL_DECODED : COL_DIM, "%s", |
| 914 |
c.name[0] != '\0' ? c.name : "(closing)"); |
| 915 |
ImGui::TableSetColumnIndex(2); |
| 916 |
ImGui::TextUnformatted(c.read && c.write ? "read/write" |
| 917 |
: c.read ? "read" |
| 918 |
: c.write ? "write" |
| 919 |
: "-"); |
| 920 |
ImGui::TableSetColumnIndex(3); |
| 921 |
ImGui::Text("%u", c.bytes_read); |
| 922 |
ImGui::TableSetColumnIndex(4); |
| 923 |
ImGui::Text("%u", c.bytes_written); |
| 924 |
} |
| 925 |
ImGui::EndTable(); |
| 926 |
} |
| 927 |
} |
| 928 |
|
| 929 |
void |
| 930 |
draw_sd_files() |
| 931 |
{ |
| 932 |
sdcard_debug_state_t sd; |
| 933 |
sdcard_debug_get_state(&sd); |
| 934 |
|
| 935 |
if (!sd.attached) { |
| 936 |
explain_no_sdcard(); |
| 937 |
return; |
| 938 |
} |
| 939 |
|
| 940 |
ImGui::TextWrapped( |
| 941 |
"The emulator only ever sees 512-byte blocks here -- the filesystem inside the " |
| 942 |
"image is parsed by the ROM running on the emulated machine, not by us. To put " |
| 943 |
"names to that traffic the emulator parses the image itself:"); |
| 944 |
ImGui::TextColored(COL_DIM, "This sub-tab is the files inside the image. The SD Card tab " |
| 945 |
"is the same card as a device -- commands, blocks and status."); |
| 946 |
|
| 947 |
if (ImGui::SmallButton(sdcard_fat_ready() ? "Rebuild index" : "Build index")) { |
| 948 |
sdcard_reindex(); |
| 949 |
} |
| 950 |
tip("Read the image's partition table, boot sector, FATs and directory tree, and map " |
| 951 |
"every cluster back to a path."); |
| 952 |
|
| 953 |
if (!sdcard_fat_ready()) { |
| 954 |
ImGui::SameLine(); |
| 955 |
ImGui::TextColored(COL_DIM, "not indexed"); |
| 956 |
ImGui::Spacing(); |
| 957 |
ImGui::TextWrapped("Only FAT16 and FAT32 images can be indexed. If the button leaves " |
| 958 |
"this saying \"not indexed\", the image is neither."); |
| 959 |
return; |
| 960 |
} |
| 961 |
|
| 962 |
ImGui::SameLine(); |
| 963 |
ImGui::TextColored(COL_DIM, "%s \xc2\xb7 %u bytes/cluster \xc2\xb7 %d entries", |
| 964 |
sdcard_fat_type_name(), sdcard_fat_bytes_per_cluster(), |
| 965 |
sdcard_fat_file_count()); |
| 966 |
|
| 967 |
if (sdcard_fat_is_stale()) { |
| 968 |
ImGui::SameLine(); |
| 969 |
ImGui::TextColored(COL_WRITE, "\xc2\xb7 STALE"); |
| 970 |
tip("The machine wrote to the FAT or a directory since this index was built, so " |
| 971 |
"names and extents may no longer be right. Rebuild to refresh.\n\n" |
| 972 |
"The index is never refreshed automatically: quietly serving a filename that " |
| 973 |
"had since become wrong would be worse than saying so."); |
| 974 |
} |
| 975 |
if (sdcard_fat_truncated()) { |
| 976 |
ImGui::SameLine(); |
| 977 |
ImGui::TextColored(COL_WRITE, "\xc2\xb7 TRUNCATED"); |
| 978 |
tip("The image holds more files than the index will hold. What is listed is " |
| 979 |
"correct; it is just not everything."); |
| 980 |
} |
| 981 |
|
| 982 |
ImGui::SetNextItemWidth(dbgui_field_width("wwwwwwwwwwwwwwww")); |
| 983 |
ImGui::InputTextWithHint("##sdfilter", "filter", s_file_filter, sizeof(s_file_filter)); |
| 984 |
tip("Show only paths containing this text."); |
| 985 |
ImGui::SameLine(); |
| 986 |
ImGui::TextColored(COL_DIM, "files the machine has touched are listed first, in green"); |
| 987 |
|
| 988 |
if (ImGui::BeginTable("sd_files", 4, |
| 989 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | |
| 990 |
DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 991 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 992 |
ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch); |
| 993 |
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, |
| 994 |
dbgui_col_width("Size", "888888888")); |
| 995 |
ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed, |
| 996 |
dbgui_col_width("Read", "888888888")); |
| 997 |
ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed, |
| 998 |
dbgui_col_width("Written", "888888888")); |
| 999 |
ImGui::TableHeadersRow(); |
| 1000 |
|
| 1001 |
|
| 1002 |
|
| 1003 |
|
| 1004 |
|
| 1005 |
const int n = sdcard_fat_file_count(); |
| 1006 |
for (int pass = 0; pass < 2; pass++) { |
| 1007 |
for (int i = 0; i < n; i++) { |
| 1008 |
const sdcard_fat_file_t *f = sdcard_fat_file_at(i); |
| 1009 |
if (f == nullptr || f->path == nullptr) { |
| 1010 |
continue; |
| 1011 |
} |
| 1012 |
const bool touched = (f->bytes_read > 0 || f->bytes_written > 0); |
| 1013 |
if (touched != (pass == 0)) { |
| 1014 |
continue; |
| 1015 |
} |
| 1016 |
if (s_file_filter[0] != '\0' && strstr(f->path, s_file_filter) == nullptr) { |
| 1017 |
continue; |
| 1018 |
} |
| 1019 |
|
| 1020 |
ImGui::TableNextRow(); |
| 1021 |
ImGui::TableSetColumnIndex(0); |
| 1022 |
if (touched) { |
| 1023 |
ImGui::TextColored(COL_DECODED, "%s", f->path); |
| 1024 |
} else { |
| 1025 |
ImGui::TextColored(f->is_dir ? COL_DIM |
| 1026 |
: ImGui::GetStyle().Colors[ImGuiCol_Text], |
| 1027 |
"%s", f->path); |
| 1028 |
} |
| 1029 |
ImGui::TableSetColumnIndex(1); |
| 1030 |
if (f->is_dir) { |
| 1031 |
ImGui::TextColored(COL_DIM, "<dir>"); |
| 1032 |
} else { |
| 1033 |
ImGui::Text("%u", f->size); |
| 1034 |
} |
| 1035 |
ImGui::TableSetColumnIndex(2); |
| 1036 |
ImGui::Text("%llu", (unsigned long long)f->bytes_read); |
| 1037 |
ImGui::TableSetColumnIndex(3); |
| 1038 |
ImGui::Text("%llu", (unsigned long long)f->bytes_written); |
| 1039 |
} |
| 1040 |
} |
| 1041 |
ImGui::EndTable(); |
| 1042 |
} |
| 1043 |
} |
| 1044 |
|
| 1045 |
void |
| 1046 |
draw_files_tab() |
| 1047 |
{ |
| 1048 |
|
| 1049 |
|
| 1050 |
|
| 1051 |
if (ImGui::BeginTabBar("files_sub")) { |
| 1052 |
if (ImGui::BeginTabItem("Host filesystem")) { |
| 1053 |
draw_host_files(); |
| 1054 |
ImGui::EndTabItem(); |
| 1055 |
} |
| 1056 |
if (ImGui::BeginTabItem("SD image")) { |
| 1057 |
draw_sd_files(); |
| 1058 |
ImGui::EndTabItem(); |
| 1059 |
} |
| 1060 |
ImGui::EndTabBar(); |
| 1061 |
} |
| 1062 |
} |
| 1063 |
|
| 1064 |
|
| 1065 |
|
| 1066 |
|
| 1067 |
|
| 1068 |
struct JoyBit { |
| 1069 |
uint16_t mask; |
| 1070 |
const char *name; |
| 1071 |
}; |
| 1072 |
|
| 1073 |
const JoyBit JOY_DPAD[] = {{JOY_BIT_DPAD_UP, "Up"}, |
| 1074 |
{JOY_BIT_DPAD_DOWN, "Down"}, |
| 1075 |
{JOY_BIT_DPAD_LEFT, "Left"}, |
| 1076 |
{JOY_BIT_DPAD_RIGHT, "Right"}}; |
| 1077 |
const JoyBit JOY_FACE[] = {{JOY_BIT_A, "A"}, |
| 1078 |
{JOY_BIT_B, "B"}, |
| 1079 |
{JOY_BIT_X, "X"}, |
| 1080 |
{JOY_BIT_Y, "Y"}}; |
| 1081 |
const JoyBit JOY_OTHER[] = {{JOY_BIT_LEFT_SHOULDER, "L"}, |
| 1082 |
{JOY_BIT_RIGHT_SHOULDER, "R"}, |
| 1083 |
{JOY_BIT_SELECT, "Select"}, |
| 1084 |
{JOY_BIT_START, "Start"}}; |
| 1085 |
|
| 1086 |
|
| 1087 |
|
| 1088 |
void |
| 1089 |
draw_button_group(const char *label, const JoyBit *bits, size_t count, uint16_t mask) |
| 1090 |
{ |
| 1091 |
ImGui::TextColored(COL_DIM, "%-6s", label); |
| 1092 |
for (size_t b = 0; b < count; b++) { |
| 1093 |
ImGui::SameLine(); |
| 1094 |
if ((mask & bits[b].mask) == 0) { |
| 1095 |
ImGui::TextColored(COL_ON, "[%s]", bits[b].name); |
| 1096 |
} else { |
| 1097 |
ImGui::TextDisabled("%s", bits[b].name); |
| 1098 |
} |
| 1099 |
} |
| 1100 |
} |
| 1101 |
|
| 1102 |
void |
| 1103 |
draw_joystick_tab() |
| 1104 |
{ |
| 1105 |
joystick_debug_state_t js; |
| 1106 |
joystick_debug_get_state(&js); |
| 1107 |
|
| 1108 |
ImGui::TextUnformatted("How the machine reads a controller"); |
| 1109 |
help("The controllers are shift registers, not a parallel port.\n\n" |
| 1110 |
"The VIA asserts NESLATCH (PA2), which snapshots every controller's buttons at " |
| 1111 |
"once. It then pulses NESCLK (PA3) sixteen times, and each pulse shifts the next " |
| 1112 |
"button bit onto that slot's data line -- NESDAT0..3 on PA7..PA4. All four " |
| 1113 |
"controllers are read in parallel, one bit at a time.\n\n" |
| 1114 |
"That is why the raw byte at $9F01 is hard to read directly: it holds one bit of " |
| 1115 |
"four different controllers, and which bit depends on how far through the shift " |
| 1116 |
"sequence you are. The decoded buttons below are the useful view."); |
| 1117 |
|
| 1118 |
ImGui::Spacing(); |
| 1119 |
ImGui::TextUnformatted("Latch (PA2)"); |
| 1120 |
ImGui::SameLine(); |
| 1121 |
lamp(js.latch, js.latch ? "asserted -- sampling now" : "idle"); |
| 1122 |
help("While asserted, the controllers reload their shift registers from the live " |
| 1123 |
"button state. A game pulses this once per frame."); |
| 1124 |
|
| 1125 |
ImGui::Text("Controller data bits: $%02X", js.data); |
| 1126 |
help("The joystick contribution to VIA1 port A -- bits 7-4, one per controller slot, " |
| 1127 |
"carrying whichever bit is currently being shifted out. Bits 3-0 are always zero " |
| 1128 |
"here; the I2C lines that share port A are merged in separately when the CPU " |
| 1129 |
"actually reads $9F01, so this is not the whole byte the CPU sees."); |
| 1130 |
|
| 1131 |
ImGui::Separator(); |
| 1132 |
|
| 1133 |
for (int i = 0; i < NUM_JOYSTICKS; i++) { |
| 1134 |
const joystick_slot_debug_t &s = js.slots[i]; |
| 1135 |
ImGui::PushID(i); |
| 1136 |
|
| 1137 |
ImGui::Text("Slot %d", i); |
| 1138 |
ImGui::SameLine(); |
| 1139 |
if (!s.enabled) { |
| 1140 |
ImGui::TextDisabled("disabled"); |
| 1141 |
help("Not enabled. Enable a slot with -joy1 .. -joy4 on the command line. A " |
| 1142 |
"disabled slot reads to the machine as \"nothing pressed\"."); |
| 1143 |
} else if (s.virtual_active) { |
| 1144 |
ImGui::TextColored(COL_ON, "driven by a debug client"); |
| 1145 |
help("A DAP client is holding this controller through the x16/joystick request, " |
| 1146 |
"which needs no host gamepad. It overrides any gamepad bound to the slot " |
| 1147 |
"until the client releases it."); |
| 1148 |
} else if (!s.controller_bound) { |
| 1149 |
ImGui::TextColored(COL_DIM, "enabled, no gamepad connected"); |
| 1150 |
help("The slot exists as far as the machine is concerned, but no host gamepad " |
| 1151 |
"is bound to it, so it reports nothing pressed."); |
| 1152 |
} else { |
| 1153 |
ImGui::TextColored(COL_ON, "connected"); |
| 1154 |
} |
| 1155 |
|
| 1156 |
if (s.enabled) { |
| 1157 |
ImGui::Indent(); |
| 1158 |
draw_button_group("D-pad", JOY_DPAD, sizeof(JOY_DPAD) / sizeof(JOY_DPAD[0]), |
| 1159 |
s.button_mask); |
| 1160 |
draw_button_group("Face", JOY_FACE, sizeof(JOY_FACE) / sizeof(JOY_FACE[0]), |
| 1161 |
s.button_mask); |
| 1162 |
draw_button_group("Other", JOY_OTHER, sizeof(JOY_OTHER) / sizeof(JOY_OTHER[0]), |
| 1163 |
s.button_mask); |
| 1164 |
|
| 1165 |
ImGui::TextColored(COL_DIM, "raw $%04X shifting $%04X", s.button_mask, |
| 1166 |
s.shift_mask); |
| 1167 |
help("Raw values, for when you are debugging the read routine rather than the " |
| 1168 |
"input.\n\n" |
| 1169 |
"\"raw\" is the latched button state, and a pressed button is a ZERO bit " |
| 1170 |
"because the wire is active-low -- so an idle pad reads $FFFF.\n\n" |
| 1171 |
"\"shifting\" is what remains to be clocked out. It advances on every " |
| 1172 |
"NESCLK pulse, so part-way through a read it will not match the raw value."); |
| 1173 |
ImGui::Unindent(); |
| 1174 |
} |
| 1175 |
|
| 1176 |
ImGui::PopID(); |
| 1177 |
ImGui::Separator(); |
| 1178 |
} |
| 1179 |
} |
| 1180 |
|
| 1181 |
|
| 1182 |
|
| 1183 |
|
| 1184 |
|
| 1185 |
void |
| 1186 |
draw_via_ports(int which, const via_debug_state_t &v) |
| 1187 |
{ |
| 1188 |
|
| 1189 |
|
| 1190 |
|
| 1191 |
ImGui::TextUnformatted("Port pins"); |
| 1192 |
help("What each bit of each port is physically connected to, and whether the VIA is " |
| 1193 |
"currently driving it (output) or reading it (input) -- which is what the data " |
| 1194 |
"direction register decides.\n\n" |
| 1195 |
"Level is shown only for output pins. An input pin's level is composed at read " |
| 1196 |
"time from whatever device is on the other end, so the register file the debugger " |
| 1197 |
"snapshots does not hold it."); |
| 1198 |
|
| 1199 |
if (ImGui::BeginTable("via_pins", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1200 |
ImGui::TableSetupColumn("Pin", ImGuiTableColumnFlags_WidthFixed, |
| 1201 |
dbgui_col_width("Pin", "PA0")); |
| 1202 |
ImGui::TableSetupColumn("Dir", ImGuiTableColumnFlags_WidthFixed, |
| 1203 |
dbgui_col_width("Dir", "output")); |
| 1204 |
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, |
| 1205 |
dbgui_col_width("Level", "0")); |
| 1206 |
ImGui::TableSetupColumn("Signal", ImGuiTableColumnFlags_WidthStretch); |
| 1207 |
ImGui::TableHeadersRow(); |
| 1208 |
|
| 1209 |
const struct { |
| 1210 |
char port; |
| 1211 |
uint8_t data; |
| 1212 |
uint8_t ddr; |
| 1213 |
} ports[2] = {{'A', v.regs[1], v.regs[3]}, {'B', v.regs[0], v.regs[2]}}; |
| 1214 |
|
| 1215 |
for (int p = 0; p < 2; p++) { |
| 1216 |
for (int bit = 7; bit >= 0; bit--) { |
| 1217 |
const bool is_out = (ports[p].ddr >> bit) & 1; |
| 1218 |
const bool level = (ports[p].data >> bit) & 1; |
| 1219 |
|
| 1220 |
ImGui::TableNextRow(); |
| 1221 |
ImGui::TableSetColumnIndex(0); |
| 1222 |
ImGui::Text("P%c%d", ports[p].port, bit); |
| 1223 |
ImGui::TableSetColumnIndex(1); |
| 1224 |
ImGui::TextColored(COL_DIM, "%s", is_out ? "output" : "input"); |
| 1225 |
ImGui::TableSetColumnIndex(2); |
| 1226 |
if (is_out) { |
| 1227 |
ImGui::TextColored(level ? COL_ON : COL_DIM, "%d", level ? 1 : 0); |
| 1228 |
} else { |
| 1229 |
|
| 1230 |
|
| 1231 |
|
| 1232 |
|
| 1233 |
|
| 1234 |
ImGui::TextDisabled("-"); |
| 1235 |
} |
| 1236 |
ImGui::TableSetColumnIndex(3); |
| 1237 |
ImGui::TextUnformatted(io_via_pin_name(which, ports[p].port, bit)); |
| 1238 |
} |
| 1239 |
} |
| 1240 |
ImGui::EndTable(); |
| 1241 |
} |
| 1242 |
} |
| 1243 |
|
| 1244 |
void |
| 1245 |
draw_one_via(int which, const char *name) |
| 1246 |
{ |
| 1247 |
|
| 1248 |
|
| 1249 |
const bool fitted = (which == 0) || has_via2; |
| 1250 |
if (!fitted) { |
| 1251 |
ImGui::TextColored(COL_DIM, "%s is not fitted.", name); |
| 1252 |
ImGui::Spacing(); |
| 1253 |
ImGui::TextWrapped("The second VIA is optional on the X16 and drives the user port. " |
| 1254 |
"This emulator wires its pins to nothing, so it is only useful " |
| 1255 |
"for exercising the timers and interrupts."); |
| 1256 |
return; |
| 1257 |
} |
| 1258 |
|
| 1259 |
via_debug_state_t v; |
| 1260 |
via_debug_get_state(which, &v); |
| 1261 |
|
| 1262 |
const uint8_t acr = v.regs[0x0b]; |
| 1263 |
const uint8_t ifr = v.regs[0x0d]; |
| 1264 |
const uint8_t ier = v.regs[0x0e]; |
| 1265 |
|
| 1266 |
ImGui::TextUnformatted("Timers"); |
| 1267 |
help("Two 16-bit counters running down at the system clock. They are the usual source " |
| 1268 |
"of periodic interrupts on this machine."); |
| 1269 |
|
| 1270 |
ImGui::Indent(); |
| 1271 |
ImGui::Text("T1 %5u %s", v.timer_count[0], v.timer_running[0] ? "running" : "stopped"); |
| 1272 |
ImGui::SameLine(); |
| 1273 |
ImGui::TextColored(COL_DIM, "reload %u \xc2\xb7 %s", v.timer_latch[0], |
| 1274 |
(acr & 0x40) ? "free-running" : "one-shot"); |
| 1275 |
help("Timer 1. In one-shot mode it counts down once, raises its interrupt flag and " |
| 1276 |
"stops. In free-running mode it reloads from the latch and repeats, which is how " |
| 1277 |
"a steady interrupt tick is produced. ACR bit 6 chooses between them.\n\n" |
| 1278 |
"Writing T1C_H starts it; writing T1L_H only changes the reload value."); |
| 1279 |
|
| 1280 |
ImGui::Text("T2 %5u %s", v.timer_count[1], v.timer_running[1] ? "running" : "stopped"); |
| 1281 |
ImGui::SameLine(); |
| 1282 |
ImGui::TextColored(COL_DIM, "reload %u \xc2\xb7 %s", v.timer_latch[1], |
| 1283 |
(acr & 0x20) ? "counting PB6 pulses" : "one-shot"); |
| 1284 |
help("Timer 2 is one-shot only. ACR bit 5 switches it from counting clock cycles to " |
| 1285 |
"counting falling edges on PB6, which is how external pulses get measured."); |
| 1286 |
|
| 1287 |
ImGui::TextUnformatted("PB7 output"); |
| 1288 |
ImGui::SameLine(); |
| 1289 |
if (acr & 0x80) { |
| 1290 |
lamp(v.pb7_output, v.pb7_output ? "high" : "low"); |
| 1291 |
} else { |
| 1292 |
ImGui::TextDisabled("disabled"); |
| 1293 |
} |
| 1294 |
help("With ACR bit 7 set, timer 1 toggles pin PB7 on every underflow, turning the timer " |
| 1295 |
"into a square-wave generator that needs no CPU attention at all. With it clear, " |
| 1296 |
"PB7 is an ordinary port pin."); |
| 1297 |
ImGui::Unindent(); |
| 1298 |
|
| 1299 |
ImGui::Separator(); |
| 1300 |
ImGui::TextUnformatted("Interrupts"); |
| 1301 |
help("A source raises its flag in IFR. That only pulls the CPU's IRQ line if the same " |
| 1302 |
"bit is also enabled in IER -- so a set flag on its own does not mean an interrupt " |
| 1303 |
"fired. Look for a bit that is set in both columns."); |
| 1304 |
|
| 1305 |
if (ImGui::BeginTable("via_irq", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1306 |
ImGui::TableSetupColumn("Bit", ImGuiTableColumnFlags_WidthFixed, |
| 1307 |
dbgui_col_width("Bit", "8")); |
| 1308 |
ImGui::TableSetupColumn("Flag", ImGuiTableColumnFlags_WidthFixed, |
| 1309 |
dbgui_col_width("Flag", "set")); |
| 1310 |
ImGui::TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed, |
| 1311 |
dbgui_col_width("Enabled", "yes")); |
| 1312 |
ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch); |
| 1313 |
ImGui::TableHeadersRow(); |
| 1314 |
|
| 1315 |
for (int bit = 6; bit >= 0; bit--) { |
| 1316 |
const bool flag = (ifr >> bit) & 1; |
| 1317 |
const bool en = (ier >> bit) & 1; |
| 1318 |
ImGui::TableNextRow(); |
| 1319 |
ImGui::TableSetColumnIndex(0); |
| 1320 |
ImGui::TextColored(COL_DIM, "%d", bit); |
| 1321 |
ImGui::TableSetColumnIndex(1); |
| 1322 |
ImGui::TextColored(flag ? COL_WRITE : COL_DIM, "%s", flag ? "set" : "-"); |
| 1323 |
ImGui::TableSetColumnIndex(2); |
| 1324 |
ImGui::TextColored(en ? COL_ON : COL_DIM, "%s", en ? "yes" : "no"); |
| 1325 |
ImGui::TableSetColumnIndex(3); |
| 1326 |
ImGui::TextUnformatted(io_via_irq_bit_name(bit)); |
| 1327 |
} |
| 1328 |
ImGui::EndTable(); |
| 1329 |
} |
| 1330 |
|
| 1331 |
|
| 1332 |
|
| 1333 |
const bool irq_asserted = (ifr & ier & 0x7f) != 0; |
| 1334 |
ImGui::TextColored(irq_asserted ? COL_WRITE : COL_DIM, "IRQ line: %s", |
| 1335 |
irq_asserted ? "asserted" : "idle"); |
| 1336 |
help("Set when any flag above is both raised and enabled. While it is asserted, this " |
| 1337 |
"VIA is holding the CPU's interrupt line down."); |
| 1338 |
|
| 1339 |
ImGui::Separator(); |
| 1340 |
draw_via_ports(which, v); |
| 1341 |
|
| 1342 |
ImGui::Separator(); |
| 1343 |
ImGui::TextUnformatted("Registers"); |
| 1344 |
help("The raw 65C22 register file -- hover a name for what it does.\n\n" |
| 1345 |
"Several of these have side effects when the CPU reads them: reading a timer " |
| 1346 |
"counter clears its interrupt flag. The debugger reads a snapshot instead, so " |
| 1347 |
"looking here changes nothing."); |
| 1348 |
|
| 1349 |
if (ImGui::BeginTable("via_regs", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1350 |
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed, |
| 1351 |
dbgui_col_width("Addr", "$FFFF")); |
| 1352 |
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, |
| 1353 |
dbgui_col_width("Name", "ORA/IRA")); |
| 1354 |
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, |
| 1355 |
dbgui_col_width("Value", "$FF")); |
| 1356 |
ImGui::TableSetupColumn("Binary", ImGuiTableColumnFlags_WidthStretch); |
| 1357 |
ImGui::TableHeadersRow(); |
| 1358 |
|
| 1359 |
const uint16_t base = (which == 0) ? 0x9f00 : 0x9f10; |
| 1360 |
for (int r = 0; r < 15; r++) { |
| 1361 |
const uint16_t addr = (uint16_t)(base + r); |
| 1362 |
ImGui::TableNextRow(); |
| 1363 |
ImGui::TableSetColumnIndex(0); |
| 1364 |
ImGui::TextColored(COL_DIM, "$%04X", addr); |
| 1365 |
ImGui::TableSetColumnIndex(1); |
| 1366 |
ImGui::TextUnformatted(io_reg_name(addr)); |
| 1367 |
tip(io_reg_purpose(addr)); |
| 1368 |
ImGui::TableSetColumnIndex(2); |
| 1369 |
ImGui::Text("$%02X", v.regs[r]); |
| 1370 |
dbgui_hover_value_tooltip(io_reg_name(addr), v.regs[r], 1); |
| 1371 |
ImGui::TableSetColumnIndex(3); |
| 1372 |
char bits[16]; |
| 1373 |
dbgui_format_binary(bits, sizeof(bits), v.regs[r], 8); |
| 1374 |
ImGui::TextColored(COL_DIM, "%s", bits); |
| 1375 |
} |
| 1376 |
ImGui::EndTable(); |
| 1377 |
} |
| 1378 |
} |
| 1379 |
|
| 1380 |
void |
| 1381 |
draw_via_tab() |
| 1382 |
{ |
| 1383 |
if (ImGui::BeginTabBar("via_sub")) { |
| 1384 |
if (ImGui::BeginTabItem("VIA1")) { |
| 1385 |
ImGui::TextWrapped("VIA1 is where the I2C bus, the joysticks and the IEC serial " |
| 1386 |
"bus all meet -- three unrelated things sharing two 8-bit " |
| 1387 |
"ports."); |
| 1388 |
ImGui::Separator(); |
| 1389 |
draw_one_via(0, "VIA1"); |
| 1390 |
ImGui::EndTabItem(); |
| 1391 |
} |
| 1392 |
if (ImGui::BeginTabItem("VIA2")) { |
| 1393 |
draw_one_via(1, "VIA2"); |
| 1394 |
ImGui::EndTabItem(); |
| 1395 |
} |
| 1396 |
ImGui::EndTabBar(); |
| 1397 |
} |
| 1398 |
} |
| 1399 |
|
| 1400 |
|
| 1401 |
|
| 1402 |
|
| 1403 |
|
| 1404 |
void |
| 1405 |
draw_i2c_bus() |
| 1406 |
{ |
| 1407 |
i2c_debug_state_t i2c; |
| 1408 |
i2c_debug_get_state(&i2c); |
| 1409 |
|
| 1410 |
ImGui::TextWrapped("A two-wire bus hanging off VIA1 port A. Exactly two devices answer on " |
| 1411 |
"it: the SMC and the RTC. Everything the keyboard, mouse, power button " |
| 1412 |
"and clock do passes through here."); |
| 1413 |
ImGui::Separator(); |
| 1414 |
|
| 1415 |
if (ImGui::BeginTable("i2c_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1416 |
ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed, |
| 1417 |
dbgui_text_width("Transactions ")); |
| 1418 |
ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch); |
| 1419 |
|
| 1420 |
row_begin("State", |
| 1421 |
"Where the bus is within the current byte. Transfers are bit-at-a-time, so " |
| 1422 |
"catching it mid-byte is normal. \"idle\" means no transaction is in " |
| 1423 |
"flight, which is where the bus spends most of its time."); |
| 1424 |
if (i2c.state < 0) { |
| 1425 |
ImGui::TextUnformatted("idle"); |
| 1426 |
} else if (i2c.state == 0) { |
| 1427 |
ImGui::TextUnformatted("start condition"); |
| 1428 |
} else { |
| 1429 |
ImGui::Text("shifting bit %d of 8", i2c.state); |
| 1430 |
} |
| 1431 |
row_end(); |
| 1432 |
|
| 1433 |
row_begin("Device", |
| 1434 |
"Which device the current transaction is addressed to. $42 is the SMC and " |
| 1435 |
"$6F the RTC; anything else goes unacknowledged, which usually means " |
| 1436 |
"software is probing for hardware that is not there."); |
| 1437 |
if (i2c.device_name[0] != '\0') { |
| 1438 |
ImGui::Text("$%02X (%s)", i2c.device, i2c.device_name); |
| 1439 |
} else if (i2c.device != 0) { |
| 1440 |
ImGui::TextColored(COL_BAD, "$%02X (not acknowledged)", i2c.device); |
| 1441 |
} else { |
| 1442 |
ImGui::TextDisabled("none"); |
| 1443 |
} |
| 1444 |
row_end(); |
| 1445 |
|
| 1446 |
row_kv("Direction", |
| 1447 |
"Which way the current transaction is moving data. A read is normally preceded " |
| 1448 |
"by a write that selects the register.", |
| 1449 |
"%s", i2c.read_mode ? "reading from device" : "writing to device"); |
| 1450 |
|
| 1451 |
row_begin("Byte", |
| 1452 |
"The byte being shifted in or out, and how far into the transaction it is. " |
| 1453 |
"The first byte written is normally the register number."); |
| 1454 |
ImGui::Text("$%02X", i2c.value); |
| 1455 |
ImGui::SameLine(); |
| 1456 |
ImGui::TextColored(COL_DIM, "(byte %d of this transaction)", i2c.count); |
| 1457 |
row_end(); |
| 1458 |
|
| 1459 |
row_kv("Lines", |
| 1460 |
"The raw bus wires. SCL is the clock, always driven by the machine. SDA " |
| 1461 |
"carries data in both directions, so it has a separate in and out here.", |
| 1462 |
"SCL in %d SDA in %d SDA out %d", i2c.clk_in, i2c.data_in, i2c.data_out); |
| 1463 |
|
| 1464 |
row_kv("Transactions", |
| 1465 |
"Counted since start-up. A growing gap between the two means transactions are " |
| 1466 |
"being abandoned part-way, which normally indicates a bus problem.", |
| 1467 |
"%u started, %u completed", i2c.transactions_started, |
| 1468 |
i2c.transactions_completed); |
| 1469 |
|
| 1470 |
row_kv("Bytes", "Data bytes moved over the bus since start-up, excluding addresses.", |
| 1471 |
"%u read, %u written", i2c.bytes_read, i2c.bytes_written); |
| 1472 |
ImGui::EndTable(); |
| 1473 |
} |
| 1474 |
} |
| 1475 |
|
| 1476 |
void |
| 1477 |
draw_smc() |
| 1478 |
{ |
| 1479 |
smc_debug_state_t smc; |
| 1480 |
smc_debug_get_state(&smc); |
| 1481 |
|
| 1482 |
ImGui::TextWrapped("The System Management Controller: a small microcontroller that owns " |
| 1483 |
"the keyboard, the mouse, the power and reset buttons and the case " |
| 1484 |
"LEDs. The main CPU talks to it over I2C at address $42."); |
| 1485 |
ImGui::Separator(); |
| 1486 |
|
| 1487 |
if (ImGui::BeginTable("smc_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1488 |
ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed, |
| 1489 |
dbgui_text_width("Default read reg ")); |
| 1490 |
ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch); |
| 1491 |
|
| 1492 |
row_begin("Default read reg", |
| 1493 |
"Which register a bare read returns -- one where the machine reads from the " |
| 1494 |
"SMC without first writing a register number. The KERNAL sets this once at " |
| 1495 |
"start-up so its keyboard polling loop can be a single short read.\n\n" |
| 1496 |
"$41 is the power-on default, and returns a keyboard byte."); |
| 1497 |
ImGui::Text("$%02X", smc.default_read_op); |
| 1498 |
ImGui::SameLine(); |
| 1499 |
ImGui::TextColored(COL_DECODED, "%s", io_smc_reg_name(smc.default_read_op)); |
| 1500 |
tip(io_smc_reg_purpose(smc.default_read_op)); |
| 1501 |
|
| 1502 |
row_kv("Transfer offset", |
| 1503 |
"How many bytes into the current I2C transfer the SMC is. It resets at the " |
| 1504 |
"start of each transaction: byte 0 is the register number, byte 1 the value. " |
| 1505 |
"Anything other than 0 means a transfer is in flight right now.", |
| 1506 |
"%u", smc.i2c_data_pos); |
| 1507 |
|
| 1508 |
row_begin("Activity LED", |
| 1509 |
"The case's disk-activity lamp. Real hardware dims it by PWM, but this " |
| 1510 |
"emulator models it as simply on or off, switching at a value of 128."); |
| 1511 |
lamp(smc.activity_led >= 128, smc.activity_led >= 128 ? "on" : "off"); |
| 1512 |
row_end(); |
| 1513 |
|
| 1514 |
row_kv("Keyboard buffer", |
| 1515 |
"Key events the SMC is holding until the machine collects them. It is a " |
| 1516 |
"16-byte ring; if this sits full, the machine has stopped polling.", |
| 1517 |
"%u byte%s waiting", smc.kbd_fill, smc.kbd_fill == 1 ? "" : "s"); |
| 1518 |
|
| 1519 |
row_kv("Mouse buffer", |
| 1520 |
"Mouse movement waiting to be collected. Packets are three bytes, or four when " |
| 1521 |
"a wheel is reported.", |
| 1522 |
"%u byte%s waiting", smc.mse_fill, smc.mse_fill == 1 ? "" : "s"); |
| 1523 |
|
| 1524 |
row_kv("Mouse packets", "Complete mouse packets queued behind the byte count above.", |
| 1525 |
"%u", smc.mse_count); |
| 1526 |
ImGui::EndTable(); |
| 1527 |
} |
| 1528 |
|
| 1529 |
if (smc.requested_reset) { |
| 1530 |
ImGui::TextColored(COL_BAD, "Reset requested"); |
| 1531 |
tip("The SMC has been told to reboot the machine and is waiting for the emulator to " |
| 1532 |
"act on it."); |
| 1533 |
} |
| 1534 |
} |
| 1535 |
|
| 1536 |
void |
| 1537 |
draw_rtc() |
| 1538 |
{ |
| 1539 |
rtc_debug_state_t rtc; |
| 1540 |
rtc_debug_get_state(&rtc); |
| 1541 |
|
| 1542 |
ImGui::TextWrapped("A battery-backed real-time clock (MCP7940N) on the same I2C bus, at " |
| 1543 |
"address $6F. It also carries 64 bytes of non-volatile memory that " |
| 1544 |
"survive a power cycle."); |
| 1545 |
ImGui::Separator(); |
| 1546 |
|
| 1547 |
if (ImGui::BeginTable("rtc_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1548 |
ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed, |
| 1549 |
dbgui_text_width("Transfer offset ")); |
| 1550 |
ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch); |
| 1551 |
|
| 1552 |
row_kv("Date and time", |
| 1553 |
"The clock's own idea of the time. It is stored on the device as BCD and shown " |
| 1554 |
"here decoded. The year field only holds 00-99, meaning 2000-2099.", |
| 1555 |
"20%02d-%02d-%02d %02d:%02d:%02d", rtc.year, rtc.month, rtc.day, rtc.hours, |
| 1556 |
rtc.minutes, rtc.seconds); |
| 1557 |
|
| 1558 |
row_begin("Oscillator", |
| 1559 |
"Whether the clock is ticking. Stopped is the normal state for an RTC that " |
| 1560 |
"has never been set -- the machine starts it when it first writes the time."); |
| 1561 |
lamp(rtc.running, rtc.running ? "running" : "stopped"); |
| 1562 |
row_end(); |
| 1563 |
|
| 1564 |
row_kv("Hour format", |
| 1565 |
"A flag in the hours register. It only affects how the device reports the " |
| 1566 |
"hour, not how it counts.", |
| 1567 |
"%s", rtc.h24 ? "24-hour" : "12-hour"); |
| 1568 |
|
| 1569 |
row_kv("Day of week", |
| 1570 |
"1-7, exactly as stored. The RTC does not derive this from the date -- " |
| 1571 |
"whatever set the time chose it, so it is free to disagree with the date " |
| 1572 |
"beside it.", |
| 1573 |
"%d", rtc.day_of_week); |
| 1574 |
|
| 1575 |
row_kv("Transfer offset", |
| 1576 |
"How many bytes into the current I2C transfer the RTC is. Byte 0 selects the " |
| 1577 |
"register, so anything other than 0 means a transfer is in progress.", |
| 1578 |
"%u", rtc.i2c_data_pos); |
| 1579 |
|
| 1580 |
row_kv("NVRAM", |
| 1581 |
"Non-volatile storage at RTC registers $20-$5F. The emulator writes it back to " |
| 1582 |
"the file given by -nvram. \"modified\" means there are changes not yet saved.", |
| 1583 |
"64 bytes, %s", rtc.nvram_dirty ? "modified" : "unmodified"); |
| 1584 |
ImGui::EndTable(); |
| 1585 |
} |
| 1586 |
} |
| 1587 |
|
| 1588 |
void |
| 1589 |
draw_i2c_tab() |
| 1590 |
{ |
| 1591 |
if (ImGui::BeginTabBar("i2c_sub")) { |
| 1592 |
if (ImGui::BeginTabItem("Bus")) { |
| 1593 |
draw_i2c_bus(); |
| 1594 |
ImGui::EndTabItem(); |
| 1595 |
} |
| 1596 |
if (ImGui::BeginTabItem("SMC")) { |
| 1597 |
draw_smc(); |
| 1598 |
ImGui::EndTabItem(); |
| 1599 |
} |
| 1600 |
if (ImGui::BeginTabItem("RTC")) { |
| 1601 |
draw_rtc(); |
| 1602 |
ImGui::EndTabItem(); |
| 1603 |
} |
| 1604 |
ImGui::EndTabBar(); |
| 1605 |
} |
| 1606 |
} |
| 1607 |
|
| 1608 |
|
| 1609 |
|
| 1610 |
|
| 1611 |
|
| 1612 |
void |
| 1613 |
draw_serial_line(const char *name, int level, const char *what) |
| 1614 |
{ |
| 1615 |
ImGui::TableNextRow(); |
| 1616 |
ImGui::TableSetColumnIndex(0); |
| 1617 |
ImGui::TextColored(COL_DIM, "%s", name); |
| 1618 |
ImGui::TableSetColumnIndex(1); |
| 1619 |
ImGui::TextColored(level ? COL_ON : COL_DIM, "%d", level); |
| 1620 |
ImGui::TableSetColumnIndex(2); |
| 1621 |
ImGui::TextUnformatted(what); |
| 1622 |
} |
| 1623 |
|
| 1624 |
void |
| 1625 |
draw_serial_tab() |
| 1626 |
{ |
| 1627 |
ImGui::TextWrapped("The IEC serial bus -- the Commodore disk-drive and printer bus. Three " |
| 1628 |
"wires, driven bit by bit in software, wired to VIA1 port B."); |
| 1629 |
ImGui::Spacing(); |
| 1630 |
ImGui::TextWrapped("Host-filesystem file access does NOT use these lines. It is " |
| 1631 |
"intercepted at the KERNAL level, well above the wire, so the bus stays " |
| 1632 |
"idle while files are being read. These only move when real IEC " |
| 1633 |
"signalling happens, which needs -serial and a device on the bus."); |
| 1634 |
ImGui::Separator(); |
| 1635 |
|
| 1636 |
if (ImGui::BeginTable("serial_lines", 3, |
| 1637 |
ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 1638 |
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, |
| 1639 |
dbgui_col_width("Line", "DATA out")); |
| 1640 |
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, |
| 1641 |
dbgui_col_width("Level", "0")); |
| 1642 |
ImGui::TableSetupColumn("What it is for", ImGuiTableColumnFlags_WidthStretch); |
| 1643 |
ImGui::TableHeadersRow(); |
| 1644 |
|
| 1645 |
draw_serial_line("ATN in", serial_port.in.atn, |
| 1646 |
"Attention. The computer asserts it to interrupt every device on the " |
| 1647 |
"bus and announce that a command, not data, is coming next."); |
| 1648 |
draw_serial_line("CLK in", serial_port.in.clk, |
| 1649 |
"Clock, as driven by the computer. Each transition clocks one bit of " |
| 1650 |
"the byte being sent."); |
| 1651 |
draw_serial_line("DATA in", serial_port.in.data, |
| 1652 |
"Data, as driven by the computer."); |
| 1653 |
draw_serial_line("CLK out", serial_port.out.clk, |
| 1654 |
"Clock, as driven by the attached device when it is the talker."); |
| 1655 |
draw_serial_line("DATA out", serial_port.out.data, |
| 1656 |
"Data, as driven by the attached device -- both when sending bytes " |
| 1657 |
"and when holding the line to say it is not ready yet."); |
| 1658 |
ImGui::EndTable(); |
| 1659 |
} |
| 1660 |
|
| 1661 |
ImGui::Spacing(); |
| 1662 |
ImGui::TextWrapped("The bus is open-collector and active-low: a device pulls a line down " |
| 1663 |
"to assert it, so 0 is the asserted state and an undriven line floats " |
| 1664 |
"to 1."); |
| 1665 |
} |
| 1666 |
|
| 1667 |
|
| 1668 |
|
| 1669 |
void |
| 1670 |
io_panel_render(bool *p_open) |
| 1671 |
{ |
| 1672 |
if (!ImGui::Begin("I/O", p_open)) { |
| 1673 |
dbgui_window_end(); |
| 1674 |
return; |
| 1675 |
} |
| 1676 |
dbgui_window_zoom("io"); |
| 1677 |
|
| 1678 |
apply_capture_settings(); |
| 1679 |
|
| 1680 |
ImGui::TextDisabled("Hover any label or value for what it means."); |
| 1681 |
|
| 1682 |
if (ImGui::BeginTabBar("io_tabs", ImGuiTabBarFlags_DrawSelectedOverline)) { |
| 1683 |
if (ImGui::BeginTabItem("Activity")) { |
| 1684 |
draw_activity_tab(); |
| 1685 |
ImGui::EndTabItem(); |
| 1686 |
} |
| 1687 |
if (ImGui::BeginTabItem("SD Card")) { |
| 1688 |
draw_sdcard_tab(); |
| 1689 |
ImGui::EndTabItem(); |
| 1690 |
} |
| 1691 |
if (ImGui::BeginTabItem("Files")) { |
| 1692 |
draw_files_tab(); |
| 1693 |
ImGui::EndTabItem(); |
| 1694 |
} |
| 1695 |
if (ImGui::BeginTabItem("Joysticks")) { |
| 1696 |
draw_joystick_tab(); |
| 1697 |
ImGui::EndTabItem(); |
| 1698 |
} |
| 1699 |
if (ImGui::BeginTabItem("VIA")) { |
| 1700 |
draw_via_tab(); |
| 1701 |
ImGui::EndTabItem(); |
| 1702 |
} |
| 1703 |
if (ImGui::BeginTabItem("I2C")) { |
| 1704 |
draw_i2c_tab(); |
| 1705 |
ImGui::EndTabItem(); |
| 1706 |
} |
| 1707 |
if (ImGui::BeginTabItem("Serial")) { |
| 1708 |
draw_serial_tab(); |
| 1709 |
ImGui::EndTabItem(); |
| 1710 |
} |
| 1711 |
ImGui::EndTabBar(); |
| 1712 |
} |
| 1713 |
|
| 1714 |
dbgui_window_end(); |
| 1715 |
} |
| 1716 |
|
| 1717 |
DebugPanelRegistration s_reg("I/O", io_panel_render, true); |
| 1718 |
|
| 1719 |
} |