mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-24 22:55:40 +00:00
503 lines
15 KiB
C++
503 lines
15 KiB
C++
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011 Bart Trzynadlowski, Nik Henson
|
|
**
|
|
** This file is part of Supermodel.
|
|
**
|
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
|
** the terms of the GNU General Public License as published by the Free
|
|
** Software Foundation, either version 3 of the License, or (at your option)
|
|
** any later version.
|
|
**
|
|
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
|
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
** more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License along
|
|
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
/*
|
|
* 53C810.cpp
|
|
*
|
|
* Implementation of the C53C810 class: NCR 53C810 SCSI controller.
|
|
*
|
|
* Notes:
|
|
* ------
|
|
* - VF3 does something weird: it writes DSP (triggering automatic code
|
|
* execution because MAN=0) and THEN sets single step mode, expecting an
|
|
* interrupt to occur. I suspect this is incorrect operation and that
|
|
* the SCRIPTS processor either enters single-step mode while the memory
|
|
* transfer is underway (though it is unlikely because it's such a short
|
|
* transfer), or that single step can occur even when the device is "halted"
|
|
* (which would mean the SCRIPTS processor executes an invalid instruction
|
|
* once or twice without the VF3 SCSI driver noticing). Unfortunately, the
|
|
* former is not feasible to emulate. If automatic SCRIPTS execution is
|
|
* disabled when single-stepping is enabled, Scud Race breaks (glitchy,
|
|
* jerky graphics). Enabling automatic execution and also allowing single
|
|
* stepping to occur when the processor is halted seems to work, but it
|
|
* causes invalid instructions to be hit each time.
|
|
* - pg 2-22 (42) of the manual has description of how to clear interrupts.
|
|
*
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include "Supermodel.h"
|
|
|
|
|
|
/******************************************************************************
|
|
Save States
|
|
******************************************************************************/
|
|
|
|
void C53C810::SaveState(CBlockFile *SaveState)
|
|
{
|
|
SaveState->NewBlock("53C810", __FILE__);
|
|
SaveState->Write(Ctx.regs, sizeof(Ctx.regs));
|
|
SaveState->Write(&Ctx.regTEMP, sizeof(Ctx.regTEMP));
|
|
SaveState->Write(&Ctx.regDSP, sizeof(Ctx.regDSP));
|
|
SaveState->Write(&Ctx.regDSPS, sizeof(Ctx.regDSPS));
|
|
SaveState->Write(&Ctx.regDBC, sizeof(Ctx.regDBC));
|
|
SaveState->Write(&Ctx.regDCMD, sizeof(Ctx.regDCMD));
|
|
SaveState->Write(&Ctx.regDCNTL, sizeof(Ctx.regDCNTL));
|
|
SaveState->Write(&Ctx.regDMODE, sizeof(Ctx.regDMODE));
|
|
SaveState->Write(&Ctx.regDSTAT, sizeof(Ctx.regDSTAT));
|
|
SaveState->Write(&Ctx.regISTAT, sizeof(Ctx.regISTAT));
|
|
}
|
|
|
|
void C53C810::LoadState(CBlockFile *SaveState)
|
|
{
|
|
if (OKAY != SaveState->FindBlock("53C810"))
|
|
{
|
|
ErrorLog("Unable to load 53C810 state. Save state file is corrupt.");
|
|
return;
|
|
}
|
|
|
|
SaveState->Read(Ctx.regs, sizeof(Ctx.regs));
|
|
SaveState->Read(&Ctx.regTEMP, sizeof(Ctx.regTEMP));
|
|
SaveState->Read(&Ctx.regDSP, sizeof(Ctx.regDSP));
|
|
SaveState->Read(&Ctx.regDSPS, sizeof(Ctx.regDSPS));
|
|
SaveState->Read(&Ctx.regDBC, sizeof(Ctx.regDBC));
|
|
SaveState->Read(&Ctx.regDCMD, sizeof(Ctx.regDCMD));
|
|
SaveState->Read(&Ctx.regDCNTL, sizeof(Ctx.regDCNTL));
|
|
SaveState->Read(&Ctx.regDMODE, sizeof(Ctx.regDMODE));
|
|
SaveState->Read(&Ctx.regDSTAT, sizeof(Ctx.regDSTAT));
|
|
SaveState->Read(&Ctx.regISTAT, sizeof(Ctx.regISTAT));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
SCRIPTS Emulation
|
|
******************************************************************************/
|
|
|
|
static inline UINT32 Fetch(struct NCR53C810Context *Ctx, UINT32 offset)
|
|
{
|
|
UINT32 data = Ctx->Bus->Read32(Ctx->regDSP + offset);
|
|
return FLIPENDIAN32(data); // remember: bus is big endian, need to convert to little endian
|
|
}
|
|
|
|
//TODO: what happens if interrupt is executed in single step mode?
|
|
static bool SCRIPTS_Int_IntFly(struct NCR53C810Context *Ctx)
|
|
{
|
|
Ctx->halt = true; // halt SCRIPTS execution
|
|
Ctx->regISTAT |= 1; // DMA interrupt pending
|
|
Ctx->regDSTAT |= 4; // SCRIPTS interrupt instruction received
|
|
Ctx->IRQ->Assert(Ctx->scsiIRQ);
|
|
if ((Ctx->regDBC&0x100000)) // INTFLY
|
|
return ErrorLog("53C810 INTFLY instruction not emulated!");
|
|
// DSP not incremented (VF3 relies on this)
|
|
return OKAY;
|
|
}
|
|
|
|
static bool SCRIPTS_MoveMemory(struct NCR53C810Context *Ctx)
|
|
{
|
|
UINT32 src, dest;
|
|
unsigned numBytes, i;
|
|
|
|
// Get operands
|
|
src = Ctx->regDSPS;
|
|
dest = Ctx->regTEMP = Fetch(Ctx, 8); // word 3
|
|
numBytes = Ctx->regDBC;
|
|
// Not implemented: illegal instruction interrupt when src and dest are not aligned the same way
|
|
|
|
DebugLog("53C810: Move Memory %08X -> %08X, %X\n", src, dest, numBytes);
|
|
//if (dest==0x94000000)printf("53C810: Move Memory %08X -> %08X, %X\n", src, dest, numBytes);
|
|
|
|
// Perform a 32-bit copy if possible
|
|
for (i = 0; i < (numBytes/4); i++)
|
|
{
|
|
Ctx->Bus->Write32(dest, Ctx->Bus->Read32(src));
|
|
dest += 4;
|
|
src += 4;
|
|
}
|
|
|
|
// Finish off the last few odd bytes
|
|
numBytes &= 3;
|
|
while (numBytes)
|
|
{
|
|
Ctx->Bus->Write8(dest++, Ctx->Bus->Read8(src++));
|
|
--numBytes;
|
|
}
|
|
|
|
// Update registers
|
|
Ctx->regDBC = 0;
|
|
Ctx->regDSPS = src;
|
|
Ctx->regTEMP = dest;
|
|
Ctx->regDSP += 12;
|
|
|
|
return OKAY;
|
|
}
|
|
|
|
// Invalid instruction handler
|
|
static bool SCRIPTS_Invalid(struct NCR53C810Context *Ctx)
|
|
{
|
|
DebugLog("53C810 encountered an unrecognized instruction (%02X%06X, DSP=%08X)\n!", Ctx->regDCMD, Ctx->regDBC, Ctx->regDSP);
|
|
return FAIL;
|
|
}
|
|
|
|
void C53C810::Run(bool singleStep)
|
|
{
|
|
UINT32 op;
|
|
int i;
|
|
|
|
if (singleStep)// && !Ctx.halt)
|
|
{
|
|
// Fetch instruction (first two words are always fetched)
|
|
op = Fetch(&Ctx, 0); // word 1
|
|
Ctx.regDBC = op&0x00FFFFFF;
|
|
Ctx.regDCMD = (op>>24)&0xFF;
|
|
Ctx.regDSPS = Fetch(&Ctx, 4); // word 2
|
|
|
|
// Single step
|
|
OpTable[Ctx.regDCMD](&Ctx);
|
|
|
|
// Issue IRQ and finish
|
|
Ctx.regISTAT |= 1; // DMA interrupt pending
|
|
Ctx.regDSTAT |= 8; // single step interrupt
|
|
Ctx.IRQ->Assert(Ctx.scsiIRQ); // generate an interrupt
|
|
DebugLog("53C810: Asserted IRQ\n");
|
|
}
|
|
else
|
|
{
|
|
// Automatic mode: run (as long as the processor is not halted)
|
|
for (i = 0; (i < 100) && !Ctx.halt; i++)
|
|
{
|
|
// Fetch instruction (first two words are always fetched)
|
|
op = Fetch(&Ctx, 0); // word 1
|
|
Ctx.regDBC = op&0x00FFFFFF;
|
|
Ctx.regDCMD = (op>>24)&0xFF;
|
|
Ctx.regDSPS = Fetch(&Ctx, 4); // word 2
|
|
|
|
// Execute!
|
|
if (OpTable[Ctx.regDCMD](&Ctx) != OKAY)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert instructions into the LUT under control of the mask
|
|
void C53C810::Insert(UINT8 mask, UINT8 op, bool (*Handler)(struct NCR53C810Context *))
|
|
{
|
|
UINT32 i;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
if ((i&mask) == op)
|
|
OpTable[i] = Handler;
|
|
}
|
|
}
|
|
|
|
void C53C810::BuildOpTable(void)
|
|
{
|
|
Insert(0, 0, &SCRIPTS_Invalid);
|
|
Insert(0xE0, 0xC0, &SCRIPTS_MoveMemory);
|
|
Insert(0xF8, 0x98, &SCRIPTS_Int_IntFly);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Register and PCI Access Handlers
|
|
******************************************************************************/
|
|
|
|
void C53C810::WriteRegister(unsigned reg, UINT8 data)
|
|
{
|
|
if (reg >= 0x60)
|
|
{
|
|
ErrorLog("Write to invalid 53C810 register (%02X).", reg);
|
|
return;
|
|
}
|
|
|
|
DebugLog("53C810 write: %02X=%02X (PC=%08X, LR=%08X)\n", reg, data, ppc_get_pc(), ppc_get_lr());
|
|
|
|
// Dump everything into the register file
|
|
Ctx.regs[reg&0xFF] = data;
|
|
|
|
// Do something extra with the ones that we actually care about
|
|
// TO-DO: prevent invalid/reserved/read-only registers from being written?
|
|
switch(reg)
|
|
{
|
|
case 0x14: // ISTAT
|
|
Ctx.regISTAT = data;
|
|
DebugLog("ISTAT=%02X\n", data);
|
|
break;
|
|
case 0x1C: // TEMP 7-0
|
|
Ctx.regTEMP &= 0xFFFFFF00;
|
|
Ctx.regTEMP |= data;
|
|
break;
|
|
case 0x1D: // TEMP 15-8
|
|
Ctx.regTEMP &= 0xFFFF00FF;
|
|
Ctx.regTEMP |= (data<<8);
|
|
break;
|
|
case 0x1E: // TEMP 23-16
|
|
Ctx.regTEMP &= 0xFF00FFFF;
|
|
Ctx.regTEMP |= (data<<16);
|
|
break;
|
|
case 0x1F: // TEMP 31-24
|
|
Ctx.regTEMP &= 0x00FFFFFF;
|
|
Ctx.regTEMP |= (data<<24);
|
|
break;
|
|
case 0x24: // DBC 7-0
|
|
Ctx.regDBC &= 0xFFFFFF00;
|
|
Ctx.regDBC |= data;
|
|
break;
|
|
case 0x25: // DBC 15-8
|
|
Ctx.regDBC &= 0xFFFF00FF;
|
|
Ctx.regDBC |= (data<<8);
|
|
break;
|
|
case 0x26: // DBC 23-16
|
|
Ctx.regDBC &= 0xFF00FFFF;
|
|
Ctx.regDBC |= (data<<16);
|
|
break;
|
|
case 0x27: // DCMD
|
|
Ctx.regDCMD = data;
|
|
break;
|
|
case 0x2C: // DSP 7-0
|
|
Ctx.regDSP &= 0xFFFFFF00;
|
|
Ctx.regDSP |= data;
|
|
break;
|
|
case 0x2D: // DSP 15-8
|
|
Ctx.regDSP &= 0xFFFF00FF;
|
|
Ctx.regDSP |= (data<<8);
|
|
break;
|
|
case 0x2E: // DSP 23-16
|
|
Ctx.regDSP &= 0xFF00FFFF;
|
|
Ctx.regDSP |= (data<<16);
|
|
break;
|
|
case 0x2F: // DSP 31-24
|
|
Ctx.regDSP &= 0x00FFFFFF;
|
|
Ctx.regDSP |= (data<<24);
|
|
Ctx.halt = false; // writing this register un-halts 53C810 operation (pg.6-31 of LSI manual)
|
|
if (!(Ctx.regDMODE&1)) // if MAN=0, start SCRIPTS automatically
|
|
// To-Do: is this correct? Should single step really be tested first?
|
|
//if (!(Ctx.regDCNTL&0x10) && !(Ctx.regDMODE&1)) // if MAN=0 and not single stepping, start SCRIPTS automatically
|
|
{
|
|
DebugLog("53C810: Automatically starting (PC=%08X, LR=%08X, single step=%d)\n", ppc_get_pc(), ppc_get_lr(), !!(Ctx.regDCNTL&0x10));
|
|
Run(false); // automatic
|
|
}
|
|
break;
|
|
case 0x30: // DSPS 7-0
|
|
Ctx.regDSPS &= 0xFFFFFF00;
|
|
Ctx.regDSPS |= data;
|
|
break;
|
|
case 0x31: // DSPS 15-8
|
|
Ctx.regDSPS &= 0xFFFF00FF;
|
|
Ctx.regDSPS |= (data<<8);
|
|
break;
|
|
case 0x32: // DSPS 23-16
|
|
Ctx.regDSPS &= 0xFF00FFFF;
|
|
Ctx.regDSPS |= (data<<16);
|
|
break;
|
|
case 0x33: // DSPS 31-24
|
|
Ctx.regDSPS &= 0x00FFFFFF;
|
|
Ctx.regDSPS |= (data<<24);
|
|
break;
|
|
case 0x38: // DMODE
|
|
Ctx.regDMODE = data;
|
|
break;
|
|
case 0x3B: // DCNTL
|
|
Ctx.regDCNTL = data;
|
|
if ((Ctx.regDCNTL&0x14) == 0x14) // single step
|
|
{
|
|
DebugLog("53C810: single step: %08X, (halt=%d)\n", Ctx.regDSP, Ctx.halt);
|
|
Run(true);
|
|
}
|
|
else if ((Ctx.regDCNTL&0x04)) // start DMA bit
|
|
{
|
|
DebugLog("53C810: Manually starting\n");
|
|
Run(false);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
UINT8 C53C810::ReadRegister(unsigned reg)
|
|
{
|
|
UINT8 ret;
|
|
|
|
if (reg >= 0x60)
|
|
{
|
|
ErrorLog("Read from invalid 53C810 register (%02X).", reg);
|
|
return 0;
|
|
}
|
|
|
|
DebugLog("53C810 read: %02X (PC=%08X, LR=%08X)\n", reg, ppc_get_pc(), ppc_get_lr());
|
|
|
|
// Some registers require special handling
|
|
switch(reg)
|
|
{
|
|
case 0x0C: // DSTAT
|
|
// For now, we don't generate stacked interrupts, so always clear IRQ status
|
|
ret = Ctx.regDSTAT;
|
|
//TO-DO: manual says these should be cleared here but MAME never clears them. What's up with that?
|
|
Ctx.regISTAT &= 0xFE; // clear DIP bit (DMA interrupt)
|
|
Ctx.regDSTAT &= 0xF7; // clear SSI (single step interrupt)
|
|
//Ctx.regISTAT |= 1; // doing this is another way to fix VF3
|
|
//Ctx.regDSTAT |= 8;
|
|
Ctx.IRQ->Deassert(Ctx.scsiIRQ);
|
|
//DebugLog("53C810: DSTAT read\n");
|
|
return ret;
|
|
case 0x14: // ISTAT
|
|
//DebugLog("53C810: ISTAT read\n");
|
|
return Ctx.regISTAT;
|
|
case 0x1C: // TEMP 7-0
|
|
return Ctx.regTEMP&0xFF;
|
|
case 0x1D: // TEMP 15-8
|
|
return (Ctx.regTEMP>>8)&0xFF;
|
|
case 0x1E: // TEMP 23-16
|
|
return (Ctx.regTEMP>>16)&0xFF;
|
|
case 0x1F: // TEMP 31-24
|
|
return (Ctx.regTEMP>>24)&0xFF;
|
|
case 0x24: // DBC 7-0
|
|
return Ctx.regDBC&0xFF;
|
|
case 0x25: // DBC 15-8
|
|
return (Ctx.regDBC>>8)&0xFF;
|
|
case 0x26: // DBC 23-16
|
|
return (Ctx.regDBC>>16)&0xFF;
|
|
case 0x27: // DCMD
|
|
return Ctx.regDCMD;
|
|
case 0x2C: // DSP 7-0
|
|
return Ctx.regDSP&0xFF;
|
|
case 0x2D: // DSP 15-8
|
|
return (Ctx.regDSP>>8)&0xFF;
|
|
case 0x2E: // DSP 23-16
|
|
return (Ctx.regDSP>>16)&0xFF;
|
|
case 0x2F: // DSP 31-24
|
|
return (Ctx.regDSP>>24)&0xFF;
|
|
case 0x30: // DSPS 7-0
|
|
return Ctx.regDSPS&0xFF;
|
|
case 0x31: // DSPS 15-8
|
|
return (Ctx.regDSPS>>8)&0xFF;
|
|
case 0x32: // DSPS 23-16
|
|
return (Ctx.regDSPS>>16)&0xFF;
|
|
case 0x33: // DSPS 31-24
|
|
return (Ctx.regDSPS>>24)&0xFF;
|
|
case 0x38:
|
|
return Ctx.regDMODE;
|
|
case 0x3B: // DCNTL
|
|
return Ctx.regDCNTL;
|
|
default: // get it from the register file
|
|
break;
|
|
}
|
|
|
|
// Register file should be up to date
|
|
return Ctx.regs[reg&0xFF];
|
|
}
|
|
|
|
UINT32 C53C810::ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
|
|
{
|
|
UINT32 d;
|
|
|
|
if ((bits==8))
|
|
{
|
|
DebugLog("53C810 %d-bit PCI read request for reg=%02X\n", bits, reg);
|
|
return 0;
|
|
}
|
|
|
|
switch (reg)
|
|
{
|
|
case 0x00: // Device ID and Vendor ID
|
|
d = FLIPENDIAN32(0x00011000); // 0x1000 = LSI Logic, 0x0001 = 53c810a
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
d >>= (3-offset)*8; // offset will be 0-3; select appropriate byte
|
|
d &= 0xFF;
|
|
break;
|
|
case 16:
|
|
d >>= (2-offset)*8; // offset will be 0 or 2 only; select either high or low word
|
|
d &= 0xFFFF;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return d;
|
|
default:
|
|
DebugLog("53C810 PCI read request for reg=%02X (%d-bit)\n", reg, bits);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void C53C810::WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
|
|
{
|
|
DebugLog("53C810 PCI %d-bit write request for reg=%02X, data=%08X\n", bits, reg, data);
|
|
}
|
|
|
|
void C53C810::Reset(void)
|
|
{
|
|
memset(Ctx.regs, 0, sizeof(Ctx.regs));
|
|
Ctx.regs[0x00] = 0xC0; // SCNTL0
|
|
Ctx.regs[0x0C] = 0x80; // DSTAT
|
|
Ctx.regs[0x0F] = 0x02; // SSTAT2
|
|
Ctx.regs[0x18] = 0xFF; // reserved
|
|
Ctx.regs[0x19] = 0xF0; // CTEST1
|
|
Ctx.regs[0x1A] = 0x01; // CTEST2
|
|
Ctx.regs[0x46] = 0x60; // MACNTL
|
|
Ctx.regs[0x47] = 0x0F; // GPCNTL
|
|
Ctx.regs[0x4C] = 0x03; // STEST0
|
|
Ctx.regTEMP = 0;
|
|
Ctx.regDSP = 0;
|
|
Ctx.regDSPS = 0;
|
|
Ctx.regDBC = 0;
|
|
Ctx.regDCMD = 0;
|
|
Ctx.regDCNTL = 0;
|
|
Ctx.regDMODE = 0;
|
|
Ctx.regDSTAT = 0x80; // DMA FIFO empty
|
|
Ctx.regISTAT = 0;
|
|
Ctx.halt = false;
|
|
|
|
DebugLog("53C810 reset\n");
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Configuration, Initialization, and Shutdown
|
|
******************************************************************************/
|
|
|
|
void C53C810::Init(IBus *BusObjectPtr, CIRQ *IRQObjectPtr, unsigned scsiIRQBit)
|
|
{
|
|
Ctx.Bus = BusObjectPtr;
|
|
Ctx.IRQ = IRQObjectPtr;
|
|
Ctx.scsiIRQ = scsiIRQBit;
|
|
}
|
|
|
|
C53C810::C53C810(void)
|
|
{
|
|
BuildOpTable();
|
|
Ctx.Bus = NULL;
|
|
Ctx.IRQ = NULL;
|
|
scsiIRQ = 0;
|
|
DebugLog("Built 53C810\n");
|
|
}
|
|
|
|
C53C810::~C53C810(void)
|
|
{
|
|
Ctx.Bus = NULL;
|
|
Ctx.IRQ = NULL;
|
|
DebugLog("Destroyed 53C810\n");
|
|
}
|