// license:BSD-3-Clause
// copyright-holders:Philip Bennett
/***************************************************************************

    dspp.c

    Core implementation for the portable 3DO (M2) DSPP emulator.
    DSPP = Don's Super Performing Processor

***************************************************************************/

#include "emu.h"
#include "dspp.h"
#include "dsppfe.h"
#include "dsppdasm.h"

#include "debugger.h"


//**************************************************************************
//  CONSTANTS
//**************************************************************************

enum
{
	DSPP_PC = 1,
	DSPP_ACC,
	DSPP_FLAGS,
	DSPP_CLOCK,
};


// DRC
#define SINGLE_INSTRUCTION_MODE         (0)


#define CACHE_SIZE  (4 * 1024 * 1024)

/* compilation boundaries -- how far back/forward does the analysis extend? */
#define COMPILE_BACKWARDS_BYTES         128
#define COMPILE_FORWARDS_BYTES          512
#define COMPILE_MAX_INSTRUCTIONS        ((COMPILE_BACKWARDS_BYTES/4) + (COMPILE_FORWARDS_BYTES/4))
#define COMPILE_MAX_SEQUENCE            64

/* exit codes */
#define EXECUTE_OUT_OF_CYCLES           0
#define EXECUTE_MISSING_CODE            1
#define EXECUTE_UNMAPPED_CODE           2
#define EXECUTE_RESET_CACHE             3

//**************************************************************************
//  INTERNAL MEMORY MAPS
//**************************************************************************

void dspp_device::code_map(address_map &map)
{
	map(0x000, 0x3ff).ram();
}

void dspp_device::data_map(address_map &map)
{
	map(0x000, 0x2df).ram();
	map(0x2f0, 0x2f1).r(FUNC(dspp_device::input_r));
	map(0x2e0, 0x2e7).w(FUNC(dspp_device::output_w));
	map(0x300, 0x37f).rw(FUNC(dspp_device::fifo_osc_r), FUNC(dspp_device::fifo_osc_w));
	map(0x3bc, 0x3bc).nopr(); // ?
	map(0x3d6, 0x3d6).w(FUNC(dspp_device::input_control_w));
	map(0x3d7, 0x3d7).w(FUNC(dspp_device::output_control_w));
	map(0x3de, 0x3de).r(FUNC(dspp_device::input_status_r));
	map(0x3df, 0x3df).r(FUNC(dspp_device::output_status_r));
	map(0x3e6, 0x3e6).w(FUNC(dspp_device::cpu_int_w));
	map(0x3ee, 0x3ee).rw(FUNC(dspp_device::pc_r), FUNC(dspp_device::pc_w));
	map(0x3f6, 0x3f6).rw(FUNC(dspp_device::audlock_r), FUNC(dspp_device::audlock_w));
	map(0x3f7, 0x3f7).rw(FUNC(dspp_device::clock_r), FUNC(dspp_device::clock_w));
	map(0x3ff, 0x3ff).r(FUNC(dspp_device::noise_r));
}



//**************************************************************************
//  DEVICE INTERFACE
//**************************************************************************

DEFINE_DEVICE_TYPE(DSPP, dspp_device, "dspp", "3DO DSPP")

//-------------------------------------------------
//  dspp_device - constructor
//-------------------------------------------------

dspp_device::dspp_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: dspp_device(mconfig, DSPP, tag, owner, clock, address_map_constructor(FUNC(dspp_device::code_map), this),
		address_map_constructor(FUNC(dspp_device::data_map), this))
{
}

dspp_device::dspp_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, address_map_constructor code_map_ctor, address_map_constructor data_map_ctor)
	: cpu_device(mconfig, type, tag, owner, clock),
		m_int_handler(*this),
		m_dma_read_handler(*this),
		m_dma_write_handler(*this),
		m_code_config("code", ENDIANNESS_BIG, 16, 10, -1, code_map_ctor),
		m_data_config("data", ENDIANNESS_BIG, 16, 10, -1, data_map_ctor),
		m_output_fifo_start(0),
		m_output_fifo_count(0),
		m_dspx_reset(0),
		m_dspx_int_enable(0),
		m_dspx_channel_enable(0),
		m_dspx_channel_complete(0),
		m_dspx_channel_direction(0),
		m_dspx_channel_8bit(0),
		m_dspx_channel_sqxd(0),
		m_dspx_shadow_current_addr(0),
		m_dspx_shadow_current_count(0),
		m_dspx_shadow_next_addr(0),
		m_dspx_shadow_next_count(0),
		m_dspx_dmanext_int(0),
		m_dspx_dmanext_enable(0),
		m_dspx_consumed_int(0),
		m_dspx_consumed_enable(0),
		m_dspx_underover_int(0),
		m_dspx_underover_enable(0),
		m_dspx_audio_time(0),
		m_dspx_audio_duration(0),
		m_cache(CACHE_SIZE),
		m_drcuml(nullptr),
		m_drcfe(nullptr),
		m_drcoptions(0)
{
#if 0
	memset(m_core->m_stack, 0, sizeof(m_core->m_stack));
	memset(m_core->m_rbase, 0, sizeof(m_core->m_rbase));
	memset(m_outputs, 0, sizeof(m_outputs));
	memset(m_output_fifo, 0, sizeof(m_output_fifo));
	memset(m_fifo_dma, 0, sizeof(m_fifo_dma));
#endif
}


//-------------------------------------------------
//  device_start - start up the device
//-------------------------------------------------

void dspp_device::device_start()
{
	m_isdrc = false;//allow_drc();

	m_core = (dspp_internal_state *)m_cache.alloc_near(sizeof(dspp_internal_state));
	memset(m_core, 0, sizeof(dspp_internal_state));

	uint32_t flags = 0;
	m_drcuml = std::make_unique<drcuml_state>(*this, m_cache, flags, 1, 16, 0);

	m_drcfe = std::make_unique<dspp_frontend>(this, COMPILE_BACKWARDS_BYTES, COMPILE_FORWARDS_BYTES, SINGLE_INSTRUCTION_MODE ? 1 : COMPILE_MAX_SEQUENCE);

	// Resolve our callbacks
	m_int_handler.resolve_safe();
	m_dma_read_handler.resolve_safe(0);
	m_dma_write_handler.resolve_safe();

	// Get our address spaces
	space(AS_PROGRAM).cache(m_code_cache);
	space(AS_PROGRAM).specific(m_code);
	space(AS_DATA).specific(m_data);

	// Register our state for the debugger
	state_add(DSPP_PC,         "PC",        m_core->m_pc);
	state_add(DSPP_ACC,        "ACC",       m_core->m_acc);
	state_add(STATE_GENPC,     "GENPC",     m_core->m_pc).noshow();
#if 0
	state_add(STATE_GENFLAGS,  "GENFLAGS",  m_core->m_flags).callimport().callexport().formatstr("%6s").noshow();
	state_add(STATE_GENPCBASE, "GENPCBASE", m_ppc).noshow();

	state_add(DSPP_PS,         "PS",        m_core->m_flagsio).callimport().callexport();
	for (int regnum = 0; regnum < 32; regnum++)
		state_add(DSPP_R0 + regnum, tempstr.format("R%d", regnum), m_src2val[REGBASE + regnum]);
#endif

	// Register our state for saving
	save_item(NAME(m_core->m_pc));
	save_item(NAME(m_core->m_stack));
	save_item(NAME(m_core->m_stack_ptr));
	save_item(NAME(m_core->m_rbase));
	save_item(NAME(m_core->m_acc));
	save_item(NAME(m_core->m_tclock));

	save_item(NAME(m_core->m_flag_carry));
	save_item(NAME(m_core->m_flag_zero));
	save_item(NAME(m_core->m_flag_neg));
	save_item(NAME(m_core->m_flag_over));
	save_item(NAME(m_core->m_flag_exact));
	save_item(NAME(m_core->m_flag_audlock));
	save_item(NAME(m_core->m_flag_sleep));

	save_item(NAME(m_outputs));
	save_item(NAME(m_output_fifo_start));
	save_item(NAME(m_output_fifo_count));

	save_item(STRUCT_MEMBER(m_fifo_dma, m_current_addr));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_current_count));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_next_addr));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_next_count));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_prev_value));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_prev_current));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_go_forever));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_next_valid));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_reserved));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_fifo));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_dma_ptr));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_dspi_ptr));
	save_item(STRUCT_MEMBER(m_fifo_dma, m_depth));

	save_item(NAME(m_last_frame_clock));
	save_item(NAME(m_last_osc_count));
	save_item(NAME(m_osc_phase));
	save_item(NAME(m_osc_freq));

	save_item(NAME(m_core->m_partial_int));

	save_item(NAME(m_core->m_dspx_control));
	save_item(NAME(m_dspx_reset));
	save_item(NAME(m_dspx_int_enable));
	save_item(NAME(m_dspx_channel_enable));
	save_item(NAME(m_dspx_channel_complete));
	save_item(NAME(m_dspx_channel_direction));
	save_item(NAME(m_dspx_channel_8bit));
	save_item(NAME(m_dspx_channel_sqxd));
	save_item(NAME(m_dspx_shadow_current_addr));
	save_item(NAME(m_dspx_shadow_current_count));
	save_item(NAME(m_dspx_shadow_next_addr));
	save_item(NAME(m_dspx_shadow_next_count));
	save_item(NAME(m_dspx_dmanext_int));
	save_item(NAME(m_dspx_dmanext_enable));
	save_item(NAME(m_dspx_consumed_int));
	save_item(NAME(m_dspx_consumed_enable));
	save_item(NAME(m_dspx_underover_int));
	save_item(NAME(m_dspx_underover_enable));
	save_item(NAME(m_dspx_audio_time));
	save_item(NAME(m_dspx_audio_duration));

	// Set our instruction counter
	set_icountptr(m_core->m_icount);

	m_cache_dirty = true;
}


//-------------------------------------------------
//  device_reset - reset the device
//-------------------------------------------------

void dspp_device::device_reset()
{
	// initialize the state
	m_core->m_pc = 0;
	m_core->m_stack_ptr = 0;
	m_output_fifo_start = 0;
	m_output_fifo_count = 0;

	m_core->m_flag_audlock = 0;
	m_core->m_flag_sleep = 0;
	m_core->m_stack_ptr = 0;
	m_core->m_writeback = ~1; // TODO
	set_rbase(0, 0);

	// TODO: CLEAR DMA CHANNELS

	// Clear interrupts
	m_core->m_partial_int = 0;
	update_host_interrupt();

	m_cache_dirty = true;
}


//-------------------------------------------------
//  memory_space_config - return the configuration
//  of the CPU's address spaces
//-------------------------------------------------

device_memory_interface::space_config_vector dspp_device::memory_space_config() const
{
	return space_config_vector {
		std::make_pair(AS_PROGRAM, &m_code_config),
		std::make_pair(AS_DATA,    &m_data_config)
	};
}


//-------------------------------------------------
//  state_import - import state into the device,
//  after it has been set
//-------------------------------------------------

void dspp_device::state_import(const device_state_entry &entry)
{

}


//-------------------------------------------------
//  state_export - export state from the device,
//  to a known location where it can be read
//-------------------------------------------------

void dspp_device::state_export(const device_state_entry &entry)
{

}


//-------------------------------------------------
//  state_string_export - export state as a string
//  for the debugger
//-------------------------------------------------

void dspp_device::state_string_export(const device_state_entry &entry, std::string &str) const
{
	switch (entry.index())
	{
		case STATE_GENFLAGS:
			str = string_format("%c%c.%c%c%c%c%c",
							m_core->m_flag_audlock  ? 'A' : '.',
							m_core->m_flag_sleep    ? 'S' : '.',
							m_core->m_flag_carry    ? 'C' : '.',
							m_core->m_flag_zero     ? 'Z' : '.',
							m_core->m_flag_neg      ? 'N' : '.',
							m_core->m_flag_over     ? 'V' : '.',
							m_core->m_flag_exact    ? 'E' : '.');
			break;
	}
}

std::unique_ptr<util::disasm_interface> dspp_device::create_disassembler()
{
	return std::make_unique<dspp_disassembler>();
}



//**************************************************************************
//  INLINE HELPERS
//**************************************************************************

//-------------------------------------------------
//  update_pc -
//-------------------------------------------------

inline void dspp_device::update_pc()
{
	++m_core->m_pc;
}

inline void dspp_device::update_ticks()
{
	--m_core->m_tclock;
	--m_core->m_icount;
}


//-------------------------------------------------
//  readop - Read an opcode at the given address
//-------------------------------------------------

uint16_t dspp_device::read_op(offs_t pc)
{
	return m_code_cache.read_word(pc);
}


//-------------------------------------------------
//  read_data - Read a word from the data space
//-------------------------------------------------

inline uint16_t dspp_device::read_data(offs_t addr)
{
	return m_data.read_word(addr);
}


//-------------------------------------------------
//  write_data - Write a word to the data space
//-------------------------------------------------

inline void dspp_device::write_data(offs_t addr, uint16_t data)
{
	m_data.write_word(addr, data);
}


//-------------------------------------------------
//  get_interrupt_state -
//-------------------------------------------------

uint32_t dspp_device::get_interrupt_state()
{
	uint32_t host_int = m_core->m_partial_int;

	if (m_dspx_dmanext_int & m_dspx_dmanext_enable)
		host_int |= DSPX_F_INT_DMANEXT;

	if (m_dspx_consumed_int & m_dspx_consumed_enable)
		host_int |= DSPX_F_INT_CONSUMED;

	return host_int;
}


//-------------------------------------------------
//  update_host_interrupt -
//-------------------------------------------------

void dspp_device::update_host_interrupt()
{
	// TODO: Underflow/overflow interrupts
	m_int_handler(get_interrupt_state() & m_dspx_int_enable ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  parse_operand - Parse instruction operands
//-------------------------------------------------

void dspp_device::parse_operands(uint32_t numops)
{
	uint32_t addr, val = 0xBAD;
	uint32_t opidx = 0;
	uint32_t operand = 0;
	uint32_t numregs = 0;

	for (uint32_t i = 0; i < MAX_OPERANDS; ++i)
	{
		// Reset operands
		m_core->m_operands[i].value = -1;
		m_core->m_operands[i].addr = -1;
	}

	// Reset global op index
	m_core->m_opidx = 0;

	while (opidx < numops)
	{
		operand = read_op(m_core->m_pc);
		update_pc();
		update_ticks();

		if (operand & 0x8000)
		{
			// Immediate value
			if ((operand & 0xc000) == 0xc000)
			{
				val = operand & 0x1fff;

				if (operand & 0x2000)
				{
					// Left justify
					val = val << 3;
				}
				else
				{
					// Sign extend if right justified
					if (val & 0x1000)
						val |= 0xe000;
				}
				m_core->m_operands[opidx++].value = val;
			}
			else if((operand & 0xe000) == 0x8000)
			{
				// Address operand
				addr = operand & 0x03ff;

				if (operand & 0x0400)
				{
					 // Indirect
					addr = read_data(addr);
				}
				m_core->m_operands[opidx].addr = addr;

				if (operand & 0x0800)
				{
					// Write Back
					m_core->m_writeback = addr;
				}
				opidx++;
			}
			else if ((operand & 0xe000) == 0xa000)
			{
				// 1 or 2 register operand
				numregs = (operand & 0x0400) ? 2 : 1;
			}
		}
		else
		{
			numregs = 3;
		}

		if (numregs > 0)
		{
			uint32_t shifter, regdi;

			// Shift successive register operands from a single operand word
			for (uint32_t i = 0; i < numregs; ++i)
			{
				shifter = ((numregs - i) - 1) * 5;
				regdi = (operand >> shifter) & 0x1f;
				addr = translate_reg(regdi & 0xf);

				if (regdi & 0x0010)
				{
					 // Indirect?
					addr = read_data(addr);
				}

				if (numregs == 2)
				{
					if ((i == 0) && (operand & 0x1000))
						m_core->m_writeback = addr;
					else if ((i == 1) && (operand & 0x0800))
						m_core->m_writeback = addr;
				}
				else if (numregs == 1)
				{
					if (operand & 0x800)
						m_core->m_writeback = addr;
				}

				m_core->m_operands[opidx++].addr = addr;
			}
			numregs = 0;
		}
	}
}


//-------------------------------------------------
//  read_next_operand - Return the value encoded by
//  the next operand
//-------------------------------------------------

uint16_t dspp_device::read_next_operand()
{
	int32_t value = m_core->m_operands[m_core->m_opidx].value;
	//if (m_core->m_op == 0x46a0) printf("Value is %08x\n", value);

	if (value < 0)
	{
		value = read_data(m_core->m_operands[m_core->m_opidx].addr);
		//if (m_core->m_op == 0x46a0) printf("New value is %08x from %08x\n", value, m_core->m_operands[m_core->m_opidx].addr);
	}

	// Next operand
	++m_core->m_opidx;

	return value;
}


//-------------------------------------------------
//  write_next_operand - Write to the address
//  encoded by the next operand
//-------------------------------------------------

void dspp_device::write_next_operand(uint16_t value)
{
	int32_t addr = m_core->m_operands[m_core->m_opidx].addr;

	assert(addr != -1);

	write_data(addr, value);

	// Advance to the next operand
	++m_core->m_opidx;
}


//-------------------------------------------------
//  push_pc - Push program counter onto the stack
//-------------------------------------------------

inline void dspp_device::push_pc()
{
	if (m_core->m_stack_ptr < PC_STACK_DEPTH)
		m_core->m_stack[m_core->m_stack_ptr++] = m_core->m_pc;
	else
		fatalerror("DSPP stack overflow!");
}


//-------------------------------------------------
//  pop_pc - Pop program counter from the stack
//-------------------------------------------------

inline uint16_t dspp_device::pop_pc()
{
	if (m_core->m_stack_ptr == 0)
		fatalerror("DSPP stack underflow!");

	return m_core->m_stack[--m_core->m_stack_ptr];
}


//-------------------------------------------------
//  set_rbase - Set register address base
//-------------------------------------------------

inline void dspp_device::set_rbase(uint32_t base, uint32_t addr)
{
	switch (base)
	{
		case 4:
			m_core->m_rbase[1] = addr + 4 - base;
			break;
		case 0:
			m_core->m_rbase[0] = addr;
			m_core->m_rbase[1] = addr + 4 - base;
			[[fallthrough]];
		// Intentional fall-through
		case 8:
			m_core->m_rbase[2] = addr + 8 - base;
			[[fallthrough]];
		case 12:
			m_core->m_rbase[3] = addr + 12 - base;
			break;
	}
}


//-------------------------------------------------
//  translate_reg - Translate register address
//-------------------------------------------------

inline uint16_t dspp_device::translate_reg(uint16_t reg)
{
	uint32_t base = (reg >> 2) & 3;
	return m_core->m_rbase[base] + reg - (reg & ~3);
}



//**************************************************************************
//  CORE EXECUTION
//**************************************************************************

//-------------------------------------------------
//  execute_min_cycles - return minimum number of
//  cycles it takes for one instruction to execute
//-------------------------------------------------

uint32_t dspp_device::execute_min_cycles() const noexcept
{
	return 1;
}


//-------------------------------------------------
//  execute_max_cycles - return maximum number of
//  cycles it takes for one instruction to execute
//-------------------------------------------------

uint32_t dspp_device::execute_max_cycles() const noexcept
{
	return 5; // TODO ?
}


//-------------------------------------------------
//  execute_run - core execution loop
//-------------------------------------------------

void dspp_device::execute_run()
{
	if (m_isdrc)
	{
		// Only run if enabled
		do
		{
			if (m_core->m_dspx_control & DSPX_CONTROL_GWILLING)
			{
				execute_run_drc();
			}
			else
			{
				update_ticks();
				update_fifo_dma();
			}
		} while (m_core->m_icount > 0);
		return;
	}

	bool check_debugger = ((device_t::machine().debug_flags & DEBUG_FLAG_ENABLED) != 0);

	do
	{
		update_ticks();
		update_fifo_dma();

		// Only run if enabled
		if (m_core->m_dspx_control & DSPX_CONTROL_GWILLING)
		{
			if (check_debugger)
				debugger_instruction_hook(m_core->m_pc);

			m_core->m_op = read_op(m_core->m_pc);
			//printf("%04x: %04x\n", (uint16_t)m_core->m_pc, (uint16_t)m_core->m_op);
			update_pc();

			// Decode and execute
			if (m_core->m_op & 0x8000)
				exec_control();
			else
				exec_arithmetic();
		}

	} while (m_core->m_icount > 0);
}



//**************************************************************************
//  OPCODE IMPLEMENTATIONS
//**************************************************************************

//-------------------------------------------------
//  exec_super_special - Execute a super special
//  control op
//-------------------------------------------------

inline void dspp_device::exec_super_special()
{
	uint32_t sel = (m_core->m_op >> 7) & 7;

	switch (sel)
	{
		case 1: // BAC
		{
			m_core->m_pc = m_core->m_acc >> 4;
			break;
		}
		case 4: // RTS
		{
			m_core->m_pc = pop_pc();
			break;
		}
		case 5: // OP_MASK
		{
			// TODO
			break;
		}

		case 7: // SLEEP
		{
			// TODO: How does sleep work?
			--m_core->m_pc;
			m_core->m_flag_sleep = 1;
			break;
		}

		case 0: // NOP
		case 2: // Unused
		case 3:
		case 6:
			break;
	}
}


//-------------------------------------------------
//  exec_special - Execute a special control op
//-------------------------------------------------

inline void dspp_device::exec_special()
{
	switch ((m_core->m_op >> 10) & 7)
	{
		case 0:
		{
			exec_super_special();
			break;
		}
		case 1: // JUMP
		{
			m_core->m_pc = m_core->m_op & 0x3ff;
			break;
		}
		case 2: // JSR
		{
			push_pc();
			m_core->m_pc = m_core->m_op & 0x3ff;
			break;
		}
		case 3: // BFM
		{
			break;
		}
		case 4: // MOVEREG
		{
			uint32_t regdi = m_core->m_op & 0x3f;
			uint32_t addr = translate_reg(regdi & 0xf);

			// Indirect
			if (regdi & 0x0010)
			{
				addr = read_data(addr);
			}

			parse_operands(1);
			write_data(addr, read_next_operand());
			break;
		}
		case 5: // RBASE
		{
			set_rbase((m_core->m_op & 3) << 2, m_core->m_op & 0x3fc);
			break;
		}
		case 6: // MOVED
		{
			parse_operands(1);
			write_data(m_core->m_op & 0x3ff, read_next_operand());
			break;
		}
		case 7: // MOVEI
		{
			parse_operands(1);
			uint32_t addr = read_data(m_core->m_op & 0x3ff);
			write_data(addr, read_next_operand());
			break;
		}

		default:
			break;
	}
}


//-------------------------------------------------
//  exec_branch - Execute a branch control op
//-------------------------------------------------

void dspp_device::exec_branch()
{
	uint32_t mode = (m_core->m_op >> 13) & 3;
	uint32_t select = (m_core->m_op >> 12) & 1;
	uint32_t mask = (m_core->m_op >> 10) & 3;

	bool flag0, flag1;

	if (select == 0)
	{
		flag0 = m_core->m_flag_neg;
		flag1 = m_core->m_flag_over;
	}
	else
	{
		flag0 = m_core->m_flag_carry;
		flag1 = m_core->m_flag_zero;
	}

	bool mask0 = (mask & 2) != 0;
	bool mask1 = (mask & 1) != 0;

	bool branch = (flag0 || !mask0) && (flag1 || !mask1);

	if (mode == 2)
		branch = !branch;

	//printf("Branch: %d %d %d %d %d\n", branch ? 1 : 0, flag0 ? 1 : 0, mask0 ? 1 : 0, flag1 ? 1 : 0, mask1 ? 1 : 0);

	if (branch)
		m_core->m_pc = m_core->m_op & 0x3ff;
}


//-------------------------------------------------
//  exec_complex_branch - Execute a complex branch
//  control op
//-------------------------------------------------

inline void dspp_device::exec_complex_branch()
{
	uint32_t type = (m_core->m_op >> 10) & 7;

	const bool c = m_core->m_flag_carry;
	const bool z = m_core->m_flag_zero;
	const bool n = m_core->m_flag_neg;
	const bool v = m_core->m_flag_over;
	const bool x = m_core->m_flag_exact;

	bool branch = false;

	switch (type)
	{
		case 0: // BLT
			branch = (n && !v) || (!n && v);
			break;
		case 1: // BLE
			branch = ((n && !v) || (!n && v)) || z;
			break;
		case 2: // BGE
			branch = ((n && v) || (!n && !v));
			break;
		case 3: // BGT
			branch = ((n && v) || (!n && !v)) && !z;
			break;
		case 4: // BHI
			branch = c && !z;
			break;
		case 5: // BLS
			branch = !c || z;
			break;
		case 6: // BXS
			branch = x;
			break;
		case 7: // BXC
			branch = !x;
			break;
	}

	if (branch)
		m_core->m_pc = m_core->m_op & 0x3ff;
}


//-------------------------------------------------
//  exec_control - Execute a control op
//-------------------------------------------------

inline void dspp_device::exec_control()
{
	uint32_t mode = (m_core->m_op >> 13) & 3;

	switch (mode)
	{
		// Special
		case 0:
		{
			exec_special();
			break;
		}

		// Branches
		case 1: case 2:
		{
			exec_branch();
			break;
		}

		// Complex branches
		case 3:
		{
			exec_complex_branch();
			break;
		}

		default:
			fatalerror("Invalid DSPP control instruction mode");
			break;
	}
}

//-------------------------------------------------
//  sign_extend8 - Sign extend 8-bits to 32-bits
//-------------------------------------------------

static inline int32_t sign_extend8(uint8_t val)
{
	return (int32_t)(int8_t)val;
}


//-------------------------------------------------
//  sign_extend16 - Sign extend 16-bits to 32-bits
//-------------------------------------------------

static inline int32_t sign_extend16(uint16_t val)
{
	return (int32_t)(int16_t)val;
}


//-------------------------------------------------
//  sign_extend20 - Sign extend 20-bits to 32-bits
//-------------------------------------------------

static inline int32_t sign_extend20(uint32_t val)
{
	if (val & 0x00080000)
		return (int32_t)(0xfff00000 | val);
	else
		return (int32_t)val;
}


//-------------------------------------------------
//  exec_arithmetic - Execute an arithmetic op
//-------------------------------------------------

inline void dspp_device::exec_arithmetic()
{
	// Decode the various fields
	uint32_t numops = (m_core->m_op >> 13) & 3;
	uint32_t muxa = (m_core->m_op >> 10) & 3;
	uint32_t muxb = (m_core->m_op >> 8) & 3;
	uint32_t alu_op = (m_core->m_op >> 4) & 0xf;
	uint32_t barrel_code = m_core->m_op & 0xf;

	int32_t mul_res = 0;
	uint32_t alu_res = 0;

	// Check for operand overflow
	if (numops == 0 && ((muxa == 1) || (muxa == 2) || (muxb == 1) || (muxb == 2)))
		numops = 4;

	// Implicit barrel shift
	if (barrel_code == 8)
		++numops;

	// Parse ops...
	parse_operands(numops);

	if (muxa == 3 || muxb == 3)
	{
		uint32_t mul_sel = (m_core->m_op >> 12) & 1;

		int32_t op1 = sign_extend16(read_next_operand());
		int32_t op2 = sign_extend16(mul_sel ? read_next_operand() : m_core->m_acc >> 4);

		mul_res = (op1 * op2) >> 11;
	}

	int32_t alu_a, alu_b;

	//if (m_core->m_op == 0x46a0)
		//printf("Arithmetic: numops:%d, muxa:%d, muxb:%d, alu_op:%d, barrel_code:%d\n", numops, muxa, muxb, alu_op, barrel_code);

	switch (muxa)
	{
		case 0:
		{
			alu_a = m_core->m_acc;
			break;
		}
		case 1: case 2:
		{
			alu_a = read_next_operand() << 4;
			//if (m_core->m_op == 0x46a0)
				//printf("Arithmetic: Next operand: %04x\n", alu_a >> 4);
			break;
		}
		case 3:
		{
			alu_a = mul_res;
			break;
		}
	}

	switch (muxb)
	{
		case 0:
		{
			alu_b = m_core->m_acc;
			break;
		}
		case 1: case 2:
		{
			alu_b = read_next_operand() << 4;
			break;
		}
		case 3:
		{
			alu_b = mul_res;
			break;
		}
	}

	// For carry detection apparently
	alu_a &= 0x00fffff;
	alu_b &= 0x00fffff;

	switch (alu_op)
	{
		case 0: // _TRA
		{
			alu_res = alu_a;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 1: // _NEG
		{
			alu_res = -alu_b;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 2: // _+
		{
			alu_res = alu_a + alu_b;
			m_core->m_flag_over = (((alu_a & 0x80000) == (alu_b & 0x80000) && (alu_a & 0x80000) != (alu_res & 0x80000)));
			m_core->m_flag_carry = (alu_res & 0x00100000) != 0;
			break;
		}
		case 3: // _+C
		{
			alu_res = alu_a + (m_core->m_flag_carry << 4);
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = (alu_res & 0x00100000) != 0;
			break;
		}
		case 4: // _-
		{
			alu_res = alu_a - alu_b;
			m_core->m_flag_over = ((alu_a & 0x80000) == (~alu_b & 0x80000) && (alu_a & 0x80000) != (alu_res & 0x80000));
			m_core->m_flag_carry = (alu_res & 0x00100000) != 0;
			break;
		}
		case 5: // _-B
		{
			alu_res = alu_a - (m_core->m_flag_carry << 4);
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = (alu_res & 0x00100000) != 0;
			break;
		}
		case 6: // _++
		{
			alu_res = alu_a + 1;
			m_core->m_flag_over = !(alu_a & 0x80000) && (alu_res & 0x80000);
			m_core->m_flag_carry = 0;
			break;
		}
		case 7: // _--
		{
			alu_res = alu_a - 1;
			m_core->m_flag_over = (alu_a & 0x80000) && !(alu_res & 0x80000);
			m_core->m_flag_carry = 0;
			break;
		}
		case 8: // _TRL
		{
			alu_res = alu_a;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 9: // _NOT
		{
			alu_res = ~alu_a;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 10: // _AND
		{
			alu_res = alu_a & alu_b;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 11: // _NAND
		{
			alu_res = ~(alu_a & alu_b);
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 12: // _OR
		{
			alu_res = alu_a | alu_b;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 13: // _NOR
		{
			alu_res = ~(alu_a | alu_b);
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 14: // _XOR
		{
			alu_res = alu_a ^ alu_b;
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
		case 15: // _XNOR
		{
			alu_res = ~(alu_a ^ alu_b);
			m_core->m_flag_over = 0;
			m_core->m_flag_carry = 0;
			break;
		}
	}

	m_core->m_flag_neg = (alu_res & 0x00080000) != 0;
	m_core->m_flag_zero = (alu_res & 0x000ffff0) == 0;
	m_core->m_flag_exact = (alu_res & 0x0000000f) == 0;

	// Barrel shift
	static const int32_t shifts[8] = { 0, 1, 2, 3, 4, 5, 8, 16 };

	if (barrel_code == 8)
		barrel_code = read_next_operand();

	if (barrel_code & 8)
	{
		// Right shift
		uint32_t shift = shifts[(~barrel_code + 1) & 7];

		if (alu_op < 8)
		{
			// Arithmetic
			m_core->m_acc = sign_extend20(alu_res) >> shift;
		}
		else
		{
			// Logical
			m_core->m_acc = (alu_res & 0xfffff) >> shift;
		}

	}
	else
	{
		// Left shift
		uint32_t shift = shifts[barrel_code];

		if (shift == 16)
		{
			// Clip and saturate
			if (m_core->m_flag_over)
				m_core->m_acc = m_core->m_flag_neg ? 0x7ffff : 0xfff80000;
			else
				m_core->m_acc = sign_extend20(alu_res);
		}
		else
		{
			m_core->m_acc = sign_extend20(alu_res) << shift;
		}
	}

	if (m_core->m_writeback >= 0)
	{
		write_data(m_core->m_writeback, m_core->m_acc >> 4);
		m_core->m_writeback = -1;
	}
	else if (m_core->m_opidx < numops)
	{
		write_next_operand(m_core->m_acc >> 4);
	}
}



//**************************************************************************
//  FIFO DMA
//**************************************************************************

//-------------------------------------------------
//  write_dma_to_fifo -
//-------------------------------------------------

void dspp_device::write_dma_to_fifo(int32_t channel, int16_t value)
{
	fifo_dma &dma = m_fifo_dma[channel];

	dma.m_fifo[dma.m_dma_ptr] = value;

	if (dma.m_depth < DMA_FIFO_DEPTH)
	{
		dma.m_dma_ptr = (dma.m_dma_ptr + 1) & DMA_FIFO_MASK;
		dma.m_depth += 1;
	}
	else
	{
		fatalerror("DMA TO FIFO OVERFLOW");
	}
}


//-------------------------------------------------
//  write_dspp_to_fifo -
//-------------------------------------------------

void dspp_device::write_dspp_to_fifo(int32_t channel, int16_t value)
{
	fifo_dma &dma = m_fifo_dma[channel];

	dma.m_fifo[dma.m_dspi_ptr] = value;

	if (dma.m_depth < DMA_FIFO_DEPTH)
	{
		dma.m_dspi_ptr = (dma.m_dspi_ptr + 1) & DMA_FIFO_MASK;
		dma.m_depth += 1;
	}
	else
	{
		fatalerror("DSPP TO FIFO OVERFLOW");
	}
}


//-------------------------------------------------
//  read_fifo_to_dspp -
//-------------------------------------------------

int16_t dspp_device::read_fifo_to_dspp(int32_t channel)
{
	int16_t data = 0;

	fifo_dma &dma = m_fifo_dma[channel];

	if (dma.m_depth > 0)
	{
		data = dma.m_fifo[dma.m_dspi_ptr];

		dma.m_dspi_ptr = (dma.m_dspi_ptr + 1) & DMA_FIFO_MASK;
		--dma.m_depth;

		if (dma.m_depth == 0)
		{
			// Set consumed interrupt
			if (m_dspx_channel_complete & (1 << channel))
			{
				m_dspx_consumed_int |= 1 << channel;
				update_host_interrupt();
			}
		}

		dma.m_prev_current = data;
	}
	else
	{
		// TODO: Is this right?
		m_dspx_underover_int |= 1 << channel;
		update_host_interrupt();
		data = dma.m_prev_current;
	}

	return data;
}


//-------------------------------------------------
//  read_fifo_to_dma -
//-------------------------------------------------

int16_t dspp_device::read_fifo_to_dma(int32_t channel)
{
	fifo_dma &dma = m_fifo_dma[channel];

	uint32_t data = dma.m_fifo[dma.m_dma_ptr];

	if (dma.m_depth > 0)
	{
		dma.m_dma_ptr = (dma.m_dma_ptr + 1) & DMA_FIFO_MASK;
		dma.m_depth -= 1;
	}
	else
	{
		//fatalerror("FIFO TO DMA UNDERFLOW");
	}
	return data;
}


//-------------------------------------------------
//  run_oscillator -
//-------------------------------------------------

void dspp_device::run_oscillator(int32_t channel)
{
	fifo_dma &dma = m_fifo_dma[channel];

	// Add phase increment
	m_osc_phase += m_osc_freq;

	// Extract two high bits to advance FIFO
	uint32_t count = (m_osc_phase >> 15) & 3;

	// Clip to positive phase
	m_osc_phase &= 0x7fff;

	// Advance FIFO if data present
	if (count > dma.m_depth)
		count = dma.m_depth;

	for (uint32_t i = 0; i < count; ++i)
		read_fifo_to_dspp(channel);

	// Return count to program for counting samples
	m_last_osc_count = count;
}


//-------------------------------------------------
//  advance_audio_timer -
//-------------------------------------------------

void dspp_device::advance_audio_timer()
{
	// Advance time on each frame count
	++m_dspx_audio_time;

	// Interrupt on transition from 0 to 0xFFFF
	if (--m_dspx_audio_duration == 0xffff)
	{
		m_core->m_partial_int |= DSPX_F_INT_TIMER;
		update_host_interrupt();
	}
}


//-------------------------------------------------
//  advance_audio_frame -
//-------------------------------------------------

void dspp_device::advance_audio_frame()
{
	m_last_frame_clock = m_clock;
	advance_audio_timer();

	if (m_core->m_flag_audlock)
	{
		device_reset();
	}
}


//-------------------------------------------------
//  process_next_dma -
//-------------------------------------------------

void dspp_device::process_next_dma(int32_t channel)
{
	fifo_dma &dma = m_fifo_dma[channel];

	if (dma.m_current_count <= 0)
	{
		uint32_t chmask = 1 << channel;

		if (dma.m_next_valid)
		{
			dma.m_current_addr = dma.m_next_addr;
			dma.m_current_count = dma.m_next_count;

			if (!dma.m_go_forever)
				dma.m_next_valid = 0;

			// Set completion bit
			m_dspx_channel_complete &= ~chmask;
			m_dspx_dmanext_int |= chmask;
			update_host_interrupt();
		}
		else
		{
			// Disable the channel so we don't keep servicing it
			m_dspx_channel_enable &= ~chmask;
		}
	}
}


//-------------------------------------------------
//  decode_sqxd - Decompress an SQXD coded sample
//-------------------------------------------------

int16_t dspp_device::decode_sqxd(int8_t data, int16_t prev)
{
	int16_t temp = sign_extend8(data & 0xfe);
	int32_t expanded = (temp * iabs(temp)) << 1;
	int16_t output;

	if (data & 1)
	{
		expanded = expanded >> 2;
		output = expanded + prev;
	}
	else
	{
		output = expanded;
	}

	return output;
}


//-------------------------------------------------
//  service_output_dma -
//-------------------------------------------------

void dspp_device::service_output_dma(int32_t channel)
{
	fifo_dma & dma = m_fifo_dma[channel];

	if (dma.m_current_count == 0)
		return;

	// Transfer a maximum of 4 samples per tick
	uint32_t count = dma.m_current_count;

	if (count > 4)
		count = 4;

	for (uint32_t i = 0; i < count; ++i)
	{
		uint16_t sample = read_fifo_to_dma(channel);

		m_dma_write_handler(dma.m_current_addr++, sample >> 8);
		m_dma_write_handler(dma.m_current_addr++, sample & 0xff);
	}

	dma.m_current_count -= count;

	process_next_dma(channel);
}


//-------------------------------------------------
//  service_input_dma -
//-------------------------------------------------

void dspp_device::service_input_dma(int32_t channel)
{
	fifo_dma &dma = m_fifo_dma[channel];

	if (dma.m_current_count == 0)
		return;

	// Transfer a maximum of 4 samples per tick
	uint32_t count = dma.m_current_count;

	if (count > 4)
		count = 4;

	// Determine sample format
	bool is8bit = (m_dspx_channel_8bit & (1 << channel)) != 0;
	bool isSQXD = (m_dspx_channel_sqxd & (1 << channel)) != 0;

	if (is8bit)
	{
		// Fetch data from memory, {decompress}, and write to FIFO
		for (uint32_t i = 0; i < count; ++i)
		{
			int16_t sample;
			int8_t curbyte = m_dma_read_handler(dma.m_current_addr++);

			if (isSQXD)
			{
				printf("SQXD NOT TESTED!");

				sample = decode_sqxd(curbyte, dma.m_prev_value);
				dma.m_prev_value = sample;
			}
			else
			{
				sample = curbyte << 8;
			}

			write_dma_to_fifo(channel, sample);
		}
	}
	else
	{
		for (uint32_t i = 0; i < count; ++i)
		{
			int16_t sample;

			sample = m_dma_read_handler(dma.m_current_addr++) << 8;
			sample |= m_dma_read_handler(dma.m_current_addr++);

			write_dma_to_fifo(channel, sample);
		}
	}

	dma.m_current_count -= count;

	process_next_dma(channel);
}


//-------------------------------------------------
//  update_fifo_dma -
//-------------------------------------------------

void dspp_device::update_fifo_dma()
{
	uint32_t mask = m_dspx_channel_enable & ~m_dspx_channel_complete;

	while (mask != 0)
	{
		uint32_t channel = 31 - count_leading_zeros_32(mask);

		const fifo_dma & dma = m_fifo_dma[channel];

		if (m_dspx_channel_direction & (1 << channel))
		{
			if (dma.m_depth >= 4)
			{
				service_output_dma(channel);
				break;
			}
		}
		else
		{
			if (dma.m_depth <= 4)
			{
				service_input_dma(channel);
				break;
			}
		}

		mask &= (1 << channel) - 1;
	}
}


//-------------------------------------------------
//  reset_channel
//-------------------------------------------------

void dspp_device::reset_channel(int32_t channel)
{
	fifo_dma &dma = m_fifo_dma[channel];

	m_dspx_channel_complete &= ~(1 << channel);

	dma.m_dma_ptr = 0;
	dma.m_dspi_ptr = 0;
	dma.m_depth = 0;
}



//**************************************************************************
//  INTERNAL REGISTERS
//**************************************************************************

//-------------------------------------------------
//  input_r - Read digital input
//-------------------------------------------------

uint16_t dspp_device::input_r()
{
	// TODO
	return 0;
}


//-------------------------------------------------
//  output_w - Write to the 8 output registers
//-------------------------------------------------

void dspp_device::output_w(offs_t offset, uint16_t data)
{
	m_outputs[offset] = data;
}


//-------------------------------------------------
//  fifo_osc_r -
//-------------------------------------------------

uint16_t dspp_device::fifo_osc_r(offs_t offset)
{
	uint32_t data = 0;
	uint32_t channel = offset / 8;

	fifo_dma &dma = m_fifo_dma[channel];

	switch (offset & 7)
	{
		// DSPI_FIFO_OSC_OFFSET_CURRENT
		case 0:
		{
			if (dma.m_depth == 0)
				data = dma.m_prev_current;
			else
				data = dma.m_fifo[dma.m_dspi_ptr];

			break;
		}

		// DSPI_FIFO_OSC_OFFSET_NEXT
		case 1:
		{
			if (dma.m_depth == 0)
				data = dma.m_prev_current;
			else if (dma.m_depth == 1)
				data = dma.m_fifo[dma.m_dspi_ptr];
			else
				data = dma.m_fifo[(dma.m_dspi_ptr + 1) & DMA_FIFO_MASK];

			break;
		}

		// DSPI_FIFO_OSC_OFFSET_FREQUENCY
		case 2:
		{
			data = m_last_osc_count;
			break;
		}

		// DSPI_FIFO_OSC_OFFSET_PHASE
		case 3:
		{
			data = m_osc_phase;
			break;
		}

		// DSPI_FIFO_OFFSET_DATA
		case 4:
		{
			data = read_fifo_to_dspp(channel);
			break;
		}

		// DSPI_FIFO_OFFSET_CONTROL
		case 5:
		{
			data = dma.m_depth;
			break;
		}
	}

	return data;
}


//-------------------------------------------------
//  fifo_osc_w -
//-------------------------------------------------

void dspp_device::fifo_osc_w(offs_t offset, uint16_t data)
{
	uint32_t channel = offset / 8;

	switch (offset & 7)
	{
		// DSPI_FIFO_OSC_OFFSET_CURRENT
		case 0:
		{
			// Read only
			break;
		}

		// DSPI_FIFO_OSC_OFFSET_NEXT
		case 1:
		{
			// Read only
			break;
		}

		// DSPI_FIFO_OSC_OFFSET_FREQUENCY
		case 2:
		{
			m_osc_freq = data;
			run_oscillator(channel);
			break;
		}

		// DSPI_FIFO_OSC_OFFSET_PHASE
		case 3:
		{
			m_osc_phase = data;
			break;
		}

		// DSPI_FIFO_OFFSET_DATA
		case 4:
		{
			write_dspp_to_fifo(channel, data);
			break;
		}

		// DSPI_FIFO_OFFSET_CONTROL
		case 5:
		{
			// Read only
			break;
		}
	}
}


//-------------------------------------------------
//  input_control_w -
//-------------------------------------------------

void dspp_device::input_control_w(uint16_t data)
{
	// TODO
}


//-------------------------------------------------
//  output_control_w -
//-------------------------------------------------

void dspp_device::output_control_w(uint16_t data)
{
	// TODO
	if (data & 1)
	{
		uint32_t end;

		if (m_output_fifo_count == OUTPUT_FIFO_DEPTH)
		{
			// Overflow
			end = (m_output_fifo_start + m_output_fifo_count) & OUTPUT_FIFO_MASK;

			m_output_fifo_start = (m_output_fifo_start + 2) & OUTPUT_FIFO_MASK;

			m_output_fifo[(end + 0) & OUTPUT_FIFO_MASK] = m_outputs[0];
			m_output_fifo[(end + 1) & OUTPUT_FIFO_MASK] = m_outputs[1];
		}
		else
		{
			end = (m_output_fifo_start + m_output_fifo_count) & OUTPUT_FIFO_MASK;

			m_output_fifo[(end + 0) & OUTPUT_FIFO_MASK] = m_outputs[0];
			m_output_fifo[(end + 1) & OUTPUT_FIFO_MASK] = m_outputs[1];

			// Advance and update FIFO status
			m_output_fifo_count += 2;
		}

		advance_audio_frame();
	}
}


//-------------------------------------------------
//  input_status_r - Read input state
//-------------------------------------------------

uint16_t dspp_device::input_status_r()
{
	// TODO: How should this work?
	return 1;
}


//-------------------------------------------------
//  output_status_r - Return number of unread
//  entries in the output FIFO
//-------------------------------------------------

uint16_t dspp_device::output_status_r()
{
	return m_output_fifo_count;
}


//-------------------------------------------------
//  cpu_int_w - Host CPU soft interrupt
//-------------------------------------------------

void dspp_device::cpu_int_w(uint16_t data)
{
	m_core->m_partial_int |= (data << DSPX_FLD_INT_SOFT_SHIFT) & DSPX_FLD_INT_SOFT_MASK;
	update_host_interrupt();
}


//-------------------------------------------------
//  pc_r - Read program counter
//-------------------------------------------------

uint16_t dspp_device::pc_r()
{
	return m_core->m_pc;
}


//-------------------------------------------------
//  pc_w - Write program counter
//-------------------------------------------------

void dspp_device::pc_w(uint16_t data)
{
	m_core->m_pc = data;
}


//-------------------------------------------------
//  audlock_r - Read Audio Lock status
//-------------------------------------------------

uint16_t dspp_device::audlock_r()
{
	return m_core->m_flag_audlock;
}


//-------------------------------------------------
//  audlock_w - Write Audio Lock status
//-------------------------------------------------

void dspp_device::audlock_w(uint16_t data)
{
	m_core->m_flag_audlock = data & 1;
}


//-------------------------------------------------
//  clock_r - Read CPU tick counter
//-------------------------------------------------

uint16_t dspp_device::clock_r()
{
	return m_core->m_tclock;
}


//-------------------------------------------------
//  clock_w - Write CPU tick counter
//-------------------------------------------------

void dspp_device::clock_w(uint16_t data)
{
	m_core->m_tclock = data;
}


//-------------------------------------------------
//  noise_r - PRNG noise
//-------------------------------------------------

uint16_t dspp_device::noise_r()
{
	// TODO: Obviously this isn't accurate
	return machine().rand();
}



//**************************************************************************
//  EXTERNAL INTERFACE AND CONTROL REGISTERS
//**************************************************************************

//-------------------------------------------------
//  read_ext_control -
//-------------------------------------------------

uint32_t dspp_device::read_ext_control(offs_t offset)
{
	uint32_t data = 0;

	switch (offset)
	{
		// DSPX_INTERRUPT_SET
		case 0x4000/4:
		// DSPX_INTERRUPT_CLR
		case 0x4004/4:
		{
			data = get_interrupt_state();
			break;
		}

		// DSPX_INTERRUPT_ENABLE
		case 0x4008/4:
		// DSPX_INTERRUPT_DISABLE
		case 0x400C/4:
		{
			data = m_dspx_int_enable;
			break;
		}
		// DSPX_INT_DMANEXT_SET
		case 0x4010/4:
		// DSPX_INT_DMANEXT_CLR
		case 0x4014/4:
		{
			data = m_dspx_dmanext_int;
			break;
		}
		// DSPX_INT_DMANEXT_ENABLE
		case 0x4018/4:
		{
			data = m_dspx_dmanext_enable;
			break;
		}
		// DSPX_INT_CONSUMED_SET
		case 0x4020/4:
		// DSPX_INT_CONSUMED_CLR
		case 0x4024/4:
		{
			data = m_dspx_consumed_int;
			break;
		}
		// DSPX_INT_CONSUMED_ENABLE
		case 0x4028/4:
		// DSPX_INT_CONSUMED_DISABLE
		case 0x402c/4:
		{
			data = m_dspx_consumed_enable;
			break;
		}

		// DSPX_INT_UNDEROVER_SET
		case 0x4030/4:
		// DSPX_INT_UNDEROVER_CLR
		case 0x4034/4:
		{
			data = m_dspx_underover_int;
			break;
		}

		// DSPX_INT_UNDEROVER_ENABLE
		case 0x4038/4:
		// DSPX_INT_UNDEROVER_DISABLE
		case 0x403c/4:
		{
			data = m_dspx_underover_enable;
			break;
		}

		// DSPX_CHANNEL_ENABLE
		case 0x6000/4:
		// DSPX_CHANNEL_DISABLE
		case 0x6004/4:
		{
			data = m_dspx_channel_enable;
			break;
		}
		// DSPX_CHANNEL_DIRECTION_SET
		case 0x6008/4:
		// DSPX_CHANNEL_DIRECTION_CLR
		case 0x600c/4:
		{
			data = m_dspx_channel_direction;
			break;
		}
		// DSPX_CHANNEL_8BIT_SET
		case 0x6010/4:
		// DSPX_CHANNEL_8BIT_CLR
		case 0x6014/4:
		{
			data = m_dspx_channel_8bit;
			break;
		}
		// DSPX_CHANNEL_SQXD_SET
		case 0x6018/4:
		// DSPX_CHANNEL_SQXD_CLR
		case 0x601c/4:
		{
			data = m_dspx_channel_sqxd;
			break;
		}
		// DSPX_CHANNEL_STATUS
		case 0x603c/4:
		{
			data = m_dspx_channel_complete;
			break;
		}

		// DSPX_FRAME_DOWN_COUNTER:
		case 0x6040/4:
		{
			data = m_dspx_audio_duration;
			break;
		}
		// DSPX_FRAME_UP_COUNTER:
		case 0x6044/4:
		{
			data = m_dspx_audio_time;
			break;
		}

		// AUDIO_CONFIG
		case 0x6050/4:
		{
			break;
		}
		// AUDIN_CONFIG
		case 0x6060/4:
		{
			break;
		}
		// AUDOUT_CONFIG
		case 0x6068/4:
		{
			break;
		}
		// DSPX_CONTROL
		case 0x6070/4:
		{
			data = m_core->m_dspx_control;
			break;
		}
		default:
		{
			printf("DSPP: Unhandled external control read (%.4x)\n", offset << 2);
			break;
		}
	}

	return data;
}


//-------------------------------------------------
//  write_ext_control -
//-------------------------------------------------

void dspp_device::write_ext_control(offs_t offset, uint32_t data)
{
	switch (offset)
	{
		// DSPX_INTERRUPT_SET
		case 0x4000/4:
		{
			m_core->m_partial_int |= data & ~DSPX_F_INT_ALL_DMA;
			update_host_interrupt();
			break;
		}
		// DSPX_INTERRUPT_CLR
		case 0x4004/4:
		{
			m_core->m_partial_int &= ~(data & ~DSPX_F_INT_ALL_DMA);
			update_host_interrupt();
			break;
		}
		// DSPX_INTERRUPT_ENABLE
		case 0x4008/4:
		{
			m_dspx_int_enable |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INTERRUPT_DISABLE
		case 0x400C/4:
		{
			m_dspx_int_enable &= ~data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_DMANEXT_SET
		case 0x4010/4:
		{
			m_dspx_dmanext_int |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_DMANEXT_CLR
		case 0x4014/4:
		{
			m_dspx_dmanext_int &= ~data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_DMANEXT_ENABLE
		case 0x4018/4:
		{
			m_dspx_dmanext_enable |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_CONSUMED_SET
		case 0x4020/4:
		{
			m_dspx_consumed_int |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_CONSUMED_CLR
		case 0x4024/4:
		{
			m_dspx_consumed_int &= ~data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_CONSUMED_ENABLE
		case 0x4028/4:
		{
			m_dspx_consumed_enable |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_CONSUMED_DISABLE
		case 0x402c/4:
		{
			m_dspx_consumed_enable &= ~data;
			update_host_interrupt();
			break;
		}

		// DSPX_INT_UNDEROVER_SET
		case 0x4030/4:
		{
			m_dspx_underover_int |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_UNDEROVER_CLR
		case 0x4034/4:
		{
			m_dspx_underover_int &= ~data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_UNDEROVER_ENABLE
		case 0x4038/4:
		{
			m_dspx_underover_enable |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_INT_UNDEROVER_DISABLE
		case 0x403c/4:
		{
			m_dspx_underover_enable &= ~data;
			update_host_interrupt();
			break;
		}

		// DSPX_CHANNEL_ENABLE
		case 0x6000/4:
		{
			m_dspx_channel_enable |= data;
			update_host_interrupt();
			break;
		}
		// DSPX_CHANNEL_DISABLE
		case 0x6004/4:
		{
			m_dspx_channel_enable &= ~data;
			update_host_interrupt();
			break;
		}
		// DSPX_CHANNEL_DIRECTION_SET
		case 0x6008/4:
		{
			m_dspx_channel_direction |= data;
			break;
		}
		// DSPX_CHANNEL_DIRECTION_CLR
		case 0x600c/4:
		{
			m_dspx_channel_direction &= ~data;
			break;
		}
		// DSPX_CHANNEL_8BIT_SET
		case 0x6010/4:
		{
			m_dspx_channel_8bit |= data;
			break;
		}
		// DSPX_CHANNEL_8BIT_CLR
		case 0x6014/4:
		{
			m_dspx_channel_8bit &= ~data;
			break;
		}
		// DSPX_CHANNEL_SQXD_SET
		case 0x6018/4:
		{
			m_dspx_channel_sqxd |= data;
			break;
		}
		// DSPX_CHANNEL_SQXD_CLR
		case 0x601c/4:
		{
			m_dspx_channel_sqxd &= ~data;
			break;
		}
		// DSPX_CHANNEL_RESET
		case 0x6030/4:
		{
			for (uint32_t i = 0; i < NUM_DMA_CHANNELS; ++i)
			{
				if (data & (1 << i))
					reset_channel(i);
			}
			break;
		}

		// DSPX_FRAME_DOWN_COUNTER:
		case 0x6040/4:
		{
			m_dspx_audio_duration = data;
			break;
		}
		// DSPX_FRAME_UP_COUNTER:
		case 0x6044/4:
		{
			m_dspx_audio_time = data;
			break;
		}

		// AUDIO_CONFIG
		case 0x6050/4:
		{
			break;
		}

		// AUDIN_CONFIG
		case 0x6060/4:
		{
			break;
		}

		// AUDOUT_CONFIG
		case 0x6068/4:
		{
			break;
		}

		// DSPX_CONTROL
		case 0x6070/4:
		{
			m_core->m_dspx_control = data;
			break;
		}

		// DSPX_RESET
		case 0x6074/4:
		{
			if (data & 1)
				device_reset();

			// TODO: DSPX_F_RESET_INPUT and DSPX_F_RESET_OUTPUT

			break;
		}
		default:
		{
			printf("DSPP: Unhandled external control write (%.4x with %.8x)\n", offset << 2, data);
			break;
		}
	}
}


//-------------------------------------------------
//  read - host CPU read from DSPP internals
//-------------------------------------------------

uint32_t dspp_device::read(offs_t offset)
{
	if (offset < 0x1000/4)
	{
		// 16-bit code memory
		return m_code.read_word(offset);
	}
	else if (offset >= 0x1000/4 && offset < 0x2000/4)
	{
		// 16-bit data memory and registers
		return m_data.read_word((offset - 0x1000/4));
	}
	else if(offset >= 0x5000/4 && offset < 0x6000/4)
	{
		// DMA registers
		return read_dma_stack(offset - 0x5000/4);
	}
	else
	{
		// 32-bit control registers
		return read_ext_control(offset);
	}
}


//-------------------------------------------------
//  read_dma_stack -
//-------------------------------------------------

uint32_t dspp_device::read_dma_stack(offs_t offset)
{
	uint32_t data = 0;

	if (offset < 0x200 / 4)
	{
		uint32_t channel = offset / (16/4);
		uint32_t reg = offset & 3;
		fifo_dma &dma = m_fifo_dma[channel];

		switch (reg)
		{
			case 0x00/4://DSPX_DMA_ADDR_OFFSET:
			{
				data = dma.m_current_addr;
				break;
			}
			case 0x04/4://DSPX_DMA_COUNT_OFFSET:
			{
				data = dma.m_current_count;
				break;
			}
			case 0x08/4://DSPX_DMA_NEXT_ADDR_OFFSET:
			{
				data = dma.m_next_addr;
				break;
			}
			case 0x0c/4://DSPX_DMA_NEXT_COUNT_OFFSET:
			{
				data = dma.m_next_count;
				break;
			}
		}
	}
	else if (offset >= 0x200/4 && offset < (0x200/4 + (NUM_DMA_CHANNELS * (16/4))))
	{
		uint32_t channel = (offset - 0x200/4) / (16/4);
		fifo_dma &dma = m_fifo_dma[channel];

		data = dma.m_go_forever ? DSPX_F_DMA_GO_FOREVER : 0;
		data |= dma.m_next_valid ? DSPX_F_DMA_NEXTVALID : 0;
	}
	else
	{
		fatalerror("Unhandled DMA stack read");
	}

	return data;
}


//-------------------------------------------------
//  write_dma_stack -
//-------------------------------------------------

void dspp_device::write_dma_stack(offs_t offset, uint32_t data)
{
	if (offset < 0x200 / 4)
	{
		switch (offset & 3)
		{
			case 0x00/4://DSPX_DMA_ADDR_OFFSET:
			{
				m_dspx_shadow_current_addr = data;
				break;
			}
			case 0x04/4://DSPX_DMA_COUNT_OFFSET:
			{
				m_dspx_shadow_current_count = data;
				break;
			}
			case 0x08/4://DSPX_DMA_NEXT_ADDR_OFFSET:
			{
				m_dspx_shadow_next_addr = data;
				break;
			}
			case 0x0c/4://DSPX_DMA_NEXT_COUNT_OFFSET:
			{
				m_dspx_shadow_next_count = data;
				break;
			}
		}
	}
	else if (offset >= 0x200/4 && offset < (0x200/4 + (NUM_DMA_CHANNELS * (16/4))))
	{
		uint32_t channel = (offset - 0x200/4) / (16/4);
		fifo_dma &dma = m_fifo_dma[channel];

		if (data & DSPX_F_SHADOW_SET_NEXTVALID)
		{
			dma.m_next_valid = (data & DSPX_F_DMA_NEXTVALID) != 0;
		}
		if (data & DSPX_F_SHADOW_SET_FOREVER)
		{
			dma.m_go_forever = (data & DSPX_F_DMA_GO_FOREVER) != 0;
		}
		if (data & DSPX_F_SHADOW_SET_DMANEXT)
		{
			if (data & DSPX_F_INT_DMANEXT_EN)
			{
				m_dspx_dmanext_enable |= (1 << channel);
			}
			else
			{
				m_dspx_dmanext_enable &= ~(1 << channel);
			}
		}
		if (data & DSPX_F_SHADOW_SET_ADDRESS_COUNT)
		{
			if (offset & (8/4))
			{
				dma.m_next_addr = m_dspx_shadow_next_addr;
				dma.m_next_count = m_dspx_shadow_next_count;
			}
			else
			{
				dma.m_current_addr = m_dspx_shadow_current_addr;
				dma.m_current_count = m_dspx_shadow_current_count;
			}
		}
	}
	else
	{
		fatalerror("Unhandled DMA stack write");
	}
}


//-------------------------------------------------
//  write - host CPU write to DSPP internals
//-------------------------------------------------

void dspp_device::write(offs_t offset, uint32_t data)
{
	if (offset < 0x1000/4)
	{
		// 16-bit code memory
		m_code.write_word(offset, data);
	}
	else if (offset >= 0x1000/4 && offset < 0x2000/4)
	{
		// 16-bit data memory and registers
		m_data.write_word((offset - 0x1000/4), data);
	}
	else if(offset >= 0x5000/4 && offset < 0x6000/4)
	{
		// DMA registers
		write_dma_stack(offset - 0x5000/4, data);

		// Better safe than sorry...
		machine().scheduler().synchronize();
	}
	else
	{
		// 32-bit control registers
		write_ext_control(offset, data);

		// Better safe than sorry...
		machine().scheduler().synchronize();
	}
}


//-------------------------------------------------
//  read_output_fifo - Get data for the DACs
//-------------------------------------------------

uint16_t dspp_device::read_output_fifo()
{
	uint16_t data = 0;

	if (m_output_fifo_count == 0)
	{
		// Underflow
		return m_output_fifo[m_output_fifo_start];
	}

	data = m_output_fifo[m_output_fifo_start];

	m_output_fifo_start = (m_output_fifo_start + 1) & OUTPUT_FIFO_MASK;
	--m_output_fifo_count;

	return data;
}

// DEBUG!

char * GetBinary(char * buffer, uint32_t val, uint32_t bits)
{
	uint32_t i;

	for (i = 0; i < bits; ++i)
		buffer[i] = (val >> (bits - 1 - i)) & 1 ? '1' : '0';

	buffer[i] = '\0';

	return buffer;
}

void dspp_device::dump_state()
{
	// DMA
	for (uint32_t i = 0; i < NUM_DMA_CHANNELS; ++i)
	{
		printf("\n=== CHANNEL %02X ===\n", i);
		printf("CURR_ADDRESS: %08X\n", m_fifo_dma[i].m_current_addr);
		printf("CURR_COUNT:   %08X\n", m_fifo_dma[i].m_current_count);
		printf("NEXT_ADDR:    %08X\n", m_fifo_dma[i].m_next_addr);
		printf("NEXT_COUNT:   %08X\n", m_fifo_dma[i].m_next_count);
		printf("PREV_VALUE:   %08X\n", m_fifo_dma[i].m_prev_value);
		printf("PREV_CURRENT: %08X\n", m_fifo_dma[i].m_prev_current);
		printf("GO_FOREVER:   %X\n", m_fifo_dma[i].m_go_forever);
		printf("NEXT_VALID:   %X\n", m_fifo_dma[i].m_next_valid);
	}

	char buffer[64];

	printf("\n=== GLOBAL REGISTER===\n");
	printf("DSPX_CONTROL:           %08X\n", m_core->m_dspx_control);
	printf("DSPX_RESET:             %08X\n", m_dspx_reset);
	printf("DSPX_INT_ENABLE:        %08X\n", m_dspx_int_enable);
	printf("DSPX_CHANNEL_ENABLE:    %08X %s\n", m_dspx_channel_enable, GetBinary(buffer, m_dspx_channel_enable, 32));
	printf("DSPX_CHANNEL_COMPLETE:  %08X %s\n", m_dspx_channel_complete, GetBinary(buffer, m_dspx_channel_complete, 32));
	printf("DSPX_CHANNEL_DIRECTION: %08X %s\n", m_dspx_channel_direction, GetBinary(buffer, m_dspx_channel_direction, 32));
	printf("DSPX_CHANNEL_8BIT:      %08X %s\n", m_dspx_channel_8bit, GetBinary(buffer, m_dspx_channel_8bit, 32));
	printf("DSPX_CHANNEL_SQXD:      %08X %s\n", m_dspx_channel_sqxd, GetBinary(buffer, m_dspx_channel_sqxd, 32));

#if 0
	uint32_t    m_dspx_shadow_current_addr;
	uint32_t    m_dspx_shadow_current_count;
	uint32_t    m_dspx_shadow_next_addr;
	uint32_t    m_dspx_shadow_next_count;
	uint32_t    m_dspx_dmanext_int;
	uint32_t    m_dspx_dmanext_enable;
	uint32_t    m_dspx_consumed_int;
	uint32_t    m_dspx_consumed_enable;
	uint32_t    m_dspx_underover_int;
	uint32_t    m_dspx_underover_enable;
	uint32_t    m_dspx_audio_time;
	uint16_t    m_dspx_audio_duration;
#endif
}
