changeset 1117:928a65750345

Initial support for Genesis/Megadrive PBC mode. VDP still needs Mode 4 to be useful.
author Michael Pavone <pavone@retrodev.com>
date Thu, 22 Dec 2016 19:51:25 -0800
parents fe8c79f82c22
children c48d5191ddc9
files Makefile genesis.c genesis.h render.h render_sdl.c sms.c sms.h stateview.c system.c system.h vdp.c vdp.h z80_to_x86.c z80_to_x86.h
diffstat 14 files changed, 390 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Dec 22 10:51:33 2016 -0800
+++ b/Makefile	Thu Dec 22 19:51:25 2016 -0800
@@ -142,7 +142,7 @@
 ifdef NOZ80
 CFLAGS+=-DNO_Z80
 else
-MAINOBJS+= $(Z80OBJS)
+MAINOBJS+= sms.o $(Z80OBJS)
 endif
 
 ifeq ($(OS),Windows)
--- a/genesis.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/genesis.c	Thu Dec 22 19:51:25 2016 -0800
@@ -39,9 +39,9 @@
 	return 0;
 }
 
-uint16_t get_open_bus_value()
+static uint16_t get_open_bus_value(system_header *system)
 {
-	genesis_context *genesis = (genesis_context *)current_system;
+	genesis_context *genesis = (genesis_context *)system;
 	return read_dma_value(genesis->m68k->last_prefetch_address/2);
 }
 
@@ -101,7 +101,7 @@
 #define dputs
 #endif
 
-void z80_next_int_pulse(z80_context * z_context)
+static void z80_next_int_pulse(z80_context * z_context)
 {
 	genesis_context * gen = z_context->system;
 	z_context->int_pulse_start = vdp_next_vint_z80(gen->vdp);
@@ -626,7 +626,7 @@
 		} else {
 			if (location == 0x1100) {
 				value = z80_enabled ? !z80_get_busack(gen->z80, context->current_cycle) : !gen->z80->busack;
-				value |= (get_open_bus_value() >> 8) & 0xFE;
+				value |= (get_open_bus_value(&gen->header) >> 8) & 0xFE;
 				dprintf("Byte read of BUSREQ returned %d @ %d (reset: %d)\n", value, context->current_cycle, gen->z80->reset);
 			} else if (location == 0x1200) {
 				value = !gen->z80->reset;
@@ -641,12 +641,13 @@
 
 static uint16_t io_read_w(uint32_t location, m68k_context * context)
 {
+	genesis_context *gen = context->system;
 	uint16_t value = io_read(location, context);
 	if (location < 0x10000 || (location & 0x1FFF) < 0x100) {
 		value = value | (value << 8);
 	} else {
 		value <<= 8;
-		value |= get_open_bus_value() & 0xFF;
+		value |= get_open_bus_value(&gen->header) & 0xFF;
 	}
 	return value;
 }
@@ -902,6 +903,7 @@
 	gen->header.load_save = load_save;
 	gen->header.persist_save = persist_save;
 	gen->header.free_context = free_genesis;
+	gen->header.get_open_bus_value = get_open_bus_value;
 	gen->header.request_exit = request_exit;
 	gen->header.inc_debug_mode = inc_debug_mode;
 	gen->header.inc_debug_pal = inc_debug_pal;
@@ -909,6 +911,7 @@
 
 	gen->vdp = malloc(sizeof(vdp_context));
 	init_vdp_context(gen->vdp, gen->version_reg & 0x40);
+	gen->vdp->system = &gen->header;
 	gen->frame_end = vdp_cycles_to_frame_end(gen->vdp);
 	char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0").ptrval;
 	gen->max_cycles = config_cycles ? atoi(config_cycles) : DEFAULT_SYNC_INTERVAL;
@@ -929,6 +932,7 @@
 	z80_options *z_opts = malloc(sizeof(z80_options));
 	init_z80_opts(z_opts, z80_map, 5, NULL, 0, MCLKS_PER_Z80, 0xFFFF);
 	init_z80_context(gen->z80, z_opts);
+	gen->z80->next_int_pulse = z80_next_int_pulse;
 	z80_assert_reset(gen->z80, 0);
 #endif
 
--- a/genesis.h	Thu Dec 22 10:51:33 2016 -0800
+++ b/genesis.h	Thu Dec 22 19:51:25 2016 -0800
@@ -53,7 +53,6 @@
 #define Z80_RAM_BYTES 8 * 1024
 
 uint16_t read_dma_value(uint32_t address);
-uint16_t get_open_bus_value();
 m68k_context * sync_components(m68k_context *context, uint32_t address);
 genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region, rom_info *info_out);
 
--- a/render.h	Thu Dec 22 10:51:33 2016 -0800
+++ b/render.h	Thu Dec 22 19:51:25 2016 -0800
@@ -65,6 +65,8 @@
 void render_wait_quit(vdp_context * context);
 void render_wait_psg(psg_context * context);
 void render_wait_ym(ym2612_context * context);
+void render_disable_ym();
+void render_enable_ym();
 uint32_t render_audio_buffer();
 uint32_t render_sample_rate();
 void process_events();
--- a/render_sdl.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/render_sdl.c	Thu Dec 22 19:51:25 2016 -0800
@@ -44,6 +44,7 @@
 static SDL_cond * psg_cond;
 static SDL_cond * ym_cond;
 static uint8_t quitting = 0;
+static uint8_t ym_enabled = 1;
 
 static void audio_callback(void * userdata, uint8_t *byte_stream, int len)
 {
@@ -61,26 +62,45 @@
 				current_psg = NULL;
 				SDL_CondSignal(psg_cond);
 			}
-			if (!ym_buf) {
+			if (ym_enabled && !ym_buf) {
 				ym_buf = current_ym;
 				current_ym = NULL;
 				SDL_CondSignal(ym_cond);
 			}
-			if (!quitting && (!psg_buf || !ym_buf)) {
+			if (!quitting && (!psg_buf || (ym_enabled && !ym_buf))) {
 				SDL_CondWait(audio_ready, audio_mutex);
 			}
-		} while(!quitting && (!psg_buf || !ym_buf));
+		} while(!quitting && (!psg_buf || (ym_enabled && !ym_buf)));
 
 		local_quit = quitting;
 	SDL_UnlockMutex(audio_mutex);
 	if (!local_quit) {
-		for (int i = 0; i < samples; i++) {
-			*(stream++) = psg_buf[i] + *(ym_buf++);
-			*(stream++) = psg_buf[i] + *(ym_buf++);
+		if (ym_enabled) {
+			for (int i = 0; i < samples; i++)
+			{
+				*(stream++) = psg_buf[i] + *(ym_buf++);
+				*(stream++) = psg_buf[i] + *(ym_buf++);
+			}
+		} else {
+			for (int i = 0; i < samples; i++)
+			{
+				*(stream++) = psg_buf[i];
+				*(stream++) = psg_buf[i];
+			}
 		}
 	}
 }
 
+void render_disable_ym()
+{
+	ym_enabled = 0;
+}
+
+void render_enable_ym()
+{
+	ym_enabled = 1;
+}
+
 static void render_close_audio()
 {
 	SDL_LockMutex(audio_mutex);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sms.c	Thu Dec 22 19:51:25 2016 -0800
@@ -0,0 +1,221 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include "sms.h"
+#include "blastem.h"
+#include "render.h"
+#include "util.h"
+
+static void *memory_io_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	if (location & 1) {
+		sms->io.ports[0].control = ~(value << 5 & 0x60);
+		sms->io.ports[1].control = ~(value << 3 & 0x60);
+		io_data_write(sms->io.ports, value << 1, z80->current_cycle);
+		io_data_write(sms->io.ports + 1, value >> 1, z80->current_cycle);
+	} else {
+		//TODO: memory control write
+	}
+	return vcontext;
+}
+
+static uint8_t hv_read(uint32_t location, void *vcontext)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	vdp_run_context(sms->vdp, z80->current_cycle);
+	uint16_t hv = vdp_hv_counter_read(sms->vdp);
+	if (location & 1) {
+		return hv;
+	} else {
+		return hv >> 8;
+	}
+}
+
+static void *sms_psg_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	psg_run(sms->psg, z80->current_cycle);
+	psg_write(sms->psg, value);
+	return vcontext;
+}
+
+static void update_interrupts(sms_context *sms)
+{
+	uint32_t vint = vdp_next_vint(sms->vdp);
+	uint32_t hint = vdp_next_hint(sms->vdp);
+	sms->z80->int_pulse_start = vint < hint ? vint : hint;
+}
+
+static uint8_t vdp_read(uint32_t location, void *vcontext)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	vdp_run_context(sms->vdp, z80->current_cycle);
+	if (location & 1) {
+		sms->vdp->flags &= ~(FLAG2_VINT_PENDING|FLAG2_HINT_PENDING);
+		update_interrupts(sms);
+		return vdp_control_port_read(sms->vdp);
+	} else {
+		return vdp_data_port_read(sms->vdp);
+	}
+}
+
+static void *vdp_write(uint32_t location, void *vcontext, uint8_t value)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	vdp_run_context(sms->vdp, z80->current_cycle);
+	if (location & 1) {
+		vdp_control_port_write_pbc(sms->vdp, value);
+		update_interrupts(sms);
+	} else {
+		vdp_data_port_write_pbc(sms->vdp, value);
+	}
+	return vcontext;
+}
+
+static uint8_t io_read(uint32_t location, void *vcontext)
+{
+	z80_context *z80 = vcontext;
+	sms_context *sms = z80->system;
+	if (location == 0xC0 || location == 0xDC) {
+		uint8_t port_a = io_data_read(sms->io.ports, z80->current_cycle);
+		uint8_t port_b = io_data_read(sms->io.ports, z80->current_cycle);
+		return (port_a & 0x3F) | (port_b << 6);
+	}
+	if (location == 0xC1 || location == 0xDD) {
+		uint8_t port_a = io_data_read(sms->io.ports, z80->current_cycle);
+		uint8_t port_b = io_data_read(sms->io.ports, z80->current_cycle);
+		return (port_a & 0x40) | (port_b >> 2 & 0xF) | (port_b << 1 & 0x80) | 0x10;
+	}
+	return 0xFF;
+}
+
+static memmap_chunk io_map[] = {
+	{0x00, 0x40, 0xFF, 0, 0, 0, NULL, NULL, NULL, NULL,     memory_io_write},
+	{0x40, 0x80, 0xFF, 0, 0, 0, NULL, NULL, NULL, hv_read,  sms_psg_write},
+	{0x80, 0xC0, 0xFF, 0, 0, 0, NULL, NULL, NULL, vdp_read, vdp_write},
+	{0xC0, 0x100,0xFF, 0, 0, 0, NULL, NULL, NULL, io_read,  NULL}
+};
+
+static void set_speed_percent(system_header * system, uint32_t percent)
+{
+	sms_context *context = (sms_context *)system;
+	uint32_t old_clock = context->master_clock;
+	context->master_clock = ((uint64_t)context->normal_clock * (uint64_t)percent) / 100;
+
+	psg_adjust_master_clock(context->psg, context->master_clock);
+}
+
+static void run_sms(system_header *system)
+{
+	render_disable_ym();
+	sms_context *sms = (sms_context *)system;
+	uint32_t target_cycle = sms->z80->current_cycle + 3420*262;
+	while (!sms->should_return)
+	{
+		z80_run(sms->z80, target_cycle);
+		target_cycle = sms->z80->current_cycle;
+		vdp_run_context(sms->vdp, target_cycle);
+		psg_run(sms->psg, target_cycle);
+		target_cycle += 3420*262;
+		if (target_cycle > 0x10000000) {
+			uint32_t adjust = sms->z80->current_cycle - 3420*262*2;
+			io_adjust_cycles(sms->io.ports, sms->z80->current_cycle, adjust);
+			io_adjust_cycles(sms->io.ports+1, sms->z80->current_cycle, adjust);
+			z80_adjust_cycles(sms->z80, adjust);
+			vdp_adjust_cycles(sms->vdp, adjust);
+			sms->psg->cycles -= adjust;
+			target_cycle -= adjust;
+		}
+	}
+	sms->should_return = 0;
+	render_enable_ym();
+}
+
+static void start_sms(system_header *system, char *statefile)
+{
+	sms_context *sms = (sms_context *)system;
+	set_keybindings(&sms->io);
+	
+	z80_assert_reset(sms->z80, 0);
+	z80_clear_reset(sms->z80, 128*15);
+	
+	run_sms(system);
+}
+
+static void free_sms(system_header *system)
+{
+	sms_context *sms = (sms_context *)system;
+	vdp_free(sms->vdp);
+	z80_options_free(sms->z80->options);
+	free(sms->z80);
+	psg_free(sms->psg);
+	free(sms);
+}
+
+static uint16_t get_open_bus_value(system_header *system)
+{
+	return 0xFFFF;
+}
+
+static void request_exit(system_header *system)
+{
+	sms_context *sms = (sms_context *)system;
+	sms->should_return = 1;
+}
+
+sms_context *alloc_configure_sms(void *rom, uint32_t rom_size, void *extra_rom, uint32_t extra_rom_size, uint32_t opts, uint8_t force_region, rom_info *info_out)
+{
+	memset(info_out, 0, sizeof(*info_out));
+	sms_context *sms = calloc(1, sizeof(sms_context));
+	rom_size = nearest_pow2(rom_size);
+	uint32_t mask = rom_size >= 0xC000 ? 0xFFFF : rom_size-1;
+	memmap_chunk memory_map[] = {
+		{0x0000, 0xC000,  rom_size-1,         0, 0, MMAP_READ,                      rom,      NULL, NULL, NULL, NULL},
+		{0xC000, 0x10000, sizeof(sms->ram)-1, 0, 0, MMAP_READ|MMAP_WRITE|MMAP_CODE, sms->ram, NULL, NULL, NULL, NULL}
+	};
+	info_out->map = malloc(sizeof(memory_map));
+	memcpy(info_out->map, memory_map, sizeof(memory_map));
+	z80_options *zopts = malloc(sizeof(z80_options));
+	init_z80_opts(zopts, info_out->map, 2, io_map, 4, 15, 0xFF);
+	sms->z80 = malloc(sizeof(z80_context));
+	init_z80_context(sms->z80, zopts);
+	sms->z80->system = sms;
+	
+	char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0").ptrval;
+	uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : 3390;
+	
+	//TODO: Detect region and pick master clock based off of that
+	sms->normal_clock = sms->master_clock = 53693175;
+	
+	sms->psg = malloc(sizeof(psg_context));
+	psg_init(sms->psg, render_sample_rate(), sms->master_clock, 15*16, render_audio_buffer(), lowpass_cutoff);
+	
+	sms->vdp = malloc(sizeof(vdp_context));
+	init_vdp_context(sms->vdp, 0);
+	sms->vdp->system = &sms->header;
+	
+	info_out->save_type = SAVE_NONE;
+	info_out->name = strdup("Master System Game");
+	
+	setup_io_devices(config, info_out, &sms->io);
+	
+	sms->header.set_speed_percent = set_speed_percent;
+	sms->header.start_context = start_sms;
+	sms->header.resume_context = run_sms;
+	//TODO: Fill in NULL values
+	sms->header.load_save = NULL;
+	sms->header.persist_save = NULL;
+	sms->header.free_context = free_sms;
+	sms->header.get_open_bus_value = get_open_bus_value;
+	sms->header.request_exit = request_exit;
+	sms->header.inc_debug_mode = NULL;
+	sms->header.inc_debug_pal = NULL;
+	
+	return sms;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sms.h	Thu Dec 22 19:51:25 2016 -0800
@@ -0,0 +1,26 @@
+#ifndef SMS_H_
+#define SMS_H_
+
+#include "system.h"
+#include "vdp.h"
+#include "psg.h"
+#include "z80_to_x86.h"
+#include "io.h"
+
+#define SMS_RAM_SIZE (8*1024)
+
+typedef struct {
+	system_header header;
+	z80_context   *z80;
+	vdp_context   *vdp;
+	psg_context   *psg;
+	sega_io       io;
+	uint32_t      master_clock;
+	uint32_t      normal_clock;
+	uint8_t       should_return;
+	uint8_t       ram[SMS_RAM_SIZE];
+} sms_context;
+
+sms_context *alloc_configure_sms(void *rom, uint32_t rom_size, void *extra_rom, uint32_t extra_rom_size, uint32_t opts, uint8_t force_region, rom_info *info_out);
+
+#endif //SMS_H_
--- a/stateview.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/stateview.c	Thu Dec 22 19:51:25 2016 -0800
@@ -27,11 +27,6 @@
 	return NULL;
 }
 
-uint16_t get_open_bus_value()
-{
-	return 0;
-}
-
 void ym_data_write(ym2612_context * context, uint8_t value)
 {
 }
--- a/system.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/system.c	Thu Dec 22 19:51:25 2016 -0800
@@ -1,14 +1,27 @@
 #include <string.h>
 #include "system.h"
 #include "genesis.h"
+#include "sms.h"
+
+uint8_t safe_cmp(char *str, long offset, uint8_t *buffer, long filesize)
+{
+	long len = strlen(str);
+	return filesize >= offset+len && !memcmp(str, buffer + offset, len);
+}
 
 system_type detect_system_type(uint8_t *rom, long filesize)
 {
-	if (filesize >= 0x104 && !memcmp("SEGA", rom + 0x100, 4)) {
+	if (safe_cmp("SEGA", 0x100, rom, filesize)) {
 		//TODO: Differentiate between vanilla Genesis and Sega CD/32X games
 		return SYSTEM_GENESIS;
 	}
-	//TODO: Detect SMS and Jaguar ROMs here
+	if (safe_cmp("TMR SEGA", 0x1FF0, rom, filesize) 
+		|| safe_cmp("TMR SEGA", 0x3FF0, rom, filesize) 
+		|| safe_cmp("TMR SEGA", 0x7FF0, rom, filesize) 
+	) {
+		return SYSTEM_SMS;
+	}
+	//TODO: Detect Jaguar ROMs here
 	
 	//More certain checks failed, look for a valid 68K reset vector
 	if (filesize >= 8) {
@@ -27,6 +40,10 @@
 	{
 	case SYSTEM_GENESIS:
 		return &(alloc_config_genesis(rom, rom_size, lock_on, lock_on_size, opts, force_region, info_out))->header;
+#ifndef NO_Z80
+	case SYSTEM_SMS:
+		return &(alloc_configure_sms(rom, rom_size, lock_on, lock_on_size, opts, force_region, info_out))->header;
+#endif
 	default:
 		return NULL;
 	}
--- a/system.h	Thu Dec 22 10:51:33 2016 -0800
+++ b/system.h	Thu Dec 22 19:51:25 2016 -0800
@@ -19,6 +19,7 @@
 } debugger_type;
 
 typedef void (*system_fun)(system_header *);
+typedef uint16_t (*system_fun_r16)(system_header *);
 typedef void (*start_system_fun)(system_header *, char *);
 typedef void (*speed_system_fun)(system_header *, uint32_t);
 
@@ -30,6 +31,7 @@
 	system_fun        persist_save;
 	system_fun        request_exit;
 	system_fun        free_context;
+	system_fun_r16    get_open_bus_value;
 	speed_system_fun  set_speed_percent;
 	system_fun        inc_debug_mode;
 	system_fun        inc_debug_pal;
--- a/vdp.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/vdp.c	Thu Dec 22 19:51:25 2016 -0800
@@ -554,13 +554,33 @@
 			break;
 		case CRAM_WRITE: {
 			//printf("CRAM Write | %X to %X\n", start->value, (start->address/2) & (CRAM_SIZE-1));
-			write_cram(context, start->address, start->partial == 2 ? context->fifo[context->fifo_write].value : start->value);
+			if (start->partial == 1) {
+				uint16_t val;
+				if (start->address & 1) {
+					val = (context->cram[start->address >> 1 & (CRAM_SIZE-1)] & 0xFF) | start->value << 8;
+				} else {
+					val = (context->cram[start->address >> 1 & (CRAM_SIZE-1)] & 0xFF00) | start->value;
+				}
+				write_cram(context, start->address, val);
+			} else {
+				write_cram(context, start->address, start->partial == 2 ? context->fifo[context->fifo_write].value : start->value);
+			}
 			break;
 		}
 		case VSRAM_WRITE:
 			if (((start->address/2) & 63) < VSRAM_SIZE) {
 				//printf("VSRAM Write: %X to %X @ vcounter: %d, hslot: %d, cycle: %d\n", start->value, context->address, context->vcounter, context->hslot, context->cycles);
-				context->vsram[(start->address/2) & 63] = start->partial == 2 ? context->fifo[context->fifo_write].value : start->value;
+				if (start->partial == 1) {
+					if (start->address & 1) {
+						context->vsram[(start->address/2) & 63] &= 0xFF;
+						context->vsram[(start->address/2) & 63] |= start->value << 8;
+					} else {
+						context->vsram[(start->address/2) & 63] &= 0xFF00;
+						context->vsram[(start->address/2) & 63] |= start->value;
+					}
+				} else {
+					context->vsram[(start->address/2) & 63] = start->partial == 2 ? context->fifo[context->fifo_write].value : start->value;
+				}
 			}
 
 			break;
@@ -1673,6 +1693,19 @@
 	return 0;
 }
 
+void vdp_control_port_write_pbc(vdp_context *context, uint8_t value)
+{
+	if (context->flags2 & FLAG2_BYTE_PENDING) {
+		uint16_t full_val = value << 8 | context->pending_byte;
+		context->flags2 &= ~FLAG2_BYTE_PENDING;
+		//TODO: Deal with fact that Vbus->VDP DMA doesn't do anything in PBC mode
+		vdp_control_port_write(context, full_val);
+	} else {
+		context->pending_byte = value;
+		context->flags2 |= FLAG2_BYTE_PENDING;
+	}
+}
+
 int vdp_data_port_write(vdp_context * context, uint16_t value)
 {
 	//printf("data port write: %X at %d\n", value, context->cycles);
@@ -1708,6 +1741,36 @@
 	return 0;
 }
 
+void vdp_data_port_write_pbc(vdp_context * context, uint8_t value)
+{
+	if (context->flags & FLAG_PENDING) {
+		context->flags &= ~FLAG_PENDING;
+		//Should these be cleared here?
+		context->flags &= ~FLAG_READ_FETCHED;
+		context->flags2 &= ~FLAG2_READ_PENDING;
+	}
+	/*if (context->fifo_cur == context->fifo_end) {
+		printf("FIFO full, waiting for space before next write at cycle %X\n", context->cycles);
+	}*/
+	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) {
+		context->flags &= ~FLAG_DMA_RUN;
+	}
+	while (context->fifo_write == context->fifo_read) {
+		vdp_run_context(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20));
+	}
+	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;
+	cur->value = value;
+	cur->cd = context->cd;
+	cur->partial = 1;
+	if (context->fifo_read < 0) {
+		context->fifo_read = context->fifo_write;
+	}
+	context->fifo_write = (context->fifo_write + 1) & (FIFO_SIZE-1);
+	context->address += context->regs[REG_AUTOINC];
+}
+
 void vdp_test_port_write(vdp_context * context, uint16_t value)
 {
 	//TODO: implement test register
@@ -1717,7 +1780,7 @@
 {
 	context->flags &= ~FLAG_PENDING;
 	//Bits 15-10 are not fixed like Charles MacDonald's doc suggests, but instead open bus values that reflect 68K prefetch
-	uint16_t value = get_open_bus_value() & 0xFC00;
+	uint16_t value = context->system->get_open_bus_value(context->system) & 0xFC00;
 	if (context->fifo_read < 0) {
 		value |= 0x200;
 	}
--- a/vdp.h	Thu Dec 22 10:51:33 2016 -0800
+++ b/vdp.h	Thu Dec 22 19:51:25 2016 -0800
@@ -8,6 +8,7 @@
 
 #include <stdint.h>
 #include <stdio.h>
+#include "system.h"
 
 #define VDP_REGS 24
 #define CRAM_SIZE 64
@@ -52,6 +53,7 @@
 #define FLAG2_SPRITE_COLLIDE 0x08
 #define FLAG2_REGION_PAL     0x10
 #define FLAG2_EVEN_FIELD     0x20
+#define FLAG2_BYTE_PENDING   0x40
 
 #define DISPLAY_ENABLE 0x40
 
@@ -141,6 +143,7 @@
 	uint8_t     *linebuf;
 	//pointer to current line in framebuffer
 	uint32_t    *output;
+	system_header  *system;
 	uint16_t    cram[CRAM_SIZE];
 	uint32_t    colors[CRAM_SIZE*3];
 	uint32_t    debugcolors[1 << (3 + 1 + 1 + 1)];//3 bits for source, 1 bit for priority, 1 bit for shadow, 1 bit for hilight
@@ -175,6 +178,7 @@
 	uint8_t     buf_b_off;
 	uint8_t     debug;
 	uint8_t     debug_pal;
+	uint8_t     pending_byte;
 	uint8_t     *tmp_buf_a;
 	uint8_t     *tmp_buf_b;
 } vdp_context;
@@ -189,7 +193,9 @@
 uint8_t vdp_load_gst(vdp_context * context, FILE * state_file);
 uint8_t vdp_save_gst(vdp_context * context, FILE * outfile);
 int vdp_control_port_write(vdp_context * context, uint16_t value);
+void vdp_control_port_write_pbc(vdp_context * context, uint8_t value);
 int vdp_data_port_write(vdp_context * context, uint16_t value);
+void vdp_data_port_write_pbc(vdp_context * context, uint8_t value);
 void vdp_test_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);
--- a/z80_to_x86.c	Thu Dec 22 10:51:33 2016 -0800
+++ b/z80_to_x86.c	Thu Dec 22 19:51:25 2016 -0800
@@ -3476,7 +3476,7 @@
 	call(code, options->gen.load_context);
 	jmp_r(code, options->gen.scratch1);
 
-	options->run = (z80_run_fun)code->cur;
+	options->run = code->cur;
 	tmp_stack_off = code->stack_off;
 	save_callee_save_regs(code);
 #ifdef X86_64
@@ -3525,8 +3525,8 @@
 			}
 			while (context->current_cycle < context->sync_cycle)
 			{
-				if (context->int_pulse_end < context->current_cycle || context->int_pulse_end == CYCLE_NEVER) {
-					z80_next_int_pulse(context);
+				if (context->next_int_pulse && (context->int_pulse_end < context->current_cycle || context->int_pulse_end == CYCLE_NEVER)) {
+					context->next_int_pulse(context);
 				}
 				if (context->iff1) {
 					context->int_cycle = context->int_pulse_start < context->int_enable_cycle ? context->int_enable_cycle : context->int_pulse_start;
@@ -3623,7 +3623,9 @@
 		if (context->int_pulse_end < deduction) {
 			context->int_pulse_start = context->int_pulse_end = CYCLE_NEVER;
 		} else {
-			context->int_pulse_end -= deduction;
+			if (context->int_pulse_end != CYCLE_NEVER) {
+				context->int_pulse_end -= deduction;
+			}
 			if (context->int_pulse_start < deduction) {
 				context->int_pulse_start = 0;
 			} else {
--- a/z80_to_x86.h	Thu Dec 22 10:51:33 2016 -0800
+++ b/z80_to_x86.h	Thu Dec 22 19:51:25 2016 -0800
@@ -26,7 +26,8 @@
 	ZF_NUM
 };
 
-typedef void (*z80_run_fun)(void * context);
+typedef struct z80_context z80_context;
+typedef void (*z80_ctx_fun)(z80_context * context);
 
 typedef struct {
 	cpu_options     gen;
@@ -47,10 +48,10 @@
 
 	uint32_t        flags;
 	int8_t          regs[Z80_UNUSED];
-	z80_run_fun     run;
+	z80_ctx_fun     run;
 } z80_options;
 
-typedef struct {
+struct z80_context {
 	void *            native_pc;
 	uint16_t          sp;
 	uint8_t           flags[ZF_NUM];
@@ -82,10 +83,11 @@
 	uint8_t *         bp_handler;
 	uint8_t *         bp_stub;
 	uint8_t *         interp_code[256];
+	z80_ctx_fun       next_int_pulse;
 	uint8_t           reset;
 	uint8_t           busreq;
 	uint8_t           busack;
-} z80_context;
+};
 
 void translate_z80_stream(z80_context * context, uint32_t address);
 void init_z80_opts(z80_options * options, memmap_chunk const * chunks, uint32_t num_chunks, memmap_chunk const * io_chunks, uint32_t num_io_chunks, uint32_t clock_divider, uint32_t io_address_mask);
@@ -104,8 +106,6 @@
 void z80_clear_busreq(z80_context * context, uint32_t cycle);
 uint8_t z80_get_busack(z80_context * context, uint32_t cycle);
 void z80_adjust_cycles(z80_context * context, uint32_t deduction);
-//to be provided by system code
-void z80_next_int_pulse(z80_context * z_context);
 
 #endif //Z80_TO_X86_H_