- New work-in-progress frame timing code (disabled by default, compile with NEW_FRAME_TIMING defined to activate it)

- New JTAG emulation, moved into its own class, CJTAG
- Removed game-specific sun clamp hacks from CNew3D (JTAG and Real3D emulation will call the appropriate method to configure this at run-time)
- Removed JTAG from Real3D save state data and reused some of that space for new state variables having to do with the internal JTAG-based config as well as new frame timing state variables
This commit is contained in:
Bart Trzynadlowski 2017-09-24 20:52:48 +00:00
parent c4f8471d7a
commit 55bb02d4e5
10 changed files with 614 additions and 245 deletions

View file

@ -90,7 +90,7 @@ endif
HEADERS = Src/Supermodel.h Src/Games.h Src/OSD/SDL/Types.h
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/JTAG.o $(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
$(OBJ_DIR)/Render2D.o $(OBJ_DIR)/TileGen.o \
$(OBJ_DIR)/Model3.o $(OBJ_DIR)/ppc.o $(OBJ_DIR)/Main.o $(OBJ_DIR)/Audio.o $(OBJ_DIR)/Thread.o $(OBJ_DIR)/SoundBoard.o \
@ -106,6 +106,13 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/Crypto.o \
$(OBJ_DIR)/Format.o \
$(OBJ_DIR)/Logger.o \
$(OBJ_DIR)/NewConfig.o \
$(OBJ_DIR)/ByteSwap.o \
$(OBJ_DIR)/ConfigBuilders.o \
$(OBJ_DIR)/GameLoader.o \
$(OBJ_DIR)/tinyxml2.o \
$(OBJ_DIR)/ROMSet.o \
$(OBJ_DIR)/BitRegister.o \
$(OBJ_DIR)/SDLMain_tmpl.o
# If built-in debugger enabled, include all debugging classes

View file

@ -97,7 +97,7 @@ endif
HEADERS = Src/Supermodel.h Src/OSD/SDL/Types.h
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/JTAG.o $(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o $(OBJ_DIR)/R3DScrollFog.o \
$(OBJ_DIR)/Render2D.o $(OBJ_DIR)/TileGen.o \
$(OBJ_DIR)/Model3.o $(OBJ_DIR)/ppc.o $(OBJ_DIR)/Main.o $(OBJ_DIR)/Audio.o $(OBJ_DIR)/Thread.o $(OBJ_DIR)/SoundBoard.o \
@ -118,7 +118,8 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/ConfigBuilders.o \
$(OBJ_DIR)/GameLoader.o \
$(OBJ_DIR)/tinyxml2.o \
$(OBJ_DIR)/ROMSet.o
$(OBJ_DIR)/ROMSet.o \
$(OBJ_DIR)/BitRegister.o
# If built-in debugger enabled, include all debugging classes
ifeq ($(strip $(ENABLE_DEBUGGER)),yes)

View file

@ -121,7 +121,7 @@ endif
#
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/JTAG.o $(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
$(OBJ_DIR)/R3DScrollFog.o \
$(OBJ_DIR)/Render2D.o $(OBJ_DIR)/TileGen.o \
@ -138,7 +138,7 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/Crypto.o \
$(OBJ_DIR)/Logger.o \
$(OBJ_DIR)/tinyxml2.o \
$(OBJ_DIR)/ByteSwap.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/ConfigBuilders.o $(OBJ_DIR)/GameLoader.o $(OBJ_DIR)/ROMSet.o
$(OBJ_DIR)/ByteSwap.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/ConfigBuilders.o $(OBJ_DIR)/GameLoader.o $(OBJ_DIR)/ROMSet.o $(OBJ_DIR)/BitRegister.o
# If built-in debugger enabled, include all debugging classes

View file

@ -786,18 +786,6 @@ void CNew3D::RenderViewport(UINT32 addr)
vp->lightingParams[4] = (float)((vpnode[0x24] >> 8) & 0xFF) * (1.0f / 255.0f); // ambient intensity
vp->lightingParams[5] = 0.0; // reserved
// this is a hack because we haven't yet found in memory where these are set
if (m_gameName == "dayto2pe"||
m_gameName == "lamachin"||
m_gameName == "von2" ||
m_gameName == "von254g" ||
m_gameName == "von2a") {
m_sunClamp = false;
}
else {
m_sunClamp = true;
}
vp->sunClamp = m_sunClamp;
vp->intensityClamp = (m_step == 0x10); // just step 1.0 ?
vp->hardwareStep = m_step;

216
Src/Model3/JTAG.cpp Normal file
View file

@ -0,0 +1,216 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011-2017 Bart Trzynadlowski, Nik Henson, Ian Curtis
**
** 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/>.
**/
/*
* JTAG.cpp
*
* Model 3's JTAG test access port (TAP). This is accessed through the system
* register space and is connected to the Real3D chipset and possibly other
* devices. Hence, it is emulated as an independent module.
*
* It is unclear which exact JTAG standard the device conforms to (and it
* probably doesn't matter), so we assume IEEE 1149.1-1990 here.
*/
#include "Supermodel.h"
#include "Model3/JTAG.h"
#include <iostream>
// Finite state machine. Each state has two possible next states.
const CJTAG::State CJTAG::s_fsm[][2] =
{
// tms = 0 tms = 1
{ RunTestIdle, TestLogicReset }, // 0 Test-Logic/Reset
{ RunTestIdle, SelectDRScan }, // 1 Run-Test/Idle
{ CaptureDR, SelectIRScan }, // 2 Select-DR-Scan
{ ShiftDR, Exit1DR }, // 3 Capture-DR
{ ShiftDR, Exit1DR }, // 4 Shift-DR
{ PauseDR, UpdateDR }, // 5 Exit1-DR
{ PauseDR, Exit2DR }, // 6 Pause-DR
{ ShiftDR, UpdateDR }, // 7 Exit2-DR
{ RunTestIdle, SelectDRScan }, // 8 Update-DR
{ CaptureIR, TestLogicReset }, // 9 Select-IR-Scan
{ ShiftIR, Exit1IR }, // 10 Capture-IR
{ ShiftIR, Exit1IR }, // 11 Shift-IR
{ PauseIR, UpdateIR }, // 12 Exit1-IR
{ PauseIR, Exit2IR }, // 13 Pause-IR
{ ShiftIR, UpdateIR }, // 14 Exit2-IR
{ RunTestIdle, SelectDRScan } // 15 Update-IR
};
static const char *s_state[] =
{
"Test-Logic/Reset",
"Run-Test/Idle",
"Select-DR-Scan",
"Capture-DR",
"Shift-DR",
"Exit1-DR",
"Pause-DR",
"Exit2-DR",
"Update-DR",
"Select-IR-Scan",
"Capture-IR",
"Shift-IR",
"Exit1-IR",
"Pause-IR",
"Exit2-IR",
"Update-IR"
};
static void SaveBitRegister(CBlockFile *SaveState, const Util::BitRegister &reg)
{
uint16_t size = reg.Size() + 1; // include null terminator
SaveState->Write(&size, sizeof(size));
SaveState->Write(reg.ToBinaryString());
}
static void LoadBitRegister(CBlockFile *SaveState, Util::BitRegister *reg)
{
uint16_t size;
SaveState->Read(&size, sizeof(size));
char *str = new char[size];
SaveState->Read(str, size);
reg->Set(str);
}
void CJTAG::SaveState(CBlockFile *SaveState)
{
SaveState->NewBlock("JTAG", __FILE__);
SaveBitRegister(SaveState, m_instructionShiftReg);
SaveBitRegister(SaveState, m_dataShiftReg);
SaveState->Write(&m_instructionReg, sizeof(m_instructionReg));
SaveState->Write(&m_state, sizeof(m_state));
SaveState->Write(&m_lastTck, sizeof(m_lastTck));
SaveState->Write(&m_tdo, sizeof(m_tdo));
}
void CJTAG::LoadState(CBlockFile *SaveState)
{
if (OKAY != SaveState->FindBlock("JTAG"))
{
ErrorLog("Unable to load JTAG state. Save state file is corrupt.");
return;
}
LoadBitRegister(SaveState, &m_instructionShiftReg);
LoadBitRegister(SaveState, &m_dataShiftReg);
SaveState->Read(&m_instructionReg, sizeof(m_instructionReg));
SaveState->Read(&m_state, sizeof(m_state));
SaveState->Read(&m_lastTck, sizeof(m_lastTck));
SaveState->Read(&m_tdo, sizeof(m_tdo));
}
uint8_t CJTAG::Read()
{
return m_tdo;
}
void CJTAG::LoadASICIDCodes()
{
/*
* ID code retrieval has not been carefully studied but based on observation,
* it appears that the ID codes are loaded on logic reset (Step 2.x games and
* some 1.x games rely on this) as well as instruction 0x06318fc63fff. Some
* games rely on both (e.g., von2).
*/
m_dataShiftReg.SetZeros();
m_dataShiftReg.Insert(2 + 0*32 + 0, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Jupiter)));
m_dataShiftReg.Insert(2 + 1*32 + 0, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Mercury)));
m_dataShiftReg.Insert(2 + 2*32 + 0, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Venus)));
m_dataShiftReg.Insert(2 + 3*32 + 0, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Earth)));
m_dataShiftReg.Insert(2 + 4*32 + 1, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Mars)));
m_dataShiftReg.Insert(2 + 5*32 + 1, Util::Hex(m_real3D.GetASICIDCode(CReal3D::ASIC::Mars)));
}
void CJTAG::Write(uint8_t tck, uint8_t tms, uint8_t tdi, uint8_t trst)
{
tck = !!tck;
tms = !!tms;
tdi = !!tdi;
trst = !!trst;
//TODO: is trst used anywhere? If so, need to emulate.
//if (!trst)
// printf("TRST=0\n");
//printf("%d trst=%d tms=%d tdi=%d\n", tck, trst, tms, tdi);
// Transitions occur on rising edge
uint8_t lastTck = m_lastTck;
m_lastTck = tck;
if (!tck || lastTck != 0)
return;
// Current state logic
switch (m_state)
{
default:
break;
case State::TestLogicReset:
LoadASICIDCodes();
break;
case State::CaptureDR:
if (m_instructionReg == Instruction::ReadASICIDCodes)
LoadASICIDCodes();
break;
case State::ShiftDR:
m_tdo = m_dataShiftReg.ShiftOutRight(tdi);
break;
case State::UpdateDR:
if (m_instructionReg == Instruction::SetReal3DRenderConfig0 || m_instructionReg == Instruction::SetReal3DRenderConfig1)
{
uint64_t data = m_dataShiftReg.GetBits(0, 42);
m_real3D.WriteJTAGRegister(m_instructionReg, data);
}
//std::cout << "DR = " << m_dataShiftReg << std::endl;
break;
case State::CaptureIR:
// Load lower 2 bits with 01 as per IEEE 1149.1-1990
m_instructionShiftReg.Insert(44, "01");
break;
case State::ShiftIR:
m_tdo = m_instructionShiftReg.ShiftOutRight(tdi);
break;
case State::UpdateIR:
// Latch the instruction register (technically, this should occur on
// falling edge of clock as per the spec)
m_instructionReg = m_instructionShiftReg.GetBits();
//std::cout << "IR = " << Util::Hex(m_instructionReg, 12) << std::endl;
break;
}
// Go to next state
m_state = s_fsm[m_state][tms];
//printf(" -> %s\n", s_state[m_state]);
}
void CJTAG::Reset()
{
m_state = State::TestLogicReset;
DebugLog("JTAG reset\n");
}
CJTAG::CJTAG(CReal3D &real3D)
: m_real3D(real3D),
m_instructionShiftReg(46, 0),
m_dataShiftReg(197, 0)
{
DebugLog("Built JTAG logic\n");
}

86
Src/Model3/JTAG.h Normal file
View file

@ -0,0 +1,86 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011-2017 Bart Trzynadlowski, Nik Henson, Ian Curtis
**
** 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/>.
**/
/*
* JTAG.h
*
* Header file defining the CJTAG class: the Model 3's JTAG device.
*/
#ifndef INCLUDED_JTAG_H
#define INCLUDED_JTAG_H
#include "Util/BitRegister.h"
class CReal3D;
class CJTAG
{
public:
enum Instruction: uint64_t
{
ReadASICIDCodes = 0x06318fc63fff,
SetReal3DRenderConfig0 = 0x3fffffd1ffff,
SetReal3DRenderConfig1 = 0x3ffffffe8fff
};
void SaveState(CBlockFile *SaveState);
void LoadState(CBlockFile *SaveState);
uint8_t Read();
void Write(uint8_t tck, uint8_t tms, uint8_t tdi, uint8_t trst);
void Reset();
CJTAG(CReal3D &real3D);
private:
void LoadASICIDCodes();
enum State: uint8_t
{
TestLogicReset, // 0
RunTestIdle, // 1
SelectDRScan, // 2
CaptureDR, // 3
ShiftDR, // 4
Exit1DR, // 5
PauseDR, // 6
Exit2DR, // 7
UpdateDR, // 8
SelectIRScan, // 9
CaptureIR, // 10
ShiftIR, // 11
Exit1IR, // 12
PauseIR, // 13
Exit2IR, // 14
UpdateIR // 15
};
static const State s_fsm[][2];
CReal3D &m_real3D;
Util::BitRegister m_instructionShiftReg;
Util::BitRegister m_dataShiftReg;
uint64_t m_instructionReg = 0;
State m_state = State::TestLogicReset;
uint8_t m_lastTck = 0;
uint8_t m_tdo = 0;
};
#endif // INCLUDED_JTAG_H

View file

@ -814,7 +814,7 @@ UINT8 CModel3::ReadSystemRegister(unsigned reg)
//DebugLog("System register %02X read\n", reg);
return 0xFF;
case 0x10: // JTAG Test Access Port
return (GPU.ReadTAP()<< 5);
return m_jtag.Read() << 5;
default:
//DebugLog("System register %02X read\n", reg);
break;
@ -839,8 +839,14 @@ void CModel3::WriteSystemRegister(unsigned reg, UINT8 data)
DebugLog("IRQ ACK? %02X=%02X\n", reg, data);
break;
case 0x0C: // JTAG Test Access Port
GPU.WriteTAP((data>>6)&1,(data>>2)&1,(data>>5)&1,(data>>7)&1); // TCK, TMS, TDI, TRST
{
uint8_t tck = (data >> 6) & 1;
uint8_t tms = (data >> 2) & 1;
uint8_t tdi = (data >> 5) & 1;
uint8_t trst = (data >> 7) & 1; // not sure about this one (trst not required to exist by JTAG spec)
m_jtag.Write(tck, tms, tdi, trst);
break;
}
case 0x0D:
case 0x0E:
case 0x0F:
@ -1609,6 +1615,7 @@ void CModel3::SaveState(CBlockFile *SaveState)
SoundBoard.SaveState(SaveState); // also saves DSB state
DriveBoard.SaveState(SaveState);
m_cryptoDevice.SaveState(SaveState);
m_jtag.SaveState(SaveState);
}
void CModel3::LoadState(CBlockFile *SaveState)
@ -1647,6 +1654,7 @@ void CModel3::LoadState(CBlockFile *SaveState)
SoundBoard.LoadState(SaveState);
DriveBoard.LoadState(SaveState);
m_cryptoDevice.LoadState(SaveState);
m_jtag.LoadState(SaveState);
}
void CModel3::SaveNVRAM(CBlockFile *NVRAM)
@ -1750,6 +1758,136 @@ ThreadError:
m_multiThreaded = false;
}
#ifdef NEW_FRAME_TIMING
void CModel3::RunMainBoardFrame(void)
{
if (!gpusReady)
return;
UINT32 start = CThread::GetTicks();
/*
* Display timing is assumed to be driven by the System 24 tile generator
* chip. Charles MacDonald's notes state:
*
* 656 pixels per scanline:
*
* 69 pixels from /HSYNC high to /BLANK high (left border)
* 496 pixels from /BLANK high to /BLANK low (active display)
* 43 pixels from /BLANK low to /HSYNC low (right border)
* 48 pixels from /HSYNC low to /HSYNC high (horizontal sync. pulse)
*
* 424 scanlines per frame:
*
* 25 scanlines from /VSYNC high to /BLANK high (top border)
* 384 scanlines from /BLANK high to /BLANK low (active display)
* 11 scanlines from /BLANK low to /VSYNC low (bottom border)
* 4 scanlines from /VSYNC low to /VSYNC high (vertical sync. pulse)
*
* The pixel clock is 16 MHz, giving an effetive frame rate of 57.52
* frames per second.
*/
float ppcCycles = m_config["PowerPCFrequency"].ValueAs<unsigned>() * 1e6;
float frameRate = 60; // actually, 57.52 Hz
float frameCycles = ppcCycles / frameRate;
float lineCycles = frameCycles / 424; // 424 scanlines per tile generator frame
unsigned topBorderLines = 25;
unsigned activeLines = 384;
unsigned bottomBorderLines = 11;
unsigned vblLines = 4;
/*
* Scale PPC timer ratio according to speed at which the PowerPC is being
* emulated so that the observed running frequency of the PPC timer registers
* is more or less correct. This is needed to get the Virtua Striker 2
* series of games running at the right speed (they are too slow otherwise).
* Other games appear to not be affected by this ratio so much as their
* running speed depends more on the timing of the Real3D status bit below.
*/
ppc_set_timer_ratio(ppc_get_bus_freq_multipler() * 2 * ppcCycles / ppc_get_cycles_per_sec());
/*
* Active frame + bottom border. We treat this as one large chunk save for
* the sound IRQs, which we attempt to process first.
*
* Sound:
*
* Bit 0x20 of the MIDI control port appears to enable periodic interrupts,
* which are used to send MIDI commands. Often games will write 0x27, send
* a series of commands, and write 0x06 to stop. Other games, like Star
* Wars Trilogy and Sega Rally 2, will enable interrupts at the beginning
* by writing 0x37 and will disable/enable interrupts to control command
* output.
*/
unsigned remainingCycles = unsigned(activeLines * lineCycles);
unsigned irqCount = 0;
while ((midiCtrlPort & 0x20)) // 0x27 triggers IRQ sequence, 0x06 stops it
{
// Don't waste time firing MIDI interrupts if game has disabled them
if ((IRQ.ReadIRQEnable()&0x40) == 0)
break;
// Process MIDI interrupt
IRQ.Assert(0x40);
ppc_execute(200); // give PowerPC time to acknowledge IRQ
IRQ.Deassert(0x40);
ppc_execute(200); // acknowledge that IRQ was deasserted (TODO: is this really needed?)
remainingCycles -= 400;
++irqCount;
if (irqCount > 128)
{
//printf("\tMIDI FIFO OVERFLOW! (IRQEn=%02X, IRQPend=%02X)\n", IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
break;
}
}
ppc_execute(remainingCycles/2);
GPU.BeginVBlank(0); // TODO: if this actually occurs before VBL, need to rename this function
ppc_execute(remainingCycles/2);
ppc_execute(bottomBorderLines * lineCycles);
/*
* VBlank period
*/
TileGen.BeginVBlank();
//GPU.BeginVBlank(0); //TODO: remove this parameter
IRQ.Assert(0x02);
ppc_execute(vblLines * lineCycles);
IRQ.Deassert(0x02); // unnecessary because manually cleared, also probably self-clears within 1 line
GPU.EndVBlank();
TileGen.EndVBlank();
/*
* Top border/end of previous frame's VBlank: assuming here (without
* sufficient evidence) that IRQ 1 is end-of-VBL. It's certainly triggered
* once per frame, like IRQ 2, according to code I ran on a real board.
*
* We execute a number of miscellaneous, unknown IRQs on the last line of the
* top border, again without any proper justification other than to space
* them apart from known IRQs. Games will be doing most of their processing
* post-VBL (during the border and active display phases), so it seems like a
* good time to raise IRQs.
*/
// One line for IRQ 1, assuming this is some VBL-related signal
IRQ.Assert(0x01);
ppc_execute(1 * lineCycles);
IRQ.Deassert(0x01);
// The bulk of the border lines
ppc_execute ((topBorderLines - 2) * lineCycles);
// Reserve one line for miscellaneous IRQs
IRQ.Assert(0x0C);
ppc_execute(1 * lineCycles);
IRQ.Deassert(0x0C);
timings.ppcTicks = CThread::GetTicks() - start;
}
#endif
#ifndef NEW_FRAME_TIMING
void CModel3::RunMainBoardFrame(void)
{
UINT32 start = CThread::GetTicks();
@ -1847,6 +1985,7 @@ void CModel3::RunMainBoardFrame(void)
timings.ppcTicks = CThread::GetTicks() - start;
}
#endif
void CModel3::SyncGPUs(void)
{
@ -2518,6 +2657,7 @@ void CModel3::Reset(void)
TileGen.Reset();
GPU.Reset();
SoundBoard.Reset();
m_jtag.Reset();
if (DriveBoard.IsAttached())
DriveBoard.Reset();
@ -2818,7 +2958,8 @@ CModel3::CModel3(const Util::Config::Node &config)
TileGen(config),
GPU(config),
SoundBoard(config),
DriveBoard(config)
DriveBoard(config),
m_jtag(GPU)
{
// Initialize pointers so dtor can know whether to free them
memoryPool = NULL;

View file

@ -29,6 +29,7 @@
#define INCLUDED_MODEL3_H
#include "Model3/IEmulator.h"
#include "Model3/JTAG.h"
#include "Model3/Crypto.h"
#include "Util/NewConfig.h"
@ -282,6 +283,7 @@ private:
CDSB *DSB; // Digital Sound Board (type determined dynamically at load time)
CDriveBoard DriveBoard; // Drive board
CCrypto m_cryptoDevice; // Encryption device
CJTAG m_jtag; // JTAG interface
};

View file

@ -42,6 +42,7 @@
*/
#include "Supermodel.h"
#include "Model3/JTAG.h"
#include "Util/BMPFile.h"
#include <cstring>
@ -70,6 +71,9 @@
#define MEM_POOL_SIZE_DIRTY (DIRTY_SIZE(MEM_POOL_SIZE_RO))
#define MEMORY_POOL_SIZE (MEM_POOL_SIZE_RW+MEM_POOL_SIZE_RO+MEM_POOL_SIZE_DIRTY)
static void UpdateRenderConfig(IRender3D *Render3D, uint64_t internalRenderConfig[]);
/******************************************************************************
Save States
******************************************************************************/
@ -90,12 +94,15 @@ void CReal3D::SaveState(CBlockFile *SaveState)
SaveState->Write(&dmaStatus, sizeof(dmaStatus));
SaveState->Write(&dmaConfig, sizeof(dmaConfig));
SaveState->Write(&tapCurrentInstruction, sizeof(tapCurrentInstruction));
SaveState->Write(&tapIR, sizeof(tapIR));
SaveState->Write(tapID, sizeof(tapID));
SaveState->Write(&tapIDSize, sizeof(tapIDSize));
SaveState->Write(&tapTDO, sizeof(tapTDO));
SaveState->Write(&tapState, sizeof(tapState));
// These used to be occupied by JTAG state
SaveState->Write(m_internalRenderConfig, sizeof(m_internalRenderConfig));
SaveState->Write(commandPortWritten);
SaveState->Write(&m_pingPong, sizeof(m_pingPong));
for (int i = 0; i < 39; i++)
{
uint8_t nul = 0;
SaveState->Write(&nul, sizeof(uint8_t));
}
SaveState->Write(&m_vromTextureFIFOIdx, sizeof(m_vromTextureFIFOIdx));
}
@ -125,12 +132,15 @@ void CReal3D::LoadState(CBlockFile *SaveState)
SaveState->Read(&dmaStatus, sizeof(dmaStatus));
SaveState->Read(&dmaConfig, sizeof(dmaConfig));
SaveState->Read(&tapCurrentInstruction, sizeof(tapCurrentInstruction));
SaveState->Read(&tapIR, sizeof(tapIR));
SaveState->Read(tapID, sizeof(tapID));
SaveState->Read(&tapIDSize, sizeof(tapIDSize));
SaveState->Read(&tapTDO, sizeof(tapTDO));
SaveState->Read(&tapState, sizeof(tapState));
SaveState->Read(m_internalRenderConfig, sizeof(m_internalRenderConfig));
UpdateRenderConfig(Render3D, m_internalRenderConfig);
SaveState->Read(&commandPortWritten);
SaveState->Read(&m_pingPong, sizeof(m_pingPong));
for (int i = 0; i < 39; i++)
{
uint8_t nul;
SaveState->Read(&nul, sizeof(uint8_t));
}
SaveState->Read(&m_vromTextureFIFOIdx, sizeof(m_vromTextureFIFOIdx));
}
@ -140,12 +150,28 @@ void CReal3D::LoadState(CBlockFile *SaveState)
Rendering
******************************************************************************/
static void UpdateRenderConfig(IRender3D *Render3D, uint64_t internalRenderConfig[])
{
bool noSunClamp = (internalRenderConfig[0] & 0x800000) != 0 && (internalRenderConfig[1] & 0x400000) != 0;
Render3D->SetSunClamp(!noSunClamp);
}
void CReal3D::BeginVBlank(int statusCycles)
{
#ifndef NEW_FRAME_TIMING
// Calculate point at which status bit should change value. Currently the same timing is used for both the status bit in ReadRegister
// and in WriteDMARegister32/ReadDMARegister32, however it may be that they are completely unrelated. It appears that step 1.x games
// access just the former while step 2.x access the latter. It is not known yet what this bit/these bits actually represent.
statusChange = ppc_total_cycles() + statusCycles;
#else
// Buffers are swapped at a specific point in the frame if a flush (command
// port write) was performed
if (commandPortWritten)
{
m_pingPong ^= 0x02000000;
commandPortWritten = false;
}
#endif
}
void CReal3D::EndVBlank(void)
@ -157,7 +183,9 @@ uint32_t CReal3D::SyncSnapshots(void)
{
// Update read-only copy of command port flag
commandPortWrittenRO = commandPortWritten;
#ifndef NEW_FRAME_TIMING
commandPortWritten = false;
#endif
if (!m_gpuMultiThreaded)
return 0;
@ -255,181 +283,6 @@ void CReal3D::EndFrame(void)
}
/******************************************************************************
JTAG Test Access Port Simulation
What I term as "IDs" here are really boundary scan values.
******************************************************************************/
static const int tapFSM[][2] = // finite state machine, each state can lead to 2 next states
{
{ 1, 0 }, // 0 Test-Logic/Reset
{ 1, 2 }, // 1 Run-Test/Idle
{ 3, 9 }, // 2 Select-DR-Scan
{ 4, 5 }, // 3 Capture-DR
{ 4, 5 }, // 4 Shift-DR
{ 6, 8 }, // 5 Exit1-DR
{ 6, 7 }, // 6 Pause-DR
{ 4, 8 }, // 7 Exit2-DR
{ 1, 2 }, // 8 Update-DR
{ 10, 0 }, // 9 Select-IR-Scan
{ 11, 12 }, // 10 Capture-IR
{ 11, 12 }, // 11 Shift-IR
{ 13, 15 }, // 12 Exit1-IR
{ 13, 14 }, // 13 Pause-IR
{ 11, 15 }, // 14 Exit2-IR
{ 1, 2 } // 15 Update-IR
};
/*
* InsertBit():
*
* Inserts a bit into an arbitrarily long bit field. Bit 0 is assumed to be
* the MSB of the first byte in the buffer.
*/
void CReal3D::InsertBit(uint8_t *buf, unsigned bitNum, unsigned bit)
{
unsigned bitInByte = 7 - (bitNum & 7);
buf[bitNum / 8] &= ~(1 << bitInByte);
buf[bitNum / 8] |= (bit << bitInByte);
}
/*
* InsertID():
*
* Inserts a 32-bit ID code into the ID bit field.
*/
void CReal3D::InsertID(uint32_t id, unsigned startBit)
{
for (int i = 31; i >= 0; i--)
InsertBit(tapID, startBit++, (id >> i) & 1);
}
/*
* Shift():
*
* Shifts the data buffer right (towards LSB at byte 0) by 1 bit. The size of
* the number of bits must be specified. The bit shifted out of the LSB is
* returned.
*/
unsigned CReal3D::Shift(uint8_t *data, unsigned numBits)
{
// This loop takes care of all the fully-filled bytes
unsigned shiftIn = 0;
unsigned shiftOut = 0;
uint32_t i;
for (i = 0; i < numBits / 8; i++)
{
shiftOut = data[i] & 1;
data[i] >>= 1;
data[i] |= (shiftIn << 7);
shiftIn = shiftOut; // carry over to next element's MSB
}
// Take care of the last partial byte (if there is one)
if ((numBits & 7) != 0)
{
shiftOut = (data[i] >> (8 - (numBits & 7))) & 1;
data[i] >>= 1;
data[i] |= (shiftIn << 7);
}
return shiftOut;
}
unsigned CReal3D::ReadTAP(void)
{
return tapTDO;
}
void CReal3D::WriteTAP(unsigned tck, unsigned tms, unsigned tdi, unsigned trst)
{
if (!tck)
return;
// Go to next state
tapState = tapFSM[tapState][tms];
switch (tapState)
{
case 3: // Capture-DR
/*
* Read ASIC IDs.
*
* The ID Sequence is:
* - Jupiter
* - Mercury
* - Venus
* - Earth
* - Mars
* - Mars (again)
*
* Note that different Model 3 steps have different chip
* revisions, hence the different IDs returned below.
*
* On Step 1.5 and 1.0, instruction 0x0C631F8C7FFE is used to retrieve
* the ID codes but Step 2.0 is a little weirder. It seems to use this
* and either the state of the TAP after reset or other instructions
* to read the IDs as well. This can be emulated in one of 2 ways:
* Ignore the instruction and always load up the data or load the
* data on TAP reset and when the instruction is issued.
*/
if (step == 0x10)
{
InsertID(0x116C7057, 1 + 0 * 32);
InsertID(0x216C3057, 1 + 1 * 32);
InsertID(0x116C4057, 1 + 2 * 32);
InsertID(0x216C5057, 1 + 3 * 32);
InsertID(0x116C6057, 1 + 4 * 32 + 1);
InsertID(0x116C6057, 1 + 5 * 32 + 1);
}
else if (step == 0x15)
{
InsertID(0x316C7057, 1 + 0 * 32);
InsertID(0x316C3057, 1 + 1 * 32);
InsertID(0x216C4057, 1 + 2 * 32); // Lost World may to use 0x016C4057
InsertID(0x316C5057, 1 + 3 * 32);
InsertID(0x216C6057, 1 + 4 * 32 + 1);
InsertID(0x216C6057, 1 + 5 * 32 + 1);
}
else if (step >= 0x20)
{
InsertID(0x416C7057, 1 + 0 * 32);
InsertID(0x416C3057, 1 + 1 * 32);
InsertID(0x316C4057, 1 + 2 * 32); // skichamp at PC=A89F4, this value causes "NO DAUGHTER BOARD" message
InsertID(0x416C5057, 1 + 3 * 32);
InsertID(0x316C6057, 1 + 4 * 32 + 1);
InsertID(0x316C6057, 1 + 5 * 32 + 1);
}
break;
case 4: // Shift-DR
tapTDO = Shift(tapID, tapIDSize);
//printf("TAP: Shift-DR Bit %d\n", bit++);
break;
case 10: // Capture-IR
// Load lower 2 bits with 01 as per IEEE 1149.1-1990
tapIR = 1;
break;
case 11: // Shift-IR
// Shift IR towards output and load in new data from TDI
tapTDO = tapIR & 1; // shift LSB to output
tapIR >>= 1;
tapIR |= ((uint64_t) tdi << 45);
break;
case 15: // Update-IR
/*
* Latch IR (technically, this should occur on the falling edge of
* TCK)
*/
tapIR &= 0x3FFFFFFFFFFFULL;
tapCurrentInstruction = tapIR;
//printf("TAP: Update-IR %XLL\n", tapCurrentInstruction);
break;
default:
break;
}
}
/******************************************************************************
Texture Uploading and Decoding
******************************************************************************/
@ -600,7 +453,7 @@ void CReal3D::UploadTexture(uint32_t header, const uint16_t *texData)
// Process texture data
DebugLog("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
//printf("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
switch ((header>>24)&0x0F)
switch ((header>>24)&0xFF)
{
case 0x00: // texture w/ mipmaps
{
@ -652,7 +505,14 @@ void CReal3D::UploadTexture(uint32_t header, const uint16_t *texData)
break;
}
case 0x80: // MAME thinks these might be a gamma table
//break;
/*
printf("Special texture format 0x80:\n");
for (int i = 0; i < 32*32; i++)
{
printf(" %02x=%02x\n", i, texData[i]);
}
*/
break;
default: // unknown
DebugLog("Unknown texture format %02X\n", header>>24);
//printf("unknown texture format %02X\n", header>>24);
@ -884,17 +744,30 @@ void CReal3D::WritePolygonRAM(uint32_t addr, uint32_t data)
polyRAM[addr/4] = data;
}
// Internal registers accessible via JTAG port
void CReal3D::WriteJTAGRegister(uint64_t instruction, uint64_t data)
{
if (instruction == CJTAG::Instruction::SetReal3DRenderConfig0)
m_internalRenderConfig[0] = data;
else if (instruction == CJTAG::Instruction::SetReal3DRenderConfig1)
m_internalRenderConfig[1] = data;
UpdateRenderConfig(Render3D, m_internalRenderConfig);
}
// Registers seem to range from 0x00 to around 0x3C but they are not understood
uint32_t CReal3D::ReadRegister(unsigned reg)
{
DebugLog("Real3D: Read reg %X\n", reg);
if (reg == 0)
{
#ifndef NEW_FRAME_TIMING
uint32_t status = (ppc_total_cycles() >= statusChange ? 0x0 : 0x02000000);
return 0xFDFFFFFF|status;
return 0xfdffffff | status;
#else
return 0xfdffffff | m_pingPong;
#endif
}
else
return 0xFFFFFFFF;
return 0xffffffff;
}
// TODO: This returns data in the way that the PowerPC bus expects. Other functions in CReal3D should
@ -946,6 +819,7 @@ void CReal3D::Reset(void)
{
error = false;
m_pingPong = 0;
commandPortWritten = false;
commandPortWrittenRO = false;
@ -954,14 +828,13 @@ void CReal3D::Reset(void)
fifoIdx = 0;
m_vromTextureFIFOIdx = 0;
tapState = 0;
tapIDSize = 197;
dmaStatus = 0;
dmaUnknownReg = 0;
unsigned memSize = (m_gpuMultiThreaded ? MEMORY_POOL_SIZE : MEM_POOL_SIZE_RW);
memset(memoryPool, 0, memSize);
memset(m_vromTextureFIFO, 0, sizeof(m_vromTextureFIFO));
memset(m_internalRenderConfig, 0, sizeof(m_internalRenderConfig));
DebugLog("Real3D reset\n");
}
@ -986,6 +859,12 @@ void CReal3D::AttachRenderer(IRender3D *Render3DPtr)
DebugLog("Real3D attached a Render3D object\n");
}
uint32_t CReal3D::GetASICIDCode(ASIC asic) const
{
auto it = m_asicID.find(asic);
return it == m_asicID.end() ? 0 : it->second;
}
void CReal3D::SetStepping(int stepping)
{
step = stepping;
@ -1005,6 +884,42 @@ void CReal3D::SetStepping(int stepping)
if (Render3D != NULL)
Render3D->SetStepping(step);
// Set ASIC ID codes
m_asicID.clear();
if (step == 0x10)
{
m_asicID =
{
{ ASIC::Mercury, 0x216c3057 },
{ ASIC::Venus, 0x116c4057 },
{ ASIC::Earth, 0x216c5057 },
{ ASIC::Mars, 0x116c6057 },
{ ASIC::Jupiter, 0x116c7057 }
};
}
else if (step == 0x15)
{
m_asicID =
{
{ ASIC::Mercury, 0x316c3057 },
{ ASIC::Venus, 0x216c4057 },
{ ASIC::Earth, 0x316c5057 },
{ ASIC::Mars, 0x216c6057 },
{ ASIC::Jupiter, 0x316c7057 }
};
}
else if (step >= 0x20)
{
m_asicID =
{
{ ASIC::Mercury, 0x416c3057 },
{ ASIC::Venus, 0x316c4057 }, // skichamp @ pc=0xa89f4, this value causes 'NO DAUGHTER BOARD' message
{ ASIC::Earth, 0x416c5057 },
{ ASIC::Mars, 0x316c6057 },
{ ASIC::Jupiter, 0x416c7057 }
};
}
DebugLog("Real3D set to Step %d.%d\n", (step>>4)&0xF, step&0xF);
}
@ -1064,8 +979,6 @@ CReal3D::CReal3D(const Util::Config::Node &config)
vrom = NULL;
error = false;
fifoIdx = 0;
tapState = 0;
tapIDSize = 197;
m_vromTextureFIFO[0] = 0;
m_vromTextureFIFO[1] = 0;
m_vromTextureFIFOIdx = 0;

View file

@ -30,6 +30,7 @@
#define INCLUDED_REAL3D_H
#include <cstdint>
#include <map>
/*
* QueuedUploadTextures:
@ -58,6 +59,21 @@ struct QueuedUploadTextures
class CReal3D: public IPCIDevice
{
public:
/*
* ASIC Names
*
* These were determined from Virtual On, which prints them out if any of the
* ID codes are incorrect. ID codes depend on stepping.
*/
enum ASIC
{
Mercury,
Venus,
Earth,
Mars,
Jupiter
};
/*
* SaveState(SaveState):
*
@ -240,29 +256,17 @@ public:
void WritePolygonRAM(uint32_t addr, uint32_t data);
/*
* ReadTAP(void):
* WriteJTAGRegister(instruction, data):
*
* Reads the JTAG Test Access Port.
*
* Returns:
* The TDO bit (either 1 or 0).
*/
unsigned ReadTAP(void);
/*
* void WriteTAP(tck, tms, tdi, trst):
*
* Writes to the JTAG TAP. State changes only occur on the rising edge of
* the clock (tck = 1). Each of the inputs is a single bit only and must be
* either 0 or 1, or the code will fail.
* Write to an internal register using the JTAG interface. This is intended
* to be called from the JTAG emulation for instructions that are known to
* poke the internal state of Real3D ASICs.
*
* Parameters:
* tck Clock.
* tms Test mode select.
* tdi Serial data input. Must be 0 or 1 only!
* trst Reset.
* instruction Value of the JTAG instruction register.
* data Data written.
*/
void WriteTAP(unsigned tck, unsigned tms, unsigned tdi, unsigned trst);
void WriteJTAGRegister(uint64_t instruction, uint64_t data);
/*
* ReadRegister(reg):
@ -333,6 +337,20 @@ public:
*/
void AttachRenderer(IRender3D *Render3DPtr);
/*
* GetASICIDCodes(asic):
*
* Obtain ASIC ID code for the specified ASIC under the currently configured
* hardware stepping.
*
* Parameters:
* asic ASIC ID.
*
* Returns:
* The ASIC ID code. Undefined for invalid ASIC ID.
*/
uint32_t GetASICIDCode(ASIC asic) const;
/*
* SetStepping(stepping):
*
@ -456,15 +474,12 @@ private:
bool commandPortWrittenRO; // Read-only copy of flag
// Status and command registers
uint64_t statusChange;
uint32_t m_pingPong;
uint64_t statusChange;
// JTAG Test Access Port
uint64_t tapCurrentInstruction; // latched IR (not always equal to IR)
uint64_t tapIR; // instruction register (46 bits)
uint8_t tapID[32]; // ASIC ID code data buffer
unsigned tapIDSize; // size of ID data in bits
unsigned tapTDO; // bit shifted out to TDO
unsigned tapState; // current state
// Internal ASIC state
std::map<ASIC, uint32_t> m_asicID;
uint64_t m_internalRenderConfig[2] = { 0, 0 };
};