changeset 75:108e587165c0

Implement DMA (untested)
author Mike Pavone <pavone@retrodev.com>
date Fri, 21 Dec 2012 20:56:32 -0800
parents 6396dc91f61e
children 187c65f40a64
files blastem.h stateview.c vdp.c vdp.h
diffstat 4 files changed, 224 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.h	Fri Dec 21 16:38:40 2012 -0800
+++ b/blastem.h	Fri Dec 21 20:56:32 2012 -0800
@@ -1,6 +1,8 @@
 #ifndef BLASTEM_H_
 #define BLASTEM_H_
 
+#include <stdint.h>
+
 typedef struct {
 	uint32_t th_counter;
 	uint32_t timeout_cycle;
@@ -17,6 +19,7 @@
 extern io_port gamepad_2;
 
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
+uint16_t read_dma_value(uint32_t address);
 
 #endif //BLASTEM_H_
 
--- a/stateview.c	Fri Dec 21 16:38:40 2012 -0800
+++ b/stateview.c	Fri Dec 21 20:56:32 2012 -0800
@@ -2,6 +2,16 @@
 #include <stdio.h>
 #include "vdp.h"
 #include "render.h"
+#include "blastem.h"
+
+//not used, but referenced by the renderer since it handles input
+io_port gamepad_1;
+io_port gamepad_2;
+
+uint16_t read_dma_value(uint32_t address)
+{
+	return 0;
+}
 
 int main(int argc, char ** argv)
 {
@@ -28,6 +38,7 @@
 	init_vdp_context(&context);
 	vdp_load_savestate(&context, state_file);
 	vdp_run_to_vblank(&context);
+	printf("Display %s\n", (context.regs[REG_MODE_2] & DISPLAY_ENABLE) ? "enabled" : "disabled");
     render_init(width, height);
     render_context(&context);
     render_wait_quit(&context);
--- a/vdp.c	Fri Dec 21 16:38:40 2012 -0800
+++ b/vdp.c	Fri Dec 21 20:56:32 2012 -0800
@@ -1,4 +1,5 @@
 #include "vdp.h"
+#include "blastem.h"
 #include <stdlib.h>
 #include <string.h>
 
@@ -9,19 +10,13 @@
 #define MAP_BIT_H_FLIP 0x800
 #define MAP_BIT_V_FLIP 0x1000
 
-#define BIT_PAL 0x8
-#define BIT_H40 0x1
+#define BIT_PAL        0x8
+#define BIT_DMA_ENABLE 0x4
+#define BIT_H40        0x1
 
 #define SCROLL_BUFFER_SIZE 32
 #define SCROLL_BUFFER_DRAW 16
 
-#define FLAG_DOT_OFLOW 0x1
-#define FLAG_CAN_MASK  0x2
-#define FLAG_MASKED    0x4
-#define FLAG_WINDOW    0x8
-#define FLAG_PENDING   0x10
-#define FLAG_UNUSED_SLOT 0x20
-
 #define FIFO_SIZE 4
 
 void init_vdp_context(vdp_context * context)
@@ -182,45 +177,162 @@
 #define CRAM_WRITE 3
 #define VSRAM_READ 4
 #define VSRAM_WRITE 5
+#define DMA_START 0x20
 
 void external_slot(vdp_context * context)
 {
-	fifo_entry * start = (context->fifo_end - FIFO_SIZE);
-	//TODO: Implement DMA
-	if (context->fifo_cur != start && start->cycle <= context->cycles) {
-		switch (context->cd & 0x7)
+	//TODO: Figure out what happens if CD bit 4 is not set in DMA copy mode
+	//TODO: Figure out what happens when CD:0-3 is not set to a write mode in DMA operations
+	//TODO: Figure out what happens if DMA gets disabled part way through a DMA fill or DMA copy
+	if((context->regs[REG_MODE_2] & BIT_DMA_ENABLE) && (context->flags & FLAG_DMA_RUN)) {
+		uint16_t dma_len;
+		switch(context->regs[REG_DMASRC_H] & 0xC0)
 		{
-		case VRAM_WRITE:
-			if (start->partial) {
-				//printf("VRAM Write: %X to %X\n", start->value, context->address ^ 1);
-				context->vdpmem[context->address ^ 1] = start->value;
-			} else {
-				//printf("VRAM Write High: %X to %X\n", start->value >> 8, context->address);
-				context->vdpmem[context->address] = start->value >> 8;
-				start->partial = 1;
-				//skip auto-increment and removal of entry from fifo
-				return;
+		//68K -> VDP
+		case 0:
+		case 0x40:
+			switch(context->cd & 0xF)
+			{
+			case VRAM_WRITE:
+				if (context->flags & FLAG_DMA_PROG) {
+					context->vdpmem[context->address ^ 1] = context->dma_val;
+					context->flags &= ~FLAG_DMA_PROG;
+				} else {
+					context->dma_val = read_dma_value((context->regs[REG_DMASRC_H] << 16) | (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]);
+					context->vdpmem[context->address] = context->dma_val >> 8;
+					context->flags |= FLAG_DMA_PROG;
+				}
+				break;
+			case CRAM_WRITE:
+				context->cram[(context->address/2) & (CRAM_SIZE-1)] = read_dma_value((context->regs[REG_DMASRC_H] << 16) | (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]);
+				break;
+			case VSRAM_WRITE:
+				if (((context->address/2) & 63) < VSRAM_SIZE) {
+					context->vsram[(context->address/2) & 63] = read_dma_value((context->regs[REG_DMASRC_H] << 16) | (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]);
+				}
+				break;
 			}
 			break;
-		case CRAM_WRITE:
-			//printf("CRAM Write: %X to %X\n", start->value, context->address);
-			context->cram[(context->address/2) & (CRAM_SIZE-1)] = start->value;
+		//Fill
+		case 0x80:
+			switch(context->cd & 0xF)
+			{
+			case VRAM_WRITE:
+				//Charles MacDonald's VDP doc says that the low byte gets written first
+				//this doesn't make a lot of sense to me, but until I've had a change to
+				//verify it myself, I'll assume it's true
+				if (context->flags & FLAG_DMA_PROG) {
+					context->vdpmem[context->address ^ 1] = context->dma_val >> 8;
+					context->flags &= ~FLAG_DMA_PROG;
+				} else {
+					context->dma_val = read_dma_value((context->regs[REG_DMASRC_H] << 16) | (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]);
+					context->vdpmem[context->address] = context->dma_val;
+					context->flags |= FLAG_DMA_PROG;
+				}
+				break;
+			case CRAM_WRITE:
+				context->cram[(context->address/2) & (CRAM_SIZE-1)] = context->dma_val;
+				break;
+			case VSRAM_WRITE:
+				if (((context->address/2) & 63) < VSRAM_SIZE) {
+					context->vsram[(context->address/2) & 63] = context->dma_val;
+				}
+				break;
+			}
 			break;
-		case VSRAM_WRITE:
-			if (((context->address/2) & 63) < VSRAM_SIZE) {
-				//printf("VSRAM Write: %X to %X\n", start->value, context->address);
-				context->vsram[(context->address/2) & 63] = start->value;
+		//Copy
+		case 0xC0:
+			if (context->flags & FLAG_DMA_PROG) {
+				switch(context->cd & 0xF)
+				{
+				case VRAM_WRITE:
+					context->vdpmem[context->address] = context->dma_val;
+					break;
+				case CRAM_WRITE:
+					context->cram[(context->address/2) & (CRAM_SIZE-1)] = context->dma_val;
+					break;
+				case VSRAM_WRITE:
+					if (((context->address/2) & 63) < VSRAM_SIZE) {
+						context->vsram[(context->address/2) & 63] = context->dma_val;
+					}
+					break;
+				}
+				context->flags &= ~FLAG_DMA_PROG;
+			} else {
+				//I assume, that DMA copy copies from the same RAM as the destination
+				//but it's possible I'm mistaken
+				switch(context->cd & 0xF)
+				{
+				case VRAM_WRITE:
+					context->dma_val = context->vdpmem[(context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]];
+					break;
+				case CRAM_WRITE:
+					context->dma_val = context->cram[context->regs[REG_DMASRC_L] & (CRAM_SIZE-1)];
+					break;
+				case VSRAM_WRITE:
+					if ((context->regs[REG_DMASRC_L] & 63) < VSRAM_SIZE) {
+						context->dma_val = context->vsram[context->regs[REG_DMASRC_L] & 63];
+					} else {
+						context->dma_val = 0;
+					}
+					break;
+				}
+				context->flags |= FLAG_DMA_PROG;
 			}
 			break;
 		}
-		context->address += context->regs[REG_AUTOINC];
-		fifo_entry * cur = start+1;
-		if (cur < context->fifo_cur) {
-			memmove(start, cur, sizeof(fifo_entry) * (context->fifo_cur - cur));
+		if (!(context->flags & FLAG_DMA_PROG)) {
+			context->address += context->regs[REG_AUTOINC];
+			context->regs[REG_DMASRC_L] += 1;
+			dma_len = ((context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L]) - 1;
+			context->regs[REG_DMALEN_H] = dma_len >> 8;
+			context->regs[REG_DMALEN_L] = dma_len;
+			if (!dma_len) {
+				context->flags &= ~FLAG_DMA_RUN;
+			}
 		}
-		context->fifo_cur -= 1;
 	} else {
-		context->flags |= FLAG_UNUSED_SLOT;
+		fifo_entry * start = (context->fifo_end - FIFO_SIZE);
+		if (context->fifo_cur != start && start->cycle <= context->cycles) {
+			if ((context->regs[REG_MODE_2] & BIT_DMA_ENABLE) && (context->cd & DMA_START)) {
+				context->flags |= FLAG_DMA_RUN;
+				context->dma_val = start->value;
+			} else {
+				switch (context->cd & 0xF)
+				{
+				case VRAM_WRITE:
+					if (start->partial) {
+						//printf("VRAM Write: %X to %X\n", start->value, context->address ^ 1);
+						context->vdpmem[context->address ^ 1] = start->value;
+					} else {
+						//printf("VRAM Write High: %X to %X\n", start->value >> 8, context->address);
+						context->vdpmem[context->address] = start->value >> 8;
+						start->partial = 1;
+						//skip auto-increment and removal of entry from fifo
+						return;
+					}
+					break;
+				case CRAM_WRITE:
+					//printf("CRAM Write: %X to %X\n", start->value, context->address);
+					context->cram[(context->address/2) & (CRAM_SIZE-1)] = start->value;
+					break;
+				case VSRAM_WRITE:
+					if (((context->address/2) & 63) < VSRAM_SIZE) {
+						//printf("VSRAM Write: %X to %X\n", start->value, context->address);
+						context->vsram[(context->address/2) & 63] = start->value;
+					}
+					break;
+				}
+				context->address += context->regs[REG_AUTOINC];
+			}
+			fifo_entry * cur = start+1;
+			if (cur < context->fifo_cur) {
+				memmove(start, cur, sizeof(fifo_entry) * (context->fifo_cur - cur));
+			}
+			context->fifo_cur -= 1;
+		} else {
+			context->flags |= FLAG_UNUSED_SLOT;
+		}
 	}
 }
 
@@ -794,8 +906,6 @@
 	context->latched_mode = (context->regs[REG_MODE_4] & 0x81) | (context->regs[REG_MODE_2] & BIT_PAL);
 }
 
-#define DISPLAY_ENABLE 0x40
-
 int is_refresh(vdp_context * context)
 {
 	uint32_t linecyc = context->cycles % MCLKS_LINE;
@@ -886,17 +996,49 @@
 	return context->cycles;
 }
 
-void vdp_control_port_write(vdp_context * context, uint16_t value)
+void vdp_run_dma_done(vdp_context * context, uint32_t target_cycles)
+{
+	for(;;) {
+		uint32_t dmalen = (context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L];
+		if (!dmalen) {
+			dmalen = 0x10000;
+		}
+		uint32_t min_dma_complete = dmalen * (context->latched_mode & BIT_H40 ? 16 : 20);
+		if ((context->regs[REG_DMASRC_H] & 0xC0) == 0xC0 || (context->cd & 0xF) == VRAM_WRITE) {
+			//DMA copies take twice as long to complete since they require a read and a write
+			//DMA Fills and transfers to VRAM also take twice as long as it requires 2 writes for a single word
+			min_dma_complete *= 2;
+		}
+		min_dma_complete += context->cycles;
+		if (target_cycles < min_dma_complete) {
+			vdp_run_context(context, target_cycles);
+			return;
+		} else {
+			vdp_run_context(context, min_dma_complete);
+			if (!(context->flags & FLAG_DMA_RUN)) {
+				return;
+			}
+		}
+	}
+}
+
+int vdp_control_port_write(vdp_context * context, uint16_t value)
 {
 	//printf("control port write: %X\n", value);
 	if (context->flags & FLAG_PENDING) {
 		context->address = (context->address & 0x3FFF) | (value << 14);
 		context->cd = (context->cd & 0x3) | ((value >> 2) & 0x3C);
-		if (context->cd & 0x30) {
-			puts("attempt to use DMA detected!");
+		context->flags &= ~FLAG_PENDING;
+		//printf("New Address: %X, New CD: %X\n", context->address, context->cd);
+		if (context->cd & 0x20) {
+			if((context->regs[REG_DMASRC_H] & 0xC0) != 0x80) {
+				//DMA copy or 68K -> VDP, transfer starts immediately
+				context->flags |= FLAG_DMA_RUN;
+				if (!(context->regs[REG_DMASRC_H] & 0x80)) {
+					return 1;
+				}
+			}
 		}
-		//printf("New Address: %X, New CD: %X\n", context->address, context->cd);
-		context->flags &= ~FLAG_PENDING;
 	} else {
 		if ((value & 0xC000) == 0x8000) {
 			//Register write
@@ -914,6 +1056,7 @@
 			context->cd = (context->cd &0x3C) | (value >> 14);
 		}
 	}
+	return 0;
 }
 
 void vdp_data_port_write(vdp_context * context, uint16_t value)
@@ -942,6 +1085,9 @@
 	if (context->fifo_cur == context->fifo_end) {
 		value |= 0x100;
 	}
+	if (context->flags & FLAG_DMA_RUN) {
+		value |= 0x20;
+	}
 	//TODO: Lots of other bits in status port
 	return value;
 }
--- a/vdp.h	Fri Dec 21 16:38:40 2012 -0800
+++ b/vdp.h	Fri Dec 21 20:56:32 2012 -0800
@@ -28,6 +28,17 @@
 
 #define MCLKS_LINE 3420
 
+#define FLAG_DOT_OFLOW 0x1
+#define FLAG_CAN_MASK  0x2
+#define FLAG_MASKED    0x4
+#define FLAG_WINDOW    0x8
+#define FLAG_PENDING   0x10
+#define FLAG_UNUSED_SLOT 0x20
+#define FLAG_DMA_RUN   0x40
+#define FLAG_DMA_PROG  0x80
+
+#define DISPLAY_ENABLE 0x40
+
 enum {
 	REG_MODE_1=0,
 	REG_MODE_2,
@@ -43,7 +54,12 @@
 	REG_AUTOINC=0xF,
 	REG_SCROLL,
 	REG_WINDOW_H,
-	REG_WINDOW_V
+	REG_WINDOW_V,
+	REG_DMALEN_L,
+	REG_DMALEN_H,
+	REG_DMASRC_L,
+	REG_DMASRC_M,
+	REG_DMASRC_H
 } vdp_regs;
 
 typedef struct {
@@ -92,6 +108,7 @@
 	sprite_info sprite_info_list[MAX_SPRITES_LINE];
 	uint16_t    col_1;
 	uint16_t    col_2;
+	uint16_t    dma_val;
 	uint8_t     v_offset;
 	uint8_t     *tmp_buf_a;
 	uint8_t     *tmp_buf_b;
@@ -101,9 +118,11 @@
 void vdp_run_context(vdp_context * context, uint32_t target_cycles);
 //runs from current cycle count to VBLANK for the current mode, returns ending cycle count
 uint32_t vdp_run_to_vblank(vdp_context * context);
+//runs until the target cycle is reached or the current DMA operation has completed, whicever comes first
+void vdp_run_dma_done(vdp_context * context, uint32_t target_cycles);
 void vdp_load_savestate(vdp_context * context, FILE * state_file);
 void vdp_save_state(vdp_context * context, FILE * outfile);
-void vdp_control_port_write(vdp_context * context, uint16_t value);
+int vdp_control_port_write(vdp_context * context, uint16_t value);
 void vdp_data_port_write(vdp_context * context, uint16_t value);
 uint16_t vdp_control_port_read(vdp_context * context);
 uint16_t vdp_data_port_read(vdp_context * context);