Supermodel/Src/Model3/53C810.cpp
gm-matthew 3e394d1257 Only access SCSI device at 0xC0xxxxxx if it has been configured to do so
Fixes some step 1.5 games not working in last update
2024-05-04 19:43:02 +01:00

525 lines
16 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 "53C810.h"
#include <cstring>
#include "Supermodel.h"
#include "CPU/PowerPC/ppc.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.regDIEN, sizeof(Ctx.regDIEN));
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.regDIEN, sizeof(Ctx.regDIEN));
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
if (Ctx->regDIEN & 4)
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
if (Ctx.regDIEN & 8)
{
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 0x39: // DIEN
Ctx.regDIEN = 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: // DMODE
return Ctx.regDMODE;
case 0x39: // DIEN
return Ctx.regDIEN;
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);
if (reg == 4) // set base address of SCSI device
baseAddress = data & 0xFF;
}
UINT8 C53C810::GetBaseAddress(void)
{
return baseAddress;
}
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.regDIEN = 0;
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");
}