// license:BSD-3-Clause
// copyright-holders:Farfetch'd, R. Belmont
/*
 * CMPC: What happens to _S flag if the strings are identical?
 *   I suppose that it will be cleared. And is it set or cleared
 *   when the first one is a substring of the second? I suppose
 *   cleared (since _S should be (src > dst))
 * MOVC: Why MOVCS does not exist in downward version?
 * SHCHDB / SHCHDH: R27 is filled with the offset from the start or from the end?
 *
 * Strange stuff:
 *   SCHC opcodes does *not* modify _Z flag as stated in V60 manual:
 *   they do the opposite (set if not found, reset if found)
 */

#define F7AEND()  \
	return m_amlength1 + m_amlength2 + 4;

#define F7BEND()  \
	return m_amlength1 + m_amlength2 + 3;

#define F7CEND()  \
	return m_amlength1 + m_amlength2 + 3;

#define F7BCREATEBITMASK(x) \
	x = ((1 << (x)) - 1)

#define F7CCREATEBITMASK(x) \
	x = ((1 << (x)) - 1)

void v60_device::F7aDecodeOperands(am_func DecodeOp1, uint8_t dim1, am_func DecodeOp2, uint8_t dim2)
{
	uint8_t appb;
	// Decode first operand
	m_moddim = dim1;
	m_modm = m_subop & 0x40;
	m_modadd = PC + 2;
	m_amlength1 = (this->*DecodeOp1)();
	m_flag1 = m_amflag;
	m_op1 = m_amout;

	// Decode length
	appb = OpRead8(PC + 2 + m_amlength1);
	if (appb & 0x80)
		m_lenop1 = m_reg[appb & 0x1F];
	else
		m_lenop1 = appb;

	// Decode second operand
	m_moddim = dim2;
	m_modm = m_subop & 0x20;
	m_modadd = PC + 3 + m_amlength1;
	m_amlength2 = (this->*DecodeOp2)();
	m_flag2 = m_amflag;
	m_op2 = m_amout;

	// Decode length
	appb = OpRead8(PC + 3 + m_amlength1 + m_amlength2);
	if (appb & 0x80)
		m_lenop2 = m_reg[appb & 0x1F];
	else
		m_lenop2 = appb;
}

void v60_device::F7bDecodeFirstOperand(am_func DecodeOp1, uint8_t dim1)
{
	uint8_t appb;
	// Decode first operand
	m_moddim = dim1;
	m_modm = m_subop & 0x40;
	m_modadd = PC + 2;
	m_amlength1 = (this->*DecodeOp1)();
	m_flag1 = m_amflag;
	m_op1 = m_amout;

	// Decode ext
	appb = OpRead8(PC + 2 + m_amlength1);
	if (appb & 0x80)
		m_lenop1 = m_reg[appb & 0x1F];
	else
		m_lenop1 = appb;
}


void v60_device::F7bWriteSecondOperand(uint8_t dim2)
{
	m_moddim = dim2;
	m_modm = m_subop & 0x20;
	m_modadd = PC + 3 + m_amlength1;
	m_amlength2 = WriteAM();
}


void v60_device::F7bDecodeOperands(am_func DecodeOp1, uint8_t dim1, am_func DecodeOp2, uint8_t dim2)
{
	// Decode first operand
	F7bDecodeFirstOperand(DecodeOp1, dim1);
	m_bamoffset1 = m_bamoffset;

	// Decode second operand
	m_moddim = dim2;
	m_modm = m_subop & 0x20;
	m_modadd = PC + 3 + m_amlength1;
	m_amlength2 = (this->*DecodeOp2)();
	m_flag2 = m_amflag;
	m_op2 = m_amout;
	m_bamoffset2 = m_bamoffset;
}

void v60_device::F7cDecodeOperands(am_func DecodeOp1, uint8_t dim1, am_func DecodeOp2, uint8_t dim2)
{
	uint8_t appb;
	// Decode first operand
	m_moddim = dim1;
	m_modm = m_subop & 0x40;
	m_modadd = PC + 2;
	m_amlength1 = (this->*DecodeOp1)();
	m_flag1 = m_amflag;
	m_op1 = m_amout;

	// Decode second operand
	m_moddim = dim2;
	m_modm = m_subop & 0x20;
	m_modadd = PC + 2 + m_amlength1;
	m_amlength2 = (this->*DecodeOp2)();
	m_flag2 = m_amflag;
	m_op2 = m_amout;

	// Decode ext
	appb = OpRead8(PC + 2 + m_amlength1 + m_amlength2);
	if (appb & 0x80)
		m_lenop1 = m_reg[appb & 0x1F];
	else
		m_lenop1 = appb;
}

#define F7CLOADOP1BYTE(appb) \
	if (m_flag1) \
		appb = (uint8_t)(m_reg[m_op1]&0xFF); \
	else \
		appb = m_program->read_byte(m_op1);

#define F7CLOADOP2BYTE(appb) \
	if (m_flag2) \
		appb = (uint8_t)(m_reg[m_op2]&0xFF); \
	else \
		appb = m_program->read_byte(m_op2);


#define F7CSTOREOP2BYTE() \
	if (m_flag2) \
		SETREG8(m_reg[m_op2], appb); \
	else \
		m_program->write_byte(m_op2, appb);

#define F7CSTOREOP2HALF() \
	if (m_flag2) \
		SETREG16(m_reg[m_op2], apph); \
	else \
		m_program->write_word_unaligned(m_op2, apph);

uint32_t v60_device::opCMPSTRB(uint8_t bFill, uint8_t bStop)
{
	uint32_t i, dest;
	uint8_t c1, c2;

	F7aDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAMAddress, 0);

	// Filling
	if (bFill)
	{
		if (m_lenop1 < m_lenop2)
		{
			for (i = m_lenop1; i < m_lenop2; i++)
				m_program->write_byte(m_op1 + i,(uint8_t)R26);
		}
		else if (m_lenop2 < m_lenop1)
		{
			for (i = m_lenop2; i < m_lenop1; i++)
				m_program->write_byte(m_op2 + i,(uint8_t)R26);
		}
	}

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	_Z = 0;
	_S = 0;
	if (bStop) _CY = 1;

	for (i = 0; i < dest; i++)
	{
		c1 = m_program->read_byte(m_op1 + i);
		c2 = m_program->read_byte(m_op2 + i);

		if (c1 > c2)
		{
			_S = 1;   break;
		}
		else if (c2 > c1)
		{
			_S = 0;   break;
		}

		if (bStop)
			if (c1 == (uint8_t)R26 || c2 == (uint8_t)R26)
			{
				_CY = 0;
				break;
			}
	}

	R28 = m_lenop1 + i;
	R27 = m_lenop2 + i;

	if (i == dest)
	{
		if (m_lenop1 > m_lenop2)
			_S = 1;
		else if (m_lenop2 > m_lenop1)
			_S = 0;
		else
			_Z = 1;
	}

	F7AEND();
}

uint32_t v60_device::opCMPSTRH(uint8_t bFill, uint8_t bStop)
{
	uint32_t i, dest;
	uint16_t c1, c2;

	F7aDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAMAddress, 0);

	// Filling
	if (bFill)
	{
		if (m_lenop1 < m_lenop2)
		{
			for (i = m_lenop1; i < m_lenop2; i++)
				m_program->write_word_unaligned(m_op1 + i * 2,(uint16_t)R26);
		}
		else if (m_lenop2 < m_lenop1)
		{
			for (i = m_lenop2; i < m_lenop1; i++)
				m_program->write_word_unaligned(m_op2 + i * 2,(uint16_t)R26);
		}
	}

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	_Z = 0;
	_S = 0;
	if (bStop) _CY = 1;

	for (i = 0; i < dest; i++)
	{
		c1 = m_program->read_word_unaligned(m_op1 + i * 2);
		c2 = m_program->read_word_unaligned(m_op2 + i * 2);

		if (c1 > c2)
		{
			_S = 1;   break;
		}
		else if (c2 > c1)
		{
			_S = 0;   break;
		}

		if (bStop)
			if (c1 == (uint16_t)R26 || c2 == (uint16_t)R26)
			{
				_CY = 0;
				break;
			}
	}

	R28 = m_lenop1 + i * 2;
	R27 = m_lenop2 + i * 2;

	if (i == dest)
	{
		if (m_lenop1 > m_lenop2)
			_S = 1;
		else if (m_lenop2 > m_lenop1)
			_S = 0;
		else
			_Z = 1;
	}

	F7AEND();
}



uint32_t v60_device::opMOVSTRUB(uint8_t bFill, uint8_t bStop) /* TRUSTED (0, 0) (1, 0) */
{
	uint32_t i, dest;
	uint8_t c1;

//  if (bStop)
//  {
//      int a = 1;
//  }

	F7aDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAMAddress, 0);

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	for (i = 0; i < dest; i++)
	{
		m_program->write_byte(m_op2 + i,(c1 = m_program->read_byte(m_op1 + i)));

		if (bStop && c1 == (uint8_t)R26)
			break;
	}

	R28 = m_op1 + i;
	R27 = m_op2 + i;

	if (bFill && m_lenop1 < m_lenop2)
	{
		for (;i < m_lenop2; i++)
			m_program->write_byte(m_op2 + i,(uint8_t)R26);

		R27 = m_op2 + i;
	}


	F7AEND();
}

uint32_t v60_device::opMOVSTRDB(uint8_t bFill, uint8_t bStop)
{
	uint32_t i, dest;
	uint8_t c1;

	F7aDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAMAddress, 0);

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	for (i = 0; i < dest; i++)
	{
		m_program->write_byte(m_op2 + (dest - i - 1),(c1 = m_program->read_byte(m_op1 + (dest - i - 1))));

		if (bStop && c1 == (uint8_t)R26)
			break;
	}

	R28 = m_op1 + (m_lenop1 - i - 1);
	R27 = m_op2 + (m_lenop2 - i - 1);

	if (bFill && m_lenop1 < m_lenop2)
	{
		for (;i < m_lenop2; i++)
			m_program->write_byte(m_op2 + dest + (m_lenop2 - i - 1),(uint8_t)R26);

		R27 = m_op2 + (m_lenop2 - i - 1);
	}


	F7AEND();
}


uint32_t v60_device::opMOVSTRUH(uint8_t bFill, uint8_t bStop) /* TRUSTED (0, 0) (1, 0) */
{
	uint32_t i, dest;
	uint16_t c1;

//  if (bStop)
//  {   int a = 1; }

	F7aDecodeOperands(&v60_device::ReadAMAddress, 1,&v60_device::ReadAMAddress, 1);

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	for (i = 0; i < dest; i++)
	{
		m_program->write_word_unaligned(m_op2 + i * 2,(c1 = m_program->read_word_unaligned(m_op1 + i * 2)));

		if (bStop && c1 == (uint16_t)R26)
			break;
	}

	R28 = m_op1 + i * 2;
	R27 = m_op2 + i * 2;

	if (bFill && m_lenop1 < m_lenop2)
	{
		for (;i < m_lenop2; i++)
			m_program->write_word_unaligned(m_op2 + i * 2,(uint16_t)R26);

		R27 = m_op2 + i * 2;
	}

	F7AEND();
}

uint32_t v60_device::opMOVSTRDH(uint8_t bFill, uint8_t bStop)
{
	uint32_t i, dest;
	uint16_t c1;

//  if (bFill | bStop)
//  { int a = 1; }

	F7aDecodeOperands(&v60_device::ReadAMAddress, 1,&v60_device::ReadAMAddress, 1);

//  if (m_lenop1 != m_lenop2)
//  { int a = 1; }

	dest = (m_lenop1 < m_lenop2 ? m_lenop1 : m_lenop2);

	for (i = 0; i < dest; i++)
	{
		m_program->write_word_unaligned(m_op2 + (dest - i - 1) * 2,(c1 = m_program->read_word_unaligned(m_op1 + (dest - i - 1) * 2)));

		if (bStop && c1 == (uint16_t)R26)
			break;
	}

	R28 = m_op1 + (m_lenop1 - i - 1) * 2;
	R27 = m_op2 + (m_lenop2 - i - 1) * 2;

	if (bFill && m_lenop1 < m_lenop2)
	{
		for (;i < m_lenop2; i++)
			m_program->write_word_unaligned(m_op2 + (m_lenop2 - i - 1) * 2,(uint16_t)R26);

		R27 = m_op2 + (m_lenop2 - i - 1) * 2;
	}

	F7AEND();
}

uint32_t v60_device::opSEARCHUB(uint8_t bSearch)
{
	uint8_t appb;
	uint32_t i;

	F7bDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAM, 0);

	for (i = 0; i < m_lenop1; i++)
	{
		appb = (m_program->read_byte(m_op1 + i) == (uint8_t)m_op2);
		if ((bSearch && appb) || (!bSearch && !appb))
			break;
	}

	R28 = m_op1 + i;
	R27 = i;

	// This is the opposite as stated in V60 manual...
	if (i != m_lenop1)
		_Z = 0;
	else
		_Z = 1;

	F7BEND();
}

uint32_t v60_device::opSEARCHUH(uint8_t bSearch)
{
	uint8_t appb;
	uint32_t i;

	F7bDecodeOperands(&v60_device::ReadAMAddress, 1,&v60_device::ReadAM, 1);

	for (i = 0; i < m_lenop1; i++)
	{
		appb = (m_program->read_word_unaligned(m_op1 + i * 2) == (uint16_t)m_op2);
		if ((bSearch && appb) || (!bSearch && !appb))
			break;
	}

	R28 = m_op1 + i * 2;
	R27 = i;

	if (i != m_lenop1)
		_Z = 0;
	else
		_Z = 1;

	F7BEND();
}

uint32_t v60_device::opSEARCHDB(uint8_t bSearch)
{
	uint8_t appb;
	int32_t i;

	F7bDecodeOperands(&v60_device::ReadAMAddress, 0,&v60_device::ReadAM, 0);

	for (i = m_lenop1; i >= 0; i--)
	{
		appb = (m_program->read_byte(m_op1 + i) == (uint8_t)m_op2);
		if ((bSearch && appb) || (!bSearch && !appb))
			break;
	}

	R28 = m_op1 + i;
	R27 = i;

	// This is the opposite as stated in V60 manual...
	if ((uint32_t)i != m_lenop1)
		_Z = 0;
	else
		_Z = 1;

	F7BEND();
}

uint32_t v60_device::opSEARCHDH(uint8_t bSearch)
{
	uint8_t appb;
	int32_t i;

	F7bDecodeOperands(&v60_device::ReadAMAddress, 1,&v60_device::ReadAM, 1);

	for (i = m_lenop1 - 1; i >= 0; i--)
	{
		appb = (m_program->read_word_unaligned(m_op1 + i * 2) == (uint16_t)m_op2);
		if ((bSearch && appb) || (!bSearch && !appb))
			break;
	}

	R28 = m_op1 + i * 2;
	R27 = i;

	if ((uint32_t)i != m_lenop1)
		_Z = 0;
	else
		_Z = 1;

	F7BEND();
}


uint32_t v60_device::opSCHCUB() { return opSEARCHUB(1); }
uint32_t v60_device::opSCHCUH() { return opSEARCHUH(1); }
uint32_t v60_device::opSCHCDB() { return opSEARCHDB(1); }
uint32_t v60_device::opSCHCDH() { return opSEARCHDH(1); }
uint32_t v60_device::opSKPCUB() { return opSEARCHUB(0); }
uint32_t v60_device::opSKPCUH() { return opSEARCHUH(0); }
uint32_t v60_device::opSKPCDB() { return opSEARCHDB(0); }
uint32_t v60_device::opSKPCDH() { return opSEARCHDH(0); }

uint32_t v60_device::opCMPCB() { return opCMPSTRB(0, 0); }
uint32_t v60_device::opCMPCH() { return opCMPSTRH(0, 0); }
uint32_t v60_device::opCMPCFB() { return opCMPSTRB(1, 0); }
uint32_t v60_device::opCMPCFH() { return opCMPSTRH(1, 0); }
uint32_t v60_device::opCMPCSB() { return opCMPSTRB(0, 1); }
uint32_t v60_device::opCMPCSH() { return opCMPSTRH(0, 1); }

uint32_t v60_device::opMOVCUB() { return opMOVSTRUB(0, 0); }
uint32_t v60_device::opMOVCUH() { return opMOVSTRUH(0, 0); }
uint32_t v60_device::opMOVCFUB() { return opMOVSTRUB(1, 0); }
uint32_t v60_device::opMOVCFUH() { return opMOVSTRUH(1, 0); }
uint32_t v60_device::opMOVCSUB() { return opMOVSTRUB(0, 1); }
uint32_t v60_device::opMOVCSUH() { return opMOVSTRUH(0, 1); }

uint32_t v60_device::opMOVCDB() { return opMOVSTRDB(0, 0); }
uint32_t v60_device::opMOVCDH() { return opMOVSTRDH(0, 0); }
uint32_t v60_device::opMOVCFDB() { return opMOVSTRDB(1, 0); }
uint32_t v60_device::opMOVCFDH() { return opMOVSTRDH(1, 0); }

uint32_t v60_device::opEXTBFZ() /* TRUSTED */
{
	F7bDecodeFirstOperand(&v60_device::BitReadAM, 11);

	F7BCREATEBITMASK(m_lenop1);

	m_modwritevalw = (m_op1 >> m_bamoffset) & m_lenop1;

	F7bWriteSecondOperand(2);

	F7BEND();
}

uint32_t v60_device::opEXTBFS() /* TRUSTED */
{
	F7bDecodeFirstOperand(&v60_device::BitReadAM, 11);

	F7BCREATEBITMASK(m_lenop1);

	m_modwritevalw = (m_op1 >> m_bamoffset) & m_lenop1;
	if (m_modwritevalw & ((m_lenop1 + 1) >> 1))
		m_modwritevalw |= ~m_lenop1;

	F7bWriteSecondOperand(2);

	F7BEND();
}

uint32_t v60_device::opEXTBFL()
{
	uint32_t appw;

	F7bDecodeFirstOperand(&v60_device::BitReadAM, 11);

	appw = m_lenop1;
	F7BCREATEBITMASK(m_lenop1);

	m_modwritevalw = (m_op1 >> m_bamoffset) & m_lenop1;
	m_modwritevalw <<= 32 - appw;

	F7bWriteSecondOperand(2);

	F7BEND();
}

uint32_t v60_device::opSCHBS(uint32_t bSearch1)
{
	uint32_t i, data;
	uint32_t offset;

	F7bDecodeFirstOperand(&v60_device::BitReadAMAddress, 10);

	// Read first uint8_t
	m_op1 += m_bamoffset / 8;
	data = m_program->read_byte(m_op1);
	offset = m_bamoffset & 7;

	// Scan bitstring
	for (i = 0; i < m_lenop1; i++)
	{
		// Update the work register
		R28 = m_op1;

		// There is a 0 / 1 at current offset?
		if ((bSearch1 && (data&(1 << offset))) ||
			(!bSearch1 && !(data&(1 << offset))))
			break;

		// Next bit please
		offset++;
		if (offset == 8)
		{
			// Next uint8_t please
			offset = 0;
			m_op1++;
			data = m_program->read_byte(m_op1);
		}
	}

	// Set zero if bit not found
	_Z = (i == m_lenop1);

	// Write to destination the final offset
	m_modwritevalw = i;
	F7bWriteSecondOperand(2);

	F7BEND();
}

uint32_t v60_device::opSCH0BSU() { return opSCHBS(0); }
uint32_t v60_device::opSCH1BSU() { return opSCHBS(1); }

uint32_t v60_device::opINSBFR()
{
	uint32_t appw;
	F7cDecodeOperands(&v60_device::ReadAM, 2,&v60_device::BitReadAMAddress, 11);

	F7CCREATEBITMASK(m_lenop1);

	m_op2 += m_bamoffset / 8;
	appw = m_program->read_dword_unaligned(m_op2);
	m_bamoffset &= 7;

	appw &= ~(m_lenop1 << m_bamoffset);
	appw |=  (m_lenop1 & m_op1) << m_bamoffset;

	m_program->write_dword_unaligned(m_op2, appw);

	F7CEND();
}

uint32_t v60_device::opINSBFL()
{
	uint32_t appw;
	F7cDecodeOperands(&v60_device::ReadAM, 2,&v60_device::BitReadAMAddress, 11);

	m_op1 >>= (32 - m_lenop1);

	F7CCREATEBITMASK(m_lenop1);

	m_op2 += m_bamoffset / 8;
	appw = m_program->read_dword_unaligned(m_op2);
	m_bamoffset &= 7;

	appw &= ~(m_lenop1 << m_bamoffset);
	appw |=  (m_lenop1 & m_op1) << m_bamoffset;

	m_program->write_dword_unaligned(m_op2, appw);

	F7CEND();
}

uint32_t v60_device::opMOVBSD()
{
	uint32_t i;
	uint8_t srcdata, dstdata;

	F7bDecodeOperands(&v60_device::BitReadAMAddress, 10, &v60_device::BitReadAMAddress, 10);

//  if (m_lenop1 != 1)
//  { int a = 1; }

	m_bamoffset1 += m_lenop1 - 1;
	m_bamoffset2 += m_lenop1 - 1;

	m_op1 += m_bamoffset1 / 8;
	m_op2 += m_bamoffset2 / 8;

	m_bamoffset1 &= 7;
	m_bamoffset2 &= 7;

	srcdata = m_program->read_byte(m_op1);
	dstdata = m_program->read_byte(m_op2);

	for (i = 0; i < m_lenop1; i++)
	{
		// Update work registers
		R28 = m_op1;
		R27 = m_op2;

		dstdata &= ~(1 << m_bamoffset2);
		dstdata |= ((srcdata >> m_bamoffset1) & 1) << m_bamoffset2;

		if (m_bamoffset1 == 0)
		{
			m_bamoffset1 = 8;
			m_op1--;
			srcdata = m_program->read_byte(m_op1);
		}
		if (m_bamoffset2 == 0)
		{
			m_program->write_byte(m_op2, dstdata);
			m_bamoffset2 = 8;
			m_op2--;
			dstdata = m_program->read_byte(m_op2);
		}

		m_bamoffset1--;
		m_bamoffset2--;
	}

	// Flush of the final data
	if (m_bamoffset2 != 7)
		m_program->write_byte(m_op2, dstdata);

	F7BEND();
}

uint32_t v60_device::opMOVBSU()
{
	uint32_t i;
	uint8_t srcdata, dstdata;

	F7bDecodeOperands(&v60_device::BitReadAMAddress, 10, &v60_device::BitReadAMAddress, 10);

	m_op1 += m_bamoffset1 / 8;
	m_op2 += m_bamoffset2 / 8;

	m_bamoffset1 &= 7;
	m_bamoffset2 &= 7;

	srcdata = m_program->read_byte(m_op1);
	dstdata = m_program->read_byte(m_op2);

	for (i = 0; i < m_lenop1; i++)
	{
		// Update work registers
		R28 = m_op1;
		R27 = m_op2;

		dstdata &= ~(1 << m_bamoffset2);
		dstdata |= ((srcdata >> m_bamoffset1) & 1) << m_bamoffset2;

		m_bamoffset1++;
		m_bamoffset2++;
		if (m_bamoffset1 == 8)
		{
			m_bamoffset1 = 0;
			m_op1++;
			srcdata = m_program->read_byte(m_op1);
		}
		if (m_bamoffset2 == 8)
		{
			m_program->write_byte(m_op2, dstdata);
			m_bamoffset2 = 0;
			m_op2++;
			dstdata = m_program->read_byte(m_op2);
		}
	}

	// Flush of the final data
	if (m_bamoffset2 != 0)
		m_program->write_byte(m_op2, dstdata);

	F7BEND();
}

// RADM 0x20f4b8 holds the time left

uint32_t v60_device::opADDDC()
{
	uint8_t appb;
	uint8_t src, dst;

	F7cDecodeOperands(&v60_device::ReadAM, 0, &v60_device::ReadAMAddress, 0);

	if (m_lenop1 != 0)
	{
		logerror("ADDDC %x (pat: %x)\n", m_op1, m_lenop1);
	}

	F7CLOADOP2BYTE(appb);

	src = (uint8_t)(m_op1 >> 4) * 10 + (uint8_t)(m_op1 & 0xF);
	dst = (appb >> 4) * 10 + (appb & 0xF);

	appb = src + dst + (_CY?1:0);

	if (appb >= 100)
	{
		appb -= 100;
		_CY = 1;
	}
	else
		_CY = 0;

	// compute z flag:
	// cleared if result non-zero or carry generated
	// unchanged otherwise
	if (appb != 0 || _CY)
		_Z = 0;

	appb = ((appb / 10) << 4) | (appb % 10);

	F7CSTOREOP2BYTE();
	F7CEND();
}

uint32_t v60_device::opSUBDC()
{
	int8_t appb;
	uint32_t src, dst;

	F7cDecodeOperands(&v60_device::ReadAM, 0, &v60_device::ReadAMAddress, 0);

	if (m_lenop1 != 0)
	{
		logerror("SUBDC %x (pat: %x)\n", m_op1, m_lenop1);
	}

	F7CLOADOP2BYTE(appb);

	src = (uint32_t)(m_op1 >> 4) * 10 + (uint32_t)(m_op1 & 0xF);
	dst = ((appb & 0xF0) >> 4) * 10 + (appb & 0xF);

	// Note that this APPB must be SIGNED!
	appb = (int32_t)dst - (int32_t)src - (_CY?1:0);

	if (appb < 0)
	{
		appb += 100;
		_CY = 1;
	}
	else
		_CY = 0;

	// compute z flag:
	// cleared if result non-zero or carry generated
	// unchanged otherwise
	if (appb != 0 || _CY)
		_Z = 0;

	appb = ((appb / 10) << 4) | (appb % 10);

	F7CSTOREOP2BYTE();
	F7CEND();
}

uint32_t v60_device::opSUBRDC()
{
	int8_t appb;
	uint32_t src, dst;

	F7cDecodeOperands(&v60_device::ReadAM, 0, &v60_device::ReadAMAddress, 0);

	if (m_lenop1 != 0)
	{
		logerror("SUBRDC %x (pat: %x)\n", m_op1, m_lenop1);
	}

	F7CLOADOP2BYTE(appb);

	src = (uint32_t)(m_op1 >> 4) * 10 + (uint32_t)(m_op1 & 0xF);
	dst = ((appb & 0xF0) >> 4) * 10 + (appb & 0xF);

	// Note that this APPB must be SIGNED!
	appb = (int32_t)src - (int32_t)dst - (_CY?1:0);

	if (appb < 0)
	{
		appb += 100;
		_CY = 1;
	}
	else
		_CY = 0;

	// compute z flag:
	// cleared if result non-zero or carry generated
	// unchanged otherwise
	if (appb != 0 || _CY)
		_Z = 0;

	appb = ((appb / 10) << 4) | (appb % 10);

	F7CSTOREOP2BYTE();
	F7CEND();
}

uint32_t v60_device::opCVTDPZ()
{
	uint16_t apph;

	F7cDecodeOperands(&v60_device::ReadAM, 0, &v60_device::ReadAMAddress, 1);

	apph = (uint16_t)(((m_op1 >> 4) & 0xF) | ((m_op1 & 0xF) << 8));
	apph |= (m_lenop1);
	apph |= (m_lenop1 << 8);

	// Z flag is unchanged if src is zero, cleared otherwise
	if (m_op1 != 0) _Z = 0;

	F7CSTOREOP2HALF();
	F7CEND();
}

uint32_t v60_device::opCVTDZP()
{
	uint8_t appb;
	F7cDecodeOperands(&v60_device::ReadAM, 1, &v60_device::ReadAMAddress, 0);

	if ((m_op1 & 0xF0) != (m_lenop1 & 0xF0) || ((m_op1 >> 8) & 0xF0) != (m_lenop1 & 0xF0))
	{
		// Decimal exception
		logerror("CVTD.ZP Decimal exception #1!\n");
	}

	if ((m_op1 & 0xF) > 9 || ((m_op1 >> 8) & 0xF) > 9)
	{
		// Decimal exception
		logerror("CVTD.ZP Decimal exception #2!\n");
	}

	appb = (uint8_t)(((m_op1 >> 8) & 0xF) | ((m_op1 & 0xF) << 4));
	if (appb != 0) _Z = 0;

	F7CSTOREOP2BYTE();
	F7CEND();
}

uint32_t v60_device::op58UNHANDLED()
{
	fatalerror("Unhandled 58 opcode at PC: /%06x\n", PC);
	return 0; /* never reached, fatalerror won't return */
}

uint32_t v60_device::op5AUNHANDLED()
{
	fatalerror("Unhandled 5A opcode at PC: /%06x\n", PC);
	return 0; /* never reached, fatalerror won't return */
}

uint32_t v60_device::op5BUNHANDLED()
{
	fatalerror("Unhandled 5B opcode at PC: /%06x\n", PC);
	return 0; /* never reached, fatalerror won't return */
}

uint32_t v60_device::op5DUNHANDLED()
{
	fatalerror("Unhandled 5D opcode at PC: /%06x\n", PC);
	return 0; /* never reached, fatalerror won't return */
}

uint32_t v60_device::op59UNHANDLED()
{
	fatalerror("Unhandled 59 opcode at PC: /%06x\n", PC);
	return 0; /* never reached, fatalerror won't return */
}

const v60_device::am_func v60_device::s_Op59Table[32] =
{
	&v60_device::opADDDC,
	&v60_device::opSUBDC,
	&v60_device::opSUBRDC,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::opCVTDPZ,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::opCVTDZP,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED,
	&v60_device::op59UNHANDLED
};


const v60_device::am_func v60_device::s_Op5BTable[32] =
{
	&v60_device::opSCH0BSU,
	&v60_device::op5BUNHANDLED,
	&v60_device::opSCH1BSU,
		&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::opMOVBSU,
	&v60_device::opMOVBSD,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED,
	&v60_device::op5BUNHANDLED
};


const v60_device::am_func v60_device::s_Op5DTable[32] =
{
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::opEXTBFS,
	&v60_device::opEXTBFZ,
	&v60_device::opEXTBFL,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::opINSBFR,
	&v60_device::opINSBFL,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED,
	&v60_device::op5DUNHANDLED
};

const v60_device::am_func v60_device::s_Op58Table[32] =
{
	&v60_device::opCMPCB,
	&v60_device::opCMPCFB,
	&v60_device::opCMPCSB,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::opMOVCUB,
	&v60_device::opMOVCDB,
	&v60_device::opMOVCFUB,
	&v60_device::opMOVCFDB,
	&v60_device::opMOVCSUB,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::opSCHCUB,
	&v60_device::opSCHCDB,
	&v60_device::opSKPCUB,
	&v60_device::opSKPCDB,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED,
	&v60_device::op58UNHANDLED
};

const v60_device::am_func v60_device::s_Op5ATable[32] =
{
	&v60_device::opCMPCH,
	&v60_device::opCMPCFH,
	&v60_device::opCMPCSH,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::opMOVCUH,
	&v60_device::opMOVCDH,
	&v60_device::opMOVCFUH,
	&v60_device::opMOVCFDH,
	&v60_device::opMOVCSUH,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::opSCHCUH,
	&v60_device::opSCHCDH,
	&v60_device::opSKPCUH,
	&v60_device::opSKPCDH,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED,
	&v60_device::op5AUNHANDLED
};

uint32_t v60_device::op58()
{
	m_subop = OpRead8(PC + 1);

	return (this->*s_Op58Table[m_subop & 0x1F])();
}

uint32_t v60_device::op5A()
{
	m_subop = OpRead8(PC + 1);

	return (this->*s_Op5ATable[m_subop & 0x1F])();
}

uint32_t v60_device::op5B()
{
	m_subop = OpRead8(PC + 1);

	return (this->*s_Op5BTable[m_subop & 0x1F])();
}

uint32_t v60_device::op5D()
{
	m_subop = OpRead8(PC + 1);

	return (this->*s_Op5DTable[m_subop & 0x1F])();
}

uint32_t v60_device::op59()
{
	m_subop = OpRead8(PC + 1);

	return (this->*s_Op59Table[m_subop & 0x1F])();
}
