// license:BSD-3-Clause
// copyright-holders:Andrew Gardner
#include "dsp16.h"

#define DSP_LINE(__DSP_DOCLINE__) printf("0x%04x - %d (%s)\n", m_pc, __LINE__, __DSP_DOCLINE__);

// TODO:
//   * AUC has a CLR field for writing to A0 & A1 + sign extension + psw + zero lower bits
//     implement as a clean function (page 2-7)
//   * Implement saturation overflow (SAT on AUC) (page 2-8)
//   * Implement p alignment (ALIGN on AUC) (page 2-9)
//   * When a register is used as a memory pointer. its value is compared with re. If its value is
//     equal to the contents of re and the postincrement is +1, then the value in rb is copied into
//     the register after the memory access is complete. See Section 4.2.3.
//   * CPU flags go to the PSW & conditionTest() works on that (Page 3-4)
//   * Some instructions are not interruptible.
//


// NOTES:
// When y is used in an assembly-language instruction, the DSPI6/DSPI6A device will read
// or write the high half (bits 16-31) of the y register  (page 2-7)

// The YL register is the lower half of the 32 bit Y register
void* dsp16_device::addressYL()
{
	return (void*)(((UINT8*)&m_y) + 2);
}


// Flag getters
bool dsp16_device::lmi()
{
	return m_psw & 0x8000;
}

bool dsp16_device::leq()
{
	return m_psw & 0x4000;
}

bool dsp16_device::llv()
{
	return m_psw & 0x2000;
}

bool dsp16_device::lmv()
{
	return m_psw & 0x1000;
}


void dsp16_device::writeRegister(void* reg, const UINT16 &value)
{
	// Make sure you're not attempting to write somewhere this function doesn't support.
	if (reg == &m_p || reg == &m_a0 || reg == &m_a1)
	{
		logerror("dsp16::writeRegister called on invalid register at PC 0x%04x.\n", m_pc);
		return;
	}

	if (reg == &m_auc || reg == &m_c0 || reg == &m_c1 || reg == &m_c2)
	{
		// 8 bit registers
		*(UINT8*)reg = value & 0x00ff;
	}
	else if (reg == &m_psw)
	{
		// Writes to the a0 & a1 guard bits too
		m_a0 &= U64(0x0ffffffff);
		m_a0 |= U64(m_psw & 0x000f) << 32;
		m_a1 &= U64(0x0ffffffff);
		m_a1 |= U64(m_psw & 0x01e0) << 27;
		m_psw = value;
	}
	else if (reg == &m_i)
	{
		// 12 bit register
		m_i = value & 0x0fff;
	}
	else if (reg == &m_y)
	{
		// Y register
		// TODO - Automatic clearing of yl may be selected (according to the CLR field of the auc register)  (page 2-7)
		m_y = (value << 16) | (m_y & 0x0000ffff);
	}
	else if (reg == addressYL())
	{
		// Yl register (Writes to yl do not change the data in the high half of y)
		m_y = value | (m_y & 0xffff0000);
	}
	else
	{
		// Everything else
		*(UINT16*)reg = value;
	}
}


bool dsp16_device::conditionTest(const UINT8& CON)
{
	switch (CON)
	{
		case 0x00: return lmi();   // mi (negative result)
		case 0x01: return !lmi();  // pl (positive result)
		case 0x02: return leq();   // eq (result == 0)
		case 0x03: return !leq();  // ne (result != 0)
		case 0x04: return llv();   // lvs (logical overflow set)
		case 0x05: return !llv();  // lvc (logical overflow clear)
		case 0x06: return lmv();   // mvs (math. overflow set)
		case 0x07: return !lmv();  // mvc (math. overflow clear)
		case 0x08: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // heads (random bit set)
		case 0x09: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // tails (random bit clear)
		case 0x0a: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // c0ge (counter0 >= 0)*
		case 0x0b: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // c0lt (counter0 < 0)*
		case 0x0c: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // c1ge (counter1 >= 0)*
		case 0x0d: printf("UNIMPLEMENTED condition check @ PC 0x%04x\n", m_pc); return false;   // c1lt (counter1 < 0)*
		case 0x0e: return true;    // true (always)
		case 0x0f: return false;   // false (never)
		case 0x10: return (!lmi() && !leq());   // gt (result > 0)
		case 0x11: return (lmi()  ||  leq());   // le (result <= 0)
		default: logerror("Unrecognized condition at PC=0x%04x\n", m_pc); break;
	}

	// Testing each of these conditions (*) increments the respective counter being tested  (page 3-5)

	return false;
}


void* dsp16_device::registerFromRImmediateField(const UINT8& R)
{
	switch (R)
	{
		case 0x00: return (void*)&m_j;
		case 0x01: return (void*)&m_k;
		case 0x02: return (void*)&m_rb;
		case 0x03: return (void*)&m_re;
		case 0x04: return (void*)&m_r0;
		case 0x05: return (void*)&m_r1;
		case 0x06: return (void*)&m_r2;
		case 0x07: return (void*)&m_r3;

		default: return nullptr;
	}
	return nullptr;
}


void* dsp16_device::registerFromRTable(const UINT8 &R)
{
	switch (R)
	{
		case 0x00: return (void*)&m_r0;
		case 0x01: return (void*)&m_r1;
		case 0x02: return (void*)&m_r2;
		case 0x03: return (void*)&m_r3;
		case 0x04: return (void*)&m_j;
		case 0x05: return (void*)&m_k;
		case 0x06: return (void*)&m_rb;
		case 0x07: return (void*)&m_re;
		case 0x08: return (void*)&m_pt;
		case 0x09: return (void*)&m_pr;
		case 0x0a: return (void*)&m_pi;
		case 0x0b: return (void*)&m_i;

		case 0x10: return (void*)&m_x;
		case 0x11: return (void*)&m_y;
		case 0x12: return (void*)addressYL();
		case 0x13: return (void*)&m_auc;    // zero extended
		case 0x14: return (void*)&m_psw;
		case 0x15: return (void*)&m_c0;     // sign extended
		case 0x16: return (void*)&m_c1;     // sign extended
		case 0x17: return (void*)&m_c2;     // sign extended
		case 0x18: return (void*)&m_sioc;
		case 0x19: return (void*)&m_srta;
		case 0x1a: return (void*)&m_sdx;
		case 0x1b: logerror("dsp16::registerFromRTable tdms requested 0x%04x.\n", m_pc); break;
		case 0x1c: return (void*)&m_pioc;
		case 0x1d: return (void*)&m_pdx0;
		case 0x1e: return (void*)&m_pdx1;

		default: return nullptr;
	}
	return nullptr;
}


void dsp16_device::executeF1Field(const UINT8& F1, const UINT8& D, const UINT8& S)
{
	// TODO: I'm pretty sure we need to feed X into these as well - Double check

	// Note these instructions read right-to-left, so act accordingly  (page 3-6)
	// y & p are sign extended  (page 3-9)
	// implementation details  (page 3-9)

	// Where is are the results going?
	UINT64* destinationReg = nullptr;
	switch (D)
	{
		case 0x00: destinationReg = &m_a0; break;
		case 0x01: destinationReg = &m_a1; break;
		default: break;
	}

	// Which source is being used?
	UINT64* sourceReg = nullptr;
	switch (S)
	{
		case 0x00: sourceReg = &m_a0; break;
		case 0x01: sourceReg = &m_a1; break;
		default: break;
	}


	// We must compute into an intermediate variable to compute flags on
	UINT64 result = 0;
	bool justATest = false;

	switch (F1)
	{
		case 0x00:
		{
			// Ad = p   p = x*y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x01:
		{
			// Ad = aS+p   p = x*y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x02:
		{
			// p = x*y
			// TODO: What happens to the flags in this operation?
			const INT16 y = (m_y & 0xffff0000) >> 16;
			m_p = (INT32)((INT16)m_x * y);
			justATest = true;
			break;
		}
		case 0x03:
		{
			// Ad = aS-p   p = x*y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x04:
		{
			// Ad = p
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x05:
		{
			// Ad = aS+p
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x06:
		{
			// nop
			justATest = true;
			break;
		}
		case 0x07:
		{
			// Ad = aS-p
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x08:
		{
			// Ad = aS|y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x09:
		{
			// Ad = aS^y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x0a:
		{
			// aS&y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			justATest = true;
			break;
		}
		case 0x0b:
		{
			// aS-y
			INT64 aS = *sourceReg;
			if (aS & U64(0x800000000))
				aS |= U64(0xfffffff000000000);

			INT64 y  = (m_y & 0xffff0000) >> 16;
			if (y & 0x8000)
				y |= U64(0xffffffffffff0000);

			result = aS-y;
			justATest = true;
			break;
		}
		case 0x0c:
		{
			// Ad = y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x0d:
		{
			// Ad = aS+y
			INT64 aS = *sourceReg;
			if (aS & U64(0x800000000))
				aS |= U64(0xfffffff000000000);

			INT64 y  = (m_y & 0xffff0000) >> 16;
			if (y & 0x8000)
				y |= U64(0xffffffffffff0000);

			result = aS+y;
			break;
		}
		case 0x0e:
		{
			// Ad = aS&y
			printf("UNIMPLEMENTED F1 operation @ PC 0x%04x (%d)\n", m_pc, __LINE__);
			break;
		}
		case 0x0f:
		{
			// Ad = aS-y
			INT64 aS = *sourceReg;
			if (aS & U64(0x800000000))
				aS |= U64(0xfffffff000000000);

			INT64 y  = (m_y & 0xffff0000) >> 16;
			if (y & 0x8000)
				y |= U64(0xffffffffffff0000);

			result = aS-y;
			break;
		}
	}

	// CPU Flags  (page 3-4)
	// LMI (logical minus)
	if (result & U64(0x800000000))
		m_psw |= 0x8000;
	else
		m_psw &= (~0x8000);

	// LEQ (logical equal)
	if (result == U64(0x000000000))
		m_psw |= 0x4000;
	else
		m_psw &= (~0x4000);

	// LLV (logical overflow)
	// TODO

	// LMV (mathematical overflow)
	if ((result & U64(0xf00000000)) != U64(0xf00000000) &&
		(result & U64(0xf00000000)) != U64(0x000000000))
		m_psw |= 0x1000;
	else
		m_psw &= (~0x1000);

	// If it was a real operation, make sure the data goes where it should
	if (!justATest)
		*destinationReg = (UINT64)result & U64(0x0000000fffffffff);
}


UINT16* dsp16_device::registerFromYFieldUpper(const UINT8& Y)
{
	UINT16* destinationReg = nullptr;
	const UINT8 N = (Y & 0x0c) >> 2;
	switch (N)
	{
		case 0x00: destinationReg = &m_r0; break;
		case 0x01: destinationReg = &m_r1; break;
		case 0x02: destinationReg = &m_r2; break;
		case 0x03: destinationReg = &m_r3; break;
		default: break;
	}
	return destinationReg;
}


void dsp16_device::executeYFieldPost(const UINT8& Y)
{
	UINT16* opReg = registerFromYFieldUpper(Y);

	const UINT8 lower = Y & 0x03;
	switch (lower)
	{
		case 0x00: /* nop */ break;
		case 0x01: (*opReg)++; break;
		case 0x02: (*opReg)--; break;
		case 0x03: (*opReg) += m_j; break;  // TODO: J is signed
	}
}


void dsp16_device::executeZFieldPartOne(const UINT8& Z, UINT16* rN)
{
	const UINT8 lower = Z & 0x03;
	switch (lower)
	{
		case 0x00: /* nop */ break;
		case 0x01: (*rN)++; break;
		case 0x02: (*rN)--; break;
		case 0x03: (*rN) += m_j; break;  // TODO: J is signed
	}
}


void dsp16_device::executeZFieldPartTwo(const UINT8& Z, UINT16* rN)
{
	const UINT8 lower = Z & 0x03;
	switch (lower)
	{
		case 0x00: (*rN)++; break;
		case 0x01: /* nop */   break;
		case 0x02: (*rN) += 2; break;
		case 0x03: (*rN) += m_k; break;  // TODO: K is signed
	}
}


void dsp16_device::execute_one(const UINT16& op, UINT8& cycles, UINT8& pcAdvance)
{
	cycles = 1;
	pcAdvance = 0;

// NOTE: pages 3-5 through 3-19 are good english descriptions of what's up

	const UINT8 opcode = (op >> 11) & 0x1f;
	switch(opcode)
	{
		// Format 1: Multiply/ALU Read/Write Group
		case 0x06:
		{
			DSP_LINE("3-38")
			// F1, Y  :  (page 3-38)
			const UINT8 Y = (op & 0x000f);
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			executeYFieldPost(Y);
			cycles = 1;
			pcAdvance = 1;
			break;
		}
		case 0x04: case 0x1c:
		{
			DSP_LINE("3-40")
			// F1 Y=a0[1] | F1 Y=a1[1]  :  (page 3-40)
			const UINT8 Y = (op & 0x000f);
			//const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			UINT16* destinationReg = registerFromYFieldUpper(Y);
			// (page 3-18)
			UINT16 aRegValue = 0x0000;
			if (op & 0xc000)
			{
				aRegValue = (m_a0 & U64(0x0ffff0000)) >> 16;
			}
			else
			{
				aRegValue = (m_a1 & U64(0x0ffff0000)) >> 16;
			}
			data_write(*destinationReg, aRegValue);
			executeYFieldPost(Y);
			executeF1Field(F1, D, S);
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x16:
		{
			DSP_LINE("3-42")
			// F1, x = Y  :  (page 3-42)
			const UINT8 Y = (op & 0x000f);
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			UINT16* sourceReg = registerFromYFieldUpper(Y);
			writeRegister(&m_x, data_read(*sourceReg));
			executeYFieldPost(Y);
			cycles = 1;
			pcAdvance = 1;
			break;
		}
		case 0x17:
		{
			DSP_LINE("3-44")
			// F1, y[l] = Y  :  (page 3-44)
			const UINT8 Y = (op & 0x000f);
			const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			UINT16* sourceReg = registerFromYFieldUpper(Y);
			UINT16 sourceValue = data_read(*sourceReg);
			switch (X)
			{
				case 0x00: writeRegister(addressYL(), sourceValue); break;
				case 0x01: writeRegister(&m_y, sourceValue); break;
				default: break;
			}
			executeYFieldPost(Y);
			cycles = 1;
			pcAdvance = 1;
			break;
		}
		case 0x1f:
		{
			DSP_LINE("3-46")
			// F1, y = Y, x = *pt++[i]  :  (page 3-46)
			const UINT8 Y = (op & 0x000f);
			const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			UINT16* sourceRegR = registerFromYFieldUpper(Y);
			writeRegister(&m_y, data_read(*sourceRegR));
			executeYFieldPost(Y);
			writeRegister(&m_x, data_read(m_pt));
			switch (X)
			{
				case 0x00: m_pt++;      break;
				case 0x01: m_pt += m_i; break;
			}
			cycles = 2;     // TODO: 1 if cached
			pcAdvance = 1;
			break;
		}
		case 0x19: case 0x1b:
		{
			DSP_LINE("3-48")
			// F1, y = a0|1, x = *pt++[i]  :  (page 3-48)
			const UINT8 Y = (op & 0x000f);
			const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			bool useA1 = (opcode == 0x1b);
			if (Y != 0x00) printf("Unknown opcode @ PC=0x%04x", m_pc);
			m_y = (useA1) ? (m_a1 & 0xffffffff) : (m_a0 & 0xffffffff);      // TODO: What happens to Ax when it goes 32 bit (pc=3f & pc=47)?
			executeF1Field(F1, D, S);
			writeRegister(&m_x, data_read(m_pt));                           // TODO: EXM Pin & internal/external ROM?  Research.
			switch (X)
			{
				case 0x00: m_pt++;      break;
				case 0x01: m_pt += m_i; break;
			}
			cycles = 2;     // TODO: 1 if cached
			pcAdvance = 1;
			break;
		}
		case 0x14:
		{
			DSP_LINE("3-53")
			// F1, Y = y[l]  :  (page 3-53)
			const UINT8 Y = (op & 0x000f);
			const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			UINT16* destinationReg = registerFromYFieldUpper(Y);
			UINT16 yRegValue = 0x0000;
			switch (X)
			{
				case 0x00: yRegValue = (m_y & 0x0000ffff); break;
				case 0x01: yRegValue = (m_y & 0xffff0000) >> 16; break;
				default: break;
			}
			data_write(*destinationReg, yRegValue);
			executeYFieldPost(Y);
			cycles = 2;
			pcAdvance = 1;
			break;
		}

		// Format 1a: Multiply/ALU Read/Write Group (TODO: Figure out major typo in docs on p3-51)
		case 0x07:
		{
			DSP_LINE("3-50")
			// F1, At[1] = Y  :  (page 3-50)
			// TODO: What does the X field do here, exactly?
			const UINT8 Y = (op & 0x000f);
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 aT = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, !aT, S);
			UINT64* destinationReg = nullptr;
			switch(aT)
			{
				case 0: destinationReg = &m_a1; break;
				case 1: destinationReg = &m_a0; break;
				default: break;
			}
			UINT16 sourceAddress = *(registerFromYFieldUpper(Y));
			INT64 sourceValueSigned = (INT16)data_read(sourceAddress);
			*destinationReg = sourceValueSigned & U64(0xffffffffff);
			executeYFieldPost(Y);
			cycles = 1;
			pcAdvance = 1;
			break;
		}

		// Format 2: Multiply/ALU Read/Write Group
		case 0x15:
		{
			DSP_LINE("3-54")
			// F1, Z : y[l]  :  (page 3-54)
			const UINT8 Z = (op & 0x000f);
			const UINT8 X = (op & 0x0010) >> 4;
			const UINT8 S = (op & 0x0200) >> 9;
			const UINT8 D = (op & 0x0400) >> 10;
			const UINT8 F1 = (op & 0x01e0) >> 5;
			executeF1Field(F1, D, S);
			UINT16 temp = 0x0000;
			UINT16* rN = registerFromYFieldUpper(Z);
			switch (X)
			{
				case 0x00:
					temp = m_y & 0x0000ffff;
					m_y &= 0xffff0000;
					m_y |= data_read(*rN);
					executeZFieldPartOne(Z, rN);
					data_write(*rN, temp);
					executeZFieldPartTwo(Z, rN);
					break;
				case 0x01:
					temp = (m_y & 0xffff0000) >> 16;
					m_y &= 0x0000ffff;
					m_y |= (data_read(*rN) << 16);
					executeZFieldPartOne(Z, rN);
					data_write(*rN, temp);
					executeZFieldPartTwo(Z, rN);
					break;
			}
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x1d:
		{
			DSP_LINE("?")
			// F1, Z : y, x=*pt++[i]
			//const UINT8 Z = (op & 0x000f);
			//const UINT8 X = (op & 0x0010) >> 4;
			//const UINT8 S = (op & 0x0200) >> 9;
			//const UINT8 D = (op & 0x0400) >> 10;
			//const UINT8 F1 = (op & 0x01e0) >> 5;
			break;
		}

		// Format 2a: Multiply/ALU Read/Write Group
		case 0x05:
		{
			DSP_LINE("?")
			// F1, Z : aT[1]
			//const UINT8 Z = (op & 0x000f);
			//const UINT8 X = (op & 0x0010) >> 4;
			//const UINT8 S = (op & 0x0200) >> 9;
			//const UINT8 aT = (op & 0x0400) >> 10;
			//const UINT8 F1 = (op & 0x01e0) >> 5;
			break;
		}

		// Format 3: Special Functions
		case 0x12:
		case 0x13:
		{
			DSP_LINE("3-36")
			// if|ifc CON F2  (page 3-36)
			const UINT8 CON = (op & 0x001f);
			//const UINT8 S = (op & 0x0200) >> 9;
			//const UINT8 D = (op & 0x0400) >> 10;
			//const UINT8 F2 = (op & 0x01e0) >> 5;
			bool conditionFulfilled = conditionTest(CON);
			if (conditionFulfilled)
			{
				printf("Fulfilled condition not yet implemented @ PC=0x%04x\n", m_pc);
			}
			cycles = 1;
			pcAdvance = 1;
			break;
		}

		// Format 4: Branch Direct Group
		case 0x00: case 0x01:
		{
			DSP_LINE("3-20")
			// goto JA  :  (page 3-20) (DONE)
			const UINT16 JA = (op & 0x0fff) | (m_pc & 0xf000);
			m_pc = JA;
			cycles = 2;
			pcAdvance = 0;
			break;
		}

		case 0x10: case 0x11:
		{
			DSP_LINE("3-23")
			// call JA  :  (page 3-23)
			const UINT16 JA = (op & 0x0fff) | (m_pc & 0xf000);
			m_pr = m_pc + 1;
			m_pc = JA;
			cycles = 2;
			pcAdvance = 0;
			break;
		}

		// Format 5: Branch Indirect Group
		case 0x18:
		{
			DSP_LINE("3-21")
			// goto B  :  (page 3-21)
			const UINT8 B = (op & 0x0700) >> 8;
			switch (B)
			{
				case 0x00: m_pc = m_pr; break;
				case 0x01: printf("UNIMPLEMENTED branch instruction @ PC 0x%04x\n", m_pc); break;
				case 0x02: printf("UNIMPLEMENTED branch instruction @ PC 0x%04x\n", m_pc); break;
				case 0x03: printf("UNIMPLEMENTED branch instruction @ PC 0x%04x\n", m_pc); break;
				default: logerror("DSP16: Invalid branch indirect instruction executed at PC=0x%04x\n.", m_pc); break;
			}
			cycles = 2;
			pcAdvance = 0;
			break;
		}

		// Format 6: Contitional Branch Qualifier/Software Interrupt (icall)
		case 0x1a:
		{
			DSP_LINE("3-22")
			// if CON [goto/call/return]  :  (page 3-22)
			const UINT8 CON = (op & 0x001f);
			bool conditionFulfilled = conditionTest(CON);
			cycles = 3;                 // TODO: This may need to interact with the next opcode to make sure it doesn't exceed 3?
			pcAdvance = 1;
			if (!conditionFulfilled)
			{
				pcAdvance = 2;
			}
			break;
		}

		// Format 7: Data Move Group
		case 0x09: case 0x0b:
		{
			DSP_LINE("3-29")
			// R = aS  :  (page 3-29)
			// TODO: Fix register pdxX (pc=338)
			const UINT8 R = (op & 0x03f0) >> 4;
			const UINT8 S = (op & 0x1000) >> 12;
			void* destinationReg = registerFromRTable(R);
			UINT64* sourceReg = (S) ? &m_a1 : &m_a0;
			UINT16 sourceValue = (*sourceReg & U64(0x0ffff0000)) >> 16;
			writeRegister(destinationReg, sourceValue);
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x08:
		{
			DSP_LINE("3-30")
			// aT = R  :  (page 3-30)
			const UINT8 R  = (op & 0x03f0) >> 4;
			const UINT8 aT = (op & 0x0400) >> 10;
			UINT64* destinationReg = nullptr;
			switch(aT)
			{
				case 0: destinationReg = &m_a1; break;
				case 1: destinationReg = &m_a0; break;
				default: break;
			}
			void* sourceReg = registerFromRTable(R);
			*destinationReg &= U64(0x00000ffff);
			*destinationReg |= (*(UINT16*)sourceReg) << 16;     // TODO: Fix for all registers
			if (*(UINT16*)sourceReg & 0x8000)
				*destinationReg |= U64(0xf00000000);
			// TODO: Special function encoding
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x0f:
		{
			DSP_LINE("3-32")
			// R = Y  :  (page 3-32)
			const UINT8 Y = (op & 0x000f);
			const UINT8 R = (op & 0x03f0) >> 4;
			UINT16* sourceReg = registerFromYFieldUpper(Y);
			void* destinationReg = registerFromRTable(R);
			writeRegister(destinationReg, data_read(*sourceReg));
			executeYFieldPost(Y);
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x0c:
		{
			DSP_LINE("3-33")
			// Y = R  :  (page 3-33)
			// TODO: Zero & Sign extend i, c0, c1, c2, and auc
			const UINT8 Y = (op & 0x000f);
			const UINT8 R = (op & 0x03f0) >> 4;
			UINT16* destinationReg = registerFromYFieldUpper(Y);
			UINT16* sourceReg = (UINT16*)registerFromRTable(R);     // TODO: This won't work for certain registers!
			data_write(*destinationReg, *sourceReg);                //       Fix in data_write() maybe?
			executeYFieldPost(Y);
			cycles = 2;
			pcAdvance = 1;
			break;
		}
		case 0x0d:
		{
			DSP_LINE("?")
			// Z : R
			//const UINT8 Z = (op & 0x000f);
			//const UINT8 R = (op & 0x03f0) >> 4;
			break;
		}

		// Format 8: Data Move (immediate operand - 2 words)
		case 0x0a:
		{
			DSP_LINE("3-28")
			// R = N  :  (page 3-28) (DONE)
			// NOTE: The docs speak of register sources & sign extension, but this is a register
			// destination, so, typo?  If so, what does one do with the overflow bits?
			const UINT8 R = (op & 0x03f0) >> 4;
			const UINT16 iVal = opcode_read(1);
			void* destinationReg = registerFromRTable(R);
			writeRegister(destinationReg, iVal);
			cycles = 2;
			pcAdvance = 2;
			break;
		}

		// Format 9: Short Immediate Group
		case 0x02: case 0x03:
		{
			DSP_LINE("3-27")
			// R = M  :  (page 3-27)
			// TODO: Figure out notes about the DSP16A vs the DSP16.  9 bit is very DSP16...
			const UINT16 M = (op & 0x01ff);
			const UINT8  R = (op & 0x0e00) >> 9;
			void* destinationReg = registerFromRImmediateField(R);
			// Sign extend if the destination is j or k
			UINT16 mValue = M;
			if (destinationReg == &m_j || destinationReg == &m_k)
			{
				if (mValue & 0x0100) mValue |= 0xfe00;
			}
			writeRegister(destinationReg, mValue);
			cycles = 1;
			pcAdvance = 1;
			break;
		}

		// Format 10: do - redo
		case 0x0e:
		{
			DSP_LINE("3-25/3-26")
			// do|redo K  :  (pages 3-25 & 3-26)
			// TODO: The timings are intricate to say the least...
			const UINT8 K = (op & 0x007f);
			const UINT8 NI = (op & 0x0780) >> 7;
			if (NI != 0)
			{
				// Do
				m_cacheStart = m_pc + 1;
				m_cacheEnd   = m_pc + 1 + NI;
				m_cacheIterations = K-1;    // -1 because we check the counter @ the end
				cycles = 1;
				pcAdvance = 1;
			}
			else
			{
				// Redo
				m_cacheIterations = K-1;    // -1 because we check the counter @ the end
				m_cacheRedoNextPC = m_pc + 1;
				m_pc = m_cacheStart;
				cycles = 2;
				pcAdvance = 0;
			}
			break;
		}

		// RESERVED
		case 0x1e:
		{
			DSP_LINE("XXX")
			break;
		}

		// UNKNOWN
		default:
		{
			DSP_LINE("XXX")
			break;
		}
	}

	// Handle end-of-cache conditions for do|redos
	if (m_cacheIterations == 0 && m_cacheRedoNextPC != CACHE_INVALID)
	{
		// You've reached the end of a cache loop after a redo opcode.
		m_pc = m_cacheRedoNextPC;
		m_cacheRedoNextPC = CACHE_INVALID;
		pcAdvance = 0;
	}
	if (m_cacheIterations > 0 && (m_pc+pcAdvance == m_cacheEnd))
	{
		// A regular iteration on a cached loop.
		m_cacheIterations--;
		m_pc = m_cacheStart;
		pcAdvance = 0;
	}
}
