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 /* * 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,30 +256,18 @@ 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 m_asicID; + uint64_t m_internalRenderConfig[2] = { 0, 0 }; };