changeset 54:3b79cbcf6846

Get Flavio's color bar demo kind of sort of working
author Mike Pavone <pavone@retrodev.com>
date Tue, 18 Dec 2012 02:16:42 -0800
parents 44e661913a51
children 8317f174d916
files 68kinst.c Makefile m68k_to_x86.c m68k_to_x86.h render.h render_sdl.c runtime.S vdp.c vdp.h
diffstat 9 files changed, 420 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/68kinst.c	Sun Dec 16 22:25:29 2012 -0800
+++ b/68kinst.c	Tue Dec 18 02:16:42 2012 -0800
@@ -1223,7 +1223,11 @@
 		strcpy(dst+ret, cond_mnem[decoded->extra.cond]);
 		ret = strlen(dst);
 		if (decoded->op != M68K_SCC) {
-			ret += sprintf(dst+ret, " #%d <%X>", decoded->src.params.immed, decoded->address + 2 + decoded->src.params.immed);
+			if (decoded->op == M68K_DBCC) {
+				ret += sprintf(dst+ret, " d%d, #%d <%X>", decoded->dst.params.regs.pri, decoded->src.params.immed, decoded->address + 2 + decoded->src.params.immed);
+			} else {
+				ret += sprintf(dst+ret, " #%d <%X>", decoded->src.params.immed, decoded->address + 2 + decoded->src.params.immed);
+			}
 			return ret;
 		}
 		break;
--- a/Makefile	Sun Dec 16 22:25:29 2012 -0800
+++ b/Makefile	Tue Dec 18 02:16:42 2012 -0800
@@ -1,6 +1,9 @@
 LIBS=sdl
 
-all : dis trans stateview
+all : dis trans stateview blastem
+
+blastem : blastem.o 68kinst.o gen_x86.o m68k_to_x86.o runtime.o mem.o vdp.o render_sdl.o
+	$(CC) -o blastem blastem.o 68kinst.o gen_x86.o m68k_to_x86.o runtime.o mem.o vdp.o render_sdl.o `pkg-config --libs $(LIBS)`
 
 dis : dis.o 68kinst.o
 	$(CC) -o dis dis.o 68kinst.o
--- a/m68k_to_x86.c	Sun Dec 16 22:25:29 2012 -0800
+++ b/m68k_to_x86.c	Tue Dec 18 02:16:42 2012 -0800
@@ -151,6 +151,30 @@
 		ea->mode = MODE_REG_DIRECT;
 		ea->base = SCRATCH1;
 		break;
+	case MODE_ABSOLUTE:
+	case MODE_ABSOLUTE_SHORT:
+		if (inst->src.addr_mode == MODE_ABSOLUTE) {
+			out = cycles(out, BUS*2);
+		} else {
+			out = cycles(out, BUS);
+		}
+		out = mov_ir(out, inst->src.params.immed, SCRATCH1, SZ_D);
+		out = check_cycles(out);
+		switch (inst->extra.size)
+		{
+		case OPSIZE_BYTE:
+			out = call(out, (char *)m68k_read_byte_scratch1);
+			break;
+		case OPSIZE_WORD:
+			out = call(out, (char *)m68k_read_word_scratch1);
+			break;
+		case OPSIZE_LONG:
+			out = call(out, (char *)m68k_read_long_scratch1);
+			break;
+		}
+		ea->mode = MODE_REG_DIRECT;
+		ea->base = SCRATCH1;
+		break;
 	case MODE_IMMEDIATE:
 		if (inst->variant != VAR_QUICK) {
 			if (inst->extra.size == OPSIZE_LONG) {
@@ -332,6 +356,10 @@
 	x86_ea src;
 	dst = translate_m68k_src(inst, &src, dst, opts);
 	reg = native_reg(&(inst->dst), opts);
+	//update statically set flags
+	dst = mov_ir(dst, 0, FLAG_V, SZ_B);
+	dst = mov_ir(dst, 0, FLAG_C, SZ_B);
+	
 	if (src.mode == MODE_REG_DIRECT) {
 		flags_reg = src.base;
 	} else {
@@ -362,6 +390,9 @@
 		} else {
 			dst = mov_irdisp8(dst, src.disp, CONTEXT, reg_offset(&(inst->dst)), inst->extra.size);
 		}
+		dst = cmp_ir(dst, 0, flags_reg, inst->extra.size);
+		dst = setcc_r(dst, CC_Z, FLAG_Z);
+		dst = setcc_r(dst, CC_S, FLAG_N);
 		break;
 	case MODE_AREG_PREDEC:
 		dec_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : 1);
@@ -386,6 +417,9 @@
 		} else {
 			dst = mov_ir(dst, src.disp, SCRATCH1, inst->extra.size);
 		}
+		dst = cmp_ir(dst, 0, flags_reg, inst->extra.size);
+		dst = setcc_r(dst, CC_Z, FLAG_Z);
+		dst = setcc_r(dst, CC_S, FLAG_N);
 		switch (inst->extra.size)
 		{
 		case OPSIZE_BYTE:
@@ -407,6 +441,40 @@
 			}
 		}
 		break;
+	case MODE_ABSOLUTE:
+	case MODE_ABSOLUTE_SHORT:
+		if (src.mode == MODE_REG_DIRECT) {
+			if (src.base != SCRATCH1) {
+				dst = mov_rr(dst, src.base, SCRATCH1, inst->extra.size);
+			}
+		} else if (src.mode == MODE_REG_DISPLACE8) {
+			dst = mov_rdisp8r(dst, src.base, src.disp, SCRATCH1, inst->extra.size);
+		} else {
+			dst = mov_ir(dst, src.disp, SCRATCH1, inst->extra.size);
+		}
+		if (inst->dst.addr_mode == MODE_ABSOLUTE) {
+			dst = cycles(dst, BUS*2);
+		} else {
+			dst = cycles(dst, BUS);
+		}
+		dst = mov_ir(dst, inst->dst.params.immed, SCRATCH2, SZ_D);
+		dst = cmp_ir(dst, 0, flags_reg, inst->extra.size);
+		dst = setcc_r(dst, CC_Z, FLAG_Z);
+		dst = setcc_r(dst, CC_S, FLAG_N);
+		dst = check_cycles(dst);
+		switch (inst->extra.size)
+		{
+		case OPSIZE_BYTE:
+			dst = call(dst, (char *)m68k_write_byte);
+			break;
+		case OPSIZE_WORD:
+			dst = call(dst, (char *)m68k_write_word);
+			break;
+		case OPSIZE_LONG:
+			dst = call(dst, (char *)m68k_write_long_highfirst);
+			break;
+		}
+		break;
 	default:
 		printf("address mode %d not implemented (move dst)\n", inst->dst.addr_mode);
 		exit(1);
@@ -414,12 +482,6 @@
 
 	//add cycles for prefetch
 	dst = cycles(dst, BUS);
-	//update flags
-	dst = mov_ir(dst, 0, FLAG_V, SZ_B);
-	dst = mov_ir(dst, 0, FLAG_C, SZ_B);
-	dst = cmp_ir(dst, 0, flags_reg, inst->extra.size);
-	dst = setcc_r(dst, CC_Z, FLAG_Z);
-	dst = setcc_r(dst, CC_S, FLAG_N);
 	dst = check_cycles(dst);
 	return dst;
 }
--- a/m68k_to_x86.h	Sun Dec 16 22:25:29 2012 -0800
+++ b/m68k_to_x86.h	Tue Dec 18 02:16:42 2012 -0800
@@ -35,6 +35,8 @@
 	uint32_t		target_cycle;
 	uint32_t		current_cycle;
 	uint16_t        *mem_pointers[NUM_MEM_AREAS];
+	void            *next_context;
+	uint16_t        value;
 	native_map_slot *native_code_map;
 	void            *options;
 } m68k_context;
@@ -44,3 +46,5 @@
 void start_68k_context(m68k_context * context, uint32_t address);
 void init_x86_68k_opts(x86_68k_options * opts);
 void init_68k_context(m68k_context * context, native_map_slot * native_code_map, void * opts);
+void m68k_reset(m68k_context * context);
+
--- a/render.h	Sun Dec 16 22:25:29 2012 -0800
+++ b/render.h	Tue Dec 18 02:16:42 2012 -0800
@@ -5,6 +5,7 @@
 void render_init(int width, int height);
 void render_context(vdp_context * context);
 void render_wait_quit(vdp_context * context);
+void wait_render_frame(vdp_context * context);
 
 #endif //RENDER_SDL_H_
 
--- a/render_sdl.c	Sun Dec 16 22:25:29 2012 -0800
+++ b/render_sdl.c	Tue Dec 18 02:16:42 2012 -0800
@@ -6,6 +6,8 @@
 SDL_Surface *screen;
 uint8_t render_dbg = 0;
 
+uint32_t last_frame = 0;
+
 void render_init(int width, int height)
 {
 	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
@@ -31,6 +33,7 @@
 	uint16_t *buf_16;
 	uint32_t *buf_32; 
 	uint8_t b,g,r;
+	last_frame = SDL_GetTicks();
 	if (SDL_MUSTLOCK(screen)) {
 		if (SDL_LockSurface(screen) < 0) {
 			return;
@@ -43,7 +46,6 @@
     } else {
     	repeat_y = repeat_x;
     }
-    printf("w: %d, h: %d, repeat_x: %d, repeat_y: %d\n", screen->clip_rect.w, screen->clip_rect.h, repeat_x, repeat_y);
     switch (screen->format->BytesPerPixel) {
     case 2:
         buf_16 = (uint16_t *)screen->pixels;
@@ -151,3 +153,38 @@
 	}
 }
 
+#define FRAME_DELAY 16
+#define MIN_DELAY 10
+
+void wait_render_frame(vdp_context * context)
+{
+	SDL_Event event;
+	while(SDL_PollEvent(&event)) {
+		switch (event.type) {
+		case SDL_KEYDOWN:
+			//TODO: Update emulated gamepads
+			if (event.key.keysym.sym == SDLK_LEFTBRACKET) {
+				render_dbg = !render_dbg;
+				render_context(context);
+			}
+			break;
+		case SDL_QUIT:
+			exit(0);
+		}
+	}
+	//TODO: Adjust frame delay so we actually get 60 FPS rather than 62.5 FPS
+	uint32_t current = SDL_GetTicks();
+	uint32_t desired = last_frame + FRAME_DELAY;
+	if (current < desired) {
+		uint32_t delay = last_frame + FRAME_DELAY - current;
+		//TODO: Calculate MIN_DELAY at runtime
+		if (delay > MIN_DELAY) {
+			SDL_Delay((delay/MIN_DELAY)*MIN_DELAY);
+		}
+		while ((desired) < SDL_GetTicks()) {
+		}
+	}
+	render_context(context);
+}
+
+
--- a/runtime.S	Sun Dec 16 22:25:29 2012 -0800
+++ b/runtime.S	Tue Dec 18 02:16:42 2012 -0800
@@ -1,15 +1,42 @@
 
 	.global handle_cycle_limit
 handle_cycle_limit:
+	call m68k_save_context
+	mov %rsi, %rdi
+	call sync_components
+	mov %rax, %rsi
+	call m68k_load_context
+	ret
+
+do_vdp_port_write:
+	call m68k_save_context
+	mov %rcx, %rdx
+	call vdp_port_write
+	mov %rax, %rsi
+	call m68k_load_context
+	ret
+
+do_vdp_port_read:
+	call m68k_save_context
+	call vdp_port_read
+	mov %rax, %rsi
+	call m68k_load_context
+	mov 120(%rsi), %cx
 	ret
 	
+bad_access_msg:
+	.asciz "Program tried to access illegal 68K address %X\n"
+	
 	.global m68k_write_word
+	.global vdp_psg_w
 m68k_write_word:
 	and $0xFFFFFF, %rdi
 	cmp $0x400000, %edi
 	jle cart_w
 	cmp $0xE00000, %edi
 	jge workram_w
+	cmp $0xC00000, %edi
+	jge vdp_psg_w
 	jmp inccycles
 workram_w:
 	and $0xFFFF, %rdi
@@ -18,6 +45,48 @@
 cart_w:
 	mov %cx, (%r8, %rdi)
 	jmp inccycles
+vdp_psg_w:
+	test $0x2700E0, %edi
+	jnz crash
+	and $0x1F, %edi
+	cmp $4, %edi
+	jl try_fifo_write
+	jmp do_vdp_port_write
+try_fifo_write:
+	push %rdx
+	push %rbx
+	/* fetch VDP context pointer from 68K context */
+	mov 112(%rsi), %rdx
+	/* get fifo_cur and compare it to fifo_end */
+	mov (%rdx), %rbx
+	cmp %rbx, 8(%rdx)
+	/* bail out if fifo is full */
+	je fifo_fallback
+	/* populate FIFO entry */
+	mov %cx, 4(%rbx) /* value */
+	movb $0, 6(%rbx) /* partial */
+	mov %eax, %ecx
+	shl $3, %ecx /* multiply by 68K cycle by 7 to get MCLK cycle */
+	sub %eax, %ecx
+	mov %ecx, (%rbx) /* cycle */
+	/* update fifo_cur and store back in 68K context */
+	add $8, %rbx
+	mov %rbx, (%rdx)
+	/* clear pending flag */
+	andb $0xEF, 19(%rdx)
+	pop %rbx
+	pop %rdx
+	jmp inccycles
+fifo_fallback:
+	pop %rbx
+	pop %rdx
+	jmp do_vdp_port_write
+crash:
+	mov %edi, %esi
+	lea bad_access_msg(%rip), %rdi
+	call printf
+	mov $1, %rdi
+	call exit
 
 	.global m68k_write_byte
 m68k_write_byte:
@@ -64,20 +133,26 @@
 	jle cart
 	cmp $0xE00000, %ecx
 	jge workram
+	cmp $0xC00000, %edi
+	jge vdp_psg
 	xor %cx, %cx
+	dec %cx
 	jmp inccycles
 workram:
 	and $0xFFFF, %rcx
 	mov (%r9, %rcx), %cx
 	jmp inccycles
+vdp_psg:
+	test $0x2700E0, %edi
+	jnz crash
+	and $0x1F, %edi
+	jmp do_vdp_port_read
 cart:
 	mov (%r8, %rcx), %cx
 inccycles:
 	add $4, %rax
 	cmp %rbp, %rax
-	jge sync
-	ret
-sync:
+	jge handle_cycle_limit
 	ret
 	
 	.global m68k_read_long_scratch1
@@ -103,6 +178,7 @@
 	cmp $0xE00000, %ecx
 	jge workram_b
 	xor %cl, %cl
+	dec %cl
 	jmp inccycles
 workram_b:
 	and $0xFFFF, %rcx
@@ -146,6 +222,7 @@
 	mov %r13d, 40(%rsi) /* a0 */
 	mov %r14d, 44(%rsi) /* a1 */
 	mov %r15d, 68(%rsi) /* a7 */
+	mov %eax, 76(%rsi) /* current cycle count */
 	ret
 
 	.global m68k_load_context
--- a/vdp.c	Sun Dec 16 22:25:29 2012 -0800
+++ b/vdp.c	Tue Dec 18 02:16:42 2012 -0800
@@ -2,7 +2,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#define MCLKS_LINE 3420
 #define NTSC_ACTIVE 225
 #define PAL_ACTIVE 241
 #define BUF_BIT_PRIORITY 0x40
@@ -20,16 +19,24 @@
 #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)
 {
 	memset(context, 0, sizeof(context));
 	context->vdpmem = malloc(VRAM_SIZE);
 	context->framebuf = malloc(FRAMEBUF_SIZE);
+	memset(context->framebuf, 0, FRAMEBUF_SIZE);
 	context->linebuf = malloc(LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
+	memset(context->linebuf, 0, LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
 	context->tmp_buf_a = context->linebuf + LINEBUF_SIZE;
 	context->tmp_buf_b = context->tmp_buf_a + SCROLL_BUFFER_SIZE;
 	context->sprite_draws = MAX_DRAWS;
+	context->fifo_cur = malloc(sizeof(fifo_entry) * FIFO_SIZE);
+	context->fifo_end = context->fifo_cur + FIFO_SIZE;
 }
 
 void render_sprite_cells(vdp_context * context)
@@ -168,9 +175,52 @@
 	}
 }
 
+#define VRAM_READ 0
+#define VRAM_WRITE 1
+#define CRAM_READ 8
+#define CRAM_WRITE 3
+#define VSRAM_READ 4
+#define VSRAM_WRITE 5
+
 void external_slot(vdp_context * context)
 {
-	//TODO: Implement me
+	fifo_entry * start = (context->fifo_end - FIFO_SIZE);
+	//TODO: Implement DMA
+	if (context->fifo_cur != start && start->cycle <= context->cycles) {
+		switch (context->cd & 0x7)
+		{
+		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: %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 & (CRAM_SIZE-1)] = start->value;
+			break;
+		case VSRAM_WRITE:
+			if ((context->address & 63) < VSRAM_SIZE) {
+				printf("VSRAM Write: %X to %X\n", start->value, context->address);
+				context->vsram[context->address & 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;
+	}
 }
 
 #define WINDOW_RIGHT 0x80
@@ -213,7 +263,7 @@
 			}
 			offset = address + line_offset + (((column - 2) * 2) & mask);
 			context->col_1 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1];
-			printf("Window | top: %d, bot: %d, left: %d, right: %d, base: %X, line: %X offset: %X, tile: %X, reg: %X\n", top_line, bottom_line, left_col, right_col, address, line_offset, offset, ((context->col_1 & 0x3FF) << 5), context->regs[REG_WINDOW]);
+			//printf("Window | top: %d, bot: %d, left: %d, right: %d, base: %X, line: %X offset: %X, tile: %X, reg: %X\n", top_line, bottom_line, left_col, right_col, address, line_offset, offset, ((context->col_1 & 0x3FF) << 5), context->regs[REG_WINDOW]);
 			offset = address + line_offset + (((column - 1) * 2) & mask);
 			context->col_2 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1];
 			context->v_offset = (line) & 0x7;
@@ -524,7 +574,7 @@
 		address += line * 4;
 		context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1];
 		context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3];
-		printf("%d: HScroll A: %d, HScroll B: %d\n", line, context->hscroll_a, context->hscroll_b);
+		//printf("%d: HScroll A: %d, HScroll B: %d\n", line, context->hscroll_a, context->hscroll_b);
 		break;
 	case 36:
 	//!HSYNC high
@@ -743,16 +793,58 @@
 	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;
+	if (context->latched_mode & BIT_H40) {
+		linecyc = linecyc/16;
+		return (linecyc == 73 || linecyc == 105 || linecyc == 137 || linecyc == 169 || linecyc == 201);
+	} else {
+		linecyc = linecyc/20;
+		return (linecyc == 66 || linecyc == 98 || linecyc == 130 || linecyc == 162);
+	}
+}
+
+void check_render_bg(vdp_context * context, int32_t line)
+{
+	if (line > 0) {
+		line -= 1;
+		uint16_t * start = NULL, *end = NULL;
+		uint32_t linecyc = (context->cycles % MCLKS_LINE);
+		if (context->latched_mode & BIT_H40) {
+			linecyc /= 16;
+			if (linecyc >= 55 && linecyc <= 207 && !((linecyc-55) % 8)) {
+				uint32_t x = ((linecyc-55)&(~0xF))*2;
+				start = context->framebuf + line * 320 + x;
+				end = start + 16;
+			}
+		} else {
+			linecyc /= 20;
+			if (linecyc >= 48 && linecyc <= 168 && !((linecyc-48) % 8)) {
+				uint32_t x = ((linecyc-48)&(~0xF))*2;
+				start = context->framebuf + line * 256 + x;
+				end = start + 16;
+			}
+		}
+		while (start != end) {
+			*start = context->regs[REG_BG_COLOR] & 0x3F;
+			++start;
+		}
+	}
+}
+
 void vdp_run_context(vdp_context * context, uint32_t target_cycles)
 {
 	while(context->cycles < target_cycles)
 	{
 		uint32_t line = context->cycles / MCLKS_LINE;
 		uint32_t active_lines = context->latched_mode & BIT_PAL ? PAL_ACTIVE : NTSC_ACTIVE;
-		if (line < active_lines) {
-			if (!line) {
-				latch_mode(context);
-			}
+		if (!line) {
+			latch_mode(context);
+		}
+		if (line < active_lines && context->regs[REG_MODE_2] & DISPLAY_ENABLE) {
 			//first sort-of active line is treated as 255 internally
 			//it's used for gathering sprite info for line 
 			line = (line - 1) & 0xFF;
@@ -762,15 +854,26 @@
 			if (context->latched_mode & BIT_H40){
 				//TODO: Deal with nasty clock switching during HBLANK
 				linecyc = linecyc/16;
+				vdp_h40(line, linecyc, context);
 				context->cycles += 16;
-				vdp_h40(line, linecyc, context);
 			} else {
 				linecyc = linecyc/20;
+				vdp_h32(line, linecyc, context);
 				context->cycles += 20;
-				vdp_h32(line, linecyc, context);
 			}
 		} else {
-			//TODO: Empty FIFO
+			if (!is_refresh(context)) {
+				external_slot(context);
+			}
+			if (line < active_lines) {
+				check_render_bg(context, line);
+			}
+			if (context->latched_mode & BIT_H40){
+				//TODO: Deal with nasty clock switching during HBLANK
+				context->cycles += 16;
+			} else {
+				context->cycles += 20;
+			}
 		}
 	}
 }
@@ -782,6 +885,94 @@
 	return context->cycles;
 }
 
+void 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);
+		context->flags &= ~FLAG_PENDING;
+	} else {
+		if ((value & 0xC000) == 0x8000) {
+			//Register write
+			uint8_t reg = (value >> 8) & 0x1F;
+			if (reg < VDP_REGS) {
+				printf("register %d set to %X\n", reg, value);
+				context->regs[reg] = value;
+			}
+		} else {
+			context->flags |= FLAG_PENDING;
+			context->address = (context->address &0xC000) | (value & 0x3FFF);
+			context->cd = (context->cd &0x3C) | (value >> 14);
+		}
+	}
+}
+
+void vdp_data_port_write(vdp_context * context, uint16_t value)
+{
+	printf("data port write: %X\n", value);
+	context->flags &= ~FLAG_PENDING;
+	if (context->fifo_cur == context->fifo_end) {
+		printf("FIFO full, waiting for space before next write at cycle %X\n", context->cycles);
+	}
+	while (context->fifo_cur == context->fifo_end) {
+		vdp_run_context(context, context->cycles + ((context->latched_mode & BIT_H40) ? 16 : 20));
+	}
+	context->fifo_cur->cycle = context->cycles;
+	context->fifo_cur->value = value;
+	context->fifo_cur->partial = 0;
+	context->fifo_cur++;
+}
+
+uint16_t vdp_control_port_read(vdp_context * context)
+{
+	context->flags &= ~FLAG_PENDING;
+	uint16_t value = 0x3400;
+	if (context->fifo_cur == (context->fifo_end - FIFO_SIZE)) {
+		value |= 0x200;
+	}
+	if (context->fifo_cur == context->fifo_end) {
+		value |= 0x100;
+	}
+	//TODO: Lots of other bits in status port
+	return value;
+}
+
+uint16_t vdp_data_port_read(vdp_context * context)
+{
+	context->flags &= ~FLAG_PENDING;
+	if (!(context->cd & 1)) {
+		return 0;
+	}
+	//Not sure if the FIFO should be drained before processing a read or not, but it would make sense
+	context->flags &= ~FLAG_UNUSED_SLOT;
+	while (!(context->flags & FLAG_UNUSED_SLOT)) {
+		vdp_run_context(context, context->cycles + ((context->latched_mode & BIT_H40) ? 16 : 20));
+	}
+	uint16_t value = 0;
+	switch (context->cd & 0x7)
+	{
+	case VRAM_READ:
+		value = context->vdpmem[context->address] << 8;
+		context->flags &= ~FLAG_UNUSED_SLOT;
+		while (!(context->flags & FLAG_UNUSED_SLOT)) {
+			vdp_run_context(context, context->cycles + ((context->latched_mode & BIT_H40) ? 16 : 20));
+		}
+		value |= context->vdpmem[context->address ^ 1];
+		break;
+	case CRAM_READ:
+		value = context->cram[(context->address/2) & (CRAM_SIZE-1)];
+		break;
+	case VSRAM_READ:
+		if (((context->address / 2) & 63) < VSRAM_SIZE) {
+			value = context->vsram[context->address & 63];
+		}
+		break;
+	}
+	context->address += context->regs[REG_AUTOINC];
+	return value;
+}
+
 #define GST_VDP_REGS 0xFA
 #define GST_VDP_MEM 0x12478
 
@@ -802,3 +993,4 @@
 	fseek(state_file, GST_VDP_MEM, SEEK_SET);
 	fread(context->vdpmem, 1, VRAM_SIZE, state_file);
 }
+
--- a/vdp.h	Sun Dec 16 22:25:29 2012 -0800
+++ b/vdp.h	Tue Dec 18 02:16:42 2012 -0800
@@ -26,6 +26,8 @@
 #define FBUF_SRC_S 0x6000
 #define FBUF_SRC_BG 0x8000
 
+#define MCLKS_LINE 3420
+
 enum {
 	REG_MODE_1=0,
 	REG_MODE_2,
@@ -58,6 +60,17 @@
 } sprite_info;
 
 typedef struct {
+	uint32_t cycle;
+	uint16_t value;
+	uint8_t  partial;
+} fifo_entry;
+
+typedef struct {
+	fifo_entry  *fifo_cur;
+	fifo_entry  *fifo_end;
+	uint16_t    address;
+	uint8_t     cd;
+	uint8_t		flags;
 	//cycle count in MCLKs
 	uint32_t    cycles;
 	uint8_t     *vdpmem;
@@ -80,7 +93,6 @@
 	uint16_t    col_1;
 	uint16_t    col_2;
 	uint8_t     v_offset;
-	uint8_t		flags;
 	uint8_t     *tmp_buf_a;
 	uint8_t     *tmp_buf_b;
 } vdp_context;
@@ -90,5 +102,9 @@
 //runs from current cycle count to VBLANK for the current mode, returns ending cycle count
 uint32_t vdp_run_to_vblank(vdp_context * context);
 void vdp_load_savestate(vdp_context * context, FILE * state_file);
+void 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);
 
 #endif //VDP_H_