diff --git a/Makefiles/Makefile.SDL.OSX.GCC b/Makefiles/Makefile.SDL.OSX.GCC
index f93c106..399dc8e 100644
--- a/Makefiles/Makefile.SDL.OSX.GCC
+++ b/Makefiles/Makefile.SDL.OSX.GCC
@@ -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
diff --git a/Makefiles/Makefile.SDL.UNIX.GCC b/Makefiles/Makefile.SDL.UNIX.GCC
index 4616197..a7df945 100644
--- a/Makefiles/Makefile.SDL.UNIX.GCC
+++ b/Makefiles/Makefile.SDL.UNIX.GCC
@@ -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)
diff --git a/Makefiles/Makefile.SDL.Win32.GCC b/Makefiles/Makefile.SDL.Win32.GCC
index 6462c45..a630a7a 100644
--- a/Makefiles/Makefile.SDL.Win32.GCC
+++ b/Makefiles/Makefile.SDL.Win32.GCC
@@ -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
diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp
index f66dfd5..aca76d2 100644
--- a/Src/Graphics/New3D/New3D.cpp
+++ b/Src/Graphics/New3D/New3D.cpp
@@ -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;
diff --git a/Src/Model3/JTAG.cpp b/Src/Model3/JTAG.cpp
new file mode 100644
index 0000000..dbef5b5
--- /dev/null
+++ b/Src/Model3/JTAG.cpp
@@ -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 .
+ **/
+
+/*
+ * 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
+
+// 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 ®)
+{
+ 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");
+}
\ No newline at end of file
diff --git a/Src/Model3/JTAG.h b/Src/Model3/JTAG.h
new file mode 100644
index 0000000..44d6adf
--- /dev/null
+++ b/Src/Model3/JTAG.h
@@ -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 .
+ **/
+
+/*
+ * 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
diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp
index bead70c..517c30e 100644
--- a/Src/Model3/Model3.cpp
+++ b/Src/Model3/Model3.cpp
@@ -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() * 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;
diff --git a/Src/Model3/Model3.h b/Src/Model3/Model3.h
index d309f3e..ea6dbaf 100644
--- a/Src/Model3/Model3.h
+++ b/Src/Model3/Model3.h
@@ -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
};
diff --git a/Src/Model3/Real3D.cpp b/Src/Model3/Real3D.cpp
index f55bac8..491951e 100644
--- a/Src/Model3/Real3D.cpp
+++ b/Src/Model3/Real3D.cpp
@@ -42,6 +42,7 @@
*/
#include "Supermodel.h"
+#include "Model3/JTAG.h"
#include "Util/BMPFile.h"
#include
@@ -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;
+ 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;
diff --git a/Src/Model3/Real3D.h b/Src/Model3/Real3D.h
index db63267..f87f6aa 100644
--- a/Src/Model3/Real3D.h
+++ b/Src/Model3/Real3D.h
@@ -30,6 +30,7 @@
#define INCLUDED_REAL3D_H
#include
+#include