/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2003-2024 The Supermodel Team
**
** 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.
*
* All of the video board ASICs and the 3D-RAM chips are JTAG-compatible; their
* JTAG lines are daisy chained together. Usually only one device is accessed at
* a time (the other devices are given the BYPASS instruction) but some
* procedures such as reading ID codes and performing boundary scan tests call
* for accessing most or all of the devices at once.
*
* 3D-RAM chips are only used for boundary scan tests (currently unemulated).
*/
#include "JTAG.h"
#include "Supermodel.h"
#include "Real3D.h"
bool CJTAGDevice::Shift(bool tdi)
{
bool tdo = m_shiftReg[0];
m_shiftReg >>= 1;
m_shiftReg[m_shiftRegSize - 1] = tdi;
return tdo;
}
bool CJTAGDevice::ReadTDO()
{
return m_shiftReg[0];
}
void CJTAGDevice::SaveShiftRegister(CBlockFile* SaveState)
{
SaveState->Write(m_shiftReg.to_string());
}
void CJTAGDevice::LoadShiftRegister(CBlockFile* SaveState)
{
char str[MAX_REGISTER_LENGTH + 1]; // add one char for null terminator
SaveState->Read(str, sizeof(str));
std::bitset tempReg{ str };
m_shiftReg = tempReg;
}
void CASIC::SaveStateToBlock(CBlockFile* SaveState)
{
SaveShiftRegister(SaveState);
SaveState->Write(&m_shiftRegSize, sizeof(m_shiftRegSize));
SaveState->Write(&m_instructionReg, sizeof(m_instructionReg));
SaveState->Write(&m_idCode, sizeof(m_idCode));
SaveState->Write(&m_modeword, sizeof(m_modeword));
}
void CASIC::LoadStateFromBlock(CBlockFile* SaveState)
{
LoadShiftRegister(SaveState);
SaveState->Read(&m_shiftRegSize, sizeof(m_shiftRegSize));
SaveState->Read(&m_instructionReg, sizeof(m_instructionReg));
SaveState->Read(&m_idCode, sizeof(m_idCode));
SaveState->Read(&m_modeword, sizeof(m_modeword));
}
void CASIC::CaptureDR()
{
switch (m_instructionReg)
{
case 3: // IDCODE
m_shiftRegSize = 32;
m_shiftReg = m_idCode;
break;
case 8: // MODEWORD
m_shiftRegSize = 32;
m_shiftReg = m_modeword;
break;
default: // BYPASS
m_shiftRegSize = 1;
m_shiftReg = 0;
}
}
void CASIC::CaptureIR()
{
m_shiftRegSize = 5;
m_shiftReg = 1;
}
void CASIC::UpdateDR()
{
switch (m_instructionReg)
{
case 8: // MODEWORD
m_modeword = m_shiftReg.to_ulong();
m_real3D.WriteJTAGModeword(m_deviceName, m_modeword);
break;
}
}
void CASIC::UpdateIR()
{
m_instructionReg = (uint8_t)m_shiftReg.to_ulong();
}
void CASIC::Reset()
{
m_shiftRegSize = 32;
m_shiftReg = m_idCode;
m_instructionReg = 3; // IDCODE
}
CASIC::CASIC(CReal3D& real3D, CASIC::Name deviceName) :
m_real3D(real3D),
m_deviceName(deviceName)
{
}
void C3DRAM::SaveStateToBlock(CBlockFile* SaveState)
{
SaveShiftRegister(SaveState);
SaveState->Write(&m_shiftRegSize, sizeof(m_shiftRegSize));
SaveState->Write(&m_instructionReg, sizeof(m_instructionReg));
}
void C3DRAM::LoadStateFromBlock(CBlockFile* SaveState)
{
LoadShiftRegister(SaveState);
SaveState->Read(&m_shiftRegSize, sizeof(m_shiftRegSize));
SaveState->Read(&m_instructionReg, sizeof(m_instructionReg));
}
void C3DRAM::CaptureDR()
{
switch (m_instructionReg)
{
default: // BYPASS
m_shiftRegSize = 1;
m_shiftReg = 0;
}
}
void C3DRAM::CaptureIR()
{
m_shiftRegSize = 4;
m_shiftReg = 0b1001;
}
void C3DRAM::UpdateDR()
{
switch (m_instructionReg)
{
// no instructions implemented yet
}
}
void C3DRAM::UpdateIR()
{
m_instructionReg = (uint8_t)m_shiftReg.to_ulong();
}
void C3DRAM::Reset()
{
m_shiftRegSize = 1;
m_shiftReg = 0;
m_instructionReg = 15; // BYPASS
}
// Finite state machine. Each state has two possible next states.
const CJTAG::State CJTAG::s_fsm[][2] =
{
// tms = 0 tms = 1
{ State::RunTestIdle, State::TestLogicReset }, // 0 Test-Logic/Reset
{ State::RunTestIdle, State::SelectDRScan }, // 1 Run-Test/Idle
{ State::CaptureDR, State::SelectIRScan }, // 2 Select-DR-Scan
{ State::ShiftDR, State::Exit1DR }, // 3 Capture-DR
{ State::ShiftDR, State::Exit1DR }, // 4 Shift-DR
{ State::PauseDR, State::UpdateDR }, // 5 Exit1-DR
{ State::PauseDR, State::Exit2DR }, // 6 Pause-DR
{ State::ShiftDR, State::UpdateDR }, // 7 Exit2-DR
{ State::RunTestIdle, State::SelectDRScan }, // 8 Update-DR
{ State::CaptureIR, State::TestLogicReset }, // 9 Select-IR-Scan
{ State::ShiftIR, State::Exit1IR }, // 10 Capture-IR
{ State::ShiftIR, State::Exit1IR }, // 11 Shift-IR
{ State::PauseIR, State::UpdateIR }, // 12 Exit1-IR
{ State::PauseIR, State::Exit2IR }, // 13 Pause-IR
{ State::ShiftIR, State::UpdateIR }, // 14 Exit2-IR
{ State::RunTestIdle, State::SelectDRScan } // 15 Update-IR
};
void CJTAG::SaveState(CBlockFile* SaveState)
{
SaveState->NewBlock("JTAG2", __FILE__);
SaveState->Write(&m_state, sizeof(m_state));
SaveState->Write(&m_lastTck, sizeof(m_lastTck));
SaveState->Write(&m_tdo, sizeof(m_tdo));
SaveState->Write(&m_numDevices, sizeof(m_numDevices));
for (auto& device : m_device)
{
device->SaveStateToBlock(SaveState);
}
}
void CJTAG::LoadState(CBlockFile* LoadState)
{
if (Result::OKAY != LoadState->FindBlock("JTAG2"))
{
ErrorLog("Unable to load JTAG state. Save state file is corrupt.");
return;
}
LoadState->Read(&m_state, sizeof(m_state));
LoadState->Read(&m_lastTck, sizeof(m_lastTck));
LoadState->Read(&m_tdo, sizeof(m_tdo));
LoadState->Read(&m_numDevices, sizeof(m_numDevices));
for (auto& device : m_device)
{
device->LoadStateFromBlock(LoadState);
}
}
bool CJTAG::Read() const
{
return m_tdo;
}
void CJTAG::Write(bool tck, bool tms, bool tdi, bool trst)
{
if (!trst)
{
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->Reset();
return;
}
if (m_lastTck == tck)
return;
m_lastTck = tck;
if (tck)
{
switch (m_state)
{
case State::TestLogicReset:
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->Reset();
break;
case State::CaptureDR:
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->CaptureDR();
break;
case State::CaptureIR:
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->CaptureIR();
break;
case State::ShiftDR:
case State::ShiftIR:
for (uint32_t i = 0; i < m_numDevices; i++)
tdi = m_device[i]->Shift(tdi);
break;
}
// Go to next state
m_state = s_fsm[static_cast(m_state)][tms];
}
else
{
switch (m_state)
{
case State::ShiftDR:
case State::ShiftIR:
m_tdo = m_device[m_numDevices - 1]->ReadTDO();
break;
case State::UpdateDR:
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->UpdateDR();
break;
case State::UpdateIR:
for (uint32_t i = 0; i < m_numDevices; i++)
m_device[i]->UpdateIR();
break;
}
}
}
void CJTAG::Reset()
{
m_state = State::TestLogicReset;
for (auto& device : m_device)
device->Reset();
DebugLog("JTAG reset\n");
}
void CJTAG::SetStepping(int stepping)
{
switch (stepping)
{
case 0x21:
m_mercury.SetIDCode(0x416c3057);
m_venus.SetIDCode(0x316c4057);
m_earth0.SetIDCode(0x516c5057);
m_earth1.SetIDCode(0x516c5057);
m_mars00.SetIDCode(0x316c6057);
m_mars01.SetIDCode(0x316c6057);
m_mars10.SetIDCode(0x316c6057);
m_mars11.SetIDCode(0x316c6057);
m_jupiter.SetIDCode(0x516c7057);
m_numDevices = 17;
break;
case 0x20:
m_mercury.SetIDCode(0x416c3057);
m_venus.SetIDCode(0x316c4057);
m_earth0.SetIDCode(0x416c5057);
m_earth1.SetIDCode(0x416c5057);
m_mars00.SetIDCode(0x316c6057);
m_mars01.SetIDCode(0x316c6057);
m_mars10.SetIDCode(0x316c6057);
m_mars11.SetIDCode(0x316c6057);
m_jupiter.SetIDCode(0x416c7057);
m_numDevices = 17;
break;
case 0x15:
m_mercury.SetIDCode(0x316c3057);
m_venus.SetIDCode(0x216c4057);
m_earth0.SetIDCode(0x316c5057);
m_mars00.SetIDCode(0x216c6057);
m_mars01.SetIDCode(0x216c6057);
m_jupiter.SetIDCode(0x316c7057);
m_numDevices = 10;
break;
case 0x10:
default:
m_mercury.SetIDCode(0x216c3057);
m_venus.SetIDCode(0x116c4057);
m_earth0.SetIDCode(0x216c5057);
m_mars00.SetIDCode(0x116c6057);
m_mars01.SetIDCode(0x116c6057);
m_jupiter.SetIDCode(0x116c7057);
m_numDevices = 10;
}
}
// Only the first ASIC of each type has its modeword written to the Real3D object
CJTAG::CJTAG(CReal3D& real3D) :
m_mercury(real3D, CASIC::Name::Mercury),
m_venus(real3D, CASIC::Name::Venus),
m_earth0(real3D, CASIC::Name::Earth),
m_earth1(real3D, CASIC::Name::Dummy),
m_mars00(real3D, CASIC::Name::Mars),
m_mars01(real3D, CASIC::Name::Dummy),
m_mars10(real3D, CASIC::Name::Dummy),
m_mars11(real3D, CASIC::Name::Dummy),
m_jupiter(real3D, CASIC::Name::Jupiter)
{
DebugLog("Built JTAG logic\n");
}