changeset 2718:8ce5d1a7ef54

Initial work to integrate PAC-less LaserActive emulation
author Michael Pavone <pavone@retrodev.com>
date Wed, 16 Jul 2025 12:32:28 -0700
parents 04007ac9ee3b
children f817aedf5e53
files Makefile blastem.c genesis.c laseractive.c laseractive.h system.c system.h upd78k2.cpu upd78k2_util.c
diffstat 9 files changed, 298 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Jul 16 07:36:01 2025 -0700
+++ b/Makefile	Wed Jul 16 12:32:28 2025 -0700
@@ -283,7 +283,8 @@
 COREOBJS:=system.o genesis.o vdp.o io.o romdb.o hash.o xband.o realtec.o i2c.o nor.o $(M68KOBJS) \
 	sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
 	$(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o gen_player.o coleco.o pico_pcm.o ymz263b.o \
-	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o
+	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o \
+	laseractive.o upd78k2_dis.o upd78k2.o
 
 ifdef NOZ80
 CFLAGS+=-DNO_Z80
@@ -337,10 +338,10 @@
 LIBORDERONLY:=$(LIBOBJDIR)
 ifdef NEW_CORE
 ifeq ($(wildcard $(OBJDIR)/*.d),)
-ORDERONLY+= m68k.c z80.c
+ORDERONLY+= m68k.c z80.c upd78k2.c
 endif
 ifeq ($(wildcard $(LIBOBJDIR)/*.d),)
-LIBORDERONLY+= m68k.c z80.c
+LIBORDERONLY+= m68k.c z80.c upd78k2.c
 endif
 endif
 
--- a/blastem.c	Wed Jul 16 07:36:01 2025 -0700
+++ b/blastem.c	Wed Jul 16 12:32:28 2025 -0700
@@ -477,6 +477,8 @@
 					stype = force_stype = SYSTEM_JAGUAR;
 				} else if (!strcmp("media", argv[i])) {
 					stype = force_stype = SYSTEM_MEDIA_PLAYER;
+				} else if (!strcmp("laser", argv[i])) {
+					stype = force_stype = SYSTEM_LASERACTIVE;
 				} else {
 					fatal_error("Unrecognized machine type %s\n", argv[i]);
 				}
@@ -520,6 +522,7 @@
 					"                   pico   - Sega Pico\n"
 					"                   copera - Yamaha Copera\n"
 					"                   media  - Media Player\n"
+					"                   laser  - Pioneer LaserActive (no PAC)\n"
 					"	-f          Toggles fullscreen mode\n"
 					"	-g          Disable OpenGL rendering\n"
 					"	-s FILE     Load a GST format savestate from FILE\n"
--- a/genesis.c	Wed Jul 16 07:36:01 2025 -0700
+++ b/genesis.c	Wed Jul 16 12:32:28 2025 -0700
@@ -555,9 +555,8 @@
 	gen->refresh_counter = gen->refresh_counter % interval;
 }
 
-#include <limits.h>
 #define ADJUST_BUFFER (8*MCLKS_LINE*313)
-#define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER)
+#define MAX_NO_ADJUST (UINT32_MAX-ADJUST_BUFFER)
 
 static m68k_context *sync_components(m68k_context * context, uint32_t address)
 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laseractive.c	Wed Jul 16 12:32:28 2025 -0700
@@ -0,0 +1,240 @@
+#include <limits.h>
+#include "laseractive.h"
+#include "io.h"
+#include "render.h"
+#include "render_audio.h"
+#include "blastem.h"
+#include "util.h"
+
+static void gamepad_down(system_header *system, uint8_t pad, uint8_t button)
+{
+	if (pad != 1) {
+		return;
+	}
+	laseractive *la = (laseractive *)system;
+	switch (button)
+	{
+	case BUTTON_A:
+		la->upd->port_input[7] &= ~0x40; //LD open
+		break;
+	case BUTTON_B:
+		la->upd->port_input[7] &= ~0x80; //CD open
+		break;
+	case BUTTON_C:
+		la->upd->port_input[7] &= ~0x10; //Digital memory
+		break;
+	case BUTTON_START:
+		la->upd->port_input[7] &= ~0x20; //play
+		break;
+	}
+	
+}
+
+static void gamepad_up(system_header *system, uint8_t pad, uint8_t button)
+{
+	if (pad != 1) {
+		return;
+	}
+	laseractive *la = (laseractive *)system;
+	switch (button)
+	{
+	case BUTTON_A:
+		la->upd->port_input[7] |= 0x40; //LD open
+		break;
+	case BUTTON_B:
+		la->upd->port_input[7] |= 0x80; //CD open
+		break;
+	case BUTTON_C:
+		la->upd->port_input[7] |= 0x10; //Digital memory
+		break;
+	case BUTTON_START:
+		la->upd->port_input[7] |= 0x20; //play
+		break;
+	}
+}
+
+#define ADJUST_BUFFER 12000000 // one second of cycles
+#define MAX_NO_ADJUST (UINT32_MAX-ADJUST_BUFFER)
+
+static void resume_laseractive(system_header *system)
+{
+	
+	pixel_t bg_color = render_map_color(0, 0, 255);
+	pixel_t led_on = render_map_color(0, 255, 0);
+	pixel_t led_standby = render_map_color(255, 0, 0);
+	pixel_t led_off = render_map_color(0, 0, 0);
+	laseractive *la = (laseractive *)system;
+	audio_source *audio = render_audio_source("laseractive_tmp", 12000000, 250, 2);
+	static const uint32_t cycles_per_frame = 12000000 / 60;
+	static const uint32_t cycles_per_audio_chunk = 16 * 250;
+	uint32_t next_video = la->upd->cycles + cycles_per_frame;
+	uint32_t next_audio = la->upd->cycles + cycles_per_audio_chunk;
+	uint32_t next = next_audio < next_video ? next_audio : next_video;
+	while (!la->header.should_exit)
+	{
+		upd78k2_execute(la->upd, next);
+		while (la->upd->cycles >= next_audio) {
+			for (int i = 0; i < 16; i++)
+			{
+				render_put_stereo_sample(audio, 0, 0);
+			}
+			next_audio += cycles_per_audio_chunk;
+		}
+		while (la->upd->cycles >= next_video) {
+			int pitch;
+			pixel_t *fb = render_get_framebuffer(FRAMEBUFFER_ODD, &pitch);
+			pixel_t led_state[5] = {
+				/*power*/la->upd->port_data[0] & 0x08 ? led_on : la->upd->port_data[0] & 0x40 ? led_standby : led_off,
+				/*play*/la->upd->port_data[0] & 0x10 ? led_on : led_off,
+				/*memory*/la->upd->port_data[0] & 0x20 ? led_standby : led_off,
+				/*cd*/la->upd->port_data[6] & 0x02 ? led_on : led_off,
+				/*ld*/la->upd->port_data[0] & 0x80 ? led_on : led_off
+			};
+			pixel_t *line = fb;
+			for (int y = 0; y < 224 + 11 - 8; y++)
+			{
+				pixel_t *cur = line;
+				for (int x = 0; x < 320 + 13 + 14; x++)
+				{
+					*(cur++) = bg_color;
+				}
+				
+				line += pitch / sizeof(pixel_t);
+			}
+			for (int y = 224 + 11 - 8; y < 224 + 11 + 8; y++)
+			{
+				pixel_t *cur = line;
+				for (int x = 0; x < 13; x++)
+				{
+					*(cur++) = bg_color;
+				}
+				for (int x = 13; x < 13 + 8 * 5; x++)
+				{
+					*(cur++) = led_state[(x-13) >> 3];
+				}
+				for (int x = 13 + 8 * 5; x < 320 + 13 + 14; x++)
+				{
+					*(cur++) = bg_color;
+				}
+				
+				line += pitch / sizeof(pixel_t);
+			}
+			render_framebuffer_updated(FRAMEBUFFER_ODD, 320);
+			next_video += cycles_per_frame;
+		}
+		if (la->upd->cycles > MAX_NO_ADJUST) {
+			uint32_t deduction = la->upd->cycles - ADJUST_BUFFER;
+			upd78k2_adjust_cycles(la->upd, deduction);
+			next_audio -= deduction;
+			next_video -= deduction;
+		}
+		next = next_audio < next_video ? next_audio : next_video;
+	}
+	render_free_source(audio);
+}
+
+static void start_laseractive(system_header *system, char *statefile)
+{
+	laseractive *la = (laseractive *)system;
+	la->upd->port_input[2] = 0xFF;
+	la->upd->port_input[3] = 0x20;
+	la->upd->port_input[7] = 0xF7;
+	la->upd->pc = la->upd_rom[0] | la->upd_rom[1] << 8;
+	resume_laseractive(system);
+}
+
+static void free_laseractive(system_header *system)
+{
+	laseractive *la = (laseractive *)system;
+	free(la->upd->opts->gen.memmap);
+	free(la->upd->opts);
+	free(la->upd);
+	free(la);
+}
+
+static void request_exit(system_header *system)
+{
+	//TODO: implement me
+}
+
+static void toggle_debug_view(system_header *system, uint8_t debug_view)
+{
+#ifndef IS_LIB
+#endif
+}
+
+static void inc_debug_mode(system_header * system)
+{
+}
+
+static void soft_reset(system_header * system)
+{
+	//TODO: implement me
+}
+
+static void set_speed_percent(system_header * system, uint32_t percent)
+{
+	//TODO: implement me
+}
+
+laseractive *alloc_laseractive(system_media *media, uint32_t opts)
+{
+	laseractive *la = calloc(1, sizeof(laseractive));
+	la->header.start_context = start_laseractive;
+	la->header.resume_context = resume_laseractive;
+	la->header.request_exit = request_exit;
+	la->header.soft_reset = soft_reset;
+	la->header.free_context = free_laseractive;
+	la->header.set_speed_percent = set_speed_percent;
+	la->header.gamepad_down = gamepad_down;
+	la->header.gamepad_up = gamepad_up;
+	la->header.toggle_debug_view = toggle_debug_view;
+	la->header.inc_debug_mode = inc_debug_mode;
+	la->header.type = SYSTEM_LASERACTIVE;
+	la->header.info.name = strdup(media->name);
+	char *upd_rom_path = tern_find_path_default(config, "system\0laseractive_upd_rom\0", (tern_val){.ptrval = "laseractive_dyw_1322a.bin"}, TVAL_PTR).ptrval;
+	uint32_t firmware_size;
+	if (is_absolute_path(upd_rom_path)) {
+		FILE *f = fopen(upd_rom_path, "rb");
+		if (f) {
+			long to_read = file_size(f);
+			if (to_read > sizeof(la->upd_rom)) {
+				to_read = sizeof(la->upd_rom);
+			}
+			firmware_size = fread(la->upd_rom, 1, to_read, f);
+			if (!firmware_size) {
+				warning("Failed to read from %s\n", upd_rom_path);
+			}
+			fclose(f);
+		} else {
+			warning("Failed to open %s\n", upd_rom_path);
+		}
+	} else {
+		uint8_t *tmp = read_bundled_file(upd_rom_path, &firmware_size);
+		if (tmp) {
+			if (firmware_size > sizeof(la->upd_rom)) {
+				firmware_size = sizeof(la->upd_rom);
+			}
+			memcpy(la->upd_rom, tmp, firmware_size);
+			free(tmp);
+		} else {
+			warning("Failed to open %s\n", upd_rom_path);
+		}
+	}
+	static const memmap_chunk base_upd_map[] = {
+		{ 0x0000, 0xE000, 0xFFFF, .flags = MMAP_READ,},
+		{ 0xFB00, 0xFD00, 0x1FF, .flags = MMAP_READ | MMAP_WRITE | MMAP_CODE},
+		{ 0xFD00, 0xFE00, 0xFF, .flags = MMAP_READ | MMAP_WRITE | MMAP_CODE},
+		{ 0xFF00, 0xFFFF, 0xFF, .read_8 = upd78237_sfr_read, .write_8 = upd78237_sfr_write}
+	};
+	memmap_chunk *upd_map = calloc(4, sizeof(memmap_chunk));
+	memcpy(upd_map, base_upd_map, sizeof(base_upd_map));
+	upd_map[0].buffer = la->upd_rom;
+	upd_map[1].buffer = la->upd_pram;
+	upd_map[2].buffer = la->upd_pram + 512;
+	upd78k2_options *options = calloc(1, sizeof(upd78k2_options));
+	init_upd78k2_opts(options, upd_map, 4);
+	options->gen.address_mask = options->gen.max_address = 0xFFFF; //expanded memory space is not used
+	la->upd = init_upd78k2_context(options);
+	return la;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laseractive.h	Wed Jul 16 12:32:28 2025 -0700
@@ -0,0 +1,16 @@
+#ifndef LASERACTIVE_H_
+#define LASERACTIVE_H_
+
+#include "system.h"
+#include "upd78k2.h"
+
+typedef struct {
+	system_header   header;
+	upd78k2_context *upd;
+	uint8_t         upd_pram[768];
+	uint8_t         upd_rom[0xE000]; //ROM is actually 64K, but only first 56K are mapped in
+} laseractive;
+
+laseractive *alloc_laseractive(system_media *media, uint32_t opts);
+
+#endif //LASERACTIVE_H_
--- a/system.c	Wed Jul 16 07:36:01 2025 -0700
+++ b/system.c	Wed Jul 16 12:32:28 2025 -0700
@@ -9,6 +9,7 @@
 #include "paths.h"
 #include "util.h"
 #include "cdimage.h"
+#include "laseractive.h"
 
 #define SMD_HEADER_SIZE 512
 #define SMD_MAGIC1 0x03
@@ -405,6 +406,8 @@
 	case SYSTEM_PICO:
 	case SYSTEM_COPERA:
 		return &(alloc_config_pico(media->buffer, media->size, lock_on, lock_on_size, opts, force_region, stype))->header;
+	case SYSTEM_LASERACTIVE:
+		return &(alloc_laseractive(media, opts))->header;
 	default:
 		return NULL;
 	}
--- a/system.h	Wed Jul 16 07:36:01 2025 -0700
+++ b/system.h	Wed Jul 16 12:32:28 2025 -0700
@@ -23,7 +23,8 @@
 	SYSTEM_MEDIA_PLAYER,
 	SYSTEM_COLECOVISION,
 	SYSTEM_PICO,
-	SYSTEM_COPERA
+	SYSTEM_COPERA,
+	SYSTEM_LASERACTIVE
 } system_type;
 
 typedef enum {
--- a/upd78k2.cpu	Wed Jul 16 07:36:01 2025 -0700
+++ b/upd78k2.cpu	Wed Jul 16 12:32:28 2025 -0700
@@ -71,6 +71,7 @@
 	upd78k2_context *init_upd78k2_context(upd78k2_options *opts);
 	void upd78k2_sync_cycle(upd78k2_context *upd, uint32_t target_cycle);
 	typedef void (upd_io_fun)(upd78k2_context *upd, uint8_t offset);
+	void upd78k2_adjust_cycles(upd78k2_context *upd, uint32_t deduction);
 
 #Prefix bytes
 # 0000 0001 -> saddr becomes sfr, mem becomes &mem
--- a/upd78k2_util.c	Wed Jul 16 07:36:01 2025 -0700
+++ b/upd78k2_util.c	Wed Jul 16 12:32:28 2025 -0700
@@ -1,13 +1,18 @@
 #include <string.h>
 
+//#define FETCH_DEBUG
 void upd78k2_read_8(upd78k2_context *upd)
 {
+#ifdef FETCH_DEBUG
 	uint32_t tmp = upd->scratch1;
+#endif
 	upd->scratch1 = read_byte(upd->scratch1, (void **)upd->mem_pointers, &upd->opts->gen, upd);
+#ifdef FETCH_DEBUG
 	if (tmp == upd->pc) {
 		printf("uPD78K/II fetch %04X: %02X, AX=%02X%02X BC=%02X%02X DE=%02X%02X HL=%02X%02X SP=%04X\n", tmp, upd->scratch1,
 			upd->main[1], upd->main[0], upd->main[3], upd->main[2], upd->main[5], upd->main[4], upd->main[7], upd->main[6], upd->sp);
 	}
+#endif
 	//FIXME: cycle count
 	upd->cycles += 2 * upd->opts->gen.clock_divider;
 }
@@ -179,9 +184,11 @@
 			next_int = cycle;
 		}
 	}
+#ifdef FETCH_DEBUG
 	if (next_int != upd->int_cycle) {
 		printf("UPD78K/II int cycle: %u, cur cycle %u\n", next_int, upd->cycles);
 	}
+#endif
 	upd->int_cycle = next_int;
 }
 
@@ -492,3 +499,24 @@
 	}
 	fatal_error("upd78k2_calc_vector: %X\n", upd->scratch1);
 }
+
+void upd78k2_adjust_cycles(upd78k2_context *upd, uint32_t deduction)
+{
+	upd78k2_update_timer0(upd);
+	upd78k2_update_timer1(upd);
+	if (upd->cycles <= deduction) {
+		upd->cycles = 0;
+	} else {
+		upd->cycles -= deduction;
+	}
+	if (upd->tm0_cycle <= deduction) {
+		upd->tm0_cycle = 0;
+	} else {
+		upd->tm0_cycle -= deduction;
+	}
+	if (upd->tm1_cycle <= deduction) {
+		upd->tm1_cycle = 0;
+	} else {
+		upd->tm1_cycle -= deduction;
+	}
+}