diff --git a/core/cpu.c b/core/cpu.c index 026cbe464..2ebcfdb27 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -63,7 +63,7 @@ uint32_t cpu_address_mode(uint32_t address, bool mode) { } static void cpu_prefetch(uint32_t address, bool mode) { cpu.ADL = mode; - cpu.registers.PC = cpu_address_mode(address, mode); + REG_WRITE_EX(DBG_REG_PC, cpu.registers.PC, cpu_address_mode(address, mode)); cpu.prefetch = mem_read_cpu(cpu.registers.PC, true); } static uint8_t cpu_fetch_byte(void) { @@ -96,7 +96,7 @@ static uint32_t cpu_fetch_word_no_prefetch(void) { cpu_fetch_byte(); value |= cpu.prefetch << 16; } - cpu.registers.PC++; + REG_WRITE_EX(DBG_REG_PC, cpu.registers.PC, cpu.registers.PC + 1); return value; } @@ -126,13 +126,17 @@ static void cpu_write_word(uint32_t address, uint32_t value) { } static uint8_t cpu_pop_byte_mode(bool mode) { - return mem_read_cpu(cpu_address_mode(cpu.registers.stack[mode].hl++, mode), false); + uint32_t addr = cpu_address_mode(cpu.registers.stack[mode].hl, mode); + REG_WRITE_EX(DBG_REG_ID_SP(mode), cpu.registers.stack[mode].hl, cpu.registers.stack[mode].hl + 1); + return mem_read_cpu(addr, false); } static uint8_t cpu_pop_byte(void) { return cpu_pop_byte_mode(cpu.L); } static void cpu_push_byte_mode(uint8_t value, bool mode) { - mem_write_cpu(cpu_address_mode(--cpu.registers.stack[mode].hl, mode), value); + uint32_t new_sp = cpu.registers.stack[mode].hl - 1; + REG_WRITE_EX(DBG_REG_ID_SP(mode), cpu.registers.stack[mode].hl, new_sp); + mem_write_cpu(cpu_address_mode(new_sp, mode), value); } static void cpu_push_byte(uint8_t value) { cpu_push_byte_mode(value, cpu.L); @@ -172,46 +176,49 @@ static void cpu_write_out(uint16_t pio, uint8_t value) { } static uint32_t cpu_read_sp(void) { - return cpu.registers.stack[cpu.L].hl; + return REG_READ_EX(DBG_REG_ID_SP(cpu.L), cpu.registers.stack[cpu.L].hl); } static void cpu_write_sp(uint32_t value) { - cpu.registers.stack[cpu.L].hl = value; + REG_WRITE_EX(DBG_REG_ID_SP(cpu.L), cpu.registers.stack[cpu.L].hl, value); } static uint8_t cpu_read_index_low(void) { - return cpu.registers.index[cpu.PREFIX].l; + return REG_READ_EX(DBG_REG_ID_INDEX_L(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].l); } static void cpu_write_index_low(uint8_t value) { - cpu.registers.index[cpu.PREFIX].l = value; + REG_WRITE_EX(DBG_REG_ID_INDEX_L(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].l, value); } static uint8_t cpu_read_index_high(void) { - return cpu.registers.index[cpu.PREFIX].h; + return REG_READ_EX(DBG_REG_ID_INDEX_H(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].h); } static void cpu_write_index_high(uint8_t value) { - cpu.registers.index[cpu.PREFIX].h = value; + REG_WRITE_EX(DBG_REG_ID_INDEX_H(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].h, value); } static uint32_t cpu_read_index(void) { - return cpu.registers.index[cpu.PREFIX].hl; + return REG_READ_EX(DBG_REG_ID_INDEX(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].hl); } static void cpu_write_index(uint32_t value) { - cpu.registers.index[cpu.PREFIX].hl = value; + REG_WRITE_EX(DBG_REG_ID_INDEX(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].hl, value); } static void cpu_write_index_partial_mode(uint32_t value) { value = cpu_mask_mode(value, cpu.L); if (cpu.L) { - cpu.registers.index[cpu.PREFIX].hl = value; + REG_WRITE_EX(DBG_REG_ID_INDEX(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].hl, value); } else { + /* Partial mode write still conceptually writes the 16-bit shadow; + trigger on the full-index ID so masks/overlaps apply uniformly. */ + DBG_REG_TOUCH_W(DBG_REG_ID_INDEX(cpu.PREFIX), cpu.registers.index[cpu.PREFIX].hls, value); cpu.registers.index[cpu.PREFIX].hls = value; } } static uint32_t cpu_read_other_index(void) { - return cpu.registers.index[cpu.PREFIX ^ 1].hl; + return REG_READ_EX(DBG_REG_ID_INDEX(cpu.PREFIX ^ 1), cpu.registers.index[cpu.PREFIX ^ 1].hl); } static void cpu_write_other_index(uint32_t value) { - cpu.registers.index[cpu.PREFIX ^ 1].hl = value; + REG_WRITE_EX(DBG_REG_ID_INDEX(cpu.PREFIX ^ 1), cpu.registers.index[cpu.PREFIX ^ 1].hl, value); } static uint32_t cpu_index_address(void) { @@ -225,28 +232,28 @@ static uint32_t cpu_index_address(void) { static uint8_t cpu_read_reg(int i) { uint8_t value; switch (i) { - case 0: value = cpu.registers.B; break; - case 1: value = cpu.registers.C; break; - case 2: value = cpu.registers.D; break; - case 3: value = cpu.registers.E; break; + case 0: value = REG_READ_EX(DBG_REG_B, cpu.registers.B); break; + case 1: value = REG_READ_EX(DBG_REG_C, cpu.registers.C); break; + case 2: value = REG_READ_EX(DBG_REG_D, cpu.registers.D); break; + case 3: value = REG_READ_EX(DBG_REG_E, cpu.registers.E); break; case 4: value = cpu_read_index_high(); break; case 5: value = cpu_read_index_low(); break; case 6: value = cpu_read_byte(cpu_index_address()); break; - case 7: value = cpu.registers.A; break; + case 7: value = REG_READ_EX(DBG_REG_A, cpu.registers.A); break; default: unreachable(); } return value; } static void cpu_write_reg(int i, uint8_t value) { switch (i) { - case 0: cpu.registers.B = value; break; - case 1: cpu.registers.C = value; break; - case 2: cpu.registers.D = value; break; - case 3: cpu.registers.E = value; break; + case 0: REG_WRITE_EX(DBG_REG_B, cpu.registers.B, value); break; + case 1: REG_WRITE_EX(DBG_REG_C, cpu.registers.C, value); break; + case 2: REG_WRITE_EX(DBG_REG_D, cpu.registers.D, value); break; + case 3: REG_WRITE_EX(DBG_REG_E, cpu.registers.E, value); break; case 4: cpu_write_index_high(value); break; case 5: cpu_write_index_low(value); break; case 6: cpu_write_byte(cpu_index_address(), value); break; - case 7: cpu.registers.A = value; break; + case 7: REG_WRITE_EX(DBG_REG_A, cpu.registers.A, value); break; default: unreachable(); } } @@ -262,28 +269,28 @@ static void cpu_read_write_reg(int read, int write) { static uint8_t cpu_read_reg_prefetched(int i, uint32_t address) { uint8_t value; switch (i) { - case 0: value = cpu.registers.B; break; - case 1: value = cpu.registers.C; break; - case 2: value = cpu.registers.D; break; - case 3: value = cpu.registers.E; break; + case 0: value = REG_READ_EX(DBG_REG_B, cpu.registers.B); break; + case 1: value = REG_READ_EX(DBG_REG_C, cpu.registers.C); break; + case 2: value = REG_READ_EX(DBG_REG_D, cpu.registers.D); break; + case 3: value = REG_READ_EX(DBG_REG_E, cpu.registers.E); break; case 4: value = cpu_read_index_high(); break; case 5: value = cpu_read_index_low(); break; case 6: value = cpu_read_byte(address); break; - case 7: value = cpu.registers.A; break; + case 7: value = REG_READ_EX(DBG_REG_A, cpu.registers.A); break; default: unreachable(); } return value; } static void cpu_write_reg_prefetched(int i, uint32_t address, uint8_t value) { switch (i) { - case 0: cpu.registers.B = value; break; - case 1: cpu.registers.C = value; break; - case 2: cpu.registers.D = value; break; - case 3: cpu.registers.E = value; break; + case 0: REG_WRITE_EX(DBG_REG_B, cpu.registers.B, value); break; + case 1: REG_WRITE_EX(DBG_REG_C, cpu.registers.C, value); break; + case 2: REG_WRITE_EX(DBG_REG_D, cpu.registers.D, value); break; + case 3: REG_WRITE_EX(DBG_REG_E, cpu.registers.E, value); break; case 4: cpu_write_index_high(value); break; case 5: cpu_write_index_low(value); break; case 6: cpu_write_byte(address, value); break; - case 7: cpu.registers.A = value; break; + case 7: REG_WRITE_EX(DBG_REG_A, cpu.registers.A, value); break; default: unreachable(); } } @@ -291,8 +298,8 @@ static void cpu_write_reg_prefetched(int i, uint32_t address, uint8_t value) { static uint32_t cpu_read_rp(int i) { uint32_t value; switch (i) { - case 0: value = cpu.registers.BC; break; - case 1: value = cpu.registers.DE; break; + case 0: value = REG_READ_EX(DBG_REG_BC, cpu.registers.BC); break; + case 1: value = REG_READ_EX(DBG_REG_DE, cpu.registers.DE); break; case 2: value = cpu_read_index(); break; case 3: value = cpu_read_sp(); break; default: unreachable(); @@ -302,8 +309,8 @@ static uint32_t cpu_read_rp(int i) { static void cpu_write_rp(int i, uint32_t value) { value = cpu_mask_mode(value, cpu.L); switch (i) { - case 0: cpu.registers.BC = value; break; - case 1: cpu.registers.DE = value; break; + case 0: REG_WRITE_EX(DBG_REG_BC, cpu.registers.BC, value); break; + case 1: REG_WRITE_EX(DBG_REG_DE, cpu.registers.DE, value); break; case 2: cpu_write_index(value); break; case 3: cpu_write_sp(value); break; default: unreachable(); @@ -312,14 +319,14 @@ static void cpu_write_rp(int i, uint32_t value) { static uint32_t cpu_read_rp2(int i) { if (i == 3) { - return cpu.registers.AF; + return REG_READ_EX(DBG_REG_AF, cpu.registers.AF); } else { return cpu_read_rp(i); } } static void cpu_write_rp2(int i, uint32_t value) { if (i == 3) { - cpu.registers.AF = value; + REG_WRITE_EX(DBG_REG_AF, cpu.registers.AF, value); } else { cpu_write_rp(i, value); } @@ -328,9 +335,9 @@ static void cpu_write_rp2(int i, uint32_t value) { static uint32_t cpu_read_rp3(int i) { uint32_t value; switch (i) { - case 0: value = cpu.registers.BC; break; - case 1: value = cpu.registers.DE; break; - case 2: value = cpu.registers.HL; break; + case 0: value = REG_READ_EX(DBG_REG_BC, cpu.registers.BC); break; + case 1: value = REG_READ_EX(DBG_REG_DE, cpu.registers.DE); break; + case 2: value = REG_READ_EX(DBG_REG_HL, cpu.registers.HL); break; case 3: value = cpu_read_index(); break; default: unreachable(); } @@ -339,9 +346,9 @@ static uint32_t cpu_read_rp3(int i) { static void cpu_write_rp3(int i, uint32_t value) { value = cpu_mask_mode(value, cpu.L); switch (i) { - case 0: cpu.registers.BC = value; break; - case 1: cpu.registers.DE = value; break; - case 2: cpu.registers.HL = value; break; + case 0: REG_WRITE_EX(DBG_REG_BC, cpu.registers.BC, value); break; + case 1: REG_WRITE_EX(DBG_REG_DE, cpu.registers.DE, value); break; + case 2: REG_WRITE_EX(DBG_REG_HL, cpu.registers.HL, value); break; case 3: cpu_write_index(value); break; default: unreachable(); } @@ -373,25 +380,26 @@ static void cpu_execute_daa(void) { v += 0x60; } if (r->flags.N) { - r->A -= v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A - v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_parity(r->A) | cpuflag_subtract(r->flags.N) | cpuflag_c(v >= 0x60) - | cpuflag_halfcarry_b_sub(old, v, 0); + | cpuflag_halfcarry_b_sub(old, v, 0)); } else { - r->A += v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A + v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_parity(r->A) | cpuflag_subtract(r->flags.N) | cpuflag_c(v >= 0x60) - | cpuflag_halfcarry_b_add(old, v, 0); + | cpuflag_halfcarry_b_add(old, v, 0)); } } static uint32_t cpu_dec_bc_partial_mode() { uint32_t value = cpu_mask_mode(cpu.registers.BC - 1, cpu.L); if (cpu.L) { - cpu.registers.BC = value; + REG_WRITE_EX(DBG_REG_BC, cpu.registers.BC, value); } else { + DBG_REG_TOUCH_W(DBG_REG_BC, cpu.registers.BCS, value); cpu.registers.BCS = value; } return value; @@ -450,7 +458,7 @@ static void cpu_trap_rewind(uint_fast8_t rewind) { eZ80registers_t *r = &cpu.registers; cpu_prefetch_discard(); cpu.cycles++; - r->PC = cpu_mask_mode(r->PC - rewind, cpu.ADL); + REG_WRITE_EX(DBG_REG_PC, r->PC, cpu_mask_mode(r->PC - rewind, cpu.ADL)); cpu_clear_context(); cpu_interrupt(0x00); } @@ -489,59 +497,59 @@ static void cpu_execute_alu(int i, uint8_t v) { switch (i) { case 0: /* ADD A, v */ old = r->A; - r->A += v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A + v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_overflow_b_add(old, v, r->A) | cpuflag_subtract(0) | cpuflag_carry_b(old + v) - | cpuflag_halfcarry_b_add(old, v, 0); + | cpuflag_halfcarry_b_add(old, v, 0)); break; case 1: /* ADC A, v */ old = r->A; - r->A += v + r->flags.C; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A + v + r->flags.C)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_overflow_b_add(old, v, r->A) | cpuflag_subtract(0) | cpuflag_carry_b(old + v + r->flags.C) - | cpuflag_halfcarry_b_add(old, v, r->flags.C); + | cpuflag_halfcarry_b_add(old, v, r->flags.C)); break; case 2: /* SUB v */ old = r->A; - r->A -= v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A - v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_overflow_b_sub(old, v, r->A) | cpuflag_subtract(1) | cpuflag_carry_b(old - v) - | cpuflag_halfcarry_b_sub(old, v, 0); + | cpuflag_halfcarry_b_sub(old, v, 0)); break; case 3: /* SBC v */ old = r->A; - r->A -= v + r->flags.C; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A - v - r->flags.C)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_overflow_b_sub(old, v, r->A) | cpuflag_subtract(1) | cpuflag_carry_b(old - v - r->flags.C) - | cpuflag_halfcarry_b_sub(old, v, r->flags.C); + | cpuflag_halfcarry_b_sub(old, v, r->flags.C)); break; case 4: /* AND v */ - r->A &= v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A & v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_parity(r->A) - | FLAG_H; + | FLAG_H); break; case 5: /* XOR v */ - r->A ^= v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) - | cpuflag_undef(r->F) | cpuflag_parity(r->A); + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A ^ v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + | cpuflag_undef(r->F) | cpuflag_parity(r->A)); break; case 6: /* OR v */ - r->A |= v; - r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) - | cpuflag_undef(r->F) | cpuflag_parity(r->A); + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->A | v)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(r->A) | cpuflag_zero(r->A) + | cpuflag_undef(r->F) | cpuflag_parity(r->A)); break; case 7: /* CP v */ old = r->A - v; - r->F = cpuflag_sign_b(old) | cpuflag_zero(old) + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_b(old) | cpuflag_zero(old) | cpuflag_undef(r->F) | cpuflag_subtract(1) | cpuflag_carry_b(r->A - v) | cpuflag_overflow_b_sub(r->A, v, old) - | cpuflag_halfcarry_b_sub(r->A, v, 0); + | cpuflag_halfcarry_b_sub(r->A, v, 0)); break; } } @@ -591,14 +599,15 @@ static void cpu_execute_rot(int y, int z, uint32_t address, uint8_t value) { unreachable(); } cpu_write_reg_prefetched(z, address, value); - r->F = cpuflag_c(new_c) | cpuflag_sign_b(value) | cpuflag_parity(value) - | cpuflag_undef(r->F) | cpuflag_zero(value); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_c(new_c) | cpuflag_sign_b(value) | cpuflag_parity(value) + | cpuflag_undef(r->F) | cpuflag_zero(value)); } static void cpu_execute_rot_acc(int y) { eZ80registers_t *r = &cpu.registers; uint8_t old; + uint8_t __oldA = r->A, __oldF = r->F; switch (y) { case 0: /* RLCA */ old = (r->A & 0x80) > 0; @@ -645,6 +654,8 @@ static void cpu_execute_rot_acc(int y) r->flags.N = 0; break; } + DBG_REG_TOUCH_W(DBG_REG_A, __oldA, r->A); + DBG_REG_TOUCH_W(DBG_REG_F, __oldF, r->F); } static void cpu_execute_bli() { @@ -672,7 +683,7 @@ static void cpu_execute_bli() { } /* LDI, LDD, LDIR, LDDR */ cpu_write_byte(r->DE, cpu_read_byte(r->HL)); - r->DE = cpu_mask_mode(r->DE + delta, cpu.L); + REG_WRITE_EX(DBG_REG_DE, r->DE, cpu_mask_mode(r->DE + delta, cpu.L)); r->flags.H = 0; r->flags.PV = cpu_dec_bc_partial_mode() != 0; /* Do not mask BC */ r->flags.N = 0; @@ -793,7 +804,7 @@ static void cpu_execute_bli() { return; } /* All block instructions */ - r->HL = cpu_mask_mode(r->HL + delta, cpu.L); + REG_WRITE_EX(DBG_REG_HL, r->HL, cpu_mask_mode(r->HL + delta, cpu.L)); cpu.cycles += internalCycles; if (repeat) { switch (cpu.context.opcode) { @@ -966,8 +977,8 @@ void cpu_execute(void) { break; case 1: /* EX af,af' */ w = r->AF; - r->AF = r->_AF; - r->_AF = w; + REG_WRITE_EX(DBG_REG_AF, r->AF, r->_AF); + REG_WRITE_EX(DBG_REG_AFP, r->_AF, w); break; case 2: /* DJNZ d */ s = cpu_fetch_offset(); @@ -1197,14 +1208,14 @@ void cpu_execute(void) { break; case 1: /* EXX */ w = r->BC; - r->BC = r->_BC; - r->_BC = w; + REG_WRITE_EX(DBG_REG_BC, r->BC, r->_BC); + REG_WRITE_EX(DBG_REG_BCP, r->_BC, w); w = r->DE; - r->DE = r->_DE; - r->_DE = w; + REG_WRITE_EX(DBG_REG_DE, r->DE, r->_DE); + REG_WRITE_EX(DBG_REG_DEP, r->_DE, w); w = r->HL; - r->HL = r->_HL; - r->_HL = w; + REG_WRITE_EX(DBG_REG_HL, r->HL, r->_HL); + REG_WRITE_EX(DBG_REG_HLP, r->_HL, w); break; case 2: /* JP (rr) */ cpu_prefetch_discard(); @@ -1285,8 +1296,8 @@ void cpu_execute(void) { break; case 5: /* EX DE, HL */ w = cpu_mask_mode(r->DE, cpu.L); - r->DE = cpu_mask_mode(r->HL, cpu.L); - r->HL = w; + REG_WRITE_EX(DBG_REG_DE, r->DE, cpu_mask_mode(r->HL, cpu.L)); + REG_WRITE_EX(DBG_REG_HL, r->HL, w); break; case 6: /* DI */ cpu.IEF_wait = cpu.IEF1 = cpu.IEF2 = false; @@ -1410,18 +1421,18 @@ void cpu_execute(void) { op_word = cpu_mask_mode(cpu_read_rp(context.p), cpu.L); if (context.q == 0) { /* SBC HL, rp[p] */ new_word = old_word - op_word - r->flags.C; - r->HL = cpu_mask_mode(new_word, cpu.L); - r->F = cpuflag_sign_w(r->HL, cpu.L) | cpuflag_zero(r->HL) + REG_WRITE_EX(DBG_REG_HL, r->HL, cpu_mask_mode(new_word, cpu.L)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_w(r->HL, cpu.L) | cpuflag_zero(r->HL) | cpuflag_undef(r->F) | cpuflag_overflow_w_sub(old_word, op_word, r->HL, cpu.L) | cpuflag_subtract(1) | cpuflag_carry_w(new_word, cpu.L) - | cpuflag_halfcarry_w_sub(old_word, op_word, r->flags.C); + | cpuflag_halfcarry_w_sub(old_word, op_word, r->flags.C)); } else { /* ADC HL, rp[p] */ new_word = old_word + op_word + r->flags.C; - r->HL = cpu_mask_mode(new_word, cpu.L); - r->F = cpuflag_sign_w(r->HL, cpu.L) | cpuflag_zero(r->HL) + REG_WRITE_EX(DBG_REG_HL, r->HL, cpu_mask_mode(new_word, cpu.L)); + REG_WRITE_EX(DBG_REG_F, r->F, cpuflag_sign_w(r->HL, cpu.L) | cpuflag_zero(r->HL) | cpuflag_undef(r->F) | cpuflag_overflow_w_add(old_word, op_word, r->HL, cpu.L) | cpuflag_subtract(0) | cpuflag_carry_w(new_word, cpu.L) - | cpuflag_halfcarry_w_add(old_word, op_word, r->flags.C); + | cpuflag_halfcarry_w_add(old_word, op_word, r->flags.C)); } break; case 3: @@ -1444,7 +1455,7 @@ void cpu_execute(void) { break; case 1: /* LEA IX, IY + d */ cpu.PREFIX = 3; - r->IX = cpu_index_address(); + REG_WRITE_EX(DBG_REG_IX, r->IX, cpu_index_address()); break; case 2: /* TST A, n */ new = r->A & cpu_fetch_byte(); @@ -1478,7 +1489,7 @@ void cpu_execute(void) { break; case 2: /* LEA IY, IX + d */ cpu.PREFIX = 2; - r->IY = cpu_index_address(); + REG_WRITE_EX(DBG_REG_IY, r->IY, cpu_index_address()); break; case 3: case 6: /* OPCODETRAP */ @@ -1489,7 +1500,7 @@ void cpu_execute(void) { break; case 5: /* LD MB, A */ if (cpu.ADL) { - r->MBASE = r->A; + REG_WRITE_EX(DBG_REG_MBASE, r->MBASE, r->A); } break; case 7: /* STMIX */ @@ -1511,7 +1522,7 @@ void cpu_execute(void) { cpu_push_word(r->IY + cpu_fetch_offset()); break; case 5: /* LD A, MB */ - r->A = r->MBASE; + REG_WRITE_EX(DBG_REG_A, r->A, r->MBASE); break; case 6: /* SLP */ cpu.cycles++; @@ -1525,19 +1536,19 @@ void cpu_execute(void) { case 7: switch (context.y) { case 0: /* LD I, A */ - r->I = r->A; + REG_WRITE_EX(DBG_REG_I, r->I, r->A); break; case 1: /* LD R, A */ - r->R = r->A << 1 | r->A >> 7; + REG_WRITE_EX(DBG_REG_R, r->R, (uint8_t)(r->A << 1 | r->A >> 7)); break; case 2: /* LD A, I */ - r->A = r->I; + REG_WRITE_EX(DBG_REG_A, r->A, r->I); r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_pv(cpu.IEF1) | cpuflag_subtract(0) | cpuflag_c(r->flags.C); break; case 3: /* LD A, R */ - r->A = r->R >> 1 | r->R << 7; + REG_WRITE_EX(DBG_REG_A, r->A, (uint8_t)(r->R >> 1 | r->R << 7)); r->F = cpuflag_sign_b(r->A) | cpuflag_zero(r->A) | cpuflag_undef(r->F) | cpuflag_pv(cpu.IEF1) | cpuflag_subtract(0) | cpuflag_c(r->flags.C); @@ -1581,14 +1592,14 @@ void cpu_execute(void) { break; } cpu_execute_bli_start: - r->PC = cpu_address_mode(r->PC - 2 - cpu.SUFFIX, cpu.ADL); + REG_WRITE_EX(DBG_REG_PC, r->PC, cpu_address_mode(r->PC - 2 - cpu.SUFFIX, cpu.ADL)); cpu.context = context; cpu_execute_bli_continue: cpu_execute_bli(); if (cpu.inBlock) { goto cpu_execute_continue; } else { - r->PC = cpu_address_mode(r->PC + 2 + cpu.SUFFIX, cpu.ADL); + REG_WRITE_EX(DBG_REG_PC, r->PC, cpu_address_mode(r->PC + 2 + cpu.SUFFIX, cpu.ADL)); } break; case 3: /* There are only a few of these, so a simple switch for these shouldn't matter too much */ @@ -1599,10 +1610,10 @@ void cpu_execute(void) { case 0xCB: /* OTDRX */ goto cpu_execute_bli_start; case 0xC7: /* LD I, HL */ - r->I = r->HL; + REG_WRITE_EX(DBG_REG_I, r->I, r->HL); break; case 0xD7: /* LD HL, I */ - r->HL = cpu_mask_mode(r->I | (r->MBASE << 16), cpu.L); + REG_WRITE_EX(DBG_REG_HL, r->HL, cpu_mask_mode(r->I | (r->MBASE << 16), cpu.L)); r->F = cpuflag_undef(r->F); break; default: /* OPCODETRAP */ diff --git a/core/debug/debug.c b/core/debug/debug.c index a41b6507e..85ce87aee 100644 --- a/core/debug/debug.c +++ b/core/debug/debug.c @@ -110,7 +110,7 @@ void debug_open(int reason, uint32_t data) { } } - if ((debug_get_flags() & DBG_IGNORE) && (reason >= DBG_BREAKPOINT && reason <= DBG_PORT_WRITE)) { + if ((debug_get_flags() & DBG_IGNORE) && (reason >= DBG_BREAKPOINT && reason <= DBG_REG_WRITE)) { return; } @@ -142,6 +142,135 @@ void debug_open(int reason, uint32_t data) { cpu.flashDelayCycles = debug.flashDelayCycles; } +static bool reg_bit_get(const uint64_t mask, const unsigned id) { + return (id < 64) && ((mask >> id) & 1u); +} + +static void reg_bit_set(uint64_t *mask, const unsigned id, const bool set) { + if (id >= 64) { return; } + if (set) { + *mask |= (1ull << id); + } else { + *mask &= ~(1ull << id); + } +} + +#define BIT(x) (1ull << (x)) +static const uint64_t dbg_reg_trigger_mask[DBG_REG_COUNT] = { + [DBG_REG_A] = BIT(DBG_REG_A) | BIT(DBG_REG_AF), + [DBG_REG_F] = BIT(DBG_REG_F) | BIT(DBG_REG_AF), + [DBG_REG_AF] = BIT(DBG_REG_AF) | BIT(DBG_REG_A) | BIT(DBG_REG_F), + + [DBG_REG_B] = BIT(DBG_REG_B) | BIT(DBG_REG_BC), + [DBG_REG_C] = BIT(DBG_REG_C) | BIT(DBG_REG_BC), + [DBG_REG_BC] = BIT(DBG_REG_BC) | BIT(DBG_REG_B) | BIT(DBG_REG_C), + + [DBG_REG_D] = BIT(DBG_REG_D) | BIT(DBG_REG_DE), + [DBG_REG_E] = BIT(DBG_REG_E) | BIT(DBG_REG_DE), + [DBG_REG_DE] = BIT(DBG_REG_DE) | BIT(DBG_REG_D) | BIT(DBG_REG_E), + + [DBG_REG_H] = BIT(DBG_REG_H) | BIT(DBG_REG_HL), + [DBG_REG_L] = BIT(DBG_REG_L) | BIT(DBG_REG_HL), + [DBG_REG_HL] = BIT(DBG_REG_HL) | BIT(DBG_REG_H) | BIT(DBG_REG_L), + + [DBG_REG_IXH] = BIT(DBG_REG_IXH) | BIT(DBG_REG_IX), + [DBG_REG_IXL] = BIT(DBG_REG_IXL) | BIT(DBG_REG_IX), + [DBG_REG_IX] = BIT(DBG_REG_IX) | BIT(DBG_REG_IXH) | BIT(DBG_REG_IXL), + + [DBG_REG_IYH] = BIT(DBG_REG_IYH) | BIT(DBG_REG_IY), + [DBG_REG_IYL] = BIT(DBG_REG_IYL) | BIT(DBG_REG_IY), + [DBG_REG_IY] = BIT(DBG_REG_IY) | BIT(DBG_REG_IYH) | BIT(DBG_REG_IYL), + + [DBG_REG_AP] = BIT(DBG_REG_AP) | BIT(DBG_REG_AFP), + [DBG_REG_FP] = BIT(DBG_REG_FP) | BIT(DBG_REG_AFP), + [DBG_REG_AFP] = BIT(DBG_REG_AFP) | BIT(DBG_REG_AP) | BIT(DBG_REG_FP), + + [DBG_REG_BP] = BIT(DBG_REG_BP) | BIT(DBG_REG_BCP), + [DBG_REG_CP] = BIT(DBG_REG_CP) | BIT(DBG_REG_BCP), + [DBG_REG_BCP] = BIT(DBG_REG_BCP) | BIT(DBG_REG_BP) | BIT(DBG_REG_CP), + + [DBG_REG_DP] = BIT(DBG_REG_DP) | BIT(DBG_REG_DEP), + [DBG_REG_EP] = BIT(DBG_REG_EP) | BIT(DBG_REG_DEP), + [DBG_REG_DEP] = BIT(DBG_REG_DEP) | BIT(DBG_REG_DP) | BIT(DBG_REG_EP), + + [DBG_REG_HP] = BIT(DBG_REG_HP) | BIT(DBG_REG_HLP), + [DBG_REG_LP] = BIT(DBG_REG_LP) | BIT(DBG_REG_HLP), + [DBG_REG_HLP] = BIT(DBG_REG_HLP) | BIT(DBG_REG_HP) | BIT(DBG_REG_LP), + + [DBG_REG_SPS] = BIT(DBG_REG_SPS), + [DBG_REG_SPL] = BIT(DBG_REG_SPL), + [DBG_REG_PC] = BIT(DBG_REG_PC), + [DBG_REG_I] = BIT(DBG_REG_I), + [DBG_REG_R] = BIT(DBG_REG_R), + [DBG_REG_MBASE] = BIT(DBG_REG_MBASE), +}; + +uint32_t debug_norm_reg_value(const unsigned regID, const uint32_t value) { + switch (regID) { + // 8 bit regs + case DBG_REG_A: case DBG_REG_F: case DBG_REG_B: case DBG_REG_C: + case DBG_REG_D: case DBG_REG_E: case DBG_REG_H: case DBG_REG_L: + case DBG_REG_IXH: case DBG_REG_IXL: case DBG_REG_IYH: case DBG_REG_IYL: + case DBG_REG_AP: case DBG_REG_FP: case DBG_REG_BP: case DBG_REG_CP: + case DBG_REG_DP: case DBG_REG_EP: case DBG_REG_HP: case DBG_REG_LP: + case DBG_REG_R: case DBG_REG_MBASE: + return value & 0xFFu; + // 16 bit regs + case DBG_REG_AF: case DBG_REG_AFP: case DBG_REG_SPS: case DBG_REG_I: + return value & 0xFFFFu; + // 24 bit regs + case DBG_REG_BC: case DBG_REG_BCP: case DBG_REG_DE: case DBG_REG_DEP: + case DBG_REG_HL: case DBG_REG_HLP: case DBG_REG_IX: case DBG_REG_IY: + case DBG_REG_SPL: case DBG_REG_PC: + return value & 0xFFFFFFu; + default: + return value; + } +} + +void debug_reg_watch(const unsigned regID, const int mask, const bool set) { + if (mask & DBG_MASK_READ) { + reg_bit_set(&debug.reg_watch_r, regID, set); + } + + if (mask & DBG_MASK_WRITE) { + reg_bit_set(&debug.reg_watch_w, regID, set); + } +} + +int debug_reg_get_mask(const unsigned regID) { + int mask = 0; + + if (reg_bit_get(debug.reg_watch_r, regID)) { + mask |= DBG_MASK_READ; + } + + if (reg_bit_get(debug.reg_watch_w, regID)) { + mask |= DBG_MASK_WRITE; + } + + return mask; +} + +void debug_touch_reg_read(const unsigned regID) { + if (!(debug.reg_watch_r & dbg_reg_trigger_mask[regID])) { return; } + debug_open(DBG_REG_READ, regID); +} + +void debug_touch_reg_write(const unsigned regID, const uint32_t oldValue, const uint32_t new_value) { + if (!(debug.reg_watch_w & dbg_reg_trigger_mask[regID])) { + return; + } + + const uint32_t old_v = debug_norm_reg_value(regID, oldValue); + const uint32_t new_v = debug_norm_reg_value(regID, new_value); + if (old_v == new_v) { + return; + } + + debug_open(DBG_REG_WRITE, regID); +} + void debug_watch(uint32_t addr, int mask, bool set) { addr &= 0xFFFFFF; if (set) { diff --git a/core/debug/debug.h b/core/debug/debug.h index 3a4fd1cba..9d690927d 100644 --- a/core/debug/debug.h +++ b/core/debug/debug.h @@ -1,5 +1,3 @@ -#ifdef DEBUG_SUPPORT - #ifndef DEBUG_H #define DEBUG_H @@ -27,6 +25,8 @@ enum { DBG_WATCHPOINT_WRITE, /* hit a write watchpoint */ DBG_PORT_READ, /* read a monitored port */ DBG_PORT_WRITE, /* wrote a monitored port */ + DBG_REG_READ, /* read of a watched CPU register */ + DBG_REG_WRITE, /* write of a watched CPU register */ DBG_NMI_TRIGGERED, /* triggered a non maskable interrupt */ DBG_WATCHDOG_TIMEOUT, /* watchdog timer reset */ DBG_MISC_RESET, /* miscellaneous reset */ @@ -126,6 +126,7 @@ bool debug_get_executing_basic_prgm(char *name); #define DBG_BASIC_CMDEXEC_BIT (1 << 6) #define DBG_BASIC_PROGEXECUTING_BIT (1 << 1) +#ifdef DEBUG_SUPPORT typedef struct { bool mode : 1; bool popped : 1; @@ -155,6 +156,9 @@ typedef struct { uint8_t *addr; uint8_t *port; + + uint64_t reg_watch_r; /* bitmask of DBG_MASK_READ for dbg_reg_t ids */ + uint64_t reg_watch_w; /* bitmask of DBG_MASK_WRITE for dbg_reg_t ids */ bool basicMode; bool basicModeLive; bool basicDeferPC; @@ -183,11 +187,143 @@ enum { void debug_step_switch(void); void debug_clear_step(void); void debug_clear_basic_step(void); +#endif -#ifdef __cplusplus +/* register watchpoints */ +/* these ids correspond to logical CPU registers shown in the UI */ +typedef enum { + DBG_REG_A = 0, + DBG_REG_F, + DBG_REG_B, + DBG_REG_C, + DBG_REG_D, + DBG_REG_E, + DBG_REG_H, + DBG_REG_L, + DBG_REG_IXH, + DBG_REG_IXL, + DBG_REG_IYH, + DBG_REG_IYL, + DBG_REG_AP, /* A' */ + DBG_REG_FP, /* F' */ + DBG_REG_BP, /* B' */ + DBG_REG_CP, /* C' */ + DBG_REG_DP, /* D' */ + DBG_REG_EP, /* E' */ + DBG_REG_HP, /* H' */ + DBG_REG_LP, /* L' */ + DBG_REG_AF, /* 16-bit */ + DBG_REG_BC, /* 24-bit */ + DBG_REG_DE, /* 24-bit */ + DBG_REG_HL, /* 24-bit */ + DBG_REG_IX, /* 24-bit */ + DBG_REG_IY, /* 24-bit */ + DBG_REG_AFP, /* 16-bit */ + DBG_REG_BCP, /* 24-bit */ + DBG_REG_DEP, /* 24-bit */ + DBG_REG_HLP, /* 24-bit */ + DBG_REG_SPS, /* 16-bit */ + DBG_REG_SPL, /* 24-bit */ + DBG_REG_PC, /* 24-bit */ + DBG_REG_I, /* 16-bit */ + DBG_REG_R, /* 8-bit */ + DBG_REG_MBASE,/* 8-bit */ + DBG_REG_COUNT +} dbg_reg_t; + +#ifdef DEBUG_SUPPORT +/* enable/disable register watch for a given id and mask (DBG_MASK_READ/WRITE) */ +void debug_reg_watch(unsigned regID, int mask, bool set); +/* get current mask (DBG_MASK_READ/WRITE) for a register id */ +int debug_reg_get_mask(unsigned regID); +/* helpers to be invoked around register accesses */ +void debug_touch_reg_read(unsigned regID); +void debug_touch_reg_write(unsigned regID, uint32_t oldValue, uint32_t new_value); +/* normalize a register value to its natural width (8/16/24) */ +uint32_t debug_norm_reg_value(unsigned regID, uint32_t value); +#else +static inline void debug_reg_watch(unsigned regID, int mask, bool set) { + (void)regID; + (void)mask; + (void)set; +} + +static inline int debug_reg_get_mask(unsigned regID) { + (void)regID; + return 0; +} + +static inline void debug_touch_reg_read(unsigned regID) { + (void)regID; +} + +static inline void debug_touch_reg_write(unsigned regID, uint32_t oldValue, uint32_t new_value) { + (void)regID; + (void)oldValue; + (void)new_value; +} + +static inline uint32_t debug_norm_reg_value(unsigned regID, uint32_t value) { + (void)regID; + return value; } #endif +#ifdef DEBUG_SUPPORT +/* direct touch helper for write only sites */ +#define DBG_REG_TOUCH_W(ID, OLD, NEW) \ + do { debug_touch_reg_write((unsigned)(ID), (uint32_t)(OLD), (uint32_t)(NEW)); } while (0) + +/* trigger helpers to wrap reads/writes + * REG_READ returns the single evaluated value of EXPR + * REG_WRITE evaluates LVAL once to capture OLD, and again to assign + */ +#define REG_READ_EX(ID, EXPR) \ + (__extension__({ \ + uint32_t __v = (uint32_t)(EXPR); \ + debug_touch_reg_read((unsigned)(ID)); \ + __v; \ + })) + +#define REG_WRITE_EX(ID, LVAL, VAL) \ + (__extension__({ \ + uint32_t __old = (uint32_t)(LVAL); \ + uint32_t __new = (uint32_t)(VAL); \ + debug_touch_reg_write((unsigned)(ID), __old, __new); \ + (LVAL) = (__new); \ + })) +#else +#define DBG_REG_TOUCH_W(ID, OLD, NEW) \ + do { \ + (void)(ID); \ + (void)(OLD); \ + (void)(NEW); \ + } while (0) + +#define REG_READ_EX(ID, EXPR) \ + (__extension__({ \ + (void)(ID); \ + (uint32_t)(EXPR); \ + })) + +#define REG_WRITE_EX(ID, LVAL, VAL) \ + (__extension__({ \ + (void)(ID); \ + uint32_t __new = (uint32_t)(VAL); \ + (LVAL) = (__new); \ + })) +#endif + +/* map CPU context to register IDs */ +/* eZ80 PREFIX: 0 = HL, 2 = IX, 3 = IY */ +#define DBG_REG_ID_INDEX(PFX) (( (PFX) == 0 ) ? DBG_REG_HL : ( (PFX) == 2 ? DBG_REG_IX : DBG_REG_IY )) +#define DBG_REG_ID_INDEX_H(PFX) (( (PFX) == 0 ) ? DBG_REG_H : ( (PFX) == 2 ? DBG_REG_IXH : DBG_REG_IYH )) +#define DBG_REG_ID_INDEX_L(PFX) (( (PFX) == 0 ) ? DBG_REG_L : ( (PFX) == 2 ? DBG_REG_IXL : DBG_REG_IYL )) + +#define DBG_REG_ID_SP(MODE_L) ( (MODE_L) ? DBG_REG_SPL : DBG_REG_SPS ) + +#ifdef __cplusplus +} #endif #endif diff --git a/gui/qt/debugger.cpp b/gui/qt/debugger.cpp index 8699144ea..3f90a2033 100644 --- a/gui/qt/debugger.cpp +++ b/gui/qt/debugger.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #ifdef _MSC_VER #include @@ -48,9 +49,44 @@ #include #endif +using namespace Qt::StringLiterals; + const QString MainWindow::DEBUG_UNSET_ADDR = QStringLiteral("XXXXXX"); const QString MainWindow::DEBUG_UNSET_PORT = QStringLiteral("XXXX"); +namespace { + // Register name lookup for watchpoint UI + constexpr std::array kRegIdToName = { { + // order must match dbg_reg_t + "A"_L1, "F"_L1, "B"_L1, "C"_L1, "D"_L1, "E"_L1, "H"_L1, "L"_L1, + "IXH"_L1, "IXL"_L1, "IYH"_L1, "IYL"_L1, + "A'"_L1, "F'"_L1, "B'"_L1, "C'"_L1, "D'"_L1, "E'"_L1, "H'"_L1, "L'"_L1, + "AF"_L1, "BC"_L1, "DE"_L1, "HL"_L1, "IX"_L1, "IY"_L1, + "AF'"_L1, "BC'"_L1, "DE'"_L1, "HL'"_L1, + "SPS"_L1, "SPL"_L1, "PC"_L1, "I"_L1, "R"_L1, "MBASE"_L1 + } }; + + struct RegObjEntry { + QLatin1String name; + int id; + }; + + inline constexpr std::array kRegObjMap = { { + {"af"_L1, DBG_REG_AF}, {"hl"_L1, DBG_REG_HL}, {"de"_L1, DBG_REG_DE}, {"bc"_L1, DBG_REG_BC}, + {"ix"_L1, DBG_REG_IX}, {"iy"_L1, DBG_REG_IY}, + {"af_"_L1, DBG_REG_AFP}, {"hl_"_L1, DBG_REG_HLP}, {"de_"_L1, DBG_REG_DEP}, {"bc_"_L1, DBG_REG_BCP}, + {"spl"_L1, DBG_REG_SPL}, {"pc"_L1, DBG_REG_PC}, {"sps"_L1, DBG_REG_SPS}, + } }; + + int regIdFromObjectName(const QString &nameparam) { + for (const auto & entry : kRegObjMap) { + if (nameparam == entry.name) { + return entry.id; + } + } + return -1; // should never happen? + } +} // ----------------------------------------------- // Debugger Initialization // ----------------------------------------------- @@ -70,11 +106,13 @@ void MainWindow::debugInit() { ui->de_regView->installEventFilter(this); ui->rregView->installEventFilter(this); ui->hl->installEventFilter(this); + ui->af->installEventFilter(this); ui->bc->installEventFilter(this); ui->de->installEventFilter(this); ui->ix->installEventFilter(this); ui->iy->installEventFilter(this); ui->hl_->installEventFilter(this); + ui->af_->installEventFilter(this); ui->bc_->installEventFilter(this); ui->de_->installEventFilter(this); ui->pc->installEventFilter(this); @@ -187,6 +225,20 @@ void MainWindow::debugImportFile(const QString &file) { for (QString &equFile : m_equateFiles) { equatesAddFile(equFile); } + + for (unsigned id = 0; id < kRegIdToName.size(); ++id) { + const QString keyBase = QStringLiteral("regwatch/") + QString(kRegIdToName[id]); + const QString rKey = keyBase + QStringLiteral("/read"); + const QString wKey = keyBase + QStringLiteral("/write"); + const QString rVal = info.value(rKey).toString(); + const QString wVal = info.value(wKey).toString(); + if (!rVal.isEmpty()) { + debug_reg_watch(id, DBG_MASK_READ, rVal == TXT_YES || rVal.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0); + } + if (!wVal.isEmpty()) { + debug_reg_watch(id, DBG_MASK_WRITE, wVal == TXT_YES || wVal.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0); + } + } } void MainWindow::debugExportFile(const QString &filename) const { @@ -258,6 +310,14 @@ void MainWindow::debugExportFile(const QString &filename) const { info.setValue(QStringLiteral("portmonitor/freeze"), portF); info.setValue(QStringLiteral("equates/files"), m_equateFiles); + + // Save register watch settings + for (unsigned id = 0; id < kRegIdToName.size(); ++id) { + const QString keyBase = QStringLiteral("regwatch/") + QString(kRegIdToName[id]); + const int mask = debug_reg_get_mask(id); + info.setValue(keyBase + QStringLiteral("/read"), (mask & DBG_MASK_READ) ? TXT_YES : TXT_NO); + info.setValue(keyBase + QStringLiteral("/write"), (mask & DBG_MASK_WRITE) ? TXT_YES : TXT_NO); + } info.sync(); } @@ -463,6 +523,13 @@ void MainWindow::debugCommand(int reason, uint32_t data) { debugRaise(); gotoMemAddrNoRaise(static_cast(hex2int(input))); return; + case DBG_REG_READ: + case DBG_REG_WRITE: { + const QString rn = kRegIdToName[data]; + const QString act = (reason == DBG_REG_READ) ? tr("Read") : tr("Wrote"); + text = act + tr(" register ") + rn; + break; + } case DBG_PORT_READ: case DBG_PORT_WRITE: input = int2hex(data, 4); @@ -2215,11 +2282,77 @@ void MainWindow::handleCtrlClickLine(QLineEdit *edit) { // Tooltips // ------------------------------------------------ +struct ActionRec { + QAction *act; + unsigned id; + int which; +}; + bool MainWindow::eventFilter(QObject *obj, QEvent *e) { if (!guiDebug) { return QMainWindow::eventFilter(obj, e); } + // Register watchpoint context menu + if (e->type() == QEvent::ContextMenu) { + const QString name = obj->objectName(); + const int regId = regIdFromObjectName(name); + + if (regId >= 0) { + const auto *ce = static_cast(e); + QMenu menu(this); + + QVector recs; + + auto addRegMenu = [&](const unsigned id) { + const QString regName = kRegIdToName[id]; + QMenu *grp = menu.addMenu(regName); + const int m = debug_reg_get_mask(id); + + QAction *watchReadAction = grp->addAction(tr("Watch reads")); + watchReadAction->setCheckable(true); + watchReadAction->setChecked(m & DBG_MASK_READ); + + QAction *watchWriteAction = grp->addAction(tr("Watch writes")); + watchWriteAction->setCheckable(true); + watchWriteAction->setChecked(m & DBG_MASK_WRITE); + + recs.push_back({watchReadAction, id, DBG_MASK_READ}); + recs.push_back({watchWriteAction, id, DBG_MASK_WRITE}); + }; + + addRegMenu(static_cast(regId)); + + switch (regId) { + case DBG_REG_AF: addRegMenu(DBG_REG_A); addRegMenu(DBG_REG_F); break; + case DBG_REG_AFP: addRegMenu(DBG_REG_AP); addRegMenu(DBG_REG_FP); break; + case DBG_REG_BC: addRegMenu(DBG_REG_B); addRegMenu(DBG_REG_C); break; + case DBG_REG_BCP: addRegMenu(DBG_REG_BP); addRegMenu(DBG_REG_CP); break; + case DBG_REG_DE: addRegMenu(DBG_REG_D); addRegMenu(DBG_REG_E); break; + case DBG_REG_DEP: addRegMenu(DBG_REG_DP); addRegMenu(DBG_REG_EP); break; + case DBG_REG_HL: addRegMenu(DBG_REG_H); addRegMenu(DBG_REG_L); break; + case DBG_REG_HLP: addRegMenu(DBG_REG_HP); addRegMenu(DBG_REG_LP); break; + case DBG_REG_IX: addRegMenu(DBG_REG_IXH); addRegMenu(DBG_REG_IXL); break; + case DBG_REG_IY: addRegMenu(DBG_REG_IYH); addRegMenu(DBG_REG_IYL); break; + default: break; + } + + const QAction *sel = menu.exec(ce->globalPos()); + if (sel) { + for (const auto & rec : recs) { + if (rec.act == sel) { + const bool set = rec.act->isChecked(); + debug_reg_watch(rec.id, rec.which, set); + break; + } + } + } + + e->accept(); + return true; + } + } + // Mouse back/forward in Disassembly view if (obj == m_disasm && e->type() == QEvent::MouseButtonPress) { auto *me = static_cast(e);