// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay
// thanks-to:Happy

/*
 * Silicon Graphics GE5 and HQ1 devices.
 *
 * This board handles the interface to the host, and mainly consists of the HQ1
 * instruction sequencer and the WTL3132 floating-point accelerator. The board
 * also contains instruction and data RAM for the HQ1, and a FIFO for host
 * communication.
 *
 * The undocumented HQ1 microcode instruction format is relatively well decoded
 * now, but the exact timing and function of some operations remains unknown.
 *
 * TODO:
 *   - implement host dma
 *   - verify some operations
 *   - implement single stepping
 *   - redo disassembly
 *   - save state
 */

#include "emu.h"
#include "debugger.h"
#include "sgi_ge5.h"

#define LOG_GENERAL   (1U << 0)
#define LOG_TOKEN     (1U << 1)
#define LOG_MEMORY    (1U << 2)
#define LOG_DMA       (1U << 3)

//#define VERBOSE       (LOG_GENERAL)
#include "logmacro.h"

static char const *const token_diag[] =
{
	"DIAG_DATA",          "DIAG_INIT",          "DIAG_DRAMTEST",      "DIAG_DMA_IG",
	"DIAG_DMA_IB",        "DIAG_DMA_GB",        "DIAG_CHARPOS",       "DIAG_WRITEFULLPIX",
	"DIAG_DRAWLINE",      "DIAG_DK3_FIFO",      "DIAG_DK3_FINFLGS",   "DIAG_DRAW4SPANS",
	"DIAG_DRAWFLATSPAN",  "DIAG_DRAWSPAN",      "DIAG_LIFECHECK",     "DIAG_LOADRE",
	"DIAG_READPIXDMA",    "DIAG_READPIXELS",    "DIAG_SCREENCLEAR",   "DIAG_WRITEPIXDMA",
	"DIAG_FASTCLEAR20",   "DIAG_DRAWCHAR",      "DIAG_STRINGINIT",    "DIAG_STRINGEND",
	"DIAG_FASTCHAR",      "DIAG_DRAWLONGSPANS", nullptr,              nullptr,

	// turbo option
	nullptr,              nullptr,              "DIAG_DSPLOAD",       "DIAG_DSPRAMDATA",
	"DIAG_DSPRAMADDR",    "DIAG_DSPFIFO",       "DIAG_DSPSPAN",       "DIAG_DSPRD",
	"DIAG_DSPWR",         "DIAG_DSPINTRAM",     "DIAG_DSPSCOPE",      "DIAG_RESCOPE",
};

static char const *const token_puc[] =
{
	"PUC_DATA",           "PUC_INIT",           nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              "PUC_COLOR",          "PUC_FINISH",
	"PUC_PNT2I",          "PUC_RECTI2D",        "PUC_CMOV2I",         "PUC_DRAWCHAR",
	"PUC_HAND",           "PUC_FBOPT",          "PUC_ZBOPT",          "PUC_TOPSCAN",
	"DIAG_READPIXELS",    "DIAG_WRITEFULLPIX",  "DIAG_LOADRE",        "DIAG_CHARPOS",
};

static char const *const token_gl[] =
{
	"GE_DATA",            "GE_INIT",            nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              "GE_COLOR",           "GE_FINISH0",
	"GE_PNT2I",           "GE_SBOXI",           "GE_CMOV2I",          "GE_DRAWCHAR",
	"GE_HAND",            "GE_FBOPT",           "GE_ZBOPT",           "GE_TOPSCAN",
	"GE_READPIXELS",      "GE_WRITEPIXELS",     "GE_LOADRE",          "GE_GETCPOS",
	"GE_PICKMODE",        "GE_PIXTYPE",         "GE_PIXWRITEMASK",    "GE_POPNAME",
	"GE_PUSHNAME",        "GE_READBLOCK",       "GE_RECTREAD",        "GE_READBUF",
	"GE_READPIXDMA",      "GE_AUXWRITEMASK",    "GE_READRGB",         "GE_RECTCOPY",
	"GE_RGBCOLOR",        "GE_RGBSHADERANGE",   "GE_RWMODE",          "GE_SCREENCLEAR",
	"GE_SHADEMODEL",      "GE_SHADERANGE",      "GE_WRITEBLOCK",      "GE_RECTWRITE",
	"GE_WRITEPIXDMA",     "GE_BEGINBBOX",       "GE_ZBUFFER",         "GE_ZCLEAR",
	"GE_ZOOMFACTOR",      "GE_READSOURCE",      "GE_DRAWMODE",        "GE_CZCLEAR",
	"GE_HQMSAV",          "GE_ZFUNCTION",       "GE_SETPIECES",       "GE_FLATMODE",
	"GE_LMCOLOR",         "GE_LOADAMBIENT",     "GE_DEPTHFN",         "GE_LOADDIFFUSE",
	"GE_LOADMATRIX",      "GE_MULTMATRIX",      "GE_PUSHMATRIX",      "GE_POPMATRIX",
	"GE_LOADSPECULAR",    "GE_LOADEMISSION",    "GE_LOADASUM",        "GE_LOADLCOLOR",
	nullptr,              nullptr,              "GE_CURVEIT",         "GE_LOADVIEWP",
	"GE_POLYGON",         "GE_ENDPOLYGON",      "GE_TRANSLATEI",      "GE_TRANSLATE",
	"GE_LINESTYLE",       "GE_LINEWIDTH",       "GE_VERTEX2I",        "GE_VERTEX2",
	"GE_VERTEX3I",        "GE_VERTEX3",         "GE_VERTEX4I",        "GE_VERTEX4",
	"GE_RVERTEX2I",       "GE_RVERTEX2",        "GE_RVERTEX3I",       "GE_RVERTEX3",
	"GE_CLOSEDLINE",      "GE_ENDCLOSEDLINE",   "GE_LSREPEAT",        "GE_ANTIALIAS",
	"GE_COLORF",          "GE_PNT2",            "GE_PNT3I",           "GE_PNT3",
	"GE_PNT4I",           "GE_PNT4",            nullptr,              nullptr,
	"GE_MOVE2I",          "GE_MOVE2",           "GE_MOVE3I",          "GE_MOVE3",
	"GE_MOVE4I",          "GE_MOVE4",           "GE_RMOVE2I",         "GE_RMOVE2",
	"GE_RMOVE3I",         "GE_RMOVE3",          "GE_DRAW2I",          "GE_DRAW2",
	"GE_DRAW3I",          "GE_DRAW3",           "GE_DRAW4I",          "GE_DRAW4",
	"GE_RDRAW2I",         "GE_RDRAW2",          "GE_RDRAW3I",         "GE_RDRAW3",
	"GE_ENABLWID",        "GE_LOADGE",          nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              "GE_FRONTFACE",       "GE_BACKFACE",        "GE_CONCAVE",
	"GE_PATTERN",         "GE_SETPATTERN",      "GE_LOADNORMAL",      "GE_MULTNORMAL",
	"GE_INITSTACK",       "GE_MMODE",           "GE_NORMAL",          "GE_ABNORMAL",
	"GE_LIGHTATTR1",      "GE_LIGHTATTR2",      "GE_LIGHTATTR3",      "GE_BINDLIGHT",
	"GE_LIGHTDATA4",      "GE_LIGHTMEMPTR",     "GE_LIGHTDIRECTION",  "GE_LIGHTPOSITION",
	"GE_LIGHTMOVEDATA",   "GE_BEGINMESH",       "GE_ENDMESH",         "GE_SWAPMESH",
	"GE_SBOXF",           "GE_SBOXFI",          "GE_FATPOLY",         "GE_ENDOLDPOLYGON",
	"GE_SBOX",            "GE_CURRENTWID",      nullptr,              nullptr,
	"GE_DEPTHCUE",        "GE_CMOV2",           "GE_CMOV3I",          "GE_CMOV3",
	"GE_CMOV4I",          "GE_CMOV4",           "GE_ENABDITH",        "GE_ENABWID",
	nullptr,              nullptr,              "GE_SETMATRIX",       "GE_COMPOSEMATRIX",
	"GE_LOADTOPMATRIX",   "GE_COPYMATRIX",      "GE_FEEDBACK",        "GE_ENDFEEDBACK",
	"GE_PASSTHROUGH",     "GE_FMOVE",           "GE_FDRAW",           "GE_FLINE",
	"GE_SCRMASK",         "GE_ZSOURCE",         "GE_SUBPIXEL",        "GE_SMOOTHPOINT",
	"GE_RASTEROP",        "GE_RESETLS",         nullptr,              nullptr,
	nullptr,              nullptr,              "GE_SETSURFSCALE",    "GE_SETV",
	"GE_PUSHV",           "GE_SURFP1",          "GE_SURFNTURF",       "GE_SETVHI",
	"GE_STRIP",           "GE_1LOAD1",          "GE_1LOAD3",          "GE_1LOAD4",
	"GE_SURFMODE",        "GE_DSPRD",           "GE_DSPWR",           "GE_DSPNEXT",
	"GE_DSPDUMMY",        "GE_PICKTYPE",        "GE_VERSION",         "GE_ENDBBOX",
	"GE_ENDPICKMODE",     "GE_INITNAMES",       "GE_LOADNAME",        nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	"GE_CTX0",            nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              nullptr,
	nullptr,              nullptr,              nullptr,              "GE_CTX1",
};

DEFINE_DEVICE_TYPE(SGI_GE5, sgi_ge5_device, "ge5", "SGI Geometry Engine 5")

sgi_ge5_device::sgi_ge5_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
	: cpu_device(mconfig, SGI_GE5, tag, owner, clock)
	, m_code_config("code", ENDIANNESS_BIG, 64, 15, -3, address_map_constructor(FUNC(sgi_ge5_device::code_map), this))
	, m_data_config("data", ENDIANNESS_BIG, 32, 13, -2, address_map_constructor(FUNC(sgi_ge5_device::data_map), this))
	, m_fpu(*this, "fpu")
	, m_int_cb(*this)
	, m_fifo_empty(*this)
	, m_fifo_read(*this)
	, m_re_r(*this)
	, m_re_w(*this)
	, m_icount(0)
{
}

void sgi_ge5_device::device_add_mconfig(machine_config &config)
{
	WTL3132(config, m_fpu, clock());
	m_fpu->out_fpcn().set([this](int state) { m_fpu_c = bool(state); });
	m_fpu->out_port_x().set([this](u32 data) { m_fpu_data = data; });
}


void sgi_ge5_device::code_map(address_map &map)
{
	map(0x0000, 0x3fff).ram().share("code");
}

void sgi_ge5_device::data_map(address_map &map)
{
	map(0x0000, 0x1fff).ram().share("data");
}

void sgi_ge5_device::device_start()
{
	m_int_cb.resolve_safe();
	m_fifo_empty.resolve();
	m_fifo_read.resolve();
	m_re_r.resolve();
	m_re_w.resolve();

	// TODO: save state
	state_add(STATE_GENPC,     "GENPC", m_pc).noshow();
	state_add(STATE_GENPCBASE, "CURPC", m_pc).noshow();
	state_add(0, "PC", m_pc).formatstr("%04X");

	state_add(1, "MEMPTR", m_memptr).formatstr("%04X");
	state_add(2, "REPTR",  m_reptr).formatstr("%04X");
	state_add(3, "BUS",    m_bus).formatstr("%08X");
	state_add(4, "DMACNT", m_dma_count).formatstr("%04X");

	m_fpu->state_add(*this, 5);

	set_icountptr(m_icount);
}

void sgi_ge5_device::device_reset()
{
	m_pc = 0;
	m_sp = 0;
	m_reptr = 0;
	m_memptr = 0;
	m_memptr_temp = 0;

	set_int(false);

	m_state = STALL;
}

device_memory_interface::space_config_vector sgi_ge5_device::memory_space_config() const
{
	return space_config_vector
	{
		std::make_pair(0, &m_code_config),
		std::make_pair(1, &m_data_config),
	};
}

std::unique_ptr<util::disasm_interface> sgi_ge5_device::create_disassembler()
{
	return std::make_unique<sgi_ge5_disassembler>();
}

void sgi_ge5_device::execute_run()
{
	while (m_icount > 0)
	{
		switch (m_state)
		{
		case STALL:
			m_icount = 0;
			break;

		case DECODE:
			debugger_instruction_hook(m_pc);

			// decode instruction
			decode();

			// execute secondary operation
			if (m_decode.secondary)
				secondary();

			// increment memptr
			if (m_decode.inc_memptr)
				m_memptr = (m_memptr + 1) & 0x7fff;

			// increment reptr
			if (m_decode.inc_reptr)
				m_reptr = (m_reptr + 1) & 0x3f;

			// update pc to next sequential instruction
			m_pc += m_decode.secondary ? 2 : 1;
			m_state = READ;
			break;

		case READ:
			m_state = CONTROL;

			// fetch source
			switch (m_decode.source)
			{
			case 0: // reptr
				if (m_reptr == 0x20 && !m_re_drq)
				{
					// re read stall
					m_state = READ;
					m_icount = 0;
				}
				else
					m_bus = m_re_r(m_reptr);
				break;
			case 1: // fifo
				if (m_fifo_empty())
				{
					// fifo read stall
					m_state = READ;
					m_icount = 0;
				}
				else
					m_bus = m_fifo_read();
				break;
			case 2: // memptr
				m_bus = space(1).read_dword(m_memptr);
				break;
			case 3: // fpu
				m_decode.fpu |= (2ULL << wtl3132_device::S_IOCT);
				m_bus = m_fpu_data;
				break;
			}
			break;

		case CONTROL:
			m_state = WRITE;

			switch (m_decode.control)
			{
			case 0x0: // sequential execution
				break;
			case 0x1: // unconditional branch
				m_pc = m_decode.immediate;
				break;
			case 0x2: // branch fpu less than
				if (m_fpu_c_latch)
					m_pc = m_decode.immediate;
				break;
			case 0x3: // branch fpu greater or equal
				if (!m_fpu_c_latch)
					m_pc = m_decode.immediate;
				break;
			case 0x4: // unconditional call
				m_stack[m_sp] = m_pc;
				m_sp = (m_sp + 1) & 7;
				m_pc = m_decode.immediate;
				break;
			case 0x5: // call fpu less than
				if (m_fpu_c_latch)
				{
					m_stack[m_sp] = m_pc;
					m_sp = (m_sp + 1) & 7;
					m_pc = m_decode.immediate;
				}
				break;
			case 0x6: // call fpu greater or equal
				if (!m_fpu_c_latch)
				{
					m_stack[m_sp] = m_pc;
					m_sp = (m_sp + 1) & 7;
					m_pc = m_decode.immediate;
				}
				break;
			case 0x7: // return
				m_sp = (m_sp + 7) & 7;
				m_pc = m_stack[m_sp];
				break;
			case 0x8: // fetch
				m_pc = (m_bus >> 31) & 0x1fe;

				debugger_exception_hook(m_bus >> 32);

				if (VERBOSE & LOG_TOKEN)
				{
					auto const suppressor(machine().disable_side_effects());

					u8 const token = m_bus >> 32;
					char const *string = nullptr;

					/*
					 * Magic numbers stored at specific data memory locations
					 * are used to identify specific microcode programs. Other
					 * variations may exist but are not known at this time.
					 */
					if (space(1).read_dword(0x50b) == 0x004d0003)
					{
						if (token < std::size(token_puc))
							string = token_puc[token];
					}
					else if (space(1).read_dword(0x50d) == 0x004d0005
						|| space(1).read_dword(0x536) == 0x12345678
						|| space(1).read_dword(0x540) == 0x12345678)
					{
						if (token < std::size(token_gl))
							string = token_gl[token];
					}
					else if (token < std::size(token_diag))
						string = token_diag[token];

					if (string)
						LOGMASKED(LOG_TOKEN, "fetch 0x%02x (%s)\n", token, string);
					else
						LOGMASKED(LOG_TOKEN, "fetch 0x%02x (unknown)\n", token);
				}
				else
					LOG("fetch 0x%02x\n", m_bus >> 32);

				// neutralize previous instruction writeback
				m_fpu->neut_w(0);
				break;
			case 0x9: // branch indirect
				// TODO: verify value
				m_pc = m_bus;
				break;
			case 0xa: // branch less than
				if (BIT(m_bus, 31))
					m_pc = m_decode.immediate;
				break;
			case 0xb: // branch greater or equal
				if (!BIT(m_bus, 31))
					m_pc = m_decode.immediate;
				break;
			case 0xc: // stall
				LOG("stall\n");
				m_state = STALL;
				m_icount = 0;
				break;
			case 0xd: // call less than
				if (BIT(m_bus, 31))
				{
					m_stack[m_sp] = m_pc;
					m_sp = (m_sp + 1) & 7;
					m_pc = m_decode.immediate;
				}
				break;
			case 0xe: // call greater or equal
				if (!BIT(m_bus, 31))
				{
					m_stack[m_sp] = m_pc;
					m_sp = (m_sp + 1) & 7;
					m_pc = m_decode.immediate;
				}
				break;
			case 0xf: // dma cycle
				if (--m_dma_count)
					m_pc -= m_decode.secondary ? 2 : 1;
				else
					LOGMASKED(LOG_DMA, "dma complete\n");
				break;
			}
			break;

		case WRITE:
			m_state = COMPLETE;

			// store destination
			switch (m_decode.destination)
			{
			case 0: // reptr
				if (m_reptr == 0x20 && !m_re_rdy && !m_re_drq)
				{
					// re write stall
					m_state = WRITE;
					m_icount = 0;
				}
				else if (m_reptr > 0x20 && !m_re_rdy)
				{
					// re unbuffered register write stall
					m_state = WRITE;
					m_icount = 0;
				}
				else
					m_re_w(m_reptr, m_bus);
				break;
			case 1: // TODO: bus?
				break;
			case 2: // memptr
				space(1).write_dword(m_memptr, m_bus);
				break;
			case 3: // fpu
				m_fpu->x_port_w(m_bus);
				m_decode.fpu |= (3ULL << wtl3132_device::S_IOCT);
				break;
			}
			break;

		case COMPLETE:
			m_state = DECODE;

			// restore memptr
			if (m_memptr_temp & 0x8000)
			{
				m_memptr = m_memptr_temp & 0x7fff;
				m_memptr_temp = 0;
			}

			// FIXME: fpu condition has additional 1 cycle latency
			m_fpu_c_latch = m_fpu_c;

			// fpu operation
			m_fpu->c_port_w(m_decode.fpu);
			m_fpu->clk_w(1);
			m_fpu->neut_w(1);

			m_icount--;
			break;
		}
	}
}

void sgi_ge5_device::decode()
{
	// fetch primary word
	u64 const primary = space(0).read_qword(m_pc + 0);

	// decode primary word
	m_decode.source = (primary >> 38) & 3;
	m_decode.inc_reptr = BIT(primary, 37);
	m_decode.secondary = BIT(primary, 36);
	m_decode.inc_memptr = BIT(primary, 35);
	m_decode.destination = (primary >> 33) & 3;
	m_decode.control = (primary >> 29) & 0xf;

	// decode fpu instruction
	m_decode.fpu = ((primary & 0x1fff'f800ULL) << 5) | ((primary & 0x0000'07ffULL) << 2) | (2ULL << wtl3132_device::S_ENCN);
	if (m_cwen)
		m_decode.fpu |= wtl3132_device::M_CWEN;

	// decode secondary word
	if (m_decode.secondary)
	{
		u64 const secondary = space(0).read_qword(m_pc + 1);

		m_decode.operation = (secondary >> 32) & 0xfe;
		m_decode.immediate = (secondary >> 19) & 0x3fff;
	}
}

void sgi_ge5_device::secondary()
{
	switch (m_decode.operation)
	{
	case 0x3c: // store register
		switch (m_decode.immediate)
		{
		case 0: // TODO: store pcsave?
			break;

		case 1: // store memptr
			m_bus = m_memptr;
			break;

		case 2: // store reptr
			m_bus = m_reptr;
		}
		break;

	case 0x8c: // load reptr
		m_reptr = m_bus & 0x3f;
		break;

	case 0x90: // load memptr; set reptr
		m_memptr = m_bus & 0x7fff;
		m_reptr = m_decode.immediate & 0x3f;
		break;

	case 0x9c: // set reptr
		m_reptr = m_decode.immediate & 0x3f;
		break;

	case 0xb0: // load memptr
		m_memptr = m_bus & 0x7fff;
		break;

	case 0xb4: // set memptr
		m_memptr = m_decode.immediate & 0x7fff;
		break;

	case 0xb6: // set memptr; set finish flag
		m_memptr = m_decode.immediate & 0x7fff;
		LOG("finish flag %d set (%s)\n", m_decode.immediate & 1, machine().describe_context());
		m_finish[m_decode.immediate & 1] = 1;
		break;

	case 0xb8: // set memptr_temp
		m_memptr_temp = m_memptr | 0x8000;
		m_memptr = m_decode.immediate & 0x7fff;
		break;

	case 0xbc: // nop?
		break;

	case 0xfc:
		switch (m_decode.immediate)
		{
		case 0: // TODO: assert dma ready
			LOGMASKED(LOG_DMA, "dma ready\n");
			break;

		default: // assert interrupt
			LOG("interrupt asserted\n");
			set_int(true);
			break;
		}
		break;

	case 0xfe:
		switch (m_decode.immediate)
		{
		case 0: // TODO: reset dma?
			LOGMASKED(LOG_DMA, "dma reset\n");
			break;

		default: // load dma count
			m_dma_count = m_bus;
			LOGMASKED(LOG_DMA, "dma count %d\n", m_dma_count);
			break;
		}
		break;

	default:
		logerror("unknown secondary operation 0x%02x\n", m_decode.operation);
		break;
	}
}

void sgi_ge5_device::command_w(offs_t offset, u16 data, u16 mem_mask)
{
	switch (offset)
	{
	case 0x00: // clear stall
		LOG("clear stall\n");
		m_state = DECODE;
		debugger_exception_hook(0);
		break;

	case 0x10: // set single step
	case 0x20: // clear single step
	case 0x30: // execute single step
		break;

	case 0x50: // clear interrupt
		LOG("interrupt cleared\n");
		set_int(false);
		break;
	}
}

template u32 sgi_ge5_device::code_r<false>(offs_t offset);
template u32 sgi_ge5_device::code_r<true>(offs_t offset);
template void sgi_ge5_device::code_w<false>(offs_t offset, u32 data, u32 mem_mask);
template void sgi_ge5_device::code_w<true>(offs_t offset, u32 data, u32 mem_mask);

template <bool High> u32 sgi_ge5_device::code_r(offs_t offset)
{
	m_pc = offset | offs_t(m_mar & 0x7f) << 8;
	u64 const data = space(0).read_qword(m_pc);

	return High ? u32(data >> 32) : u32(data);
}

template <bool High> void sgi_ge5_device::code_w(offs_t offset, u32 data, u32 mem_mask)
{
	m_pc = offset | offs_t(m_mar & 0x7f) << 8;

	LOGMASKED(LOG_MEMORY, "code_w msb %d offset 0x%08x data 0x%08x mask 0x%08x (%s)\n", High, m_pc, data, mem_mask, machine().describe_context());

	if (High)
	{
		u64 const mask = u64(mem_mask & 0x000000ffU) << 32;

		if (BIT(data, 8) && !BIT(data, 4))
		{
			// FIXME: this is required, but not very satisfactory
			LOGMASKED(LOG_MEMORY, "correcting unset secondary instruction bit\n");
			data |= 0x10;
		}

		space(0).write_qword(m_pc, u64(data) << 32, mask);
	}
	else
		space(0).write_qword(m_pc, data, mem_mask);
}

u32 sgi_ge5_device::data_r(offs_t offset)
{
	m_memptr = offset | offs_t(m_mar & 0x3f) << 8;

	return space(1).read_dword(m_memptr);
}

void sgi_ge5_device::data_w(offs_t offset, u32 data, u32 mem_mask)
{
	m_memptr = offset | offs_t(m_mar & 0x3f) << 8;

	space(1).write_dword(m_memptr, data, mem_mask);
}

offs_t sgi_ge5_disassembler::disassemble(std::ostream &stream, offs_t pc, data_buffer const &opcodes, data_buffer const &params)
{
	std::string src, dst;
	u16 immediate = 0;
	u32 flags = 0;

	u64 const primary = opcodes.r64(pc);

	if (BIT(primary, 36))
	{
		std::string prefix;

		u64 const secondary = opcodes.r64(pc + 1);
		u8 const opcode = (secondary >> 32) & 0xfe;
		immediate = (secondary >> 19) & 0x3fff;

		switch (opcode)
		{
		case 0x3c:
			switch (immediate)
			{
			case 0: prefix = std::string("STORE PCSAVE"); break;
			case 1: prefix = std::string("STORE MEMPTR"); break;
			case 2: prefix = std::string("STORE REPTR"); break;
			}
			break;
		case 0x8c: prefix = std::string("LOAD REPTR"); break;
		case 0x8e: prefix = std::string("LOAD MEMPTR"); break;
		case 0x90: prefix = util::string_format("LOAD MEMPTR; SET REPTR,0x%04x", immediate); break;
		case 0x9c: prefix = util::string_format("SET REPTR,0x%04x", immediate); break;
		case 0xb0: prefix = std::string("LOAD MEMPTR"); break;
		case 0xb4: prefix = util::string_format("SET MEMPTR,0x%04x", immediate); break;
		case 0xb6: prefix = util::string_format("SET MEMPTR,0x%04x; SET FF%d", immediate, immediate & 1); break;
		case 0xb8: prefix = util::string_format("SET MEMPTR_TEMP,0x%04x", immediate); break;
		case 0xbc: break;
		case 0xfc: prefix = immediate ? std::string("SET INT") : std::string("SET DMARDY"); break;
		case 0xfe: prefix = immediate ? std::string("LOAD DMACNT") : std::string("RESET DMA"); break;
		}

		if (!prefix.empty())
			stream << prefix << "; ";
	}

	u8 const opcode = (primary >> 32) & 0xff;

	u64 fpu_ctrl = 0; // ENCN=0, IOCT=0

	switch ((primary >> 29) & 0xf)
	{
	case 2:
	case 3:
	case 5:
	case 6:
		fpu_ctrl |= 0x1'0000'0000; // ENCN=1
		break;
	}

	// fstore
	if (((primary >> 38) & 3) == 3)
		fpu_ctrl |= 0x8000'0000;

	// fload
	if (((primary >> 33) & 3) == 3)
		fpu_ctrl |= 0xc000'0000;

	switch (opcode >> 6)
	{
	case 0: src = std::string("RE"); break;
	case 1: src = std::string("FIFO"); break;
	case 2: src = std::string("MEM"); break;
	case 3: src = std::string("FPU"); break;
	}

	switch ((opcode >> 1) & 3)
	{
	case 0: dst = std::string("RE"); break;
	case 1: dst = std::string("BUS"); break;
	case 2: dst = std::string("MEM"); break;
	case 3: dst = std::string("FPU"); break;
	}

	stream << wtl3132_device::disassemble(
		bitswap<34>((primary & 0x0fff'ffff) | fpu_ctrl,
			28, 27, 26,          // f
			25, 24, 23, 22, 21,  // aadd
			20, 19, 18, 17, 16,  // badd
			15, 14, 13, 12, 11,  // cadd
			29,                  // cwen*
			31, 30,              // ioct*
			10, 9, 8, 7, 6,      // dadd
			5, 4, 3,             // abin
			2, 1,                // adst
			0,                   // mbin
			33, 32))             // encn*
		<< "; ";

	switch ((primary >> 29) & 0x140)
	{
	case 0x040: stream << std::string("MEMPTR++; "); break;
	case 0x100: stream << std::string("REPTR++; "); break;
	case 0x140: stream << std::string("MEMPTR++; REPTR++; "); break;
	}

	stream << util::string_format("R:%s; W:%s", src, dst);

	// branch
	switch ((primary >> 29) & 0xf)
	{
	case 0x1: stream << util::string_format("; BRA 0x%04x", immediate); break;
	case 0x2: stream << util::string_format("; BLTF 0x%04x", immediate); break;
	case 0x3: stream << util::string_format("; BGEF 0x%04x", immediate); break;
	case 0x4: stream << util::string_format("; CALL 0x%04x", immediate); flags = STEP_OVER; break;
	case 0x5: stream << util::string_format("; CLTF 0x%04x", immediate); flags = STEP_OVER; break;
	case 0x6: stream << util::string_format("; CGEF 0x%04x", immediate); flags = STEP_OVER; break;
	case 0x7: stream << "; RET"; flags = STEP_OUT; break;
	case 0x8: stream << "; FETCH"; break;
	case 0x9: stream << util::string_format("; BRI 0x%04x", immediate); break;
	case 0xa: stream << util::string_format("; BLT 0x%04x", immediate); break;
	case 0xb: stream << util::string_format("; BGE 0x%04x", immediate); break;
	case 0xc: stream << "; STALL"; break;
	case 0xd: stream << util::string_format("; CLT 0x%04x", immediate); flags = STEP_OVER; break;
	case 0xe: stream << util::string_format("; CGE 0x%04x", immediate); flags = STEP_OVER; break;
	case 0xf: stream << "; DMA?"; flags = STEP_OVER; break;
	}

	return SUPPORTED | flags | (BIT(primary, 36) ? 2 : 1);
}
