diff upd78k2.cpu @ 2705:ab2d916380bf

WIP uPD78K/II CPU core
author Michael Pavone <pavone@retrodev.com>
date Sun, 06 Jul 2025 15:20:46 -0700
parents
children 0bd48217941a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upd78k2.cpu	Sun Jul 06 15:20:46 2025 -0700
@@ -0,0 +1,853 @@
+#"Minimum instruction cycle is 333 ns for internal ROM 500 ns for external at 12 MHz"
+#That's 4 clks for internal ROM, 6 clocks external
+#Above seems to be defined based on external clock input, but there's an internal divider
+#Instruction timing tables seem to specified in terms of this divided clock
+info
+	prefix upd78k2_
+	opcode_size 8
+	header upd78k2.h
+	body upd78k2_run_op
+	extra_tables sfr bit1 bit2 muldiv base sfrbit spmov indexed regind alt_base alt_indexed alt_regind mov_reg
+	include upd78k2_util.c
+regs
+	main 8 x a c b e d l h
+	psw 8
+	pc 16
+	sp 16
+	rbs 8
+	int_enable 8
+	int_priority_flag 8
+	chflags 8
+	zflag 8
+	scratch1 32
+	scratch2 32
+	mem_pointers ptr8 4
+	port_data 8 8
+	port_mode 8 8
+	mm 8
+	iram 8 256
+flags
+	register psw
+	I 7 none int_enable
+	Z 6 zero zflag
+	R 5 none rbs.1
+	A 4 half-carry chflags.3
+	B 3 none rbs.0
+	P 1 none int_priority_flag
+	C 0 carry chflags.7
+declare
+	uint8_t upd78237_sfr_read(uint32_t address, void *context);
+	void *upd78237_sfr_write(uint32_t address, void *context, uint8_t value);
+	void init_upd78k2_opts(upd78k2_options *opts, memmap_chunk const *chunks, uint32_t num_chunks);
+	upd78k2_context *init_upd78k2_context(upd78k2_options *opts);
+
+#Prefix bytes
+# 0000 0001 -> saddr becomes sfr, mem becomes &mem
+# 0000 0010 -> bit instructions/ conditional branches
+# 0000 0011 -> more bit instructions / conditional branches
+# 0000 0110 -> base mode
+# 0000 1000 -> saddr/sfr bit instructions / conditional branches
+# 0000 1001 -> mov !addr16/stbc
+# 0000 1010 -> indexed mode
+# 0001 0110 -> register indirect
+
+sfr_read
+	arg offset 8
+	scratch1 = offset + 0xFF00
+	ocall read_8
+
+sfr_write
+	arg offset 8
+	arg value 8
+	scratch2 = offset + 0xFF00
+	scratch1 = value
+	ocall write_8
+	
+iram_read
+	arg offset 8
+	local normal_iram 8
+	normal_iram = 1
+	if offset >=U 0xE0
+		#register bank area
+		switch rbs
+		case 0
+			if offset >=U 0xF8
+				normal_iram = 0
+			end
+		case 1
+			if offset >=U 0xF0
+				if offset >=U 0xF8
+				else
+					normal_iram = 0
+				end
+			end
+		case 2
+			if offset >=U 0xE8
+				if offset >=U 0xF0
+				else
+					normal_iram = 0
+				end
+			end
+		case 3
+			if offset >=U 0xE8
+			else
+				normal_iram = 0
+			end
+		end
+	end
+	if normal_iram
+		scratch1 = iram.offset
+	else
+		scratch1 = offset & 7
+		scratch1 = main.scratch1
+	end
+
+iram_write
+	arg offset 8
+	arg value 8
+	local normal_iram 8
+	local regnum 8
+	normal_iram = 1
+	if offset >=U 0xE0
+		#register bank area
+		switch rbs
+		case 0
+			if offset >=U 0xF8
+				normal_iram = 0
+			end
+		case 1
+			if offset >=U 0xF0
+				if offset >=U 0xF8
+				else
+					normal_iram = 0
+				end
+			end
+		case 2
+			if offset >=U 0xE8
+				if offset >=U 0xF0
+				else
+					normal_iram = 0
+				end
+			end
+		case 3
+			if offset >=U 0xE8
+			else
+				normal_iram = 0
+			end
+	end
+	if normal_iram
+		iram.offset = value
+	else
+		regnum = offset & 8
+		main.regnum = value
+	end
+
+mem_read_no_exp
+	arg addr 32
+	local offset 8
+	if addr >=U 0xFE00
+		if addr >=U 0xFF00
+			offset = addr
+			sfr_read offset
+		else
+			offset = addr
+			iram_read offset
+		end
+	else
+		scratch1 = addr
+		ocall read_8
+	end
+
+mem_write_no_exp
+	arg addr 32
+	arg value 8
+	local offset 8
+	if addr >=U 0xFE00
+		if addr >=U 0xFF00
+			offset = addr
+			sfr_write offset value
+		else
+			offset = addr
+			iram_write offset value
+		end
+	else
+		scratch2 = addr
+		scratch1 = value
+		ocall write_8
+	end
+
+upd78k2_op_fetch
+	mem_read_no_exp pc
+	pc += 1
+
+upd78k2_op_fetch_word
+	local tmp 8
+	upd78k2_op_fetch
+	tmp = scratch1
+	upd78k2_op_fetch
+	scratch1 <<= 8
+	scratch1 |= tmp
+
+upd78k2_run_op
+	upd78k2_op_fetch
+	dispatch scratch1
+
+saddr_read
+	arg offset 8
+	local tmp 8
+	if offset >=U 0xE0
+		#sfr area
+		tmp = offset - 0xE0
+		sfr_read tmp
+	else
+		tmp = offset + 0x20
+		iram_read tmp
+	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
+	else
+		tmp = offset + 0x20
+		iram_write tmp value
+	end
+
+mem_read
+	arg address 16
+	arg alt_bank 8
+	local full_address 32
+	local meg_enable 8
+	meg_enable = mm & 0x40
+	if meg_enable
+		if alt_bank
+			full_address = port_data.6 & 0xF
+		else
+			full_address = port_mode.6 & 0xF
+		end
+		full_address <<= 16
+		full_address |= address
+	else
+		full_address = address
+	end
+	mem_read_no_exp full_address
+
+mem_write
+	arg address 16
+	arg value 8
+	arg alt_bank 8
+	local full_address 32
+	local meg_enable 8
+	meg_enable = mm & 0x40
+	if meg_enable
+		if alt_bank
+			full_address = port_data.6 & 0xF
+		else
+			full_address = port_mode.6 & 0xF
+		end
+		full_address <<= 16
+		full_address |= address
+	else
+		full_address = address
+	end
+	mem_write_no_exp full_address value
+
+push_word
+	arg value 16
+	local tmp 8
+	tmp = value >> 8
+	sp -= 1
+	mem_write_no_exp sp tmp
+	tmp = value
+	sp -= 1
+	mem_write_no_exp sp tmp
+
+pop_word
+	mem_read_no_exp sp
+	dst = scratch1
+	sp += 1
+	mem_read_no_exp sp
+	scratch1 <<= 8
+	dst |= scratch1
+	sp += 1
+
+00000000 nop
+	cycles 2 #minimum cycle time appears to be 4 (ignoring internal divider)
+
+00000001 sfr_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 sfr
+
+00000010 bit1_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 bit1
+
+00000011 bit2_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 bit2
+
+00000101 muldiv_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 muldiv
+
+00000110 base_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 base
+
+00001000 sfrbit_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 sfrbit
+
+00001001 spmov_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 spmov
+
+00001010 indexed_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 indexed
+
+00010110 regind_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 regind
+
+00100100 mov_reg_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 mov_reg
+
+sfr 00000110 alt_base_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 alt_base
+
+sfr 00001010 alt_indexed_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 alt_indexed
+
+sfr 00010110 alt_regind_prefix
+	upd78k2_op_fetch
+	dispatch scratch1 alt_regind
+
+aluop
+	arg op 8
+	arg src 8
+	switch op
+	case 0
+		dst += src
+		update_flags ZAC
+	case 1
+		adc dst src dst
+		update_flags ZAC
+	case 2
+		dst -= src
+		update_flags ZAC
+	case 3
+		sbc dst src dst
+		update_flags ZAC
+	case 4
+		dst &= src
+		update_flags Z
+	case 5
+		dst ^= src
+		update_flags Z
+	case 6
+		dst |= src
+		update_flags Z
+	case 7
+		cmp dst src
+		update_flags ZAC
+	end
+
+aluop16
+	arg op 8
+	arg src 16
+	switch op
+	case 0
+		dst += src
+		update_flags ZAC
+	case 2
+		dst -= src
+		update_flags ZAC
+	case 7
+		dst -= src
+		update_flags ZAC
+	default
+		#TODO: what happens in these invalid cases
+	end
+
+10001PPP alu_r_r
+	local tmp_src 16
+	local tmp_dst 16
+	upd78k2_op_fetch
+	scratch2 = scratch1 >> 4
+	if scratch2 >=U 7
+		#TODO: is MSB just ignored or is this treated as some kind of illegal instruction or nop?
+	else
+		scratch1 &= 0xF
+		if scratch1 >=U 7
+			scratch1 &= 7
+			meta dst tmp_dst
+			tmp_dst = a << 8
+			tmp_dst |= x
+			switch scratch1
+			case 0
+				tmp_src = a << 8
+				tmp_src |= x
+			case 2
+				tmp_src = b << 8
+				tmp_src |= c
+			case 4
+				tmp_src = d << 8
+				tmp_src |= e
+			case 6
+				tmp_src = h << 8
+				tmp_src |= l
+			default
+				#TODO: what happens in these invalid cases
+			end
+			aluop16 P tmp_src
+			x = tmp_dst
+			a = tmp_dst >> 8
+		else
+			meta dst main.scratch2
+			cycles 2 #penalty for extra IRAM access since regs are technically stored there?
+			aluop P main.scratch1
+		end
+	end
+
+10101PPP alu_immed
+	upd78k2_op_fetch
+	meta dst a
+	aluop P scratch1
+	
+calc_addr_base
+	arg regpair 8
+	local tmp 16
+	upd78k2_op_fetch
+	switch regpair
+	case 0
+		#[de+byte]
+		tmp = d << 8
+		tmp |= e
+		adst += tmp
+	case 1
+		#[sp+byte]
+		adst += sp
+	case 2
+		#[hl+byte]
+		tmp = h << 8
+		tmp |= l
+		adst += tmp
+	end
+
+read_base_mode
+	arg regpair 8
+	arg alt_bank 8
+	meta adst scratch1
+	calc_addr_base regpair
+	mem_read scratch1 alt_bank
+
+write_base_mode
+	arg regpair 8
+	arg value 8
+	arg alt_bank 8
+	meta adst scratch2
+	calc_addr_base regpair
+	mem_write scratch2 value alt_bank
+	
+calc_addr_regind
+	arg regpair 8
+	local tmp 16
+	switch regpair
+	case 0
+		#[de+]
+		adst = d << 8
+		adst |= e
+		tmp = adst + 1
+		e = tmp
+		d = tmp >> 8
+	case 1
+		#[hl+]
+		adst = h << 8
+		adst |= l
+		tmp = adst + 1
+		l = tmp
+		h = tmp >> 8
+	case 2
+		#[de-]
+		adst = d << 8
+		adst |= e
+		tmp = adst - 1
+		e = tmp
+		d = tmp >> 8
+	case 3
+		#[hl-]
+		adst = h << 8
+		adst |= l
+		tmp = adst - 1
+		l = tmp
+		h = tmp >> 8
+	case 4
+		#[de]
+		adst = d << 8
+		adst |= e
+	case 5
+		#[hl]
+		adst = h << 8
+		adst |= l
+	end
+
+read_regind_mode
+	arg regpair 8
+	arg alt_bank 8
+	meta adst scratch1
+	calc_addr_regind regpair
+	mem_read scratch1 alt_bank
+
+write_regind_mode
+	arg regpair 8
+	arg value 8
+	arg alt_bank 8
+	meta adst scratch2
+	calc_addr_regind regpair
+	mem_write scratch2 value alt_bank
+
+calc_addr_indexed
+	arg index_reg 8
+	local tmp 16
+	upd78k2_op_fetch_word
+	switch index_reg
+	case 0
+		tmp = d << 8
+		tmp |= e
+		adst += tmp
+	case 1
+		adst += a
+	case 2
+		tmp = h << 8
+		tmp |= l
+		adst += tmp
+	case 3
+		adst += b
+	end
+
+read_indexed_mode
+	arg index_reg 8
+	arg alt_bank 8
+	meta adst scratch1
+	calc_addr_indexed index_reg
+	mem_read scratch1 alt_bank
+
+write_indexed_mode
+	arg index_reg 8
+	arg value 8
+	arg alt_bank 8
+	meta adst scratch2
+	calc_addr_indexed index_reg
+	mem_write scratch2 value alt_bank
+
+base 00RR1PPP alu_base
+	invalid R 3
+	read_base_mode R 0
+	meta dst a
+	aluop P scratch1
+
+alt_base 00RR1PPP alu_alt_base
+	invalid R 3
+	read_base_mode R 1
+	meta dst a
+	aluop P scratch1
+
+base 00RR0000 mov_a_base
+	invalid R 3
+	read_base_mode R 0
+	a = scratch1
+	
+alt_base 00RR0000 alt_mov_a_base
+	invalid R 3
+	read_base_mode R 1
+	a = scratch1
+
+base 10RR0000 mov_base_a
+	invalid R 3
+	write_base_mode R a 0
+
+alt_base 10RR0000 alt_mov_base_a
+	invalid R 3
+	write_base_mode R a 1
+
+regind 0RRR1PPP alu_reg_indirect
+	invalid R 6
+	invalid R 7
+	read_regind_mode R 0
+	meta dst a
+	aluop P scratch1
+
+alt_regind 0RRR1PPP alu_alt_reg_indirect
+	invalid R 6
+	invalid R 7
+	read_regind_mode R 1
+	meta dst a
+	aluop P scratch1
+
+regind 0RRR0000 mov_a_reg_indirect
+	invalid R 6
+	invalid R 7
+	read_regind_mode R 0
+	a = scratch1
+
+alt_regind 0RRR0000 alt_mov_a_reg_indirect
+	invalid R 6
+	invalid R 7
+	read_regind_mode R 1
+	a = scratch1
+	
+regind 1RRR0000 mov_reg_indirect_a
+	invalid R 6
+	invalid R 7
+	write_regind_mode R a 0
+
+alt_regind 1RRR0000 alt_mov_reg_indirect_a
+	invalid R 6
+	invalid R 7
+	write_regind_mode R a 1
+
+01010RRR mov_reg_indirect_a_short
+	invalid R 6
+	invalid R 7
+	write_regind_mode R a 0
+
+01011RRR mov_a_reg_indirect_short
+	invalid R 6
+	invalid R 7
+	read_regind_mode R 0
+	a = scratch1
+
+indexed 00RR1PPP alu_indexed
+	read_indexed_mode R 0
+	meta dst a
+	aluop P scratch1
+
+alt_indexed 00RR1PPP alu_alt_indexed
+	read_indexed_mode R 1
+	meta dst a
+	aluop P scratch1
+
+indexed 00RR0000 mov_a_indexed
+	read_indexed_mode R 0
+	a = scratch1
+
+alt_indexed 00RR0000 alt_mov_a_indexed
+	read_indexed_mode R 1
+	a = scratch1
+
+indexed 10RR0000 mov_indexed_a
+	write_indexed_mode R a 0
+
+alt_indexed 10RR0000 alt_mov_indexed_a
+	write_indexed_mode R a 1
+
+mov_reg 0DD01SS0 movw_rp_rp
+	local dst 8
+	local src 8
+	dst = D << 1
+	src = S << 1
+	main.dst = main.src
+	dst += 1
+	src += 1
+	main.dst = main.src
+
+mov_reg 0DDD0SSS mov_r_r
+	main.D = main.S
+
+01100PP0 movw_rp_immed
+	local dst 8
+	dst = P << 1
+	upd78k2_op_fetch
+	main.dst = scratch1
+	dst += 1
+	upd78k2_op_fetch
+	main.dst = scratch1
+
+11000RRR inc_r
+	cycles 2
+	main.R += 1
+	update_flags ZA
+
+11001RRR dec_r
+	cycles 2
+	main.R -= 1
+	update_flags ZA
+
+11010RRR mov_a_r
+	a = main.R
+
+muldiv 01001PP0 br_rp
+	local reg 8
+	local tmp 16
+	reg = P << 1
+	pc = main.reg
+	reg += 1
+	tmp = main.reg << 8
+	pc |= tmp
+
+00010100 br_rel
+	upd78k2_op_fetch
+	sext 16 scratch1 scratch1
+	pc += scratch1
+
+00101100 br_abs
+	upd78k2_op_fetch_word
+	pc = scratch1
+
+100000FS bcc
+	local flag 8
+	upd78k2_op_fetch
+	if F
+		flag = chflags >> 7
+	else
+		flag = zflag
+	end
+	if flag = S
+		sext 16 scratch1 scratch1
+		pc += scratch1
+	end
+
+muldiv 01011PP0 call_rp
+	local reg 8
+	local tmp 16
+	push_word pc
+	reg = P << 1
+	pc = main.reg
+	reg += 1
+	tmp = main.reg << 8
+	pc |= tmp
+
+00101000 call_long
+	local address 16
+	upd78k2_op_fetch_word
+	address = scratch1
+	push_word pc
+	pc = address
+
+10010AAA call_short
+	local address 16
+	address = A << 8
+	address |= 0x800
+	upd78k2_op_fetch
+	address |= scratch1
+	push_word pc
+	pc = address
+
+111TTTTT call_table
+	local address 16
+	mem_read_no_exp T
+	address = scratch1
+	scratch1 = T + 1
+	mem_read_no_exp scratch1
+	scratch1 <<= 8
+	address |= 8
+	push_word pc
+	pc = address
+
+01010110 ret
+	meta dst pc
+	pop_word
+
+001101PP pop_rp
+	local rp 8
+	local word 16
+	meta dst word
+	pop_word
+	main.rp = word
+	rp += 1
+	main.rp = word >> 8
+	
+001111PP push_rp
+	local rp 8
+	local word 16
+	rp = P << 1
+	rp += 1
+	word = main.rp << 8
+	rp -= 1
+	word |= main.rp
+	push_word word
+
+muldiv 101010NN sel
+	local offset 8
+	if N != rbs
+		offset = rbs << 3
+		offset = 0xF8 - offset
+		iram.offset = x
+		offset += 1
+		iram.offset = a
+		offset += 1
+		iram.offset = c
+		offset += 1
+		iram.offset = b
+		offset += 1
+		iram.offset = e
+		offset += 1
+		iram.offset = d
+		offset += 1
+		iram.offset = l
+		offset += 1
+		iram.offset = h
+		offset = N << 3
+		offset = 0xF8 - offset
+		x = iram.offset
+		offset += 1
+		a = iram.offset
+		offset += 1
+		c = iram.offset
+		offset += 1
+		b = iram.offset
+		offset += 1
+		e = iram.offset
+		offset += 1
+		d = iram.offset
+		offset += 1
+		l = iram.offset
+		offset += 1
+		h = iram.offset
+		rbs = N
+	end
+
+muldiv 11001000 incw_sp
+	sp += 1
+
+muldiv 11001001 decw_sp
+	sp -= 1
+
+00001011 movw_sfrp_immed
+	upd78k2_op_fetch
+	if scratch1 = 0xFC
+		#unclear if SP is actually mapped in the SFR space
+		#or if this is special cased, but the docs don't
+		#suggest you can write to SP with other SFR-targeting
+		#instructions so I'm assuming the latter.
+		upd78k2_op_fetch
+		sp = scratch1
+		upd78k2_op_fetch
+		scratch1 <<= 8
+		sp |= scratch1
+	else
+		scratch2 = scratch1
+		upd78k2_op_fetch
+		sfr_write scratch2 scratch1
+		scratch2 += 1
+		upd78k2_op_fetch
+		sfr_write scratch2 scratch1
+	end
+
+00001100 movw_sadrp_word
+	local offset 8
+	upd78k2_op_fetch
+	offset = scratch1
+	upd78k2_op_fetch
+	saddr_write offset scratch1
+	offset += 1
+	upd78k2_op_fetch
+	saddr_write offset scratch1
\ No newline at end of file