// license:BSD-3-Clause
// copyright-holders:Angelo Salese, Mariusz Wojcieszek
/*****************************************************************************
 *
 * scudsp.c
 * Sega SCUDSP emulator version 1.00
 *
 * copyright Angelo Salese & Mariusz Wojcieszek
 *
 * Changelog:
 * 131010: Angelo Salese
 * - Converted to CPU structure
 *
 * 110807: Angelo Salese
 * - Allow the Program Counter to be read-backable from SH-2, needed by Virtua Fighter to not
 *   get stuck on "round 1" announcement;
 *
 * 110806: Angelo Salese
 * - Allows reading from non-work ram h areas;
 * - Fixed DMA add values;
 * - Fixed a MVI condition shift flag bug, now Sega Saturn produces sound during splash screen;
 * - Removed left-over IRQ;
 *
 * 110722: Angelo Salese
 * - Added DSP IRQ command, tested with "The King of Boxing"
 *
 * 110527: Angelo Salese
 * - Fixed incorrectly setted execute flag clearance, allows animation of the Sega Saturn
 *   splash screen;
 *
 * 051129: Mariusz Wojcieszek
 * - Fixed parallel instructions which increment CT registers to update CT register only
 *   once, after dsp operation is finished. This fixes instructions like
 *   MOV MC0,X MOV MC0,Y used by vfremix
 * - Changed ALU 32bit instructions to not sign extend their result when loaded to ALU.
 *   This matches Sega's dspsim behaviour.
 * - Changed DMA addnumber handling to match Sega's dspsim.
 *
 * 050813: Mariusz Wojcieszek
 * - Fixed add number in DSP DMA
 *
 * 050412: Angelo Salese
 * - Fixed the T0F behaviour in the DMA operation,it was causing an hang in Treasure Hunt
 *   due of that.
 * - Removed the dsp.log file creation when you are not using the debug build
 *
 * 041114: Angelo Salese
 * - Finished flags in ALU opcodes
 * - SR opcode: MSB does not change.
 *
 * 040328: Mariusz Wojcieszek
 * - rewritten ALU and MUL operations using signed arithmetics
 * - improved DMA
 * - fixed MOV ALH,x
 *
 * 031211: Mariusz Wojcieszek
 * - result of ALU command is stored into ALU register
 * - X-Bus command: MOV [s],X can be executed in parallel to other X-Bus commands
 * - Y-Bus command: MOV [s],Y can be executed in parallel to other Y-Bus commands
 * - Jump and LPS/BTM support:
 *   jump addresses are absolute,
 *   prefetched instructions are executed before jump is taken
 * - after each instruction, X and Y is multiplied and contents are loaded into MUL register
 * - fixed RL8
 * - fixed MVI
 * - flags computation in MVI and JMP is partly guessed (because of errors in docs)
 * - added reading DSP mem from SH2 side
 * - overworked disassembler
 *
 *  TODO:
 * - Fix INSTA_DMA hack
 * - Fix disassembler
 * - Fix timings (no info available so far)
 * - Add control flags
 * - Croc: has a bug somewhere that never allows it to trip the ENDI opcode.
 *   Snippet of interest is:
 *   08    00823500                                            CLR A     MOV M0,PL
 *   09    08040000    OR                                      MOV ALU,A
 *   0A    D208000D    JMP NZ,$D
 *   0B    00000000    NOP
 *   0C    F8000000    ENDI
 *
 *   40    00863502                                            MOV M0,A  MOV M2,PL
 *   41    10003009    ADD                                               MOV ALL,MC0
 *   42    D3400042    JMP T0,$42
 *   43    00000000    NOP
 *   44    D0000007    JMP $7
 *
 *
 *
 *****************************************************************************/

#include "emu.h"
#include "scudsp.h"
#include "scudspdasm.h"


DEFINE_DEVICE_TYPE(SCUDSP, scudsp_cpu_device, "scudsp", "Sega SCUDSP")

/* FLAGS */
#define PRF m_flags & 0x04000000
#define EPF m_flags & 0x02000000
#define T0F m_flags & 0x00800000
#define SF  (m_flags & 0x00400000)
#define ZF  (m_flags & 0x00200000)
#define CF  m_flags & 0x00100000
#define VF  m_flags & 0x00080000
#define EF  m_flags & 0x00040000
#define ESF m_flags & 0x00020000
#define EXF m_flags & 0x00010000 // execute flag (basically tied to RESET pin)
#define LEF m_flags & 0x00008000 // change PC value
#define T0F_1 m_flags|=0x00800000
#define T0F_0 m_flags&=~0x00800000
#define EXF_0 m_flags&=~0x00010000
#define EF_1  m_flags|=0x00040000

#define SET_C(_val) (m_flags = ((m_flags & ~0x00100000) | ((_val) ? 0x00100000 : 0)))
#define SET_S(_val) (m_flags = ((m_flags & ~0x00400000) | ((_val) ? 0x00400000 : 0)))
#define SET_Z(_val) (m_flags = ((m_flags & ~0x00200000) | ((_val) ? 0x00200000 : 0)))
#define SET_V(_val) (m_flags = ((m_flags & ~0x00080000) | ((_val) ? 0x00080000 : 0)))


#define FLAGS_MASK 0x06ff8000
#define INSTA_DMA 1

#define scudsp_readop(A) m_program->read_dword(A)
#define scudsp_writeop(A, B) m_program->write_dword(A, B)
#define scudsp_readmem(A,MD) m_data->read_dword(A | (MD << 6))
#define scudsp_writemem(A,MD,B) m_data->write_dword(A | (MD << 6), B)

constexpr uint64_t concat_64(uint32_t hi, uint32_t lo) { return (uint64_t(hi) << 32) | lo; }

uint32_t scudsp_cpu_device::scudsp_get_source_mem_reg_value( uint32_t mode )
{
	if ( mode < 0x8 )
	{
		return scudsp_get_source_mem_value( mode );
	}
	else
	{
		switch( mode )
		{
			case 0x9:
				return u32((m_alu & 0x00000000ffffffffU) >> 0);
			case 0xA:
				return u32((m_alu & 0x0000ffffffff0000U) >> 16);
		}
	}
	return 0;
}

uint32_t scudsp_cpu_device::scudsp_get_source_mem_value(uint8_t mode)
{
	uint32_t value = 0;

	switch( mode )
	{
		case 0x0:   /* M0 */
			value = scudsp_readmem(m_ct0,0);
			break;
		case 0x1:   /* M1 */
			value = scudsp_readmem(m_ct1,1);
			break;
		case 0x2:   /* M2 */
			value = scudsp_readmem(m_ct2,2);
			break;
		case 0x3:   /* M3 */
			value = scudsp_readmem(m_ct3,3);
			break;
		case 0x4:   /* MC0 */
			value = scudsp_readmem(m_ct0++,0);
			m_ct0 &= 0x3f;
			break;
		case 0x5:   /* MC1 */
			value = scudsp_readmem(m_ct1++,1);
			m_ct1 &= 0x3f;
			break;
		case 0x6:   /* MC2 */
			value = scudsp_readmem(m_ct2++,2);
			m_ct2 &= 0x3f;
			break;
		case 0x7:   /* MC3 */
			value = scudsp_readmem(m_ct3++,3);
			m_ct3 &= 0x3f;
			break;
	}

	return value;
}

void scudsp_cpu_device::scudsp_set_dest_mem_reg( uint32_t mode, uint32_t value )
{
	switch( mode )
	{
		case 0x0:   /* MC0 */
			scudsp_writemem(m_ct0++,0,value);
			m_ct0 &= 0x3f;
			break;
		case 0x1:   /* MC1 */
			scudsp_writemem(m_ct1++,1,value);
			m_ct1 &= 0x3f;
			break;
		case 0x2:   /* MC2 */
			scudsp_writemem(m_ct2++,2,value);
			m_ct2 &= 0x3f;
			break;
		case 0x3:   /* MC3 */
			scudsp_writemem(m_ct3++,3,value);
			m_ct3 &= 0x3f;
			break;
		case 0x4:   /* RX */
			m_rx.ui = value;
			break;
		case 0x5:   /* PL */
			m_pl.ui = value;
			m_ph.si = (m_pl.si < 0) ? -1 : 0;
			break;
		case 0x6:   /* RA0 */
			m_ra0 = value;
			break;
		case 0x7:   /* WA0 */
			m_wa0 = value;
			break;
		case 0x8:
		case 0x9:
			/* ??? */
			break;
		case 0xa:   /* LOP */
			m_lop = value;
			break;
		case 0xb:   /* TOP */
			m_top = value;
			break;
		case 0xc:   /* CT0 */
			m_ct0 = value & 0x3f;
			break;
		case 0xd:   /* CT1 */
			m_ct1 = value & 0x3f;
			break;
		case 0xe:   /* CT2 */
			m_ct2 = value & 0x3f;
			break;
		case 0xf:   /* CT3 */
			m_ct3 = value & 0x3f;
			break;
	}
}

void scudsp_cpu_device::scudsp_set_dest_mem_reg_2( uint32_t mode, uint32_t value )
{
	if ( mode < 0xb )
	{
		scudsp_set_dest_mem_reg( mode, value );
	}
	else
	{
		switch( mode )
		{
			case 0xc:   /* PC */
				m_delay = m_pc;  /* address next after this command will be executed twice */
				m_top = m_pc;
				m_pc = value;
				break;
		}
	}
}

uint32_t scudsp_cpu_device::scudsp_compute_condition( uint32_t condition )
{
	uint32_t result = 0;

	switch( condition & 0xf )
	{
		case 0x1:   /* Z */
			result = ZF;
			break;
		case 0x2:   /* S */
			result = SF;
			break;
		case 0x3:   /* ZS */
			result = ZF | SF;
			break;
		case  0x4:  /* C */
			result = CF;
			break;
		case 0x8:   /* T0 */
			result = T0F;
			break;
	}
	if ( !(condition & 0x20) )
	{
		result = !result;
	}

	return result;
}

void scudsp_cpu_device::scudsp_set_dest_dma_mem( uint32_t memcode, uint32_t value, uint32_t counter )
{
	if ( memcode < 4 )
	{
		switch(memcode)
		{
			case 0x0:   /* MC0 */
				scudsp_writemem(((m_ct0 + counter) & 0x3f),0,value);
				break;
			case 0x1:   /* MC1 */
				scudsp_writemem(((m_ct1 + counter) & 0x3f),1,value);
				break;
			case 0x2:   /* MC2 */
				scudsp_writemem(((m_ct2 + counter) & 0x3f),2,value);
				break;
			case 0x3:   /* MC3 */
				scudsp_writemem(((m_ct3 + counter) & 0x3f),3,value);
				break;
		}
	}
	else if ( memcode == 4 )
	{
		fatalerror("scudsp_set_dest_dma_mem == 4");
		/* caused a stack overflow for sure ... */
		//dsp_reg.internal_prg[ counter & 0x100 ] = value;
	}
}

uint32_t scudsp_cpu_device::scudsp_get_mem_source_dma( uint32_t memcode, uint32_t counter )
{
	switch( memcode & 0x3 )
	{
		case 0x0:
			return scudsp_readmem(((m_ct0 + counter) & 0x3f),0);
		case 0x1:
			return scudsp_readmem(((m_ct1 + counter) & 0x3f),1);
		case 0x2:
			return scudsp_readmem(((m_ct2 + counter) & 0x3f),2);
		case 0x3:
			return scudsp_readmem(((m_ct3 + counter) & 0x3f),3);
	}
	return 0;
}


uint32_t scudsp_cpu_device::program_control_r()
{
	return (m_pc & 0xff) | (m_flags & FLAGS_MASK);
}

void scudsp_cpu_device::program_control_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
	uint32_t oldval, newval;

	oldval = (m_flags & 0xffffff00) | (m_pc & 0xff);
	newval = oldval;
	COMBINE_DATA(&newval);

	m_flags = newval & FLAGS_MASK;

	if(LEF)
		m_pc = newval & 0xff;

	//printf("%08x PRG CTRL\n",data);
	set_input_line(INPUT_LINE_RESET, (EXF) ? CLEAR_LINE : ASSERT_LINE);
}

void scudsp_cpu_device::program_w(uint32_t data)
{
	//printf("%02x %08x PRG\n",m_pc,data);
	scudsp_writeop(m_pc++, data);
}

void scudsp_cpu_device::ram_address_control_w(uint32_t data)
{
	//printf("%02x %08x PRG\n",m_pc,data);
	m_ra = data & 0xff;

	switch((m_ra & 0xc0) >> 6)
	{
		case 0: m_ct0 = (m_ra & 0x3f); break;
		case 1: m_ct1 = (m_ra & 0x3f); break;
		case 2: m_ct2 = (m_ra & 0x3f); break;
		case 3: m_ct3 = (m_ra & 0x3f); break;
	}
}

uint32_t scudsp_cpu_device::ram_address_r()
{
	uint32_t data;

	data = scudsp_get_source_mem_value( ((m_ra & 0xc0) >> 6) + 4 );

	return data;
}

void scudsp_cpu_device::ram_address_w(uint32_t data)
{
	scudsp_set_dest_mem_reg( (m_ra & 0xc0) >> 6, data );
}

void scudsp_cpu_device::scudsp_operation(uint32_t opcode)
{
	int64_t i1,i2;
	int32_t i3;
	int update_ct[4] = {0,0,0,0};
	int dsp_mem;


	/* ALU */
	switch( (opcode & 0x3c000000) >> 26 )
	{
		case 0x0:   /* NOP */
			break;
		case 0x1:   /* AND */
			i3 = m_acl.si & m_pl.si;
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z(i3 == 0);
			SET_C(0);
			SET_S(i3 < 0);
			break;
		case 0x2:   /* OR */
			i3 = m_acl.si | m_pl.si;
			m_alu = (uint64_t)(uint32_t)i3;
			SET_C(0);
			SET_S(i3 < 0);
			/* TODO: Croc and some early Psygnosis games wants Z to be 1 when the result of this one is negative.
			         Needs HW tests ... */
			if(i3 < 0)
				i3 = 0;
			SET_Z(i3 == 0);
			break;
		case 0x3:   /* XOR */
			i3 = m_acl.si ^ m_pl.si;
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z(i3 == 0);
			SET_C(0);
			SET_S(i3 < 0);
			break;
		case 0x4:   /* ADD */
			i3 = m_acl.si + m_pl.si;
			m_alu = (uint64_t)(uint32_t)i3;
			//SET_Z(i3 == 0);
			SET_Z( (i3 & s64(0xffffffffffffU)) == 0 );
			//SET_S(i3 < 0);
			SET_S( i3 & s64(0x1000000000000U));
			SET_C(i3 & s64(0x100000000U));
			SET_V(((i3) ^ (m_acl.si)) & ((i3) ^ (m_pl.si)) & 0x80000000);
			break;
		case 0x5:   /* SUB */
			i3 = m_acl.si - m_pl.si;
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z(i3 == 0);
			SET_C(i3 & s64(0x100000000U));
			SET_S(i3 < 0);
			SET_V(((m_pl.si) ^ (m_acl.si)) & ((m_pl.si) ^ (i3)) & 0x80000000);
			break;
		case 0x6:   /* AD2 */
			i1 = concat_64((int32_t)m_ph.si,m_pl.si);
			i2 = concat_64((int32_t)m_ach.si,m_acl.si);
			m_alu = i1 + i2;
			SET_Z((m_alu & s64(0xffffffffffffU)) == 0);
			SET_S((m_alu & s64(0x800000000000U)) > 0);
			SET_C((m_alu) & s64(0x1000000000000U));
			SET_V(((m_alu) ^ (i1)) & ((m_alu) ^ (i2)) & s64(0x800000000000U));
			break;
		case 0x7:   /* ??? */
			/* Unrecognized opcode */
			break;
		case 0x8:   /* SR */
			i3 = (m_acl.si >> 1) | (m_acl.si & 0x80000000);/*MSB does not change*/
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z(i3 == 0);
			SET_S(i3 < 0);
			SET_C(m_acl.ui & 0x80000000);
			break;
		case 0x9:   /* RR */
			i3 = ((m_acl.ui >> 1) & 0x7fffffff) | ((m_acl.ui << 31) & 0x80000000);
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z( i3 == 0 );
			SET_S( i3 < 0 );
			SET_C( m_acl.ui & 0x1 );
			break;
		case 0xa:   /* SL */
			i3 = m_acl.si << 1;
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z( i3 == 0 );
			SET_S( i3 < 0 );
			SET_C( m_acl.ui & 0x80000000 );
			break;
		case 0xB:   /* RL */
			i3 = ((m_acl.si << 1) & 0xfffffffe) | ((m_acl.si >> 31) & 0x1);
			m_alu = (uint64_t)(uint32_t)i3;
			SET_Z( i3 == 0 );
			SET_S( i3 < 0 );
			SET_C( m_acl.ui & 0x80000000 );
			break;
		case 0xc:
		case 0xd:
		case 0xe:
			/* Unrecognized opcode */
			break;
		case 0xF:   /* RL8 */
			i3 = rotl_32(m_acl.si, 8);
			m_alu = i3;
			SET_Z( i3 == 0 );
			SET_S( i3 < 0 );
			SET_C( m_acl.si & 0x01000000 );
			break;
	}

	/* X-Bus */
	if ( opcode & 0x2000000 )
	{
		/* MOV [s],X */
		dsp_mem = (opcode & 0x700000) >> 20;
		if ( dsp_mem & 4 )
		{
			dsp_mem &= 3;
			update_ct[dsp_mem] = 1;
		}
		m_rx.ui = scudsp_get_source_mem_value( dsp_mem );
		m_update_mul = 1;
	}
	switch( (opcode & 0x1800000) >> 23 )
	{
		case 0x0:   /* NOP */
		case 0x1:   /* NOP ? */
			break;
		case 0x2:   /* MOV MUL,P */
			m_ph.ui = u16((m_mul & 0x0000ffff00000000U) >> 32);
			m_pl.ui = u32((m_mul & 0x00000000ffffffffU) >> 0);
			break;
		case 0x3:   /* MOV [s],P */
			dsp_mem = (opcode & 0x700000) >> 20;
			if ( dsp_mem & 4 )
			{
				dsp_mem &= 3;
				update_ct[dsp_mem] = 1;
			}
			m_pl.ui = scudsp_get_source_mem_value(  dsp_mem );
			m_ph.si = (m_pl.si < 0) ? -1 : 0;
			break;
	}

	/* Y-Bus */
	if ( opcode & 0x80000 )
	{
		/* MOV [s],Y */
		dsp_mem = (opcode & 0x1C000 ) >> 14;
		if (dsp_mem & 4)
		{
			dsp_mem &= 3;
			update_ct[dsp_mem] = 1;
		}
		m_ry.ui = scudsp_get_source_mem_value( dsp_mem );
		m_update_mul = 1;
	}
	switch( (opcode & 0x60000) >> 17 )
	{
		case 0x0:   /* NOP */
			break;
		case 0x1:   /* CLR A */
			m_acl.ui = 0;
			m_ach.ui = 0;
			break;
		case 0x2:   /* MOV ALU,A */
			m_ach.ui = u16((m_alu & 0x0000ffff00000000U) >> 32);
			m_acl.ui = u32((m_alu & 0x00000000ffffffffU) >> 0);
			break;
		case 0x3:   /* MOV [s], A */
			dsp_mem = (opcode & 0x1C000 ) >> 14;
			if (dsp_mem & 4)
			{
				dsp_mem &= 3;
				update_ct[dsp_mem] = 1;
			}
			m_acl.ui = scudsp_get_source_mem_value( dsp_mem );
			m_ach.si = ((m_acl.si < 0) ? -1 : 0);
			break;
	}

	/* update CT registers */
	if ( update_ct[0] ) { m_ct0++; m_ct0 &= 0x3f; };
	if ( update_ct[1] ) { m_ct1++; m_ct1 &= 0x3f; };
	if ( update_ct[2] ) { m_ct2++; m_ct2 &= 0x3f; };
	if ( update_ct[3] ) { m_ct3++; m_ct3 &= 0x3f; };


	/* D1-Bus */
	switch( (opcode & 0x3000) >> 12 )
	{
		case 0x0:   /* NOP */
			break;
		case 0x1:   /* MOV SImm,[d] */
			scudsp_set_dest_mem_reg( (opcode & 0xf00) >> 8, (int32_t)(int8_t)(opcode & 0xff) );
			break;
		case 0x2:
			/* ??? */
			break;
		case 0x3:   /* MOV [s],[d] */
			scudsp_set_dest_mem_reg( (opcode & 0xf00) >> 8, scudsp_get_source_mem_reg_value( opcode & 0xf ) );
			break;
	}

	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_move_immediate( uint32_t opcode )
{
	uint32_t value;

	if ( opcode & 0x2000000 )
	{
		if ( scudsp_compute_condition( (opcode & 0x3F80000 ) >> 19 ) )
		{
			value = util::sext( opcode, 19 );
			scudsp_set_dest_mem_reg_2( (opcode & 0x3C000000) >> 26, value );
		}
	}
	else
	{
		value = util::sext( opcode, 25 );
		scudsp_set_dest_mem_reg_2( (opcode & 0x3C000000) >> 26, value );
	}
	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_dma( uint32_t opcode )
{
	uint8_t hold = (opcode &  0x4000) >> 14;
	uint32_t add = (opcode & 0x38000) >> 15;
	uint32_t dir_from_D0 = (opcode & 0x1000 ) >> 12;
	uint32_t dsp_mem = (opcode & 0x300) >> 8;

	T0F_1;

	if ( opcode & 0x2000 )
	{
		m_dma.size = scudsp_get_source_mem_value( opcode & 0xf );
		switch ( add & 0x7 )
		{
			case 0: m_dma.add = 0; break;
			case 1: m_dma.add = 4; break;
			default: m_dma.add = 4; break;
		}
	}
	else
	{
		m_dma.size = opcode & 0xff;
		switch( add )
		{
			case 0: m_dma.add = 0; break;  /* 0 */
			case 1: m_dma.add = 4; break;  /* 1 */
			case 2: m_dma.add = 4; break;  /* 2 */
			case 3: m_dma.add = 16; break; /* 4 */
			case 4: m_dma.add = 16; break;  /* 8 */
			case 5: m_dma.add = 64; break; /* 16 */
			case 6: m_dma.add = 128; break; /* 32 */
			case 7: m_dma.add = 256; break; /* 64 */
		}
	}

	m_dma.dir = dir_from_D0;
	if ( m_dma.dir == 0 )
	{
		m_dma.src = (m_ra0 << 2) & 0x07ffffff;
		m_dma.dst = dsp_mem;
	}
	else
	{
		m_dma.src = dsp_mem;
		m_dma.dst = (m_wa0 << 2) & 0x07ffffff;
	}

	m_dma.update = ( hold == 0 );
	m_dma.ex = 1;
	m_dma.count = 0;
	/* HACK ALERT: It looks like that scheduling craps out the m_dma parameters, why this happens I don't know ... */
	#if INSTA_DMA
	{
		uint32_t data;
		if ( m_dma.dir == 0 )
		{
			for(m_dma.count = 0;m_dma.count < m_dma.size; m_dma.count++)
			{
				data = (m_in_dma_cb(m_dma.src)<<16) | m_in_dma_cb(m_dma.src+2);
				scudsp_set_dest_dma_mem( m_dma.dst, data, m_dma.count );

				m_dma.src += m_dma.add;

				if ( m_dma.update )
				{
					m_ra0 += ((1 * m_dma.add) >> 2);
				}
			}
		}
		else
		{
			for(m_dma.count = 0;m_dma.count < m_dma.size; m_dma.count++)
			{
				data = scudsp_get_mem_source_dma( m_dma.src, m_dma.count );

				m_out_dma_cb(m_dma.dst, data >> 16 );
				m_out_dma_cb(m_dma.dst+2, data & 0xffff );

				m_dma.dst += m_dma.add;

				if ( m_dma.update )
				{
					m_wa0 += ((1 * m_dma.add) >> 2);
				}
			}
		}

		//if(m_dma.count >= m_dma.size)
		{
			m_dma.ex = 0;
			T0F_0;
		}

		m_icount -= m_dma.size;
	}
	#endif


	//printf("SRC %08x DST %08x SIZE %08x UPDATE %08x DIR %08x ADD %08x\n",m_dma.src,m_dma.dst,m_dma.size,m_dma.update,m_dma.dir,m_dma.add);

	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_jump( uint32_t opcode )
{
	if ( opcode & 0x3f80000 )
	{
		if ( scudsp_compute_condition( (opcode & 0x3f80000) >> 19 ) )
		{
			m_delay = m_pc;
			m_pc = opcode & 0xff;
		}
	}
	else
	{
		m_delay = m_pc;
		m_pc = opcode & 0xff;
	}

	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_loop(uint32_t opcode)
{
	if ( opcode & 0x8000000 )
	{
		/* LPS */
		if ( m_lop != 0 )
		{
			m_lop--;
			m_delay = m_pc;
			m_pc--;
		}
	}
	else
	{
		/* BTM */
		if ( m_lop != 0 )
		{
			m_lop--;
			m_delay = m_pc;
			m_pc = m_top;
		}
	}
	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_end(uint32_t opcode)
{
	if(opcode & 0x08000000)
	{
		/*ENDI*/
		EF_1;
		m_out_irq_cb(1);
	}

	EXF_0; /* END / ENDI */
	set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_illegal(uint32_t opcode)
{
	fatalerror("scudsp illegal opcode at 0x%04x\n", m_pc);
	m_icount -= 1;
}

void scudsp_cpu_device::scudsp_exec_dma()
{
	uint32_t data;
	if ( m_dma.dir == 0 )
	{
		data = (m_in_dma_cb(m_dma.src)<<16) | m_in_dma_cb(m_dma.src+2);
		scudsp_set_dest_dma_mem( m_dma.dst, data, m_dma.count );

		m_dma.src += m_dma.add;

		if ( m_dma.update )
		{
			m_ra0 += ((1 * m_dma.add) >> 2);
		}
	}
	else
	{
		data = scudsp_get_mem_source_dma( m_dma.src, m_dma.count );

		m_out_dma_cb(m_dma.dst, data >> 16 );
		m_out_dma_cb(m_dma.dst+2, data & 0xffff );

		m_dma.dst += m_dma.add;

		if ( m_dma.update )
		{
			m_wa0 += ((1 * m_dma.add) >> 2);
		}
	}

	m_dma.count++;
	if(m_dma.count >= m_dma.size)
	{
		m_dma.ex = 0;
		T0F_0;
	}

	m_icount -= 1;
}

/* Execute cycles */
void scudsp_cpu_device::execute_run()
{
	uint32_t opcode;

	do
	{
		m_update_mul = 0;

		debugger_instruction_hook(m_pc);

		if ( m_delay )
		{
			opcode = scudsp_readop(m_delay);
			m_delay = 0;
		}
		else
		{
			opcode = scudsp_readop(m_pc);
			m_pc++;
		}

		switch( (opcode & 0xc0000000) >> 30 )
		{
			case 0x00: /* 00 */
				scudsp_operation(opcode);
				break;
			case 0x01: /* 01 */
				scudsp_illegal(opcode);
				break;
			case 0x02: /* 10 */
				scudsp_move_immediate(opcode);
				break;
			case 0x03: /* 11 */
				switch( (opcode & 0x30000000) >> 28 )
				{
					case 0x00:
						scudsp_dma(opcode);
						break;
					case 0x01:
						scudsp_jump(opcode);
						break;
					case 0x02:
						scudsp_loop(opcode);
						break;
					case 0x03:
						scudsp_end(opcode);
						break;
				}
				break;
		}

		if ( m_update_mul == 1 )
		{
			m_mul = (int64_t)m_rx.si * (int64_t)m_ry.si;
			m_update_mul = 0;
		}

		if (m_dma.ex == 1)
		{
			scudsp_exec_dma();
		}

	} while( m_icount > 0 );
}

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

void scudsp_cpu_device::device_start()
{
	m_pc = 0;
	m_flags = 0;
	m_delay = 0;
	m_top = 0;
	m_lop = 0;
	memset(&m_rx, 0x00, sizeof(m_rx));
	m_mul = 0;
	memset(&m_ry, 0x00, sizeof(m_ry));
	m_alu = 0;
	memset(&m_ph, 0x00, sizeof(m_ph));
	memset(&m_pl, 0x00, sizeof(m_pl));
	memset(&m_ach, 0x00, sizeof(m_ach));
	memset(&m_acl, 0x00, sizeof(m_acl));
	m_ra0 = 0;
	m_wa0 = 0;
	m_ra = 0;
	m_ct0 = 0;
	m_ct1 = 0;
	m_ct2 = 0;
	m_ct3 = 0;
	memset(&m_dma, 0x00, sizeof(m_dma));

	m_program = &space(AS_PROGRAM);
	m_data = &space(AS_DATA);

	save_item(NAME(m_pc));
	save_item(NAME(m_ra));

	save_item(NAME(m_ct0));
	save_item(NAME(m_ct1));
	save_item(NAME(m_ct2));
	save_item(NAME(m_ct3));

	save_item(NAME(m_flags));
	save_item(NAME(m_delay));

	save_item(NAME(m_top));
	save_item(NAME(m_lop));
	save_item(NAME(m_rx.ui));

	save_item(NAME(m_mul));

	save_item(NAME(m_ry.ui));

	save_item(NAME(m_alu));
	save_item(NAME(m_ph.ui));
	save_item(NAME(m_pl.ui));
	save_item(NAME(m_ach.ui));
	save_item(NAME(m_acl.ui));
	save_item(NAME(m_ra0));
	save_item(NAME(m_wa0));

	save_item(NAME(m_dma.src));
	save_item(NAME(m_dma.dst));
	save_item(NAME(m_dma.size));

	// Register state for debugger
	state_add( SCUDSP_PC, "PC", m_pc ).formatstr("%02X");
	state_add( SCUDSP_FLAGS, "SR", m_flags ).formatstr("%08X");
	state_add( SCUDSP_DELAY, "DELAY", m_delay ).formatstr("%02X").noshow();
	state_add( SCUDSP_TOP, "TOP", m_top).formatstr("%02X");
	state_add( SCUDSP_LOP, "LOP", m_lop).formatstr("%03X");
	state_add( SCUDSP_RX, "RX", m_rx.ui).formatstr("%08X");
	state_add( SCUDSP_MUL, "MUL", m_mul).formatstr("%012X");
	state_add( SCUDSP_RY, "RY", m_ry.ui).formatstr("%08X");
	state_add( SCUDSP_ALU, "ALU", m_alu).formatstr("%012X");
	state_add( SCUDSP_PH, "PH", m_ph.ui).formatstr("%04X");
	state_add( SCUDSP_PL, "PL", m_pl.ui).formatstr("%08X");
	state_add( SCUDSP_ACH, "ACH", m_ach.ui).formatstr("%04X");
	state_add( SCUDSP_ACL, "ACL", m_acl.ui).formatstr("%08X");
	state_add( SCUDSP_RA0, "RA0", m_ra0).formatstr("%08X");
	state_add( SCUDSP_WA0, "WA0", m_wa0).formatstr("%08X");
	state_add( SCUDSP_RA, "RA", m_ra ).formatstr("%02X");
	state_add( SCUDSP_CT0, "CT0", m_ct0 ).formatstr("%02X");
	state_add( SCUDSP_CT1, "CT1", m_ct1 ).formatstr("%02X");
	state_add( SCUDSP_CT2, "CT2", m_ct2 ).formatstr("%02X");
	state_add( SCUDSP_CT3, "CT3", m_ct3 ).formatstr("%02X");
	state_add( STATE_GENPC, "GENPC", m_pc ).noshow();
	state_add( STATE_GENPCBASE, "CURPC", m_pc ).noshow();
	state_add( STATE_GENFLAGS, "GENFLAGS", m_flags ).formatstr("%17s").noshow();

	set_icountptr(m_icount);
}

void scudsp_cpu_device::device_reset()
{
}

void scudsp_cpu_device::execute_set_input(int irqline, int state)
{
	switch(irqline)
	{
		case SCUDSP_RESET:
			//m_reset_state = state;
			break;
	}
}

void scudsp_cpu_device::program_map(address_map &map)
{
	map(0x00, 0xff).ram();
}

void scudsp_cpu_device::data_map(address_map &map)
{
	map(0x00, 0xff).ram();
}

scudsp_cpu_device::scudsp_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: cpu_device(mconfig, SCUDSP, tag, owner, clock)
	, m_out_irq_cb(*this)
	, m_in_dma_cb(*this, 0)
	, m_out_dma_cb(*this)
	, m_program_config("program", ENDIANNESS_BIG, 32, 8, -2, address_map_constructor(FUNC(scudsp_cpu_device::program_map), this))
	, m_data_config("data", ENDIANNESS_BIG, 32, 8, -2, address_map_constructor(FUNC(scudsp_cpu_device::data_map), this))
{
}


void scudsp_cpu_device::state_string_export(const device_state_entry &entry, std::string &str) const
{
	switch (entry.index())
	{
		case STATE_GENFLAGS:
			str = string_format("%s%s%s%c%c%c%c%c%s%s%s",
				m_flags & 0x4000000 ? "PR":"..",
				m_flags & 0x2000000 ? "EP":"..",
				m_flags & 0x800000 ? "T0":"..",
				m_flags & 0x400000 ? 'S':'.',
				m_flags & 0x200000 ? 'Z':'.',
				m_flags & 0x100000 ? 'C':'.',
				m_flags & 0x80000 ? 'V':'.',
				m_flags & 0x40000 ? 'E':'.',
				m_flags & 0x20000 ? "ES":"..",
				m_flags & 0x10000 ? "EX":"..",
				m_flags & 0x8000 ? "LE":"..");
			break;
	}
}


std::unique_ptr<util::disasm_interface> scudsp_cpu_device::create_disassembler()
{
	return std::make_unique<scudsp_disassembler>();
}
