changeset 2713:a88eff3fb5d8

Initial stab at uPDK78K/II timer 0, timer 1 and corresponding interrupts
author Michael Pavone <pavone@retrodev.com>
date Thu, 10 Jul 2025 16:07:04 -0700
parents d653a8c383d3
children d30e7f605ff8
files upd78k2.cpu upd78k2_util.c upd78k2run.c
diffstat 3 files changed, 365 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/upd78k2.cpu	Thu Jul 10 16:06:33 2025 -0700
+++ b/upd78k2.cpu	Thu Jul 10 16:07:04 2025 -0700
@@ -8,6 +8,7 @@
 	header upd78k2.h
 	body upd78k2_run_op
 	sync_cycle upd78k2_sync_cycle
+	interrupt upd78k2_interrupt
 	extra_tables sfr bit1 bit2 muldiv base sfrbit spmov indexed regind alt_base alt_indexed alt_regind mov_reg
 	include upd78k2_util.c
 regs
@@ -27,14 +28,29 @@
 	mem_pointers ptr8 4
 	port_data 8 8
 	port_mode 8 8
-	mm 8
-	if0 16
-	mk0 16
-	pr0 16
-	ism0 16
-	intm0 8
-	intm1 8
-	ist 8
+	puo 8 #pull-up option register
+	pmc3 8 #port 3 pin configuration
+	cr00 16 #16-bit timer comapre register 0
+	cr01 16 #16-bit timer compare register 1
+	crc0 8 #Capture/compare control register 0
+	tm0 16 #16-bit timer register 0
+	tm0_cycle 32
+	tmc0 8 #Timer control register 0
+	cr10 8 #8-bit timer 1 compare register 0
+	cr11 8 #8-bit timer 1 compare register 1
+	tm1 8 #8-bit timer register 1
+	tm1_cycle 32
+	prm1 8 #Timer pre-scaler mode register 1
+	tmc1 8 #Timer control register 1
+	crc1 8 #Capture/compare control regiser 1
+	mm 8 #Memory expansion mode register
+	if0 16 #Interrupt request flag register
+	mk0 16 #interrupt mask flag register
+	pr0 16 #Priority specification flag register
+	ism0 16 #Interrupt service mode specification flag register
+	intm0 8 #External interrupt mode register 0
+	intm1 8 #External interrupt mode register 1
+	ist 8 #Interrupt status register
 	iram 8 256
 flags
 	register psw
@@ -112,6 +128,8 @@
 		scratch1 = offset & 7
 		scratch1 = main.scratch1
 	end
+	#FIXME cycle count
+	cycles 2
 
 iram_write
 	arg offset 8
@@ -153,6 +171,8 @@
 		regnum = offset & 8
 		main.regnum = value
 	end
+	#FIXME: cycle count
+	cycles 2
 
 mem_read_no_exp
 	arg addr 32
@@ -206,27 +226,19 @@
 
 saddr_read
 	arg offset 8
-	local tmp 8
-	if offset >=U 0xE0
-		#sfr area
-		tmp = offset - 0xE0
-		sfr_read tmp
+	if offset >=U 0x20
+		iram_read offset
 	else
-		tmp = offset + 0x20
-		iram_read tmp
+		sfr_read offset
 	end
 
 saddr_write
 	arg offset 8
 	arg value 8
-	local tmp 8
-	if offset >=U 0xE0
-		#sfr area
-		tmp = offset - 0xE0
-		sfr_write tmp value
+	if offset >=U 0x20
+		iram_write offset value
 	else
-		tmp = offset + 0x20
-		iram_write tmp value
+		sfr_write offset value
 	end
 
 mem_read
@@ -1227,4 +1239,18 @@
 	mask = 1 << B
 	mask = ~mask
 	scratch1 &= mask
-	saddr_write offset scratch1
\ No newline at end of file
+	saddr_write offset scratch1
+
+upd78k2_interrupt
+	if cycles >=U int_cycle
+		update_sync
+		if int_enable
+			scratch1 = ~mk0
+			scratch1 &= if0
+			if !=
+				ocall calc_vector
+				mem_read_no_exp scratch1
+				pc = scratch1
+			end
+		end
+	end
--- a/upd78k2_util.c	Thu Jul 10 16:06:33 2025 -0700
+++ b/upd78k2_util.c	Thu Jul 10 16:07:04 2025 -0700
@@ -8,11 +8,181 @@
 		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);
 	}
+	//FIXME: cycle count
+	upd->cycles += 2 * upd->opts->gen.clock_divider;
 }
 
 void upd78k2_write_8(upd78k2_context *upd)
 {
 	write_byte(upd->scratch2, upd->scratch1, (void **)upd->mem_pointers, &upd->opts->gen, upd);
+	//FIXME: cycle count
+	upd->cycles += 2 * upd->opts->gen.clock_divider;
+}
+
+#define CE0 0x08
+#define CE1 0x08
+#define CIF00 0x0010
+#define CIF01 0x0020
+#define CIF10 0x0040
+#define CIF11 0x0080
+
+void upd78k2_update_timer0(upd78k2_context *upd)
+{
+	uint32_t diff = (upd->cycles - upd->tm0_cycle) / upd->opts->gen.clock_divider;
+	upd->tm0_cycle += (diff & ~7) * upd->opts->gen.clock_divider;
+	diff >>= 3;
+	if (upd->tmc0 & CE0) {
+		uint32_t tmp = upd->tm0 + diff;
+		//TODO: the rest of the CR00/CR01 stuff
+		if (upd->tm0 < upd->cr00 && tmp >= upd->cr00) {
+			upd->if0 |= CIF00;
+		}
+		if (upd->tm0 < upd->cr01 && tmp >= upd->cr01) {
+			upd->if0 |= CIF01;
+			if (upd->crc0 & 8) {
+				//CR01 clear is enabled
+				if (upd->cr01) {
+					while (tmp >= upd->cr01) {
+						tmp -= upd->cr01;
+					}
+				} else {
+					tmp = 0;
+				}
+			}
+		}
+		if (tmp > 0xFFFF) {
+			upd->tmc0 |= 4;
+		}
+		upd->tm0 = tmp;
+	}
+}
+
+uint8_t upd78k2_tm1_scale(upd78k2_context *upd)
+{
+	uint8_t scale = upd->prm1 & 3;
+	if (scale < 2) {
+		scale = 2;
+	}
+	scale++;
+	return scale;
+}
+
+void upd78k2_update_timer1(upd78k2_context *upd)
+{
+	uint8_t scale = upd78k2_tm1_scale(upd);
+	uint32_t diff = (upd->cycles - upd->tm1_cycle) / upd->opts->gen.clock_divider;
+	upd->tm1_cycle += (diff & ~((1 << scale) - 1)) * upd->opts->gen.clock_divider;
+	diff >>= scale;
+	if (upd->tmc1 & CE1) {
+		//tm1 count enabled
+		uint32_t tmp = upd->tm1 + diff;
+		if (upd->tm1 < upd->cr10 && tmp >= upd->cr10) {
+			upd->if0 |= CIF10;
+		}
+		if (upd->tm1 < upd->cr11 && tmp >= upd->cr11) {
+			upd->if0 |= CIF11;
+		}
+		uint8_t do_clr11 = 0;
+		if (upd->crc1 & 2) {
+			//clr10 enabled
+			uint8_t do_clr10 = 1;
+			if ((upd->crc1 & 0xC) == 8) {
+				//clr11 also enabled
+				if (upd->cr11 < upd->cr10) {
+					do_clr10 = 0;
+					do_clr11 = 1;
+					
+				}
+			}
+			if (do_clr10) {
+				if (upd->cr10) {
+					while (tmp >= upd->cr10) {
+						tmp -= upd->cr10;
+					}
+				} else {
+					tmp = 0;
+				}
+			}
+		} else if ((upd->crc1 & 0xC) == 8) {
+			do_clr11 = 1;
+		}
+		if (do_clr11) {
+			if (upd->cr11) {
+				while (tmp >= upd->cr11) {
+					tmp -= upd->cr11;
+				}
+			} else {
+				tmp = 0;
+			}
+		}
+		if (tmp > 0xFF) {
+			upd->tmc1 |= 4;
+		}
+		upd->tm1 = tmp;
+	}
+}
+
+#define CMK00 CIF00
+#define CMK01 CIF01
+#define CMK10 CIF10
+#define CMK11 CIF11
+
+void upd78k2_calc_next_int(upd78k2_context *upd)
+{
+	uint32_t next_int = 0xFFFFFFFF;
+	if (!upd->int_enable) {
+		//maskable interrupts disabled
+		//TODO: NMIs
+		upd->int_cycle = next_int;
+		return;
+	}
+	if (upd->if0 & (~upd->mk0)) {
+		//unmasked interrupt is pending
+		upd->int_cycle = upd->cycles;
+		return;
+	}
+	uint32_t cycle;
+	if (!(upd->mk0 & CMK00) && (upd->tmc0 & CE0)) {
+		//TODO: account for clear function
+		cycle =  ((uint16_t)(upd->cr00 - upd->tm0)) << 3;
+		cycle *= upd->opts->gen.clock_divider;
+		cycle += upd->tm0_cycle;
+		if (cycle < next_int) {
+			next_int = cycle;
+		}
+	}
+	if (!(upd->mk0 & CMK01) && (upd->tmc0 & CE0)) {
+		//TODO: account for clear function
+		cycle = ((uint16_t)(upd->cr01 - upd->tm0)) << 3;
+		cycle *= upd->opts->gen.clock_divider;
+		cycle += upd->tm0_cycle;
+		if (cycle < next_int) {
+			next_int = cycle;
+		}
+	}
+	uint8_t scale = upd78k2_tm1_scale(upd);
+	if (!(upd->mk0 & CMK10) && (upd->tmc1 & CE1)) {
+		//TODO: account for clear function
+		cycle = ((uint8_t)(upd->cr10 - upd->tm1)) << scale;
+		cycle *= upd->opts->gen.clock_divider;
+		cycle += upd->tm1_cycle;
+		if (cycle < next_int) {
+			next_int = cycle;
+		}
+	}
+	if (!(upd->mk0 & CMK11) && (upd->tmc1 & CE1)) {
+		//TODO: account for clear function
+		cycle = ((uint8_t)(upd->cr11 - upd->tm1)) << scale;
+		cycle *= upd->opts->gen.clock_divider;
+		cycle += upd->tm1_cycle;
+		if (cycle < next_int) {
+			next_int = cycle;
+		}
+	}
+	if (next_int != upd->int_cycle) {
+		printf("UPD78K/II int cycle: %u, cur cycle %u\n", next_int, upd->cycles);
+	}
+	upd->int_cycle = next_int;
 }
 
 uint8_t upd78237_sfr_read(uint32_t address, void *context)
@@ -26,6 +196,14 @@
 	case 0x21:
 	case 0x26:
 		return upd->port_mode[address & 0x7];
+	case 0x5D:
+		upd78k2_update_timer0(upd);
+		printf("TMC0 Read: %02X\n", upd->tmc0);
+		return upd->tmc0;
+	case 0x5F:
+		upd78k2_update_timer1(upd);
+		printf("TMC1 Read: %02X\n", upd->tmc1);
+		return upd->tmc1;
 	case 0xC4:
 		return upd->mm;
 	case 0xE0:
@@ -64,46 +242,150 @@
 	} else {
 		switch (address)
 		{
+		case 0x00:
+		case 0x01:
+		case 0x03:
+		case 0x04:
+		case 0x05:
+		case 0x06:
+			printf("P%X: %02X\n", address & 7, value);
+			upd->port_data[address & 7] = value;
+			break;
+		case 0x10:
+			upd78k2_update_timer0(upd);
+			upd->cr00 &= 0xFF00;
+			upd->cr00 |= value;
+			printf("CR00: %04X\n", upd->cr00);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x11:
+			upd78k2_update_timer0(upd);
+			upd->cr00 &= 0xFF;
+			upd->cr00 |= value << 8;
+			printf("CR00: %04X\n", upd->cr00);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x12:
+			upd78k2_update_timer0(upd);
+			upd->cr01 &= 0xFF00;
+			upd->cr01 |= value;
+			printf("CR01: %04X\n", upd->cr00);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x13:
+			upd78k2_update_timer0(upd);
+			upd->cr01 &= 0xFF;
+			upd->cr01 |= value << 8;
+			printf("CR01: %04X\n", upd->cr01);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x14:
+			upd78k2_update_timer1(upd);
+			upd->cr10 = value;
+			printf("CR10: %02X\n", value);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x1C:
+			upd78k2_update_timer1(upd);
+			upd->cr11 = value;
+			printf("CR11: %02X\n", value);
+			upd78k2_calc_next_int(upd);
+			break;
 		case 0x20:
+		case 0x21:
 		case 0x23:
 		case 0x25:
 		case 0x26:
+			printf("PM%X: %02X\n", address & 0x7, value);
 			upd->port_mode[address & 7] = value;
 			break;
+		case 0x30:
+			upd78k2_update_timer0(upd);
+			upd->crc0 = value;
+			printf("CRC0 CLR01: %X, MOD: %X, Other: %X\n", value >> 3 & 1, value >> 6, value & 0x37);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x32:
+			upd78k2_update_timer1(upd);
+			upd->crc1 = value;
+			printf("CRC1 CLR11: %X, CM: %X, CLR10: %X\n", value >> 3 & 1, value >> 2 & 1, value >> 1 & 1);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x40:
+			upd->puo = value;
+			printf("PUO: %02X\n", value);
+			break;
+		case 0x43:
+			upd->pmc3 = value;
+			printf("PMC3 TO: %X, SO: %X, SCK: %X, TxD: %X, RxD: %X\n", value >> 4, value >> 3 & 1, value >> 2 & 1, value >> 1 & 1, value & 1);
+			break;
+		case 0x5D:
+			upd78k2_update_timer0(upd);
+			upd->tmc0 = value;
+			printf("TMC0 CE0: %X, OVF0: %X - TM3 CE3: %X\n", value >> 3 & 1, value >> 2 & 1, value >> 7 & 1);
+			if (!(value & 0x8)) {
+				upd->tm0 = 0;
+			}
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x5E:
+			upd78k2_update_timer1(upd);
+			upd->prm1 = value;
+			printf("PRM1: %02X\n", value);
+			upd78k2_calc_next_int(upd);
+			break;
+		case 0x5F:
+			upd78k2_update_timer1(upd);
+			upd->tmc1 = value;
+			printf("TMC1 CE2: %X, OVF2: %X, CMD2: %X, CE1: %X, OVF1: %X\n", value >> 7, value >> 6 & 1, value >> 5 & 1, value >> 3 & 1, value >> 2 & 1);
+			upd78k2_calc_next_int(upd);
+			break;
 		case 0xC4:
 			upd->mm = value;
 			break;
 		case 0xE0:
 			upd->if0 &= 0xFF00;
 			upd->if0 |= value;
+			upd78k2_calc_next_int(upd);
 			break;
 		case 0xE1:
 			upd->if0 &= 0xFF;
 			upd->if0 |= value << 8;
+			upd78k2_calc_next_int(upd);
 			break;
 		case 0xE4:
 			upd->mk0 &= 0xFF00;
 			upd->mk0 |= value;
+			printf("MK0: %04X (low: %02X)\n", upd->mk0, value);
+			upd78k2_sync_cycle(upd, upd->sync_cycle);
 			break;
 		case 0xE5:
 			upd->mk0 &= 0xFF;
 			upd->mk0 |= value << 8;
+			printf("MK0: %04X (hi: %02X)\n", upd->mk0, value);
+			upd78k2_sync_cycle(upd, upd->sync_cycle);
 			break;
 		case 0xE8:
 			upd->pr0 &= 0xFF00;
 			upd->pr0 |= value;
+			printf("PR0: %04X\n", upd->pr0);
+			upd78k2_sync_cycle(upd, upd->sync_cycle);
 			break;
 		case 0xE9:
 			upd->pr0 &= 0xFF;
 			upd->pr0 |= value << 8;
+			printf("PR0: %04X\n", upd->pr0);
+			upd78k2_sync_cycle(upd, upd->sync_cycle);
 			break;
 		case 0xEC:
 			upd->ism0 &= 0xFF00;
 			upd->ism0 |= value;
+			printf("ISM0: %04X\n", upd->ism0);
 			break;
 		case 0xED:
 			upd->ism0 &= 0xFF;
 			upd->ism0 |= value << 8;
+			printf("ISM0: %04X\n", upd->ism0);
 			break;
 		case 0xF4:
 			upd->intm0 = value;
@@ -129,16 +411,48 @@
 	opts->gen.memmap_chunks = num_chunks;
 	opts->gen.address_mask = 0xFFFFF;
 	opts->gen.max_address = 0xFFFFF;
+	opts->gen.clock_divider = 1;
 }
 
 upd78k2_context *init_upd78k2_context(upd78k2_options *opts)
 {
 	upd78k2_context *context = calloc(1, sizeof(upd78k2_context));
 	context->opts = opts;
+	memset(context->port_mode, 0xFF, sizeof(context->port_mode));
+	context->crc0 = 0x10;
+	context->mm = 0x20;
+	context->mk0 = 0xFFFF;
+	context->pr0 = 0xFFFF;
 	return context;
 }
 
 void upd78k2_sync_cycle(upd78k2_context *upd, uint32_t target_cycle)
 {
-	//TODO: implement me
+	upd78k2_update_timer0(upd);
+	upd78k2_update_timer1(upd);
+	upd->sync_cycle = target_cycle;
+	upd78k2_calc_next_int(upd);
 }
+
+void upd78k2_calc_vector(upd78k2_context *upd)
+{
+	uint32_t pending_enabled = upd->scratch1;
+	uint32_t vector = 0x6;
+	while (pending_enabled)
+	{
+		if (pending_enabled & 1) {
+			upd->scratch1 = vector;
+			return;
+		}
+		pending_enabled >>= 1;
+		vector += 2;
+		if (vector == 0xE) {
+			vector = 0x14;
+		} else if (vector == 0x20) {
+			vector = 0xE;
+		} else if (vector == 0x14) {
+			vector = 0x20;
+		}
+	}
+	fatal_error("upd78k2_calc_vector: %X\n", upd->scratch1);
+}
--- a/upd78k2run.c	Thu Jul 10 16:06:33 2025 -0700
+++ b/upd78k2run.c	Thu Jul 10 16:07:04 2025 -0700
@@ -65,6 +65,6 @@
 	init_upd78k2_opts(&opts, upd_map, 4);
 	upd = init_upd78k2_context(&opts);
 	upd->pc = rom[0] | rom[1] << 8;
-	upd78k2_execute(upd, 10000);
+	upd78k2_execute(upd, 10000000);
 	return 0;
 }
\ No newline at end of file