diff --git a/CHANGELOG.md b/CHANGELOG.md index a74c7e4f..cca581a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ## What's New +* **02-Jan-2024**: + + - Added a new 'web api' (a set of Javascript functions which allow controlling + the integrated debugger) for the emualators: + - KC85/2, /3 and /4 + - C64 + - CPC + - Debugger: added a 'stopwatch' window to count cycles between two breakpoints + - am40010.h: video ram data is now loaded into an internal 2-byte latch + in two steps at a 2 MHz interval + + Also check out this new VSCode extension: https://marketplace.visualstudio.com/items?itemName=floooh.vscode-kcide + * **10-Jan-2023**: A big code cleanup session affecting all emulators, and some minor emulation improvements: diff --git a/chips/am40010.h b/chips/am40010.h index 65e8e8c5..71dbc1bf 100644 --- a/chips/am40010.h +++ b/chips/am40010.h @@ -227,6 +227,7 @@ typedef struct am40010_video_t { uint8_t mode; // currently active mode updated at hsync bool sync; // state of the sync output pin bool intr; // interrupt flip-flop + uint8_t latch[2]; // store video ram bytes read at 2 MHz } am40010_video_t; // CRT beam tracking @@ -386,8 +387,7 @@ static void _am40010_init_hwcolors(am40010_t* ga) { for (size_t i = 0; i < 32; i++) { ga->hw_colors[i] = _am40010_cpc_colors[i]; } - } - else { + } else { // KC Compact colors for (size_t i = 0; i < 32; i++) { uint32_t c = 0xFF000000; @@ -414,8 +414,7 @@ static void _am40010_init_hwcolors(am40010_t* ga) { r = 0xFF; g = 0xFF; b = 0xFF; - } - else { + } else { if (i & 1) { r = 0x55; // HS set } @@ -494,8 +493,7 @@ void am40010_iorq(am40010_t* ga, uint64_t pins) { case (1<<6): if (ga->regs.inksel & (1<<4)) { ga->regs.border = data & 0x1F; - } - else { + } else { ga->regs.ink[ga->regs.inksel] = data & 0x1F; } break; @@ -585,8 +583,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->h_pos++; if (crt->h_pos == _AM40010_CRT_H_DISPLAY_START) { crt->h_blank = false; - } - else if (crt->h_pos == 64) { + } else if (crt->h_pos == 64) { // no hsync on this line new_line = true; } @@ -602,8 +599,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->v_pos++; if (crt->v_pos == _AM40010_CRT_V_DISPLAY_START) { crt->v_blank = false; - } - else if (crt->v_pos == 312) { + } else if (crt->v_pos == 312) { // no vsync on this frame new_frame = true; } @@ -625,8 +621,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) { crt->visible = true; crt->pos_x = crt->h_pos - _AM40010_CRT_VIS_X0; crt->pos_y = crt->v_pos - _AM40010_CRT_VIS_Y0; - } - else { + } else { crt->visible = false; } } @@ -686,9 +681,8 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) { // if HSYNC is off, force the clkcnt counter to 0 if (0 == (crtc_pins & AM40010_HS)) { clkcnt = 0; - } - // FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart) - else if (clkcnt < 8) { + } else if (clkcnt < 8) { + // FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart) clkcnt++; } // v_sync is on as long as hscount is < 4 @@ -702,20 +696,24 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) { return ga->video.sync; } -static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pins) { +static uint8_t _am40010_vid_read(am40010_t* ga, uint64_t crtc_pins, uint16_t cclk) { /* compute the source address from current CRTC ma (memory address) and ra (raster address) like this: - |ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|0| + |ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|cclk| Bits ma13 and m12 point to the 16 KByte page, and all other bits are the index into that page. */ const uint16_t addr = ((crtc_pins & 0x3000) << 2) | // MA13,MA12 ((crtc_pins & 0x3FF) << 1) | // MA9..MA0 - (((crtc_pins>>48) & 7) << 11); // RA0..RA2 - const uint8_t* src = &(ga->ram[addr]); + (((crtc_pins>>48) & 7) << 11) | // RA0..RA2 + cclk; + return ga->ram[addr]; +} + +static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst) { uint8_t c; uint8_t p; switch (ga->video.mode) { @@ -727,7 +725,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi 1: |0|4|2|6| */ for (size_t i = 0; i < 2; i++) { - c = *src++; + c = ga->video.latch[i]; p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)|((c>>3)&0x4)|((c<<2)&0x8)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)|((c>>2)&0x4)|((c<<3)&0x8)]; @@ -744,7 +742,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi 3: |0|4| */ for (size_t i = 0; i < 2; i++) { - c = *src++; + c = ga->video.latch[i]; p = ga->regs.ink[((c>>2)&2)|((c>>7)&1)]; *dst++ = p; *dst++ = p; p = ga->regs.ink[((c>>1)&2)|((c>>6)&1)]; @@ -758,7 +756,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi case 2: // 640x200 @ 2 colors (8 pixels per byte) for (size_t i = 0; i < 2; i++) { - c = *src++; + c = ga->video.latch[i]; *dst++ = ga->regs.ink[(c>>7)&1]; *dst++ = ga->regs.ink[(c>>6)&1]; *dst++ = ga->regs.ink[(c>>5)&1]; @@ -777,7 +775,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi 1: |x|x|2|6| */ for (size_t i = 0; i < 2; i++) { - c = *src++; + c = ga->video.latch[i]; p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)]; *dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p; p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)]; @@ -798,8 +796,7 @@ static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) { uint8_t* prev_dst; if (dst == ga->fb) { prev_dst = dst; - } - else { + } else { prev_dst = dst - 16; } uint8_t c = 0x20; @@ -816,41 +813,36 @@ static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) { c |= 0x08; } if (crtc_pins & AM40010_DE) { - _am40010_decode_pixels(ga, dst, crtc_pins); + _am40010_decode_pixels(ga, dst); for (size_t i = 0; i < 16; i++) { if (0 == (i & 2)) { dst[i] = c ^ 0x10; prev_dst[i] ^= 0x10; } } - } - else { + } else { for (size_t i = 0; i < 16; i++) { if (0 == (i & 2)) { dst[i] = c ^ 0x10; prev_dst[i] ^= 0x10; - } - else { + } else { dst[i] = 63; } } } } - } - else if (ga->crt.visible) { + } else if (ga->crt.visible) { size_t dst_x = ga->crt.pos_x * 16; size_t dst_y = ga->crt.pos_y; bool black = ga->video.sync; uint8_t* dst = &ga->fb[dst_x + dst_y * AM40010_FRAMEBUFFER_WIDTH]; if (crtc_pins & AM40010_DE) { - _am40010_decode_pixels(ga, dst, crtc_pins); - } - else if (black) { + _am40010_decode_pixels(ga, dst); + } else if (black) { for (int i = 0; i < 16; i++) { *dst++ = 63; // special 'pure black' hw color } - } - else { + } else { for (int i = 0; i < 16; i++) { *dst++ = ga->regs.border; } @@ -858,13 +850,6 @@ static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) { } } -// the actions which need to happen on CCLK (1 MHz frequency) -static inline void _am40010_do_cclk(am40010_t* ga, uint64_t crtc_pins) { - bool sync = _am40010_sync_irq(ga, crtc_pins); - _am40010_crt_tick(ga, sync); - _am40010_decode_video(ga, crtc_pins); -} - // the tick function must be called at 4 MHz uint64_t am40010_tick(am40010_t* ga, uint64_t pins) { /* The hardware has a 'main sequencer' with a rotating bit @@ -889,21 +874,29 @@ uint64_t am40010_tick(am40010_t* ga, uint64_t pins) { NOTE: Logon's Run crashes on rdy:1 and rdy:2 */ - const bool rdy = 0 != (ga->seq_tick_count & 3); - const bool cclk = 1 == (ga->seq_tick_count & 3); + const bool rdy = 0 != (ga->seq_tick_count & 3); + const bool cclk1 = 1 == (ga->seq_tick_count & 3); + const bool cclk0 = 3 == (ga->seq_tick_count & 3); if (rdy) { // READY is connected to Z80 WAIT, this sets the WAIT pin // in 3 out of 4 CPU clock cycles pins |= AM40010_READY; - } - else { + } else { pins &= ~AM40010_READY; } - if (cclk) { + if (cclk0) { + // read first video ram byte uint64_t crtc_pins = ga->cclk_cb(ga->user_data); - _am40010_do_cclk(ga, crtc_pins); + ga->video.latch[0] = _am40010_vid_read(ga, crtc_pins, 0); + bool sync = _am40010_sync_irq(ga, crtc_pins); + _am40010_crt_tick(ga, sync); ga->crtc_pins = crtc_pins; } + if (cclk1) { + // read second video ram byte + ga->video.latch[1] = _am40010_vid_read(ga, ga->crtc_pins, 1); + _am40010_decode_video(ga, ga->crtc_pins); + } // perform the per-4Mhz-tick actions, the AM40010_READY pin is also the Z80_WAIT pin if ((ga->regs.config & AM40010_CONFIG_IRQRESET) != 0) { diff --git a/systems/c64.h b/systems/c64.h index bb43c036..15e189d5 100644 --- a/systems/c64.h +++ b/systems/c64.h @@ -421,6 +421,14 @@ bool c64_is_tape_motor_on(c64_t* sys); uint32_t c64_save_snapshot(c64_t* sys, c64_t* dst); // load a snapshot, returns false if snapshot versions don't match bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src); +// perform a RUN BASIC call +void c64_basic_run(c64_t* sys); +// perform a LOAD BASIC call +void c64_basic_load(c64_t* sys); +// perform a SYS xxxx BASIC call via the BASIC input buffer +void c64_basic_syscall(c64_t* sys, uint16_t addr); +// returns the SYS call return address (can be used to set a breakpoint) +uint16_t c64_syscall_return_addr(void); #ifdef __cplusplus } // extern "C" @@ -1163,4 +1171,49 @@ bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src) { return true; } +void c64_basic_run(c64_t* sys) { + CHIPS_ASSERT(sys); + // write RUN into the keyboard buffer + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'R'); + mem_wr(&sys->mem_cpu, keybuf++, 'U'); + mem_wr(&sys->mem_cpu, keybuf++, 'N'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 4); +} + +void c64_basic_load(c64_t* sys) { + CHIPS_ASSERT(sys); + // write LOAD + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'L'); + mem_wr(&sys->mem_cpu, keybuf++, 'O'); + mem_wr(&sys->mem_cpu, keybuf++, 'A'); + mem_wr(&sys->mem_cpu, keybuf++, 'D'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 5); +} + +void c64_basic_syscall(c64_t* sys, uint16_t addr) { + CHIPS_ASSERT(sys); + // write SYS xxxx[Return] into the keyboard buffer (up to 10 chars) + uint16_t keybuf = 0x277; + mem_wr(&sys->mem_cpu, keybuf++, 'S'); + mem_wr(&sys->mem_cpu, keybuf++, 'Y'); + mem_wr(&sys->mem_cpu, keybuf++, 'S'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10000) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1000) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 100) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1) % 10) + '0'); + mem_wr(&sys->mem_cpu, keybuf++, 0x0D); + // write number of characters, this kicks off evaluation + mem_wr(&sys->mem_cpu, 0xC6, 9); +} + +uint16_t c64_syscall_return_addr(void) { + return 0xA7EA; +} #endif /* CHIPS_IMPL */ diff --git a/systems/cpc.h b/systems/cpc.h index c4093fd7..efe13916 100644 --- a/systems/cpc.h +++ b/systems/cpc.h @@ -192,7 +192,11 @@ void cpc_joystick(cpc_t* sys, uint8_t mask); // get current joystick bitmask state uint8_t cpc_joystick_mask(cpc_t* sys); // load a snapshot file (.sna or .bin) into the emulator -bool cpc_quickload(cpc_t* cpc, chips_range_t data); +bool cpc_quickload(cpc_t* cpc, chips_range_t data, bool start); +// return the exec address of a quickload file (.sna or .bin) +uint16_t cpc_quickload_exec_addr(chips_range_t data); +// return the return-address for a quickloaded file +uint16_t cpc_quickload_return_addr(cpc_t* cpc); // insert a disk image file (.dsk) bool cpc_insert_disc(cpc_t* cpc, chips_range_t data); // remove current disc @@ -252,16 +256,14 @@ void cpc_init(cpc_t* sys, const cpc_desc_t* desc) { CHIPS_ASSERT(desc->roms.cpc464.basic.ptr && (desc->roms.cpc464.basic.size == 0x4000)); memcpy(sys->rom_os, desc->roms.cpc464.os.ptr, 0x4000); memcpy(sys->rom_basic, desc->roms.cpc464.basic.ptr, 0x4000); - } - else if (CPC_TYPE_6128 == desc->type) { + } else if (CPC_TYPE_6128 == desc->type) { CHIPS_ASSERT(desc->roms.cpc6128.os.ptr && (desc->roms.cpc6128.os.size == 0x4000)); CHIPS_ASSERT(desc->roms.cpc6128.basic.ptr && (desc->roms.cpc6128.basic.size == 0x4000)); CHIPS_ASSERT(desc->roms.cpc6128.amsdos.ptr && (desc->roms.cpc6128.amsdos.size == 0x4000)); memcpy(sys->rom_os, desc->roms.cpc6128.os.ptr, 0x4000); memcpy(sys->rom_basic, desc->roms.cpc6128.basic.ptr, 0x4000); memcpy(sys->rom_amsdos, desc->roms.cpc6128.amsdos.ptr, 0x4000); - } - else { // KC Compact + } else { // KC Compact CHIPS_ASSERT(desc->roms.kcc.os.ptr && (desc->roms.kcc.os.size == 0x4000)); CHIPS_ASSERT(desc->roms.kcc.basic.ptr && (desc->roms.kcc.basic.size == 0x4000)); memcpy(sys->rom_os, desc->roms.kcc.os.ptr, 0x4000); @@ -334,12 +336,10 @@ static uint64_t _cpc_tick(cpc_t* sys, uint64_t cpu_pins) { const uint16_t addr = Z80_GET_ADDR(cpu_pins); if (cpu_pins & Z80_RD) { Z80_SET_DATA(cpu_pins, mem_rd(&sys->mem, addr)); - } - else if (cpu_pins & Z80_WR) { + } else if (cpu_pins & Z80_WR) { mem_wr(&sys->mem, addr, Z80_GET_DATA(cpu_pins)); } - } - else if ((cpu_pins & (Z80_M1|Z80_IORQ)) == Z80_IORQ) { + } else if ((cpu_pins & (Z80_M1|Z80_IORQ)) == Z80_IORQ) { /* CPU IO address decoding For address decoding, see the main board schematics! @@ -452,8 +452,7 @@ static uint64_t _cpc_tick(cpc_t* sys, uint64_t cpu_pins) { if (cpu_pins & Z80_WR) { fdd_motor(&sys->fdd, 0 != (Z80_GET_DATA(cpu_pins) & 1)); } - } - else if ((cpu_pins & (Z80_A10|Z80_A8|Z80_A7)) == Z80_A8) { + } else if ((cpu_pins & (Z80_A10|Z80_A8|Z80_A7)) == Z80_A8) { // floppy controller status/data register uint64_t fdc_pins = UPD765_CS | (cpu_pins & Z80_PIN_MASK); cpu_pins = upd765_iorq(&sys->fdc, fdc_pins) & Z80_PIN_MASK; @@ -520,8 +519,7 @@ static uint8_t _cpc_psg_in(int port_id, void* user_data) { data |= (sys->kbd_joymask | sys->joy_joymask); } return ~data; - } - else { + } else { // this shouldn't happen since the AY-3-8912 only has one IO port return 0xFF; } @@ -549,8 +547,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ ram_config_index = ram_config & 7; rom0_ptr = sys->rom_os; rom1_ptr = (rom_select == 7) ? sys->rom_amsdos : sys->rom_basic; - } - else { + } else { ram_config_index = 0; rom0_ptr = sys->rom_os; rom1_ptr = sys->rom_basic; @@ -564,8 +561,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ if (rom_enable & AM40010_CONFIG_LROMEN) { // read/write RAM mem_map_ram(&sys->mem, 0, 0x0000, 0x4000, sys->ram[i0]); - } - else { + } else { // RAM-behind-ROM mem_map_rw(&sys->mem, 0, 0x0000, 0x4000, rom0_ptr, sys->ram[i0]); } @@ -577,8 +573,7 @@ static void _cpc_bankswitch(uint8_t ram_config, uint8_t rom_enable, uint8_t rom_ if (rom_enable & AM40010_CONFIG_HROMEN) { // read/write RAM mem_map_ram(&sys->mem, 0, 0xC000, 0x4000, sys->ram[i3]); - } - else { + } else { // RAM-behind-ROM mem_map_rw(&sys->mem, 0, 0xC000, 0x4000, rom1_ptr, sys->ram[i3]); } @@ -593,8 +588,7 @@ uint32_t cpc_exec(cpc_t* sys, uint32_t micro_seconds) { for (uint32_t tick = 0; tick < num_ticks; tick++) { pins = _cpc_tick(sys, pins); } - } - else { + } else { // run with debug hook for (uint32_t tick = 0; (tick < num_ticks) && !(*sys->debug.stopped); tick++) { pins = _cpc_tick(sys, pins); @@ -617,8 +611,7 @@ void cpc_key_down(cpc_t* sys, int key_code) { case 0x0B: sys->kbd_joymask |= CPC_JOYSTICK_UP; break; default: kbd_key_down(&sys->kbd, key_code); break; } - } - else { + } else { kbd_key_down(&sys->kbd, key_code); } } @@ -634,8 +627,7 @@ void cpc_key_up(cpc_t* sys, int key_code) { case 0x0B: sys->kbd_joymask &= ~CPC_JOYSTICK_UP; break; default: kbd_key_up(&sys->kbd, key_code); break; } - } - else { + } else { kbd_key_up(&sys->kbd, key_code); } } @@ -874,7 +866,7 @@ static bool _cpc_is_valid_bin(chips_range_t data) { return true; } -static bool _cpc_load_bin(cpc_t* sys, chips_range_t data) { +static bool _cpc_load_bin(cpc_t* sys, chips_range_t data, bool start) { const uint8_t* ptr = (uint8_t*) data.ptr; const _cpc_bin_header* hdr = (const _cpc_bin_header*) ptr; ptr += sizeof(_cpc_bin_header); @@ -884,35 +876,78 @@ static bool _cpc_load_bin(cpc_t* sys, chips_range_t data) { for (uint16_t i = 0; i < len; i++) { mem_wr(&sys->mem, load_addr+i, *ptr++); } - sys->cpu.iff1 = true; - sys->cpu.iff2 = true; - sys->cpu.c = 0; // FIXME: "ROM select number" - sys->cpu.hl = start_addr; - sys->pins = z80_prefetch(&sys->cpu, 0xBD16); // MC START PROGRAM + if (start) { + // write CALL &xxxx into BASIC line buffer + const char* to_hex = "0123456789ABCDEF"; + uint16_t line_buf; + switch (sys->type) { + case CPC_TYPE_6128: + case CPC_TYPE_KCCOMPACT: + line_buf = 0xAC8A; + break; + default: + line_buf = 0xACA4; + break; + } + mem_wr(&sys->mem, line_buf++, 'C'); + mem_wr(&sys->mem, line_buf++, 'A'); + mem_wr(&sys->mem, line_buf++, 'L'); + mem_wr(&sys->mem, line_buf++, 'L'); + mem_wr(&sys->mem, line_buf++, ' '); + mem_wr(&sys->mem, line_buf++, '&'); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 12) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 8) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 4) & 0x0F]); + mem_wr(&sys->mem, line_buf++, to_hex[(start_addr >> 0) & 0x0F]); + mem_wr(&sys->mem, line_buf++, 0); + // generate a Return key press + cpc_key_down(sys, 0x0D); + cpc_key_up(sys, 0x0D); + } return true; } -bool cpc_quickload(cpc_t* sys, chips_range_t data) { +bool cpc_quickload(cpc_t* sys, chips_range_t data, bool start) { CHIPS_ASSERT(sys && sys->valid && data.ptr && (data.size > 0)); if (_cpc_is_valid_sna(data)) { return _cpc_load_sna(sys, data); - } - else if (_cpc_is_valid_bin(data)) { - return _cpc_load_bin(sys, data); - } - else { + } else if (_cpc_is_valid_bin(data)) { + return _cpc_load_bin(sys, data, start); + } else { // not a known file type, or not enough data return false; } } +uint16_t cpc_quickload_return_addr(cpc_t* sys) { + switch (sys->type) { + case CPC_TYPE_6128: + case CPC_TYPE_KCCOMPACT: + return 0xB9A2; + case CPC_TYPE_464: + return 0xB99A; + } + return 0xB9A2; +} + +uint16_t cpc_quickload_exec_addr(chips_range_t data) { + uint16_t start_addr = 0xFFFF; + if (_cpc_is_valid_sna(data)) { + const _cpc_sna_header* hdr = (const _cpc_sna_header*)data.ptr; + start_addr = (hdr->PC_h<<8) | hdr->PC_l; + } else if (_cpc_is_valid_bin(data)) { + const _cpc_bin_header* hdr = (const _cpc_bin_header*)data.ptr; + start_addr = (hdr->start_addr_h<<8)|hdr->start_addr_l; + } + return start_addr; +} + /*=== FLOPPY DISC SUPPORT ====================================================*/ static int _cpc_fdc_seektrack(int drive, int track, void* user_data) { if (0 == drive) { cpc_t* sys = (cpc_t*) user_data; return fdd_seek_track(&sys->fdd, track); - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -935,8 +970,7 @@ static int _cpc_fdc_seeksector(int drive, int side, upd765_sectorinfo_t* inout_i inout_info->st2 = sector->info.upd765.st2; } return res; - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -945,8 +979,7 @@ static int _cpc_fdc_read(int drive, int side, void* user_data, uint8_t* out_data if (0 == drive) { cpc_t* sys = (cpc_t*) user_data; return fdd_read(&sys->fdd, side, out_data); - } - else { + } else { return UPD765_RESULT_NOT_READY; } } @@ -980,8 +1013,7 @@ static void _cpc_fdc_driveinfo(int drive, void* user_data, upd765_driveinfo_t* o out_info->ready = sys->fdd.motor_on; out_info->write_protected = sys->fdd.disc.write_protected; out_info->fault = false; - } - else { + } else { out_info->physical_track = 0; out_info->sides = 1; out_info->head = 0; diff --git a/systems/kc85.h b/systems/kc85.h index 86e46a5e..ede6a5fc 100644 --- a/systems/kc85.h +++ b/systems/kc85.h @@ -274,6 +274,16 @@ extern "C" { #define KC85_TYPE_ID (0x00040000) #endif +#if defined(CHIPS_KC85_TYPE_4) +#define KC85_FREQUENCY (1770000) +#define KC85_SCANLINE_TICKS (113) +#else +#define KC85_FREQUENCY (1750000) +#define KC85_SCANLINE_TICKS (112) +#endif +#define KC85_NUM_SCANLINES (312) +#define KC85_IRM0_PAGE (4) + // bump this whenever the kc85_t struct layout changes #define KC85_SNAPSHOT_VERSION (KC85_TYPE_ID | 0x0002) @@ -478,8 +488,16 @@ const char* kc85_slot_mod_name(kc85_t* sys, uint8_t slot_addr); const char* kc85_slot_mod_short_name(kc85_t* sys, uint8_t slot_addr); // get a slot's control byte uint8_t kc85_slot_ctrl(kc85_t* sys, uint8_t slot_addr); -// load a .KCC or .TAP snapshot file into the emulator -bool kc85_quickload(kc85_t* sys, chips_range_t data); +// test if data is a valid TAP file +bool kc85_is_valid_kctap(chips_range_t data); +// test if data is a valid KCC file +bool kc85_is_valid_kcc(chips_range_t data); +// extract the exed address from a KCC file +uint16_t kc85_kcc_exec_addr(chips_range_t data); +// get the return address in ROM which is pushed on the stack for quickloaded code +uint16_t kc85_quickload_return_addr(void); +// load a .KCC or .TAP snapshot file into the emulator and optionally try to start +bool kc85_quickload(kc85_t* sys, chips_range_t data, bool start); // take snapshot, patches any pointers to zero, returns a snapshot version uint32_t kc85_save_snapshot(kc85_t* sys, kc85_t* dst); // load a snapshot, returns false if snapshot version doesn't match @@ -497,18 +515,6 @@ bool kc85_load_snapshot(kc85_t* sys, uint32_t version, const kc85_t* src); #define CHIPS_ASSERT(c) assert(c) #endif -#if defined(CHIPS_KC85_TYPE_4) -#define _KC85_FREQUENCY (1770000) -#else -#define _KC85_FREQUENCY (1750000) -#endif -#define _KC85_IRM0_PAGE (4) -#if defined(CHIPS_KC85_TYPE_4) -#define _KC85_SCANLINE_TICKS (113) -#else -#define _KC85_SCANLINE_TICKS (112) -#endif -#define _KC85_NUM_SCANLINES (312) /* IO address decoding. @@ -581,7 +587,7 @@ void kc85_init(kc85_t* sys, const kc85_desc_t* desc) { memset(sys, 0, sizeof(kc85_t)); sys->valid = true; - sys->freq_hz = _KC85_FREQUENCY; + sys->freq_hz = KC85_FREQUENCY; sys->patch_callback = desc->patch_callback; sys->debug = desc->debug; @@ -727,13 +733,23 @@ static inline void _kc85_decode_8pixels(uint8_t* dst, uint8_t pixels, uint8_t co static inline uint64_t _kc85_update_raster_counters(kc85_t* sys, uint64_t pins) { sys->video.h_tick++; - if (sys->video.h_tick == _KC85_SCANLINE_TICKS) { + // feed '_h4' into CTC CLKTRG0 and 1, per scanline: + // 0..31 ticks lo + // 32..63 ticks hi + // 64..95 ticks lo + // remainder: hi + if (sys->video.h_tick & 0x20) { + pins |= (Z80CTC_CLKTRG0 | Z80CTC_CLKTRG1); + } + // vertical blanking interval (/BI) active for the last 56 scanlines + if (sys->video.v_count & 0x100) { + pins |= (Z80CTC_CLKTRG2 | Z80CTC_CLKTRG3); + } + if (sys->video.h_tick == KC85_SCANLINE_TICKS) { sys->video.h_tick = 0; sys->video.v_count++; - if (sys->video.v_count == _KC85_NUM_SCANLINES) { + if (sys->video.v_count == KC85_NUM_SCANLINES) { sys->video.v_count = 0; - // vertical sync, trigger CTC CLKTRG2 input for video blinking effect - pins |= Z80CTC_CLKTRG2; } } return pins; @@ -761,11 +777,11 @@ static uint64_t _kc85_tick_video(kc85_t* sys, uint64_t pins) { // CPU accesses video memory, which will force the background color // a short duration // - uint8_t color_bits = sys->ram[_KC85_IRM0_PAGE][color_offset]; + uint8_t color_bits = sys->ram[KC85_IRM0_PAGE][color_offset]; bool fg_blank = 0 != (color_bits & (sys->flip_flops>>(Z80CTC_BIT_ZCTO2-7)) & (sys->pio_pins>>(Z80PIO_PIN_PB7-7)) & (1<<7)); // same as (pins & Z80_WR) && (addr >= 0x8000) && (addr < 0xC000) bool cpu_access = (pins & (Z80_WR | 0xC000)) == (Z80_WR | 0x8000); - uint8_t pixel_bits = (fg_blank || cpu_access) ? 0 : sys->ram[_KC85_IRM0_PAGE][pixel_offset]; + uint8_t pixel_bits = (fg_blank || cpu_access) ? 0 : sys->ram[KC85_IRM0_PAGE][pixel_offset]; uint8_t* dst = &(sys->fb[y*KC85_FRAMEBUFFER_WIDTH + x*8]); _kc85_decode_8pixels(dst, pixel_bits, color_bits); } @@ -805,17 +821,17 @@ static uint64_t _kc85_tick_video(kc85_t* sys, uint64_t pins) { if ((y < 256) && (x < 40)) { size_t irm_index = (sys->io84 & 1) * 2; size_t offset = (x<<8) | y; - uint8_t color_bits = sys->ram[_KC85_IRM0_PAGE + irm_index + 1][offset]; + uint8_t color_bits = sys->ram[KC85_IRM0_PAGE + irm_index + 1][offset]; uint8_t* dst = &sys->fb[y * KC85_FRAMEBUFFER_WIDTH + x * 8]; if (sys->io84 & KC85_IO84_HICOLOR) { // regular KC85/4 video mode bool fg_blank = 0 != (color_bits & (sys->flip_flops>>(Z80CTC_BIT_ZCTO2-7)) & (sys->pio_pins>>(Z80PIO_PIN_PB7-7)) & (1<<7)); - uint8_t pixel_bits = fg_blank ? 0 : sys->ram[_KC85_IRM0_PAGE + irm_index][offset]; + uint8_t pixel_bits = fg_blank ? 0 : sys->ram[KC85_IRM0_PAGE + irm_index][offset]; _kc85_decode_8pixels(dst, pixel_bits, color_bits); } else { // hicolor mode - uint8_t p0 = sys->ram[_KC85_IRM0_PAGE + irm_index][offset]; + uint8_t p0 = sys->ram[KC85_IRM0_PAGE + irm_index][offset]; uint8_t p1 = color_bits; _kc85_decode_hicolor_8pixels(dst, p0, p1); } @@ -852,7 +868,7 @@ static void _kc85_update_memory_map(kc85_t* sys) { #if !defined(CHIPS_KC85_TYPE_4) // KC85/2 and /3 // 16 KB Video RAM at 0x8000 if (pio_pins & KC85_PIO_IRM) { - mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, sys->ram[_KC85_IRM0_PAGE]); + mem_map_ram(&sys->mem, 0, 0x8000, 0x4000, sys->ram[KC85_IRM0_PAGE]); } #else // KC84/4 // 16 KB RAM at 0x4000 @@ -880,7 +896,7 @@ static void _kc85_update_memory_map(kc85_t* sys) { */ if (pio_pins & KC85_PIO_IRM) { uint32_t irm_index = (sys->io84 & 6)>>1; - uint8_t* irm_ptr = sys->ram[_KC85_IRM0_PAGE + irm_index]; + uint8_t* irm_ptr = sys->ram[KC85_IRM0_PAGE + irm_index]; /* on the KC85, an access to IRM banks other than the first is only possible for the first 10 KByte until A800, memory access to the remaining 6 KBytes @@ -890,7 +906,7 @@ static void _kc85_update_memory_map(kc85_t* sys) { mem_map_ram(&sys->mem, 0, 0x8000, 0x2800, irm_ptr); // always force access to 0xA800 and above to the first IRM bank - mem_map_ram(&sys->mem, 0, 0xA800, 0x1800, sys->ram[_KC85_IRM0_PAGE] + 0x2800); + mem_map_ram(&sys->mem, 0, 0xA800, 0x1800, sys->ram[KC85_IRM0_PAGE] + 0x2800); } // 4 KB CAOS-C ROM at 0xC000 (on top of BASIC) if (sys->io86 & KC85_IO86_CAOS_ROM_C) { @@ -917,10 +933,10 @@ static uint64_t _kc85_tick(kc85_t* sys, uint64_t pins) { } } - // tick the video system, this may return Z80CTC_CLKTRG2 on VSYNC + // tick the video system, may set CLKTRG0..3 pins = _kc85_tick_video(sys, pins); - // tick the CTC, NOTE: Z80CTC_CLKTRG2 may be set from video system + // tick the CTC { // set virtual IEIO pin because CTC is highest priority interrupt device pins |= Z80_IEIO; @@ -1462,6 +1478,19 @@ static void _kc85_exp_update_memory_mapping(kc85_t* sys) { /*=== FILE LOADING ===========================================================*/ +uint16_t kc85_quickload_return_addr(void) { + // FIXME: KC85/2: find proper return address + #if defined(CHIPS_KC85_TYPE_2) + return 0xffff; + #elif defined(CHIPS_KC85_TYPE_3) + return 0xf15c; + #elif defined(CHIPS_KC85_TYPE_4) + return 0xf17e; + #else + #error "unknown KC85 type!" + #endif +} + // common start function for all snapshot file formats static void _kc85_load_start(kc85_t* sys, uint16_t exec_addr) { sys->cpu.a = 0x00; @@ -1478,11 +1507,10 @@ static void _kc85_load_start(kc85_t* sys, uint16_t exec_addr) { mem_wr(&sys->mem, 0xb7a0, 0); #if defined(CHIPS_KC85_TYPE_3) _kc85_tick(sys, Z80_MAKE_PINS(Z80_IORQ|Z80_WR, 0x89, 0x9f)); - mem_wr16(&sys->mem, sys->cpu.sp, 0xf15c); #elif defined(CHIPS_KC85_TYPE_4) _kc85_tick(sys, Z80_MAKE_PINS(Z80_IORQ|Z80_WR, 0x89, 0xff)); - mem_wr16(&sys->mem, sys->cpu.sp, 0xf17e); #endif + mem_wr16(&sys->mem, sys->cpu.sp, kc85_quickload_return_addr()); z80_prefetch(&sys->cpu, exec_addr); } @@ -1510,7 +1538,7 @@ static void _kc85_invoke_patch_callback(kc85_t* sys, const _kc85_kcc_header* hdr } /* KCC files cannot really be identified since they have no magic number */ -static bool _kc85_is_valid_kcc(chips_range_t data) { +bool kc85_is_valid_kcc(chips_range_t data) { if (data.size <= sizeof(_kc85_kcc_header)) { return false; } @@ -1537,18 +1565,22 @@ static bool _kc85_is_valid_kcc(chips_range_t data) { return true; } -static bool _kc85_load_kcc(kc85_t* sys, chips_range_t data) { +uint16_t kc85_kcc_exec_addr(chips_range_t data) { + assert(kc85_is_valid_kcc(data)); + const _kc85_kcc_header* hdr = (const _kc85_kcc_header*) data.ptr; + return hdr->exec_addr_h<<8 | hdr->exec_addr_l; +} + +static bool _kc85_load_kcc(kc85_t* sys, chips_range_t data, bool start) { const _kc85_kcc_header* hdr = (_kc85_kcc_header*) data.ptr; uint16_t addr = hdr->load_addr_h<<8 | hdr->load_addr_l; uint16_t end_addr = hdr->end_addr_h<<8 | hdr->end_addr_l; const uint8_t* ptr = (const uint8_t*)data.ptr + sizeof(_kc85_kcc_header); while (addr < end_addr) { - /* data is continuous */ mem_wr(&sys->mem, addr++, *ptr++); } _kc85_invoke_patch_callback(sys, hdr); - /* if file has an exec-address, start the program */ - if (hdr->num_addr > 2) { + if (start && (hdr->num_addr > 2)) { _kc85_load_start(sys, hdr->exec_addr_h<<8 | hdr->exec_addr_l); } return true; @@ -1561,7 +1593,7 @@ typedef struct { _kc85_kcc_header kcc; /* from here on identical with KCC */ } _kc85_kctap_header; -static bool _kc85_is_valid_kctap(chips_range_t data) { +bool kc85_is_valid_kctap(chips_range_t data) { if (data.size <= sizeof(_kc85_kctap_header)) { return false; } @@ -1594,7 +1626,7 @@ static bool _kc85_is_valid_kctap(chips_range_t data) { return true; } -static bool _kc85_load_kctap(kc85_t* sys, chips_range_t data) { +static bool _kc85_load_kctap(kc85_t* sys, chips_range_t data, bool start) { const _kc85_kctap_header* hdr = (const _kc85_kctap_header*) data.ptr; uint16_t addr = hdr->kcc.load_addr_h<<8 | hdr->kcc.load_addr_l; uint16_t end_addr = hdr->kcc.end_addr_h<<8 | hdr->kcc.end_addr_l; @@ -1608,20 +1640,20 @@ static bool _kc85_load_kctap(kc85_t* sys, chips_range_t data) { } _kc85_invoke_patch_callback(sys, &hdr->kcc); /* if file has an exec-address, start the program */ - if (hdr->kcc.num_addr > 2) { + if (start && (hdr->kcc.num_addr > 2)) { _kc85_load_start(sys, hdr->kcc.exec_addr_h<<8 | hdr->kcc.exec_addr_l); } return true; } -bool kc85_quickload(kc85_t* sys, chips_range_t data) { +bool kc85_quickload(kc85_t* sys, chips_range_t data, bool start) { CHIPS_ASSERT(sys && sys->valid && data.ptr); /* first check for KC-TAP format, since this can be properly identified */ - if (_kc85_is_valid_kctap(data)) { - return _kc85_load_kctap(sys, data); + if (kc85_is_valid_kctap(data)) { + return _kc85_load_kctap(sys, data, start); } - else if (_kc85_is_valid_kcc(data)) { - return _kc85_load_kcc(sys, data); + else if (kc85_is_valid_kcc(data)) { + return _kc85_load_kcc(sys, data, start); } else { /* not a known file type, or not enough data */ diff --git a/ui/ui_atom.h b/ui/ui_atom.h index d0d39d9a..af90aa39 100644 --- a/ui/ui_atom.h +++ b/ui/ui_atom.h @@ -149,6 +149,7 @@ static void _ui_atom_draw_menu(ui_atom_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_bombjack.h b/ui/ui_bombjack.h index fe220a1d..6136274f 100644 --- a/ui/ui_bombjack.h +++ b/ui/ui_bombjack.h @@ -612,6 +612,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { if (ImGui::BeginMenu("Main Board")) { ImGui::MenuItem("CPU Debugger", 0, &ui->main.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->main.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->main.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->main.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->main.dbg.ui.show_heatmap); ImGui::EndMenu(); @@ -619,6 +620,7 @@ static void _ui_bombjack_draw_menu(ui_bombjack_t* ui) { if (ImGui::BeginMenu("Sound Board")) { ImGui::MenuItem("CPU Debugger", 0, &ui->sound.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->sound.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->sound.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->sound.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->sound.dbg.ui.show_heatmap); ImGui::EndMenu(); diff --git a/ui/ui_c64.h b/ui/ui_c64.h index 4055d54a..165e9397 100644 --- a/ui/ui_c64.h +++ b/ui/ui_c64.h @@ -72,6 +72,7 @@ typedef struct { c64_t* c64; // pointer to c64_t instance to track ui_c64_boot_cb boot_cb; // reboot callback function ui_dbg_texture_callbacks_t dbg_texture; // texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t ui_snapshot_desc_t snapshot; // snapshot UI setup params } ui_c64_desc_t; @@ -172,6 +173,7 @@ static void _ui_c64_draw_menu(ui_c64_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -191,6 +193,7 @@ static void _ui_c64_draw_menu(ui_c64_t* ui) { if (ui->c64->c1541.valid) { if (ImGui::BeginMenu("VC-1541 (Floppy Drive)")) { ImGui::MenuItem("CPU Debugger", 0, &ui->c1541_dbg.ui.open); + ImGui::MenuItem("Stopwatch", 0, &ui->c1541_dbg.ui.show_stopwatch); ImGui::MenuItem("Breakpoints", 0, &ui->c1541_dbg.ui.show_breakpoints); ImGui::MenuItem("Execution History", 0, &ui->c1541_dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->c1541_dbg.ui.show_heatmap); @@ -549,9 +552,13 @@ void ui_c64_init(ui_c64_t* ui, const ui_c64_desc_t* ui_desc) { desc.x = x; desc.y = y; desc.m6502 = &ui->c64->cpu; + desc.freq_hz = C64_FREQUENCY; + desc.scanline_ticks = M6569_HTOTAL; + desc.frame_ticks = M6569_HTOTAL * M6569_VTOTAL; desc.read_cb = _ui_c64_mem_read; desc.break_cb = _ui_c64_eval_bp; desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; /* custom breakpoint types */ diff --git a/ui/ui_cpc.h b/ui/ui_cpc.h index 2e47788b..c6d32cd8 100644 --- a/ui/ui_cpc.h +++ b/ui/ui_cpc.h @@ -72,6 +72,7 @@ typedef struct { cpc_t* cpc; ui_cpc_boot_t boot_cb; // user-provided callback to reboot to different config ui_dbg_texture_callbacks_t dbg_texture; // debug texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; // user-provided debugger callbacks ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t ui_snapshot_desc_t snapshot; // snapshot ui setup params } ui_cpc_desc_t; @@ -145,8 +146,7 @@ static void _ui_cpc_draw_menu(ui_cpc_t* ui) { if (ImGui::MenuItem("Joystick", 0, ui->cpc->joystick_type != CPC_JOYSTICK_NONE)) { if (ui->cpc->joystick_type == CPC_JOYSTICK_NONE) { ui->cpc->joystick_type = CPC_JOYSTICK_DIGITAL; - } - else { + } else { ui->cpc->joystick_type = CPC_JOYSTICK_NONE; } } @@ -168,6 +168,7 @@ static void _ui_cpc_draw_menu(ui_cpc_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -241,8 +242,7 @@ static void _ui_cpc_update_memmap(ui_cpc_t* ui) { ui_memmap_region(&ui->memmap, "RAM 1", 0x4000, 0x4000, true); ui_memmap_region(&ui->memmap, "RAM 2", 0x8000, 0x4000, true); ui_memmap_region(&ui->memmap, "RAM 3 (Screen)", 0xC000, 0x4000, true); - } - else { + } else { const uint8_t ram_config_index = cpc->ga.ram_config & 7; const uint8_t rom_select = cpc->ga.rom_select; ui_memmap_layer(&ui->memmap, "ROM Layer 0"); @@ -271,27 +271,21 @@ static uint8_t* _ui_cpc_memptr(cpc_t* cpc, int layer, uint16_t addr) { if (layer == _UI_CPC_MEMLAYER_GA) { uint8_t* ram = &cpc->ram[0][0]; return ram + addr; - } - else if (layer == _UI_CPC_MEMLAYER_ROMS) { + } else if (layer == _UI_CPC_MEMLAYER_ROMS) { if (addr < 0x4000) { return &cpc->rom_os[addr]; - } - else if (addr >= 0xC000) { + } else if (addr >= 0xC000) { return &cpc->rom_basic[addr - 0xC000]; - } - else { + } else { return 0; } - } - else if (layer == _UI_CPC_MEMLAYER_AMSDOS) { + } else if (layer == _UI_CPC_MEMLAYER_AMSDOS) { if ((CPC_TYPE_6128 == cpc->type) && (addr >= 0xC000)) { return &cpc->rom_amsdos[addr - 0xC000]; - } - else { + } else { return 0; } - } - else { + } else { /* one of the 7 RAM layers */ CHIPS_ASSERT((layer >= _UI_CPC_MEMLAYER_RAM0) && (layer <= _UI_CPC_MEMLAYER_RAM7)); const int ram_config_index = (CPC_TYPE_6128 == cpc->type) ? (cpc->ga.ram_config & 7) : 0; @@ -328,13 +322,11 @@ static uint8_t _ui_cpc_mem_read(int layer, uint16_t addr, void* user_data) { if (layer == _UI_CPC_MEMLAYER_CPU) { /* CPU mapped RAM layer */ return mem_rd(&cpc->mem, addr); - } - else { + } else { uint8_t* ptr = _ui_cpc_memptr(cpc, layer, addr); if (ptr) { return *ptr; - } - else { + } else { return 0xFF; } } @@ -346,8 +338,7 @@ static void _ui_cpc_mem_write(int layer, uint16_t addr, uint8_t data, void* user cpc_t* cpc = ui_cpc->cpc; if (layer == _UI_CPC_MEMLAYER_CPU) { mem_wr(&cpc->mem, addr, data); - } - else { + } else { uint8_t* ptr = _ui_cpc_memptr(cpc, layer, addr); if (ptr) { *ptr = data; @@ -599,6 +590,7 @@ void ui_cpc_init(ui_cpc_t* ui, const ui_cpc_desc_t* ui_desc) { desc.read_cb = _ui_cpc_mem_read; desc.break_cb = _ui_cpc_eval_bp; desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui; /* custom breakpoint types */ diff --git a/ui/ui_dbg.h b/ui/ui_dbg.h index b977084e..5184cadc 100644 --- a/ui/ui_dbg.h +++ b/ui/ui_dbg.h @@ -71,8 +71,6 @@ extern "C" { #define UI_DBG_MAX_USER_BREAKTYPES (8) /* max number of user breakpoint types */ #define UI_DBG_STEP_TRAPID (128) /* special trap id when step-mode active */ #define UI_DBG_BP_BASE_TRAPID (UI_DBG_STEP_TRAPID+1) /* first CPU trap-id used for breakpoints */ -#define UI_DBG_MAX_STRLEN (32) -#define UI_DBG_MAX_BINLEN (16) #define UI_DBG_NUM_LINES (256) #define UI_DBG_NUM_BACKTRACE_LINES (UI_DBG_NUM_LINES/2) #define UI_DBG_NUM_HISTORY_ITEMS (256) @@ -110,6 +108,13 @@ enum { UI_DBG_STEPMODE_TICK, }; +enum { + UI_DBG_STOP_REASON_UNKNOWN = 0, + UI_DBG_STOP_REASON_BREAK = 1, + UI_DBG_STOP_REASON_BREAKPOINT = 2, + UI_DBG_STOP_REASON_STEP = 3, +}; + /* a breakpoint description */ typedef struct ui_dbg_breakpoint_t { int type; /* UI_DBG_BREAKTYPE_* */ @@ -141,6 +146,14 @@ typedef void* (*ui_dbg_create_texture_t)(int w, int h); typedef void (*ui_dbg_update_texture_t)(void* tex_handle, void* data, int data_byte_size); /* callback to destroy a UI texture */ typedef void (*ui_dbg_destroy_texture_t)(void* tex_handle); +/* callback when emulator is being rebootet */ +typedef void (*ui_dbg_reboot_t)(void); +/* callback when emulator is being reset */ +typedef void (*ui_dbg_reset_t)(void); +/* callback when emulator has stopped at an address (stop_reason is UI_DBG_STOP_REASON_XXX) */ +typedef void (*ui_dbg_stopped_t)(int stop_reason, uint16_t addr); +/* callback when emulator has continued after stopped state */ +typedef void (*ui_dbg_continued_t)(void); /* user-defined hotkeys (all strings must be static) */ typedef struct ui_dbg_key_desc_t { @@ -163,34 +176,36 @@ typedef struct ui_dbg_texture_callbacks_t { ui_dbg_destroy_texture_t destroy_cb; // callback to destroy UI texture } ui_dbg_texture_callbacks_t; +typedef struct ui_dbg_debug_callbacks_t { + ui_dbg_reboot_t reboot_cb; + ui_dbg_reset_t reset_cb; + ui_dbg_stopped_t stopped_cb; + ui_dbg_continued_t continued_cb; +} ui_dbg_debug_callbacks_t; + typedef struct ui_dbg_desc_t { - const char* title; /* window title */ + const char* title; // window title #if defined(UI_DBG_USE_Z80) - z80_t* z80; /* Z80 CPU to track */ + z80_t* z80; // Z80 CPU to track #elif defined(UI_DBG_USE_M6502) - m6502_t* m6502; /* 6502 CPU to track */ + m6502_t* m6502; // 6502 CPU to track #endif - ui_dbg_read_t read_cb; /* callback to read memory */ - int read_layer; /* layer argument for read_cb */ - ui_dbg_user_break_t break_cb; /* optional user-breakpoint evaluation callback */ + uint32_t freq_hz; // CPU clock frequency in Hz + uint32_t scanline_ticks; // length of a raster line in clock cycles + uint32_t frame_ticks; // length of a frame in clock cycles + ui_dbg_read_t read_cb; // callback to read memory + int read_layer; // layer argument for read_cb + ui_dbg_user_break_t break_cb; // optional user-breakpoint evaluation callback ui_dbg_texture_callbacks_t texture_cbs; - void* user_data; /* user data for callbacks */ - int x, y; /* initial window pos */ - int w, h; /* initial window size, or 0 for default size */ - bool open; /* initial open state */ - ui_dbg_keys_desc_t keys; /* user-defined hotkeys */ + ui_dbg_debug_callbacks_t debug_cbs; + void* user_data; // user data for callbacks + int x, y; // initial window pos + int w, h; // initial window size, or 0 for default size + bool open; // initial open state + ui_dbg_keys_desc_t keys; // user-defined hotkeys ui_dbg_breaktype_t user_breaktypes[UI_DBG_MAX_USER_BREAKTYPES]; /* user-defined breakpoint types */ } ui_dbg_desc_t; -/* disassembler state */ -typedef struct ui_dbg_dasm_t { - uint16_t cur_addr; - int str_pos; - char str_buf[UI_DBG_MAX_STRLEN]; - int bin_pos; - uint8_t bin_buf[UI_DBG_MAX_BINLEN]; -} ui_dbg_dasm_t; - /* debugger state */ typedef struct ui_dbg_state_t { #if defined(UI_DBG_USE_Z80) @@ -199,6 +214,7 @@ typedef struct ui_dbg_state_t { m6502_t* m6502; #endif bool stopped; + bool external_debugger_connected; int step_mode; uint64_t last_tick_pins; // cpu pins in last tick uint32_t frame_id; // used in trap callback to detect when a new frame has started @@ -230,6 +246,7 @@ typedef struct ui_dbg_uistate_t { bool show_bytes; bool show_ticks; bool show_history; + bool show_stopwatch; bool request_scroll; ui_dbg_keys_desc_t keys; ui_dbg_line_t line_array[UI_DBG_NUM_LINES]; @@ -238,12 +255,15 @@ typedef struct ui_dbg_uistate_t { const char* breaktype_combo_labels[UI_DBG_MAX_BREAKTYPES]; } ui_dbg_uistate_t; +enum { + UI_DBG_HEATMAP_ITEM_OPCODE = (1<<0), + UI_DBG_HEATMAP_ITEM_WRITE = (1<<1), + UI_DBG_HEATMAP_ITEM_READ = (1<<2), +}; + typedef struct ui_dbg_heatmapitem_t { - uint32_t op_count; /* set for first instruction byte */ - uint32_t write_count; - uint32_t read_count; - uint16_t ticks; /* ticks of current instruction */ - uint16_t op_start; /* set for followup instruction bytes */ + uint8_t state; // UI_DBG_HEATMAP_ITEM_xxx + uint8_t ticks; // instruction tick count } ui_dbg_heatmapitem_t; typedef struct ui_dbg_heatmap_t { @@ -266,38 +286,88 @@ typedef struct ui_dbg_history_t { uint16_t pos; } ui_dbg_history_t; +enum { + UI_DBG_DASM_LINE_MAX_BYTES = 8, + UI_DBG_DASM_LINE_MAX_CHARS = 32, +}; + +typedef struct ui_dbg_dasm_line_t { + uint16_t addr; + uint8_t num_bytes; + uint8_t num_chars; + uint8_t bytes[UI_DBG_DASM_LINE_MAX_BYTES]; + char chars[UI_DBG_DASM_LINE_MAX_CHARS]; +} ui_dbg_dasm_line_t; + +typedef struct ui_dbg_dasm_request_t { + uint16_t addr; // base address + int offset_lines; // offset in number of ops/lines, may be negative + int num_lines; // number of lines to disassemble + ui_dbg_dasm_line_t* out_lines; // pointer to output ops, must have at least num_ops entries +} ui_dbg_dasm_request_t; + +enum { + UI_DBG_STOPWATCH_NUM = 8, +}; +typedef struct ui_dbg_stopwatch_t { + uint32_t freq_hz; + uint32_t scanline_ticks; + uint32_t frame_ticks; + uint64_t cur_ticks; + uint64_t start_ticks[UI_DBG_STOPWATCH_NUM]; +} ui_dbg_stopwatch_t; + typedef struct ui_dbg_t { bool valid; ui_dbg_read_t read_cb; int read_layer; ui_dbg_user_break_t break_cb; ui_dbg_texture_callbacks_t texture_cbs; - ui_dbg_create_texture_t create_texture_cb; - ui_dbg_update_texture_t update_texture_cb; - ui_dbg_destroy_texture_t destroy_texture_cb; + ui_dbg_debug_callbacks_t debug_cbs; void* user_data; - ui_dbg_dasm_t dasm; + ui_dbg_dasm_line_t dasm_line; ui_dbg_state_t dbg; ui_dbg_uistate_t ui; ui_dbg_heatmap_t heatmap; ui_dbg_history_t history; + ui_dbg_stopwatch_t stopwatch; } ui_dbg_t; -/* initialize a new ui_dbg_t instance */ +// initialize a new ui_dbg_t instance void ui_dbg_init(ui_dbg_t* win, ui_dbg_desc_t* desc); -/* discard ui_dbg_t instance */ +// discard ui_dbg_t instance void ui_dbg_discard(ui_dbg_t* win); -/* render the ui_dbg_t UIs */ +// notify ui_dbg that an external debugger has connected (may change some behaviour) +void ui_dbg_external_debugger_connected(ui_dbg_t* win); +// notify ui_dbg that an external debugger has disconnected (clears breakpoints and continues) +void ui_dbg_external_debugger_disconnected(ui_dbg_t* win); +// render the ui_dbg_t UIs void ui_dbg_draw(ui_dbg_t* win); -/* call after ticking the system */ +// call after ticking the system void ui_dbg_tick(ui_dbg_t* win, uint64_t pins); -/* call when resetting the emulated machine (re-initializes some data structures) */ +// call when resetting the emulated machine (re-initializes some data structures) void ui_dbg_reset(ui_dbg_t* win); -/* call when rebooting the emulated machine (re-initializes some data structures) */ +// call when rebooting the emulated machine (re-initializes some data structures) void ui_dbg_reboot(ui_dbg_t* win); +// set an execution breakpoint at address +void ui_dbg_add_breakpoint(ui_dbg_t* win, uint16_t addr); +// clear an execution breakpoint at address +void ui_dbg_remove_breakpoint(ui_dbg_t* win, uint16_t addr); +// pause/stop execution +void ui_dbg_break(ui_dbg_t* win); +// continue execution +void ui_dbg_continue(ui_dbg_t* win, bool invoke_continued_cb); +// return true if the debugger is currently stopped +bool ui_dbg_stopped(ui_dbg_t* win); +// perform a debugger step-next (step over) +void ui_dbg_step_next(ui_dbg_t* win); +// peform a debugger step-into +void ui_dbg_step_into(ui_dbg_t* win); +// request a disassembly at start address +void ui_dbg_disassemble(ui_dbg_t* win, const ui_dbg_dasm_request_t* request); #ifdef __cplusplus -} /* extern "C" */ +} // extern "C" #endif /*-- IMPLEMENTATION ----------------------------------------------------------*/ @@ -312,8 +382,7 @@ void ui_dbg_reboot(ui_dbg_t* win); static inline const char* _ui_dbg_str_or_def(const char* str, const char* def) { if (str) { return str; - } - else { + } else { return def; } } @@ -335,9 +404,9 @@ static inline uint16_t _ui_dbg_get_pc(ui_dbg_t* win) { /* disassembler callback to fetch the next instruction byte */ static uint8_t _ui_dbg_dasm_in_cb(void* user_data) { ui_dbg_t* win = (ui_dbg_t*) user_data; - uint8_t val = _ui_dbg_read_byte(win, win->dasm.cur_addr++); - if (win->dasm.bin_pos < UI_DBG_MAX_BINLEN) { - win->dasm.bin_buf[win->dasm.bin_pos++] = val; + uint8_t val = _ui_dbg_read_byte(win, win->dasm_line.addr++); + if (win->dasm_line.num_bytes < UI_DBG_DASM_LINE_MAX_BYTES) { + win->dasm_line.bytes[win->dasm_line.num_bytes++] = val; } return val; } @@ -345,38 +414,27 @@ static uint8_t _ui_dbg_dasm_in_cb(void* user_data) { /* disassembler callback to output a character */ static void _ui_dbg_dasm_out_cb(char c, void* user_data) { ui_dbg_t* win = (ui_dbg_t*) user_data; - if ((win->dasm.str_pos+1) < UI_DBG_MAX_STRLEN) { - win->dasm.str_buf[win->dasm.str_pos++] = c; - win->dasm.str_buf[win->dasm.str_pos] = 0; + if ((win->dasm_line.num_chars + 1) < UI_DBG_DASM_LINE_MAX_CHARS) { + win->dasm_line.chars[win->dasm_line.num_chars++] = c; + win->dasm_line.chars[win->dasm_line.num_chars] = 0; } } -/* disassemble the next instruction */ -static inline uint16_t _ui_dbg_disasm(ui_dbg_t* win, uint16_t pc) { - win->dasm.cur_addr = pc; - win->dasm.str_pos = 0; - win->dasm.bin_pos = 0; +// disassemble instruction at address +static inline uint16_t _ui_dbg_disasm(ui_dbg_t* win, uint16_t addr) { + memset(&win->dasm_line, 0, sizeof(win->dasm_line)); + win->dasm_line.addr = addr; #if defined(UI_DBG_USE_Z80) - z80dasm_op(pc, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); + z80dasm_op(addr, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); #elif defined(UI_DBG_USE_M6502) - m6502dasm_op(pc, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); + m6502dasm_op(addr, _ui_dbg_dasm_in_cb, _ui_dbg_dasm_out_cb, win); #endif - return win->dasm.cur_addr; + uint16_t next_addr = win->dasm_line.addr; + win->dasm_line.addr = addr; + return next_addr; } /* disassemble the an instruction, but only return the length of the instruction */ -static inline uint16_t _ui_dbg_disasm_len(ui_dbg_t* win, uint16_t pc) { - win->dasm.cur_addr = pc; - win->dasm.str_pos = 0; - win->dasm.bin_pos = 0; - #if defined(UI_DBG_USE_Z80) - uint16_t next_pc = z80dasm_op(pc, _ui_dbg_dasm_in_cb, 0, win); - #elif defined(UI_DBG_USE_M6502) - uint16_t next_pc = m6502dasm_op(pc, _ui_dbg_dasm_in_cb, 0, win); - #endif - return next_pc - pc; -} - /* check if the an instruction is a 'step over' op */ static bool _ui_dbg_is_stepover_op(uint8_t opcode) { #if defined(UI_DBG_USE_Z80) @@ -475,11 +533,17 @@ static void _ui_dbg_break(ui_dbg_t* win) { win->dbg.stopped = true; win->dbg.step_mode = UI_DBG_STEPMODE_NONE; win->ui.request_scroll = true; + if (win->debug_cbs.stopped_cb) { + win->debug_cbs.stopped_cb(UI_DBG_STOP_REASON_BREAK, win->dbg.cur_op_pc); + } } -static void _ui_dbg_continue(ui_dbg_t* win) { +static void _ui_dbg_continue(ui_dbg_t* win, bool invoke_continue_cb) { win->dbg.stopped = false; win->dbg.step_mode = UI_DBG_STEPMODE_NONE; + if (invoke_continue_cb && win->debug_cbs.continued_cb) { + win->debug_cbs.continued_cb(); + } } static void _ui_dbg_step_into(ui_dbg_t* win) { @@ -492,11 +556,10 @@ static void _ui_dbg_step_over(ui_dbg_t* win) { win->dbg.stopped = false; win->ui.request_scroll = true; uint16_t next_pc = _ui_dbg_disasm(win, _ui_dbg_get_pc(win)); - if (_ui_dbg_is_stepover_op(win->dasm.bin_buf[0])) { + if (_ui_dbg_is_stepover_op(win->dasm_line.bytes[0])) { win->dbg.step_mode = UI_DBG_STEPMODE_OVER; win->dbg.stepover_pc = next_pc; - } - else { + } else { win->dbg.step_mode = UI_DBG_STEPMODE_INTO; } } @@ -560,8 +623,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { /* address */ if (0 == line_i) { ImGui::Text("cur> %04X: ", pc); - } - else { + } else { ImGui::Text("%4d %04X: ", -line_i, pc); } ImGui::SameLine(); @@ -571,7 +633,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { if (win->ui.show_bytes) { for (int n = 0; n < num_bytes; n++) { ImGui::SameLine(x + cell_width * n); - uint8_t val = win->dasm.bin_buf[n]; + uint8_t val = win->dasm_line.bytes[n]; ImGui::Text("%02X ", val); } x += cell_width * 4; @@ -580,7 +642,7 @@ static void _ui_dbg_history_draw(ui_dbg_t* win) { /* disassembled instruction */ x += glyph_width * 4; ImGui::SameLine(x); - ImGui::Text("%s", win->dasm.str_buf); + ImGui::Text("%s", win->dasm_line.bytes); /* tick count */ x += glyph_width * 17; @@ -642,8 +704,7 @@ static int _ui_dbg_eval_op_breakpoints(ui_dbg_t* win, int trap_id, uint16_t pc) #endif break; } - } - else { + } else { for (int i = 0; (i < win->dbg.num_breakpoints) && (trap_id == 0); i++) { const ui_dbg_breakpoint_t* bp = &win->dbg.breakpoints[i]; if (bp->enabled) { @@ -767,8 +828,7 @@ static bool _ui_dbg_bp_add_exec(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = 0; bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -784,8 +844,7 @@ static bool _ui_dbg_bp_add_byte(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = _ui_dbg_read_byte(win, addr); bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -801,8 +860,7 @@ static bool _ui_dbg_bp_add_word(ui_dbg_t* win, bool enabled, uint16_t addr) { bp->val = _ui_dbg_read_word(win, addr); bp->enabled = enabled; return true; - } - else { + } else { /* no more breakpoint slots */ return false; } @@ -835,8 +893,7 @@ static void _ui_dbg_bp_toggle_exec(ui_dbg_t* win, uint16_t addr) { if (index >= 0) { /* breakpoint already exists, remove */ _ui_dbg_bp_del(win, index); - } - else { + } else { /* breakpoint doesn't exist, add a new one */ _ui_dbg_bp_add_exec(win, true, addr); } @@ -933,8 +990,7 @@ static void _ui_dbg_bp_draw(ui_dbg_t* win) { if (ImGui::IsItemHovered()) { if (bp->enabled) { ImGui::SetTooltip("Disable Breakpoint"); - } - else { + } else { ImGui::SetTooltip("Enable Breakpoint"); } } @@ -986,8 +1042,7 @@ static void _ui_dbg_bp_draw(ui_dbg_t* win) { if (bt->show_val8) { ImGui::SameLine(); bp->val = (int) ui_util_input_u8("##byte", (uint8_t)bp->val); - } - else { + } else { ImGui::SameLine(); bp->val = (int) ui_util_input_u16("##word", (uint16_t)bp->val); } @@ -1034,14 +1089,14 @@ static void _ui_dbg_heatmap_init(ui_dbg_t* win) { win->heatmap.tex_height = 256; win->heatmap.tex_width_uicombo_state = 4; win->heatmap.next_tex_width = win->heatmap.tex_width; - win->heatmap.texture = win->create_texture_cb(win->heatmap.tex_width, win->heatmap.tex_height); + win->heatmap.texture = win->texture_cbs.create_cb(win->heatmap.tex_width, win->heatmap.tex_height); win->heatmap.show_ops = win->heatmap.show_reads = win->heatmap.show_writes = true; win->heatmap.scale = 1; win->heatmap.autoclear_interval = 0; /* 0 means: no autoclear */ } static void _ui_dbg_heatmap_discard(ui_dbg_t* win) { - win->destroy_texture_cb(win->heatmap.texture); + win->texture_cbs.destroy_cb(win->heatmap.texture); } static void _ui_dbg_heatmap_update_texture_size(ui_dbg_t* win, int new_width) { @@ -1049,8 +1104,8 @@ static void _ui_dbg_heatmap_update_texture_size(ui_dbg_t* win, int new_width) { if (new_width != win->heatmap.tex_width) { win->heatmap.tex_width = new_width; win->heatmap.tex_height = (1<<16) / new_width; - win->destroy_texture_cb(win->heatmap.texture); - win->heatmap.texture = win->create_texture_cb(win->heatmap.tex_width, win->heatmap.tex_height); + win->texture_cbs.destroy_cb(win->heatmap.texture); + win->heatmap.texture = win->texture_cbs.create_cb(win->heatmap.tex_width, win->heatmap.tex_height); } } @@ -1070,19 +1125,13 @@ static void _ui_dbg_heatmap_clear_all(ui_dbg_t* win) { static void _ui_dbg_heatmap_clear_rw(ui_dbg_t* win) { for (int i = 0; i < (1<<16); i++) { - win->heatmap.items[i].read_count = 0; - win->heatmap.items[i].write_count = 0; + win->heatmap.items[i].state &= ~(UI_DBG_HEATMAP_ITEM_READ|UI_DBG_HEATMAP_ITEM_WRITE); } } static void _ui_dbg_heatmap_record_op(ui_dbg_t* win, uint16_t pc) { // record per-op heatmap events - win->heatmap.items[pc].op_count++; - win->heatmap.items[pc].op_start = 0; - int op_len = _ui_dbg_disasm_len(win, pc); - for (int i = 1; i < op_len; i++) { - win->heatmap.items[(pc + i) & 0xFFFF].op_start = pc; - } + win->heatmap.items[pc].state |= UI_DBG_HEATMAP_ITEM_OPCODE; // update last instruction's ticks win->heatmap.items[win->dbg.cur_op_pc].ticks = win->dbg.cur_op_ticks; } @@ -1091,60 +1140,58 @@ static void _ui_dbg_heatmap_record_tick(ui_dbg_t* win, uint64_t pins) { #if defined(UI_DBG_USE_Z80) if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_RD)) { const uint16_t addr = Z80_GET_ADDR(pins); - win->heatmap.items[addr].read_count++; - } - else if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_WR)) { + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_READ; + } else if ((pins & Z80_CTRL_PIN_MASK) == (Z80_MREQ|Z80_WR)) { const uint16_t addr = Z80_GET_ADDR(pins); - win->heatmap.items[addr].write_count++; + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_WRITE; } #elif defined(UI_DBG_USE_M6502) const uint16_t addr = M6502_GET_ADDR(pins); if (0 != (pins & M6502_RW)) { - win->heatmap.items[addr].read_count++; - } - else { - win->heatmap.items[addr].write_count++; + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_READ; + } else { + win->heatmap.items[addr].state |= UI_DBG_HEATMAP_ITEM_WRITE; } #endif } +static inline bool _ui_dbg_heatmap_is_opcode(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_OPCODE); +} + +static inline bool _ui_dbg_heatmap_is_read(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_READ); +} + +static inline bool _ui_dbg_heatmap_is_write(ui_dbg_t* win, uint16_t addr) { + return 0 != (win->heatmap.items[addr].state & UI_DBG_HEATMAP_ITEM_WRITE); +} + static void _ui_dbg_heatmap_update(ui_dbg_t* win) { const int frame_chunk_height = 64; - int y0 = win->heatmap.cur_y; - int y1 = win->heatmap.cur_y + frame_chunk_height; + const int y0 = win->heatmap.cur_y; + const int y1 = win->heatmap.cur_y + frame_chunk_height; win->heatmap.cur_y = (y0 + frame_chunk_height) & 255; for (int y = y0; y < y1; y++) { for (int x = 0; x < 256; x++) { - int i = y * 256 + x; + const int i = y * 256 + x; uint32_t p = 0; if (_ui_dbg_get_pc(win) == i) { p |= 0xFF00FFFF; } - if (win->heatmap.show_ops && (win->heatmap.items[i].op_count > 0)) { - uint32_t r = 0x80 + (win->heatmap.items[i].op_count>>8); - if (r > 0xFF) { r = 0xFF; } - p |= 0xFF000000 | r; + if (win->heatmap.show_ops && _ui_dbg_heatmap_is_opcode(win, (uint16_t)i)) { + p |= 0xFF0000FF; } - if (win->heatmap.show_ops && (win->heatmap.items[i].op_start != 0)) { - /* opcode followup byte */ - uint32_t r = 0x80 + (win->heatmap.items[win->heatmap.items[i].op_start].op_count>>8); - if (r > 0xFF) { r = 0xFF; } - p |= 0xFF000000 | r; + if (win->heatmap.show_writes && _ui_dbg_heatmap_is_write(win, (uint16_t)i)) { + p |= 0xFF008800; } - if (win->heatmap.show_writes && (win->heatmap.items[i].write_count > 0)) { - uint32_t g = 0x80 + (win->heatmap.items[i].write_count>>8); - if (g > 0xFF) { g = 0xFF; } - p |= 0xFF000000 | (g<<8); - } - if (win->heatmap.show_reads && (win->heatmap.items[i].read_count > 0)) { - uint32_t b = 0x80 + (win->heatmap.items[i].read_count>>8); - if (b > 0xFF) { b = 0xFF; } - p |= 0xFF000000 | (b<<16); + if (win->heatmap.show_reads && _ui_dbg_heatmap_is_read(win, (uint16_t)i)) { + p |= 0xFF880000; } win->heatmap.pixels[i] = p; } } - win->update_texture_cb(win->heatmap.texture, win->heatmap.pixels, 256*256*4); + win->texture_cbs.update_cb(win->heatmap.texture, win->heatmap.pixels, 256*256*4); } static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { @@ -1204,17 +1251,12 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { int x = (int)((mouse_pos.x - screen_pos.x) / hm->scale); int y = (int)((mouse_pos.y - screen_pos.y) / hm->scale); uint16_t addr = y * hm->tex_width + x; - if (hm->items[addr].op_start != 0) { - /* address is actually an opcode followup byte, reset to start of instruction */ - addr = hm->items[addr].op_start; - } if (ImGui::IsItemHovered()) { - if (hm->items[addr].op_count > 0) { + if (_ui_dbg_heatmap_is_opcode(win, addr)) { _ui_dbg_disasm(win, addr); ImGui::SetTooltip("%04X: %s (ticks: %d)\n(right-click for options)", - addr, win->dasm.str_buf, hm->items[addr].ticks); - } - else { + addr, win->dasm_line.chars, hm->items[addr].ticks); + } else { ImGui::SetTooltip("%04X: %02X %02X %02X %02X\n(right-click for options)", addr, _ui_dbg_read_byte(win, addr), _ui_dbg_read_byte(win, addr+1), @@ -1249,8 +1291,7 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { } } ImGui::EndPopup(); - } - else { + } else { hm->popup_addr_valid = false; } ImGui::EndChild(); @@ -1258,6 +1299,57 @@ static void _ui_dbg_heatmap_draw(ui_dbg_t* win) { ImGui::End(); } +/*== STOPWATCH WINDOW ========================================================*/ +static void _ui_dbg_stopwatch_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { + memset(&win->stopwatch, 0, sizeof(win->stopwatch)); + win->stopwatch.freq_hz = desc->freq_hz; + win->stopwatch.scanline_ticks = desc->scanline_ticks; + win->stopwatch.frame_ticks = desc->frame_ticks; +} + +static void _ui_dbg_stopwatch_reset(ui_dbg_t* win) { + win->stopwatch.cur_ticks = 0; + for (int i = 0; i < UI_DBG_STOPWATCH_NUM; i++) { + win->stopwatch.start_ticks[i] = 0; + } +} + +static void _ui_dbg_stopwatch_draw(ui_dbg_t* win) { + if (!win->ui.show_stopwatch) { + return; + } + ImGui::SetNextWindowPos(ImVec2(win->ui.init_x + win->ui.init_w, win->ui.init_y), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(-1, -1), ImGuiCond_Once); + if (ImGui::Begin("Stopwatch", &win->ui.show_stopwatch)) { + for (int i = 0; i < UI_DBG_STOPWATCH_NUM; i++) { + ImGui::PushID(i); + if (ImGui::Button("Reset")) { + win->stopwatch.start_ticks[i] = win->stopwatch.cur_ticks; + } + ImGui::SameLine(); + uint64_t cycle_count = win->stopwatch.cur_ticks - win->stopwatch.start_ticks[i]; + double ms = -1.0; + double raster_lines = -1.0; + double frames = -1.0; + if (win->stopwatch.freq_hz > 0) { + ms = ((double)cycle_count / (double)win->stopwatch.freq_hz) * 1000.0; + } + if (win->stopwatch.scanline_ticks > 0) { + raster_lines = (double)cycle_count / (double)win->stopwatch.scanline_ticks; + } + if (win->stopwatch.frame_ticks > 0) { + frames = (double)cycle_count / (double)win->stopwatch.frame_ticks; + } + ImGui::Text("%llu ticks", cycle_count); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("milliseconds: %.3f\nraster lines: %.3f\nframes: %.3f\n", ms, raster_lines, frames); + } + ImGui::PopID(); + } + } + ImGui::End(); +} + /*== UI HELPERS ==============================================================*/ static void _ui_dbg_uistate_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { ui_dbg_uistate_t* ui = &win->ui; @@ -1316,8 +1408,7 @@ static void _ui_dbg_uistate_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { if (desc->user_breaktypes[j].label) { ui->breaktypes[i] = desc->user_breaktypes[j]; ui->breaktype_combo_labels[i] = ui->breaktypes[i].label; - } - else { + } else { break; } } @@ -1340,7 +1431,7 @@ static void _ui_dbg_draw_menu(ui_dbg_t* win) { _ui_dbg_break(win); } if (ImGui::MenuItem("Continue", win->ui.keys.cont.name, false, win->dbg.stopped)) { - _ui_dbg_continue(win); + _ui_dbg_continue(win, true); } if (ImGui::MenuItem("Step Over", win->ui.keys.step_over.name, false, win->dbg.stopped)) { _ui_dbg_step_over(win); @@ -1377,9 +1468,10 @@ static void _ui_dbg_draw_menu(ui_dbg_t* win) { if (ImGui::BeginMenu("Show")) { ImGui::MenuItem("Memory Heatmap", 0, &win->ui.show_heatmap); ImGui::MenuItem("Execution History", 0, &win->ui.show_history); + ImGui::MenuItem("Breakpoints", 0, &win->ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &win->ui.show_stopwatch); ImGui::MenuItem("Registers", 0, &win->ui.show_regs); ImGui::MenuItem("Button Bar", 0, &win->ui.show_buttons); - ImGui::MenuItem("Breakpoints", 0, &win->ui.show_breakpoints); ImGui::MenuItem("Opcode Bytes", 0, &win->ui.show_bytes); ImGui::MenuItem("Opcode Ticks", 0, &win->ui.show_ticks); ImGui::EndMenu(); @@ -1431,9 +1523,9 @@ void _ui_dbg_draw_regs(ui_dbg_t* win) { char f_str[9] = { (c->f & Z80_SF) ? 'S':'-', (c->f & Z80_ZF) ? 'Z':'-', - (c->f & Z80_YF) ? 'X':'-', + (c->f & Z80_YF) ? 'Y':'-', (c->f & Z80_HF) ? 'H':'-', - (c->f & Z80_XF) ? 'Y':'-', + (c->f & Z80_XF) ? 'X':'-', (c->f & Z80_VF) ? 'V':'-', (c->f & Z80_NF) ? 'N':'-', (c->f & Z80_CF) ? 'C':'-', @@ -1483,7 +1575,7 @@ static void _ui_dbg_handle_input(ui_dbg_t* win) { if (win->dbg.stopped) { if (0 != win->ui.keys.cont.keycode) { if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.cont.keycode)) { - _ui_dbg_continue(win); + _ui_dbg_continue(win, true); } } if (0 != win->ui.keys.step_over.keycode) { @@ -1501,8 +1593,7 @@ static void _ui_dbg_handle_input(ui_dbg_t* win) { _ui_dbg_step_tick(win); } } - } - else { + } else { if (ImGui::IsKeyPressed((ImGuiKey)win->ui.keys.stop.keycode)) { _ui_dbg_break(win); } @@ -1520,7 +1611,7 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { if (win->dbg.stopped || (win->dbg.step_mode != UI_DBG_STEPMODE_NONE)) { snprintf(str, sizeof(str), "Continue (%s)", _ui_dbg_str_or_def(win->ui.keys.cont.name, "-")); if (ImGui::Button(str)) { - _ui_dbg_continue(win); + _ui_dbg_continue(win, true); } ImGui::SameLine(); snprintf(str, sizeof(str), "Over (%s)", _ui_dbg_str_or_def(win->ui.keys.step_over.name, "-")); @@ -1537,8 +1628,7 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { if (ImGui::Button(str)) { _ui_dbg_step_tick(win); } - } - else { + } else { snprintf(str, sizeof(str), "Break (%s)", _ui_dbg_str_or_def(win->ui.keys.stop.name, "-")); if (ImGui::Button(str)) { _ui_dbg_break(win); @@ -1547,6 +1637,49 @@ static void _ui_dbg_draw_buttons(ui_dbg_t* win) { ImGui::Separator(); } +/* helper function for backward scanning disassembly, tries to find + a known op in the previous 4 bytes, returns true if a known op + was found + +*/ +typedef struct { + bool is_known_op; + uint16_t addr; +} _ui_dbg_disasm_backscan_result_t; + +static _ui_dbg_disasm_backscan_result_t _ui_dbg_disasm_backscan(ui_dbg_t* win, uint16_t addr) { + bool is_known_op = false; + uint16_t bs_addr = addr - 1; + uint16_t scan_addr = bs_addr; + for (int i = 0; i < 4; i++, scan_addr--) { + if (_ui_dbg_heatmap_is_opcode(win, scan_addr)) { + // Z80: prefixed instruction? + #if defined(UI_DBG_USE_Z80) + uint16_t prev_addr = scan_addr - 1; + if (_ui_dbg_heatmap_is_opcode(win, prev_addr)) { + uint8_t maybe_prefix = _ui_dbg_read_byte(win, prev_addr); + if ((maybe_prefix == 0xCB) || (maybe_prefix == 0xDD) || (maybe_prefix == 0xED) || (maybe_prefix == 0xFD)) { + scan_addr = prev_addr; + // NOTE: we don't need a separate code path to check for double prefix FD/DD CB, since + // in such a case the CB opcode byte will be the regular opcode byte followed by + // a no-op 8-bit immediate value + } + } + #endif + // found an op start, if any unknown bytes had been skipped, ignore the op + // (it will be found again in the next iteration) + _ui_dbg_disasm(win, scan_addr); + if ((int)(win->dasm_line.num_bytes - 1) == i) { + // ok, no gap bytes, break with 'found_op' status + bs_addr = scan_addr; + is_known_op = true; + } + break; + } + } + return { is_known_op, bs_addr }; +} + /* this updates the line array currently visualized by the disassembler listing, this only happens when the PC is outside the visible area or when the memory content 'under' the line array changes @@ -1555,21 +1688,19 @@ static void _ui_dbg_update_line_array(ui_dbg_t* win, uint16_t addr) { /* one half is backtraced from current PC, the other half is 'forward tracked' from current PC */ - uint16_t bt_addr = addr; - int i; - for (i = 0; i < UI_DBG_NUM_BACKTRACE_LINES; i++) { - bt_addr -= 1; - if (win->heatmap.items[bt_addr].op_start) { - bt_addr = win->heatmap.items[bt_addr].op_start; - } - int bt_index = UI_DBG_NUM_BACKTRACE_LINES - i - 1; - win->ui.line_array[bt_index].addr = bt_addr; - win->ui.line_array[bt_index].val = _ui_dbg_read_byte(win, bt_addr); - } - for (; i < UI_DBG_NUM_LINES; i++) { - win->ui.line_array[i].addr = addr; + uint16_t bs_addr = addr; + int line_idx; + for (line_idx = 0; line_idx < UI_DBG_NUM_BACKTRACE_LINES; line_idx++) { + // scan backwards for op start in blocks of 4 bytes (== max length of an instruction) + bs_addr = _ui_dbg_disasm_backscan(win, bs_addr).addr; + const int bs_index = UI_DBG_NUM_BACKTRACE_LINES - line_idx - 1; + win->ui.line_array[bs_index].addr = bs_addr; + win->ui.line_array[bs_index].val = _ui_dbg_read_byte(win, bs_addr); + } + for (; line_idx < UI_DBG_NUM_LINES; line_idx++) { + win->ui.line_array[line_idx].addr = addr; addr = _ui_dbg_disasm(win, addr); - win->ui.line_array[i].val = win->dasm.bin_buf[0]; + win->ui.line_array[line_idx].val = win->dasm_line.bytes[0]; } } @@ -1586,11 +1717,9 @@ static bool _ui_dbg_addr_inside_line_array(ui_dbg_t* win, uint16_t addr) { uint16_t last_addr = win->ui.line_array[UI_DBG_NUM_LINES-16].addr; if (first_addr == last_addr) { return false; - } - else if (first_addr < last_addr) { + } else if (first_addr < last_addr) { return (first_addr <= addr) && (addr <= last_addr); - } - else { + } else { /* address wraps around in line_addrs array */ return (first_addr <= addr) || (addr <= last_addr); } @@ -1645,9 +1774,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { bool in_safe_area = (line_i >= (clipper.DisplayStart+safe_lines)) && (line_i <= (clipper.DisplayEnd-safe_lines)); bool is_pc_line = (addr == pc); if (is_pc_line && - (force_scroll || - (!in_safe_area && win->ui.request_scroll) || - (!in_safe_area && !win->dbg.stopped))) + (force_scroll || + (!in_safe_area && win->ui.request_scroll) || + (!in_safe_area && !win->dbg.stopped))) { win->ui.request_scroll = false; int scroll_to_line = line_i - safe_lines - 2; @@ -1666,12 +1795,11 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { bool visible_line = (line_i >= clipper.DisplayStart) && (line_i < clipper.DisplayEnd); uint16_t addr = win->ui.line_array[line_i].addr; bool is_pc_line = (addr == pc); - bool show_dasm = (line_i >= UI_DBG_NUM_BACKTRACE_LINES) || (win->heatmap.items[addr].op_count > 0); + bool show_dasm = (line_i >= UI_DBG_NUM_BACKTRACE_LINES) || _ui_dbg_heatmap_is_opcode(win, addr); const uint16_t start_addr = addr; if (show_dasm) { addr = _ui_dbg_disasm(win, addr); - } - else { + } else { addr++; } const int num_bytes = addr - start_addr; @@ -1682,10 +1810,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { } /* show data bytes or potential but not verified instructions as dimmed */ - if ((win->heatmap.items[start_addr].op_count > 0) || (start_addr == pc)) { + if (_ui_dbg_heatmap_is_opcode(win, start_addr) || (start_addr == pc)) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_Text]); - } - else { + } else { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); } @@ -1707,8 +1834,7 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { ImU32 bp_color = _ui_dbg_bp_enabled(win, bp_index) ? bp_enabled_color : bp_disabled_color; dl->AddCircleFilled(mid, 7, bp_color); dl->AddCircle(mid, 7, brd_color); - } - else if (ImGui::IsItemHovered()) { + } else if (ImGui::IsItemHovered()) { dl->AddCircle(mid, 7, bp_enabled_color); } /* current PC/step cursor */ @@ -1722,11 +1848,9 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { /* draw a separation line (for PC, breakpoint or control-flow ops) */ if (is_pc_line) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), pc_color); - } - else if (bp_index >= 0) { + } else if (bp_index >= 0) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), bp_disabled_color); - } - else if (show_dasm && _ui_dbg_is_controlflow_op(win->dasm.bin_buf[0], win->dasm.bin_buf[1])) { + } else if (show_dasm && _ui_dbg_is_controlflow_op(win->dasm_line.bytes[0], win->dasm_line.bytes[1])) { dl->AddLine(ImVec2(pos.x+16,base_y), ImVec2(pos.x+2048,base_y), ctrlflow_color); } @@ -1742,9 +1866,8 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { ImGui::SameLine(x + cell_width*n); uint8_t val; if (show_dasm) { - val = win->dasm.bin_buf[n]; - } - else { + val = win->dasm_line.bytes[n]; + } else { val = _ui_dbg_read_byte(win, start_addr+n); } ImGui::Text("%02X ", val); @@ -1756,9 +1879,8 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { x += glyph_width * 4; ImGui::SameLine(x); if (show_dasm) { - ImGui::Text("%s", win->dasm.str_buf); - } - else { + ImGui::Text("%s", win->dasm_line.chars); + } else { ImGui::Text(" "); } @@ -1770,20 +1892,16 @@ static void _ui_dbg_draw_main(ui_dbg_t* win) { if (ticks > 0) { if (is_pc_line) { ImGui::Text("%2d/%d", win->dbg.cur_op_ticks, ticks); - } - else { + } else { ImGui::Text(" %d", ticks); } - } - else if (show_dasm) { + } else if (show_dasm) { if (is_pc_line) { ImGui::Text("%2d/?", win->dbg.cur_op_ticks); - } - else { + } else { ImGui::Text("?"); } - } - else { + } else { ImGui::Text(" "); } } @@ -1825,13 +1943,13 @@ void ui_dbg_init(ui_dbg_t* win, ui_dbg_desc_t* desc) { win->read_cb = desc->read_cb; win->read_layer = desc->read_layer; win->break_cb = desc->break_cb; - win->create_texture_cb = desc->texture_cbs.create_cb; - win->update_texture_cb = desc->texture_cbs.update_cb; - win->destroy_texture_cb = desc->texture_cbs.destroy_cb; + win->texture_cbs = desc->texture_cbs; + win->debug_cbs = desc->debug_cbs; win->user_data = desc->user_data; _ui_dbg_dbgstate_init(win, desc); _ui_dbg_uistate_init(win, desc); _ui_dbg_heatmap_init(win); + _ui_dbg_stopwatch_init(win, desc); } void ui_dbg_discard(ui_dbg_t* win) { @@ -1846,6 +1964,10 @@ void ui_dbg_reset(ui_dbg_t* win) { _ui_dbg_uistate_reset(win); _ui_dbg_heatmap_reset(win); _ui_dbg_history_reset(win); + _ui_dbg_stopwatch_reset(win); + if (win->debug_cbs.reset_cb) { + win->debug_cbs.reset_cb(); + } } void ui_dbg_reboot(ui_dbg_t* win) { @@ -1854,6 +1976,9 @@ void ui_dbg_reboot(ui_dbg_t* win) { _ui_dbg_uistate_reboot(win); _ui_dbg_heatmap_reboot(win); _ui_dbg_history_reboot(win); + if (win->debug_cbs.reboot_cb) { + win->debug_cbs.reboot_cb(); + } } void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { @@ -1879,14 +2004,21 @@ void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { trap_id = _ui_dbg_eval_tick_breakpoints(win, trap_id, pins); } _ui_dbg_heatmap_record_tick(win, pins); + win->stopwatch.cur_ticks++; win->dbg.cur_op_ticks++; win->dbg.last_tick_pins = pins; if (trap_id >= UI_DBG_STEP_TRAPID) { win->dbg.stopped = true; + if (win->debug_cbs.stopped_cb) { + int stop_reason = (win->dbg.step_mode == UI_DBG_STEPMODE_NONE) ? UI_DBG_STOP_REASON_BREAKPOINT : UI_DBG_STOP_REASON_STEP; + win->debug_cbs.stopped_cb(stop_reason, win->dbg.cur_op_pc); + } win->dbg.step_mode = UI_DBG_STEPMODE_NONE; - ImGui::SetWindowFocus(win->ui.title); - win->ui.open = true; + if (!win->dbg.external_debugger_connected) { + ImGui::SetWindowFocus(win->ui.title); + win->ui.open = true; + } } win->dbg.last_trap_id = trap_id; } @@ -1894,12 +2026,118 @@ void ui_dbg_tick(ui_dbg_t* win, uint64_t pins) { void ui_dbg_draw(ui_dbg_t* win) { CHIPS_ASSERT(win && win->valid && win->ui.title); win->dbg.frame_id++; - if (!(win->ui.open || win->ui.show_heatmap || win->ui.show_breakpoints || win->ui.show_history)) { + if (!(win->ui.open || win->ui.show_heatmap || win->ui.show_breakpoints || win->ui.show_history || win->ui.show_stopwatch)) { return; } _ui_dbg_dbgwin_draw(win); _ui_dbg_heatmap_draw(win); _ui_dbg_history_draw(win); _ui_dbg_bp_draw(win); + _ui_dbg_stopwatch_draw(win); +} + +void ui_dbg_external_debugger_connected(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + win->dbg.external_debugger_connected = true; +} + +void ui_dbg_external_debugger_disconnected(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + win->dbg.external_debugger_connected = false; + // delete all breakpoints and continue execution (in case of stopped) + _ui_dbg_bp_delete_all(win); + _ui_dbg_continue(win, false); +} + +void ui_dbg_add_breakpoint(ui_dbg_t* win, uint16_t addr) { + CHIPS_ASSERT(win && win->valid && win->ui.title); + int index = _ui_dbg_bp_find(win, UI_DBG_BREAKTYPE_EXEC, addr); + if (index < 0) { + _ui_dbg_bp_add_exec(win, true, addr); + } } + +void ui_dbg_remove_breakpoint(ui_dbg_t* win, uint16_t addr) { + CHIPS_ASSERT(win && win->valid && win->ui.title); + int index = _ui_dbg_bp_find(win, UI_DBG_BREAKTYPE_EXEC, addr); + if (index >= 0) { + _ui_dbg_bp_del(win, index); + } +} + +void ui_dbg_break(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_break(win); +} + +void ui_dbg_continue(ui_dbg_t* win, bool invoke_continued_cb) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_continue(win, invoke_continued_cb); +} + +bool ui_dbg_stopped(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + return win->dbg.stopped; +} + +void ui_dbg_step_next(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_step_over(win); +} + +void ui_dbg_step_into(ui_dbg_t* win) { + CHIPS_ASSERT(win && win->valid); + _ui_dbg_step_into(win); +} + +void ui_dbg_disassemble(ui_dbg_t* win, const ui_dbg_dasm_request_t* request) { + CHIPS_ASSERT(win && win->valid); + CHIPS_ASSERT(request); + CHIPS_ASSERT(request->out_lines); + CHIPS_ASSERT(request->num_lines > 0); + + // NOTE: this code uses the same strategy as _ui_dbg_update_line_array() + // it will first scan backward and look for known instructions + // in the heatmap, and then scan forward + uint16_t bs_addr = request->addr; + int line_idx = 0; + // optional backwards scan + if (request->offset_lines < 0) { + const int num_backtrace_lines = -request->offset_lines; + for (; line_idx < num_backtrace_lines; line_idx++) { + // scan backwards for op start in block of 4 bytes (== max length of instruction) + const _ui_dbg_disasm_backscan_result_t res = _ui_dbg_disasm_backscan(win, bs_addr); + bs_addr = res.addr; + const int bs_line_idx = num_backtrace_lines - line_idx - 1; + if (res.is_known_op) { + _ui_dbg_disasm(win, bs_addr); + request->out_lines[bs_line_idx] = win->dasm_line; + } else { + uint8_t byte = _ui_dbg_read_byte(win, bs_addr); + ui_dbg_dasm_line_t* l = &request->out_lines[bs_line_idx]; + memset(l, 0, sizeof(ui_dbg_dasm_line_t)); + l->addr = bs_addr; + l->num_bytes = 1; + l->bytes[0] = byte; + l->num_chars = 3; + l->chars[0] = '?'; + l->chars[1] = '?'; + l->chars[2] = '?'; + } + } + } + + uint16_t fwd_addr = request->addr; + // if the offset is > 0, skip disassembled instructions + if (request->offset_lines > 0) { + for (int i = 0; i < request->offset_lines; i++) { + fwd_addr = _ui_dbg_disasm(win, fwd_addr); + } + } + for (; line_idx < request->num_lines; line_idx++) { + fwd_addr = _ui_dbg_disasm(win, fwd_addr); + request->out_lines[line_idx] = win->dasm_line; + } +} + #endif /* CHIPS_UI_IMPL */ diff --git a/ui/ui_kc85.h b/ui/ui_kc85.h index ed5919b7..d665776b 100644 --- a/ui/ui_kc85.h +++ b/ui/ui_kc85.h @@ -79,6 +79,7 @@ typedef struct { kc85_t* kc85; ui_kc85_boot_t boot_cb; // user-provided callback to reboot ui_dbg_texture_callbacks_t dbg_texture; // user-provided texture create/update/destroy callbacks + ui_dbg_debug_callbacks_t dbg_debug; // user-provided debugger callbacks ui_dbg_keys_desc_t dbg_keys; // user-defined hotkeys for ui_dbg_t ui_snapshot_desc_t snapshot; // snapshot system creation params } ui_kc85_desc_t; @@ -149,6 +150,7 @@ static void _ui_kc85_draw_menu(ui_kc85_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { @@ -332,8 +334,15 @@ static uint8_t _ui_kc85_mem_read(int layer, uint16_t addr, void* user_data) { kc85_t* kc85 = (kc85_t*) user_data; if (layer == 0) { return mem_rd(&kc85->mem, addr); - } - else { + } else if ((layer >= 4) && (layer < 8)) { + // IRM access + if ((addr >= 0x8000) && (addr < 0xC000)) { + return kc85->ram[KC85_IRM0_PAGE + (layer-4)][addr - 0x8000]; + } else { + return 0xFF; + } + } else { + // Motherboard, SLOT 08, SLOT 0C return mem_layer_rd(&kc85->mem, layer-1, addr); } } @@ -343,8 +352,12 @@ static void _ui_kc85_mem_write(int layer, uint16_t addr, uint8_t data, void* use kc85_t* kc85 = (kc85_t*) user_data; if (layer == 0) { mem_wr(&kc85->mem, addr, data); - } - else { + } else if ((layer >= 4) && (layer < 8)) { + // IRM access + if ((addr >= 0x8000) && (addr < 0xC000)) { + kc85->ram[KC85_IRM0_PAGE + (layer-4)][addr - 0x8000] = data; + } + } else { mem_layer_wr(&kc85->mem, layer-1, addr, data); } } @@ -363,8 +376,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.x = x; desc.y = y; desc.z80 = &ui->kc85->cpu; + desc.freq_hz = KC85_FREQUENCY; + desc.scanline_ticks = KC85_SCANLINE_TICKS; + desc.frame_ticks = KC85_SCANLINE_TICKS * KC85_NUM_SCANLINES; desc.read_cb = _ui_kc85_mem_read; desc.texture_cbs = ui_desc->dbg_texture; + desc.debug_cbs = ui_desc->dbg_debug; desc.keys = ui_desc->dbg_keys; desc.user_data = ui->kc85; ui_dbg_init(&ui->dbg, &desc); @@ -425,6 +442,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.layers[1] = "Motherboard"; desc.layers[2] = "Slot 08"; desc.layers[3] = "Slot 0C"; + #if defined(CHIPS_KC85_TYPE_4) + desc.layers[4] = "IRM 0 Pixels"; + desc.layers[5] = "IRM 0 Colors"; + desc.layers[6] = "IRM 1 Pixels"; + desc.layers[7] = "IRM 1 Colors"; + #endif desc.read_cb = _ui_kc85_mem_read; desc.write_cb = _ui_kc85_mem_write; desc.user_data = ui->kc85; @@ -449,6 +472,12 @@ void ui_kc85_init(ui_kc85_t* ui, const ui_kc85_desc_t* ui_desc) { desc.layers[1] = "Motherboard"; desc.layers[2] = "Slot 08"; desc.layers[3] = "Slot 0C"; + #if defined(CHIPS_KC85_TYPE_4) + desc.layers[4] = "IRM 0 Pixels"; + desc.layers[5] = "IRM 0 Colors"; + desc.layers[6] = "IRM 1 Pixels"; + desc.layers[7] = "IRM 1 Colors"; + #endif desc.cpu_type = UI_DASM_CPUTYPE_Z80; desc.start_addr = 0xF000; desc.read_cb = _ui_kc85_mem_read; diff --git a/ui/ui_kc85sys.h b/ui/ui_kc85sys.h index f6360bee..b9b3096e 100644 --- a/ui/ui_kc85sys.h +++ b/ui/ui_kc85sys.h @@ -166,12 +166,12 @@ void ui_kc85sys_draw(ui_kc85sys_t* win) { #if defined(CHIPS_KC85_TYPE_4) if (ImGui::CollapsingHeader("Port 84h", ImGuiTreeNodeFlags_DefaultOpen)) { const uint8_t v = win->kc85->io84; - ImGui::Text("0: Show image %d", (v & KC85_IO84_SEL_VIEW_IMG) ? 0:1); - ImGui::Text("1: Access %s", (v & KC85_IO84_SEL_CPU_COLOR) ? "PIXELS":"COLORS"); - ImGui::Text("2: Access image %d", (v & KC85_IO84_SEL_CPU_IMG) ? 0:1); + ImGui::Text("0: Show image %d", (v & KC85_IO84_SEL_VIEW_IMG) ? 1:0); + ImGui::Text("1: Access %s", (v & KC85_IO84_SEL_CPU_COLOR) ? "COLORS":"PIXELS"); + ImGui::Text("2: Access image %d", (v & KC85_IO84_SEL_CPU_IMG) ? 1:0); ImGui::Text("3: Hicolor mode %s", (v & KC85_IO84_HICOLOR) ? "OFF":"ON"); - ImGui::Text("4: RAM8 block %d", (v & KC85_IO84_SEL_RAM8) ? 0:1); - ImGui::Text("5: RAM8 ??? %d", (v & KC85_IO84_BLOCKSEL_RAM8) ? 0:1); + ImGui::Text("4: RAM8 block %d", (v & KC85_IO84_SEL_RAM8) ? 1:0); + ImGui::Text("5: RAM8 ??? %d", (v & KC85_IO84_BLOCKSEL_RAM8) ? 1:0); ImGui::Text("6..7: unused"); } if (ImGui::CollapsingHeader("Port 86h", ImGuiTreeNodeFlags_DefaultOpen)) { diff --git a/ui/ui_lc80.h b/ui/ui_lc80.h index b944279c..b46c34f5 100644 --- a/ui/ui_lc80.h +++ b/ui/ui_lc80.h @@ -727,6 +727,7 @@ static void _ui_lc80_draw_menu(ui_lc80_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->win.dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->win.dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->win.dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->win.dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->win.dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_namco.h b/ui/ui_namco.h index 26721001..9725403b 100644 --- a/ui/ui_namco.h +++ b/ui/ui_namco.h @@ -317,6 +317,7 @@ static void _ui_namco_draw_menu(ui_namco_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_vic20.h b/ui/ui_vic20.h index 4df0f6c9..3bd8137f 100644 --- a/ui/ui_vic20.h +++ b/ui/ui_vic20.h @@ -163,6 +163,7 @@ static void _ui_vic20_draw_menu(ui_vic20_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_z1013.h b/ui/ui_z1013.h index 7a780c87..28991ad5 100644 --- a/ui/ui_z1013.h +++ b/ui/ui_z1013.h @@ -137,6 +137,7 @@ static void _ui_z1013_draw_menu(ui_z1013_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_z80.h b/ui/ui_z80.h index d24e28c2..ab8787d1 100644 --- a/ui/ui_z80.h +++ b/ui/ui_z80.h @@ -8,11 +8,11 @@ ~~~C #define CHIPS_UI_IMPL ~~~ - before you include this file in *one* C++ file to create the + before you include this file in *one* C++ file to create the implementation. Optionally provide the following macros with your own implementation - + ~~~C CHIPS_ASSERT(c) ~~~ @@ -47,7 +47,7 @@ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source - distribution. + distribution. #*/ #include #include @@ -134,9 +134,9 @@ static void _ui_z80_regs(ui_z80_t* win) { char f_str[9] = { (f & Z80_SF) ? 'S':'-', (f & Z80_ZF) ? 'Z':'-', - (f & Z80_YF) ? 'X':'-', + (f & Z80_YF) ? 'Y':'-', (f & Z80_HF) ? 'H':'-', - (f & Z80_XF) ? 'Y':'-', + (f & Z80_XF) ? 'X':'-', (f & Z80_VF) ? 'V':'-', (f & Z80_NF) ? 'N':'-', (f & Z80_CF) ? 'C':'-', diff --git a/ui/ui_z9001.h b/ui/ui_z9001.h index 23cbf465..c339323f 100644 --- a/ui/ui_z9001.h +++ b/ui/ui_z9001.h @@ -143,6 +143,7 @@ static void _ui_z9001_draw_menu(ui_z9001_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) { diff --git a/ui/ui_zx.h b/ui/ui_zx.h index d7293b1a..e19bb590 100644 --- a/ui/ui_zx.h +++ b/ui/ui_zx.h @@ -158,6 +158,7 @@ static void _ui_zx_draw_menu(ui_zx_t* ui) { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("CPU Debugger", 0, &ui->dbg.ui.open); ImGui::MenuItem("Breakpoints", 0, &ui->dbg.ui.show_breakpoints); + ImGui::MenuItem("Stopwatch", 0, &ui->dbg.ui.show_stopwatch); ImGui::MenuItem("Execution History", 0, &ui->dbg.ui.show_history); ImGui::MenuItem("Memory Heatmap", 0, &ui->dbg.ui.show_heatmap); if (ImGui::BeginMenu("Memory Editor")) {