changeset 2361:3350b3c8faa8

Initial implementation of VDP register write breakpoints
author Michael Pavone <pavone@retrodev.com>
date Mon, 30 Oct 2023 00:07:56 -0700
parents 053ba4551c62
children b6c5a0fa3dfc
files debug.c debug.h gdb_remote.c vdp.c vdp.h
diffstat 5 files changed, 119 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/debug.c	Sat Oct 28 16:04:58 2023 -0700
+++ b/debug.c	Mon Oct 30 00:07:56 2023 -0700
@@ -45,10 +45,10 @@
 	return NULL;
 }
 
-bp_def ** find_breakpoint(bp_def ** cur, uint32_t address)
+bp_def ** find_breakpoint(bp_def ** cur, uint32_t address, uint8_t type)
 {
 	while (*cur) {
-		if ((*cur)->address == address) {
+		if ((*cur)->type == type && (*cur)->address == (((*cur)->mask) & address)) {
 			break;
 		}
 		cur = &((*cur)->next);
@@ -2192,7 +2192,9 @@
 		return 1;
 	}
 	bp_def *tmp = *this_bp;
-	remove_breakpoint(root->cpu_context, tmp->address);
+	if (tmp->type == BP_TYPE_CPU) {
+		remove_breakpoint(root->cpu_context, tmp->address);
+	}
 	*this_bp = (*this_bp)->next;
 	if (tmp->commands) {
 		for (uint32_t i = 0; i < tmp->num_commands; i++)
@@ -2211,12 +2213,72 @@
 	bp_def *new_bp = calloc(1, sizeof(bp_def));
 	new_bp->next = root->breakpoints;
 	new_bp->address = cmd->args[0].value;
+	new_bp->mask = 0xFFFFFF;
 	new_bp->index = root->bp_index++;
+	new_bp->type = BP_TYPE_CPU;
 	root->breakpoints = new_bp;
 	printf("68K Breakpoint %d set at %X\n", new_bp->index, cmd->args[0].value);
 	return 1;
 }
 
+static void on_vdp_reg_write(vdp_context *context, uint16_t reg, uint16_t value)
+{
+	value &= 0xFF;
+	if (context->regs[reg] == value) {
+		return;
+	}
+	genesis_context *gen = (genesis_context *)context->system;
+	debug_root *root = find_m68k_root(gen->m68k);
+	bp_def **this_bp = find_breakpoint(&root->breakpoints, reg, BP_TYPE_VDPREG);
+	int debugging = 1;
+	if (*this_bp) {
+		if ((*this_bp)->condition) {
+			uint32_t condres;
+			if (eval_expr(root, (*this_bp)->condition, &condres)) {
+				if (!condres) {
+					return;
+				}
+			} else {
+				fprintf(stderr, "Failed to eval condition for VDP Register Breakpoint %u\n", (*this_bp)->index);
+				free_expr((*this_bp)->condition);
+				(*this_bp)->condition = NULL;
+			}
+		}
+		for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++)
+		{
+			debugging = run_command(root, (*this_bp)->commands + i);
+		}
+		if (debugging) {
+			printf("VDP Register Breakpoint %d hit on register write %X - Old: %X, New: %X\n", (*this_bp)->index, reg, context->regs[reg], value);
+			gen->header.enter_debugger = 1;
+			if (gen->m68k->sync_cycle > gen->m68k->current_cycle + 1) {
+				gen->m68k->sync_cycle = gen->m68k->current_cycle + 1;
+			}
+			if (gen->m68k->target_cycle > gen->m68k->sync_cycle) {
+				gen->m68k->target_cycle = gen->m68k->sync_cycle;
+			}
+		}
+	}
+}
+
+static uint8_t cmd_vdp_reg_break(debug_root *root, parsed_command *cmd)
+{
+	bp_def *new_bp = calloc(1, sizeof(bp_def));
+	new_bp->next = root->breakpoints;
+	if (cmd->num_args) {
+		new_bp->address = cmd->args[0].value;
+		new_bp->mask = cmd->num_args > 1 ? cmd->args[1].value : 0xFF;
+	}
+	new_bp->index = root->bp_index++;
+	new_bp->type = BP_TYPE_VDPREG;
+	root->breakpoints = new_bp;
+	m68k_context *m68k = root->cpu_context;
+	genesis_context *gen = m68k->system;
+	gen->vdp->reg_hook = on_vdp_reg_write;
+	printf("VDP Register Breakpoint %d set\n", new_bp->index);
+	return 1;
+}
+
 static uint8_t cmd_advance_m68k(debug_root *root, parsed_command *cmd)
 {
 	insert_breakpoint(root->cpu_context, cmd->args[0].value, debugger);
@@ -2817,7 +2879,7 @@
 	},
 	{
 		.names = (const char *[]){
-			"vdpsregs", "vr", NULL
+			"vdpregs", "vr", NULL
 		},
 		.usage = "vdpregs",
 		.desc = "Print VDP register values with a short description",
@@ -2825,6 +2887,16 @@
 		.min_args = 0,
 		.max_args = 0
 	},
+	{
+		.names = (const char *[]){
+			"vdpregbreak", "vregbreak", "vrb", NULL
+		},
+		.usage = "vdpregbreak [REGISTER [MASK]]",
+		.desc = "Enter debugger on VDP register write. If REGISTER is provided, breakpoint will only fire for writes to that register. If MASK is also provided, it will be applied to the register number before comparison with REGISTER",
+		.impl = cmd_vdp_reg_break,
+		.min_args = 0,
+		.max_args = 2
+	},
 #ifndef NO_Z80
 	{
 		.names = (const char *[]){
@@ -2923,6 +2995,8 @@
 	bp_def *new_bp = calloc(1, sizeof(bp_def));
 	new_bp->next = root->breakpoints;
 	new_bp->address = cmd->args[0].value;
+	new_bp->mask = 0xFFFF;
+	new_bp->type = BP_TYPE_CPU;
 	new_bp->index = root->bp_index++;
 	root->breakpoints = new_bp;
 	printf("Z80 Breakpoint %d set at %X\n", new_bp->index, cmd->args[0].value);
@@ -3829,7 +3903,7 @@
 	}
 	root->address = address;
 	//Check if this is a user set breakpoint, or just a temporary one
-	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address);
+	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address, BP_TYPE_CPU);
 	if (*this_bp) {
 		if ((*this_bp)->condition) {
 			uint32_t condres;
@@ -3902,13 +3976,13 @@
 	//probably not necessary, but let's play it safe
 	address &= 0xFFFFFF;
 	if (address == root->branch_t) {
-		bp_def ** f_bp = find_breakpoint(&root->breakpoints, root->branch_f);
+		bp_def ** f_bp = find_breakpoint(&root->breakpoints, root->branch_f, BP_TYPE_CPU);
 		if (!*f_bp) {
 			remove_breakpoint(context, root->branch_f);
 		}
 		root->branch_t = root->branch_f = 0;
 	} else if(address == root->branch_f) {
-		bp_def ** t_bp = find_breakpoint(&root->breakpoints, root->branch_t);
+		bp_def ** t_bp = find_breakpoint(&root->breakpoints, root->branch_t, BP_TYPE_CPU);
 		if (!*t_bp) {
 			remove_breakpoint(context, root->branch_t);
 		}
@@ -3918,7 +3992,7 @@
 	root->address = address;
 	int debugging = 1;
 	//Check if this is a user set breakpoint, or just a temporary one
-	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address);
+	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address, BP_TYPE_CPU);
 	if (*this_bp) {
 		if ((*this_bp)->condition) {
 			uint32_t condres;
--- a/debug.h	Sat Oct 28 16:04:58 2023 -0700
+++ b/debug.h	Mon Oct 30 00:07:56 2023 -0700
@@ -98,6 +98,13 @@
 	command_block else_block;
 };
 
+enum {
+	BP_TYPE_CPU,
+	BP_TYPE_VDPREG,
+	BP_TYPE_VDPDMA,
+	BP_TYPE_VDPDATA
+};
+
 typedef struct bp_def {
 	struct bp_def  *next;
 	parsed_command *commands;
@@ -105,6 +112,8 @@
 	uint32_t       num_commands;
 	uint32_t       address;
 	uint32_t       index;
+	uint32_t       mask;
+	uint8_t        type;
 } bp_def;
 
 typedef struct debug_array debug_array;
@@ -154,7 +163,7 @@
 debug_root *find_root(void *cpu);
 debug_root *find_m68k_root(m68k_context *context);
 debug_root *find_z80_root(z80_context *context);
-bp_def ** find_breakpoint(bp_def ** cur, uint32_t address);
+bp_def ** find_breakpoint(bp_def ** cur, uint32_t address, uint8_t type);
 bp_def ** find_breakpoint_idx(bp_def ** cur, uint32_t index);
 void add_display(disp_def ** head, uint32_t *index, char format_char, char * param);
 void remove_display(disp_def ** head, uint32_t index);
--- a/gdb_remote.c	Sat Oct 28 16:04:58 2023 -0700
+++ b/gdb_remote.c	Mon Oct 30 00:07:56 2023 -0700
@@ -227,6 +227,8 @@
 			bp_def *new_bp = malloc(sizeof(bp_def));
 			new_bp->next = root->breakpoints;
 			new_bp->address = address;
+			new_bp->mask = 0xFFFFFF;
+			new_bp->type = BP_TYPE_CPU;
 			new_bp->index = root->bp_index++;
 			root->breakpoints = new_bp;
 			gdb_send_command("OK");
@@ -241,7 +243,7 @@
 		if (type < '2') {
 			uint32_t address = strtoul(command+3, NULL, 16);
 			remove_breakpoint(context, address);
-			bp_def **found = find_breakpoint(&root->breakpoints, address);
+			bp_def **found = find_breakpoint(&root->breakpoints, address, BP_TYPE_CPU);
 			if (*found)
 			{
 				bp_def * to_remove = *found;
@@ -465,20 +467,20 @@
 		fatal_error("Could not find debug root for CPU %p\n", context);
 	}
 	if ((pc & 0xFFFFFF) == root->branch_t) {
-		bp_def ** f_bp = find_breakpoint(&root->breakpoints, root->branch_f);
+		bp_def ** f_bp = find_breakpoint(&root->breakpoints, root->branch_f, BP_TYPE_CPU);
 		if (!*f_bp) {
 			remove_breakpoint(context, root->branch_f);
 		}
 		root->branch_t = root->branch_f = 0;
 	} else if((pc & 0xFFFFFF) == root->branch_f) {
-		bp_def ** t_bp = find_breakpoint(&root->breakpoints, root->branch_t);
+		bp_def ** t_bp = find_breakpoint(&root->breakpoints, root->branch_t, BP_TYPE_CPU);
 		if (!*t_bp) {
 			remove_breakpoint(context, root->branch_t);
 		}
 		root->branch_t = root->branch_f = 0;
 	}
 	//Check if this is a user set breakpoint, or just a temporary one
-	bp_def ** this_bp = find_breakpoint(&root->breakpoints, pc & 0xFFFFFF);
+	bp_def ** this_bp = find_breakpoint(&root->breakpoints, pc & 0xFFFFFF, BP_TYPE_CPU);
 	if (!*this_bp) {
 		remove_breakpoint(context, pc & 0xFFFFFF);
 	}
--- a/vdp.c	Sat Oct 28 16:04:58 2023 -0700
+++ b/vdp.c	Mon Oct 30 00:07:56 2023 -0700
@@ -1126,6 +1126,9 @@
 		if (context->fifo_read == context->fifo_write) {
 			if ((context->cd & 0x20) && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_FILL) {
 				context->flags |= FLAG_DMA_RUN;
+				if (context->dma_hook) {
+					context->dma_hook(context);
+				}
 			}
 			context->fifo_read = -1;
 		}
@@ -4705,11 +4708,17 @@
 					//only captures are from a direct color DMA demo which will generally start DMA at a very specific point in display so other values are plausible
 					//sticking with 3 slots for now until I can do some more captures
 					vdp_run_context_full(context, context->cycles + 12 * ((context->regs[REG_MODE_2] & BIT_MODE_5) && (context->regs[REG_MODE_4] & BIT_H40) ? 4 : 5));
+					vdp_dma_started();
 					context->flags |= FLAG_DMA_RUN;
-					vdp_dma_started();
+					if (context->dma_hook) {
+						context->dma_hook(context);
+					}
 					return 1;
 				} else {
 					context->flags |= FLAG_DMA_RUN;
+					if (context->dma_hook) {
+						context->dma_hook(context);
+					}
 					//printf("DMA Copy Address: %X, New CD: %X, Source: %X\n", context->address, context->cd, (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]);
 				}
 			} else {
@@ -4723,6 +4732,9 @@
 		if ((value & 0xC000) == 0x8000) {
 			//Register write
 			uint16_t reg = (value >> 8) & 0x1F;
+			if (context->reg_hook) {
+				context->reg_hook(context, reg, value);
+			}
 			vdp_reg_write(context, reg, value);
 		} else if (mode_5) {
 			context->flags |= FLAG_PENDING;
@@ -4774,6 +4786,9 @@
 	while (context->fifo_write == context->fifo_read) {
 		vdp_run_context_full(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20));
 	}
+	if (context->data_hook) {
+		context->data_hook(context, value);
+	}
 	fifo_entry * cur = context->fifo + context->fifo_write;
 	cur->cycle = context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)*FIFO_LATENCY;
 	cur->address = context->address;
--- a/vdp.h	Sat Oct 28 16:04:58 2023 -0700
+++ b/vdp.h	Mon Oct 30 00:07:56 2023 -0700
@@ -176,12 +176,9 @@
 };
 
 typedef struct vdp_context vdp_context;
-typedef void (*vdp_hook_fun)(vdp_context *);
-
-typedef struct {
-	vdp_hook_fun handler;
-	void         *data;
-} vdp_hook;
+typedef void (*vdp_hook)(vdp_context *);
+typedef void (*vdp_reg_hook)(vdp_context *, uint16_t reg, uint16_t value);
+typedef void (*vdp_data_hook)(vdp_context *, uint16_t value);
 
 struct vdp_context {
 	system_header  *system;
@@ -193,8 +190,8 @@
 	uint32_t       *debug_fbs[NUM_DEBUG_TYPES];
 	char           *kmod_msg_buffer;
 	vdp_hook       dma_hook;
-	vdp_hook       vdpreg_hook;
-	vdp_hook       data_hook;
+	vdp_reg_hook   reg_hook;
+	vdp_data_hook  data_hook;
 	uint32_t       kmod_buffer_storage;
 	uint32_t       kmod_buffer_length;
 	uint32_t       timer_start_cycle;