mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-12-01 01:55:40 +00:00
e93c5d710f
PowerPC no longer clears its own IRQ line; it is now cleared by the IRQ controller when there are no more pending interrupts. Not all games clear DMA interrupts so it was necessary to tweak the 53C810 SCSI controller and the Real3D DMA interface to only fire interrupts if a certain register is correctly set. 53C810 has the documented DIEN (DMA Interrupt Enable) register; Real3D DMA seems to use the low bit of the dmaConfig register. Also I removed the net IRQ as no games seem to actually use it.
517 lines
15 KiB
C++
517 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 "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);
|
|
}
|
|
|
|
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");
|
|
}
|