Add PoroCYon's DSP code. (#1123)
* Add PoroCYon's DSP code. * Remove some teakra iles that we dont need. * make some requested changes. * move DataMemoryOffset into namespace. * use deault param. * ad the switch change * <Generic> forget about the default parameter
This commit is contained in:
parent
2494058a71
commit
e3b4350f44
|
@ -18,6 +18,7 @@ add_library(core STATIC
|
|||
DSi.cpp
|
||||
DSi_AES.cpp
|
||||
DSi_Camera.cpp
|
||||
DSi_DSP.cpp
|
||||
DSi_I2C.cpp
|
||||
DSi_NDMA.cpp
|
||||
DSi_NWifi.cpp
|
||||
|
@ -100,6 +101,10 @@ if (ENABLE_JIT)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(teakra EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(core teakra)
|
||||
|
||||
|
||||
if (ENABLE_OGLRENDERER)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(EPOXY REQUIRED epoxy)
|
||||
|
|
66
src/DSi.cpp
66
src/DSi.cpp
|
@ -36,6 +36,7 @@
|
|||
#include "DSi_I2C.h"
|
||||
#include "DSi_SD.h"
|
||||
#include "DSi_AES.h"
|
||||
#include "DSi_DSP.h"
|
||||
#include "DSi_Camera.h"
|
||||
|
||||
#include "tiny-AES-c/aes.hpp"
|
||||
|
@ -51,6 +52,7 @@ u16 SCFG_Clock9;
|
|||
u16 SCFG_Clock7;
|
||||
u32 SCFG_EXT[2];
|
||||
u32 SCFG_MC;
|
||||
u16 SCFG_RST;
|
||||
|
||||
u8 ARM9iBIOS[0x10000];
|
||||
u8 ARM7iBIOS[0x10000];
|
||||
|
@ -95,6 +97,7 @@ bool Init()
|
|||
|
||||
if (!DSi_I2C::Init()) return false;
|
||||
if (!DSi_AES::Init()) return false;
|
||||
if (!DSi_DSP::Init()) return false;
|
||||
|
||||
NDMAs[0] = new DSi_NDMA(0, 0);
|
||||
NDMAs[1] = new DSi_NDMA(0, 1);
|
||||
|
@ -121,6 +124,7 @@ void DeInit()
|
|||
|
||||
DSi_I2C::DeInit();
|
||||
DSi_AES::DeInit();
|
||||
DSi_DSP::DeInit();
|
||||
|
||||
for (int i = 0; i < 8; i++) delete NDMAs[i];
|
||||
|
||||
|
@ -146,6 +150,7 @@ void Reset()
|
|||
|
||||
DSi_I2C::Reset();
|
||||
DSi_AES::Reset();
|
||||
DSi_DSP::Reset();
|
||||
|
||||
SDMMC->Reset();
|
||||
SDIO->Reset();
|
||||
|
@ -156,6 +161,8 @@ void Reset()
|
|||
SCFG_EXT[0] = 0x8307F100;
|
||||
SCFG_EXT[1] = 0x93FFFB06;
|
||||
SCFG_MC = 0x0010;//0x0011;
|
||||
SCFG_RST = 0;
|
||||
DSi_DSP::SetRstLine(false);
|
||||
|
||||
// LCD init flag
|
||||
GPU::DispStat[0] |= (1<<6);
|
||||
|
@ -202,6 +209,13 @@ void SoftReset()
|
|||
|
||||
DSi_AES::Reset();
|
||||
|
||||
|
||||
DSi_AES::Reset();
|
||||
// TODO: does the DSP get reset? NWRAM doesn't, so I'm assuming no
|
||||
// *HOWEVER*, the bootrom (which does get rerun) does remap NWRAM, and thus
|
||||
// the DSP most likely gets reset
|
||||
DSi_DSP::Reset();
|
||||
|
||||
LoadNAND();
|
||||
|
||||
SDMMC->Reset();
|
||||
|
@ -216,6 +230,10 @@ void SoftReset()
|
|||
SCFG_EXT[0] = 0x8307F100;
|
||||
SCFG_EXT[1] = 0x93FFFB06;
|
||||
SCFG_MC = 0x0010;//0x0011;
|
||||
// TODO: is this actually reset?
|
||||
SCFG_RST = 0;
|
||||
DSi_DSP::SetRstLine(false);
|
||||
|
||||
|
||||
// LCD init flag
|
||||
GPU::DispStat[0] |= (1<<6);
|
||||
|
@ -602,6 +620,8 @@ void MapNWRAM_B(u32 num, u8 val)
|
|||
|
||||
u8* ptr = &NWRAM_B[num << 15];
|
||||
|
||||
DSi_DSP::OnMBKCfg('B', num, oldval, val, ptr);
|
||||
|
||||
if (oldval & 0x80)
|
||||
{
|
||||
if (oldval & 0x02) oldval &= 0xFE;
|
||||
|
@ -641,6 +661,8 @@ void MapNWRAM_C(u32 num, u8 val)
|
|||
|
||||
u8* ptr = &NWRAM_C[num << 15];
|
||||
|
||||
DSi_DSP::OnMBKCfg('C', num, oldval, val, ptr);
|
||||
|
||||
if (oldval & 0x80)
|
||||
{
|
||||
if (oldval & 0x02) oldval &= 0xFE;
|
||||
|
@ -1482,6 +1504,7 @@ u8 ARM9IORead8(u32 addr)
|
|||
switch (addr)
|
||||
{
|
||||
case 0x04004000: return SCFG_BIOS & 0xFF;
|
||||
case 0x04004006: return SCFG_RST & 0xFF;
|
||||
|
||||
CASE_READ8_32BIT(0x04004040, MBK[0][0])
|
||||
CASE_READ8_32BIT(0x04004044, MBK[0][1])
|
||||
|
@ -1500,6 +1523,9 @@ u8 ARM9IORead8(u32 addr)
|
|||
return DSi_Camera::Read8(addr);
|
||||
}
|
||||
|
||||
if (addr >= 0x04004300 && addr <= 0x04004400)
|
||||
return DSi_DSP::Read16(addr);
|
||||
|
||||
return NDS::ARM9IORead8(addr);
|
||||
}
|
||||
|
||||
|
@ -1509,6 +1535,7 @@ u16 ARM9IORead16(u32 addr)
|
|||
{
|
||||
case 0x04004000: return SCFG_BIOS & 0xFF;
|
||||
case 0x04004004: return SCFG_Clock9;
|
||||
case 0x04004006: return SCFG_RST;
|
||||
case 0x04004010: return SCFG_MC & 0xFFFF;
|
||||
|
||||
CASE_READ16_32BIT(0x04004040, MBK[0][0])
|
||||
|
@ -1528,6 +1555,9 @@ u16 ARM9IORead16(u32 addr)
|
|||
return DSi_Camera::Read16(addr);
|
||||
}
|
||||
|
||||
if (addr >= 0x04004300 && addr <= 0x04004400)
|
||||
return DSi_DSP::Read32(addr);
|
||||
|
||||
return NDS::ARM9IORead16(addr);
|
||||
}
|
||||
|
||||
|
@ -1536,6 +1566,7 @@ u32 ARM9IORead32(u32 addr)
|
|||
switch (addr)
|
||||
{
|
||||
case 0x04004000: return SCFG_BIOS & 0xFF;
|
||||
case 0x04004004: return SCFG_Clock9 | ((u32)SCFG_RST << 16);
|
||||
case 0x04004008: return SCFG_EXT[0];
|
||||
case 0x04004010: return SCFG_MC & 0xFFFF;
|
||||
|
||||
|
@ -1603,6 +1634,11 @@ void ARM9IOWrite8(u32 addr, u8 val)
|
|||
//if (val == 0x80 && NDS::ARM9->R[15] == 0xFFFF0268) NDS::ARM9->Halt(1);
|
||||
return;
|
||||
|
||||
case 0x04004006:
|
||||
SCFG_RST = (SCFG_RST & 0xFF00) | val;
|
||||
DSi_DSP::SetRstLine(val & 1);
|
||||
return;
|
||||
|
||||
case 0x04004040: MapNWRAM_A(0, val); return;
|
||||
case 0x04004041: MapNWRAM_A(1, val); return;
|
||||
case 0x04004042: MapNWRAM_A(2, val); return;
|
||||
|
@ -1631,6 +1667,12 @@ void ARM9IOWrite8(u32 addr, u8 val)
|
|||
return DSi_Camera::Write8(addr, val);
|
||||
}
|
||||
|
||||
if (addr >= 0x04004300 && addr <= 0x04004400)
|
||||
{
|
||||
DSi_DSP::Write8(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
return NDS::ARM9IOWrite8(addr, val);
|
||||
}
|
||||
|
||||
|
@ -1642,6 +1684,11 @@ void ARM9IOWrite16(u32 addr, u16 val)
|
|||
Set_SCFG_Clock9(val);
|
||||
return;
|
||||
|
||||
case 0x04004006:
|
||||
SCFG_RST = val;
|
||||
DSi_DSP::SetRstLine(val & 1);
|
||||
return;
|
||||
|
||||
case 0x04004040:
|
||||
MapNWRAM_A(0, val & 0xFF);
|
||||
MapNWRAM_A(1, val >> 8);
|
||||
|
@ -1690,6 +1737,12 @@ void ARM9IOWrite16(u32 addr, u16 val)
|
|||
return DSi_Camera::Write16(addr, val);
|
||||
}
|
||||
|
||||
if (addr >= 0x04004300 && addr <= 0x04004400)
|
||||
{
|
||||
DSi_DSP::Write16(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
return NDS::ARM9IOWrite16(addr, val);
|
||||
}
|
||||
|
||||
|
@ -1697,6 +1750,12 @@ void ARM9IOWrite32(u32 addr, u32 val)
|
|||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x04004004:
|
||||
Set_SCFG_Clock9(val & 0xFFFF);
|
||||
SCFG_RST = val >> 16;
|
||||
DSi_DSP::SetRstLine((val >> 16) & 1);
|
||||
break;
|
||||
|
||||
case 0x04004008:
|
||||
{
|
||||
u32 oldram = (SCFG_EXT[0] >> 14) & 0x3;
|
||||
|
@ -2106,6 +2165,13 @@ void ARM7IOWrite32(u32 addr, u32 val)
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if (addr >= 0x04004300 && addr <= 0x04004400)
|
||||
{
|
||||
DSi_DSP::Write32(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
return NDS::ARM7IOWrite32(addr, val);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ namespace DSi
|
|||
{
|
||||
|
||||
extern u16 SCFG_BIOS;
|
||||
extern u16 SCFG_Clock9;
|
||||
extern u32 SCFG_EXT[2];
|
||||
|
||||
|
||||
extern u8 ARM9iBIOS[0x10000];
|
||||
extern u8 ARM7iBIOS[0x10000];
|
||||
|
|
|
@ -0,0 +1,592 @@
|
|||
/*
|
||||
Copyright 2020 PoroCYon
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "teakra/include/teakra/teakra.h"
|
||||
|
||||
#include "DSi.h"
|
||||
#include "DSi_DSP.h"
|
||||
#include "FIFO.h"
|
||||
#include "NDS.h"
|
||||
|
||||
|
||||
namespace DSi_DSP
|
||||
{
|
||||
|
||||
Teakra::Teakra* TeakraCore;
|
||||
|
||||
bool SCFG_RST;
|
||||
|
||||
u16 DSP_PADR;
|
||||
u16 DSP_PCFG;
|
||||
u16 DSP_PSTS;
|
||||
u16 DSP_PSEM;
|
||||
u16 DSP_PMASK;
|
||||
u16 DSP_PCLEAR;
|
||||
u16 DSP_CMD[3];
|
||||
u16 DSP_REP[3];
|
||||
|
||||
u64 DSPTimestamp;
|
||||
|
||||
FIFO<u16, 16> PDATAReadFifo/*, *PDATAWriteFifo*/;
|
||||
int PDataDMALen = 0;
|
||||
|
||||
constexpr u32 DataMemoryOffset = 0x20000; // from Teakra memory_interface.h
|
||||
// NOTE: ^ IS IN DSP WORDS, NOT IN BYTES!
|
||||
|
||||
u16 GetPSTS()
|
||||
{
|
||||
u16 r = DSP_PSTS & (1<<9); // this is the only sticky bit
|
||||
//r &= ~((1<<2)|(1<<7)); // we support instant resets and wrfifo xfers
|
||||
r |= (1<<8); // write fifo is always empty (inf. speed)
|
||||
|
||||
if ( PDATAReadFifo.IsFull ()) r |= 1<<5;
|
||||
if (!PDATAReadFifo.IsEmpty()) r |=(1<<6)|(1<<0);
|
||||
|
||||
if (!TeakraCore->SendDataIsEmpty(0)) r |= 1<<13;
|
||||
if (!TeakraCore->SendDataIsEmpty(1)) r |= 1<<14;
|
||||
if (!TeakraCore->SendDataIsEmpty(2)) r |= 1<<15;
|
||||
if ( TeakraCore->RecvDataIsReady(0)) r |= 1<<10;
|
||||
if ( TeakraCore->RecvDataIsReady(1)) r |= 1<<11;
|
||||
if ( TeakraCore->RecvDataIsReady(2)) r |= 1<<12;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void IrqRep0()
|
||||
{
|
||||
if (DSP_PCFG & (1<< 9)) NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
}
|
||||
void IrqRep1()
|
||||
{
|
||||
if (DSP_PCFG & (1<<10)) NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
}
|
||||
void IrqRep2()
|
||||
{
|
||||
if (DSP_PCFG & (1<<11)) NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
}
|
||||
void IrqSem()
|
||||
{
|
||||
DSP_PSTS |= 1<<9;
|
||||
// apparently these are always fired?
|
||||
NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
}
|
||||
|
||||
void AudioCb(std::array<s16, 2> frame)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool Init()
|
||||
{
|
||||
TeakraCore = new Teakra::Teakra();
|
||||
SCFG_RST = false;
|
||||
|
||||
if (!TeakraCore) return false;
|
||||
|
||||
TeakraCore->SetRecvDataHandler(0, IrqRep0);
|
||||
TeakraCore->SetRecvDataHandler(1, IrqRep1);
|
||||
TeakraCore->SetRecvDataHandler(2, IrqRep2);
|
||||
|
||||
TeakraCore->SetSemaphoreHandler(IrqSem);
|
||||
|
||||
// these happen instantaneously and without too much regard for bus aribtration
|
||||
// rules, so, this might have to be changed later on
|
||||
Teakra::AHBMCallback cb;
|
||||
cb.read8 = DSi::ARM9Read8;
|
||||
cb.write8 = DSi::ARM9Write8;
|
||||
cb.read16 = DSi::ARM9Read16;
|
||||
cb.write16 = DSi::ARM9Write16;
|
||||
cb.read32 = DSi::ARM9Read32;
|
||||
cb.write32 = DSi::ARM9Write32;
|
||||
TeakraCore->SetAHBMCallback(cb);
|
||||
|
||||
TeakraCore->SetAudioCallback(AudioCb);
|
||||
|
||||
//PDATAReadFifo = new FIFO<u16>(16);
|
||||
//PDATAWriteFifo = new FIFO<u16>(16);
|
||||
|
||||
return true;
|
||||
}
|
||||
void DeInit()
|
||||
{
|
||||
//if (PDATAWriteFifo) delete PDATAWriteFifo;
|
||||
if (TeakraCore) delete TeakraCore;
|
||||
|
||||
//PDATAReadFifo = NULL;
|
||||
//PDATAWriteFifo = NULL;
|
||||
TeakraCore = NULL;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
DSPTimestamp = 0;
|
||||
|
||||
DSP_PADR = 0;
|
||||
DSP_PCFG = 0;
|
||||
DSP_PSTS = 0;
|
||||
DSP_PSEM = 0;
|
||||
DSP_PMASK = 0xff;
|
||||
DSP_PCLEAR = 0;
|
||||
DSP_CMD[2] = DSP_CMD[1] = DSP_CMD[0] = 0;
|
||||
DSP_REP[2] = DSP_REP[1] = DSP_REP[0] = 0;
|
||||
PDataDMALen = 0;
|
||||
|
||||
PDATAReadFifo.Clear();
|
||||
//PDATAWriteFifo->Clear();
|
||||
TeakraCore->Reset();
|
||||
|
||||
NDS::CancelEvent(NDS::Event_DSi_DSP);
|
||||
}
|
||||
|
||||
bool IsRstReleased()
|
||||
{
|
||||
return SCFG_RST;
|
||||
}
|
||||
void SetRstLine(bool release)
|
||||
{
|
||||
SCFG_RST = release;
|
||||
Reset();
|
||||
DSPTimestamp = NDS::ARM9Timestamp; // only start now!
|
||||
}
|
||||
|
||||
void OnMBKCfg(char bank, u32 slot, u8 oldcfg, u8 newcfg, u8* nwrambacking)
|
||||
{
|
||||
if (bank != 'B' && bank != 'C')
|
||||
{
|
||||
printf("WTF?? (DSP MBK recfg, nonsense bank '%c')\n", bank);
|
||||
return;
|
||||
}
|
||||
|
||||
bool olddsp = (oldcfg & 3) >= 2, // was mapped to the DSP
|
||||
newdsp = (newcfg & 3) >= 2; // will be mapped to the DSP
|
||||
|
||||
// nothing changes
|
||||
if (olddsp == newdsp)
|
||||
return;
|
||||
|
||||
const u8* src;
|
||||
u8* dst;
|
||||
|
||||
if (newdsp)
|
||||
{
|
||||
// need to map stuff to DSP memory (== Teakra-owned memory) from NWRAM
|
||||
src = nwrambacking;
|
||||
dst = &TeakraCore->GetDspMemory()[((newcfg >> 2) & 7) << 15];
|
||||
|
||||
if (bank == 'C') // C: DSP data (B: DSP code)
|
||||
dst += DataMemoryOffset*2;
|
||||
}
|
||||
else //if (olddsp)
|
||||
{
|
||||
// it was mapped to the DSP, but now we have to copy it out, back to NWRAM
|
||||
src = &TeakraCore->GetDspMemory()[((oldcfg >> 2) & 7) << 15];
|
||||
dst = nwrambacking;
|
||||
|
||||
if (bank == 'C') // C: DSP data (B: DSP code)
|
||||
src += DataMemoryOffset*2;
|
||||
}
|
||||
|
||||
memcpy(dst, src, 1<<15); // 1 full slot
|
||||
}
|
||||
|
||||
inline bool IsDSPCoreEnabled()
|
||||
{
|
||||
return (DSi::SCFG_Clock9 & (1<<1)) && SCFG_RST && (DSP_PCFG & (1<<0));
|
||||
}
|
||||
|
||||
bool DSPCatchUp()
|
||||
{
|
||||
//asm volatile("int3");
|
||||
if (!IsDSPCoreEnabled())
|
||||
{
|
||||
// nothing to do, but advance the current time so that we don't do an
|
||||
// unreasonable amount of cycles when rst is released
|
||||
if (DSPTimestamp < NDS::ARM9Timestamp)
|
||||
DSPTimestamp = NDS::ARM9Timestamp;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 curtime = NDS::ARM9Timestamp;
|
||||
|
||||
if (DSPTimestamp >= curtime) return true; // ummmm?!
|
||||
|
||||
u64 backlog = curtime - DSPTimestamp;
|
||||
|
||||
while (backlog & (1uLL<<32)) // god I hope this never happens
|
||||
{
|
||||
Run((u32)(backlog & ((1uLL<<32)-1)));
|
||||
backlog = curtime - DSPTimestamp;
|
||||
}
|
||||
Run((u32)backlog);
|
||||
|
||||
return true;
|
||||
}
|
||||
void DSPCatchUpU32(u32 _) { DSPCatchUp(); }
|
||||
|
||||
void PDataDMAWrite(u16 wrval)
|
||||
{
|
||||
u32 addr = DSP_PADR;
|
||||
|
||||
switch (DSP_PCFG & (7<<12)) // memory region select
|
||||
{
|
||||
case 0<<12: // data
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
TeakraCore->DataWriteA32(addr, wrval);
|
||||
break;
|
||||
case 1<<12: // mmio
|
||||
TeakraCore->MMIOWrite(addr & 0x7FF, wrval);
|
||||
break;
|
||||
case 5<<12: // program
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
TeakraCore->ProgramWrite(addr, wrval);
|
||||
break;
|
||||
case 7<<12:
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
// only do stuff when AHBM is configured correctly
|
||||
if (TeakraCore->AHBMGetDmaChannel(0) == 0 && TeakraCore->AHBMGetDirection(0) == 1/*W*/)
|
||||
{
|
||||
switch (TeakraCore->AHBMGetUnitSize(0))
|
||||
{
|
||||
case 0: /* 8bit */ DSi::ARM9Write8 (addr, (u8)wrval); break;
|
||||
case 1: /* 16 b */ TeakraCore->AHBMWrite16(addr, wrval); break;
|
||||
// does it work like this, or should it first buffer two u16's
|
||||
// until it has enough data to write to the actual destination?
|
||||
// -> this seems to be correct behavior!
|
||||
case 2: /* 32 b */ TeakraCore->AHBMWrite32(addr, wrval); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
if (DSP_PCFG & (1<<1)) // auto-increment
|
||||
++DSP_PADR; // overflows and stays within a 64k 'page' // TODO: is this +1 or +2?
|
||||
|
||||
NDS::SetIRQ(0, NDS::IRQ_DSi_DSP); // wrfifo empty
|
||||
}
|
||||
// TODO: FIFO interrupts! (rd full, nonempty)
|
||||
u16 PDataDMARead()
|
||||
{
|
||||
u16 r = 0;
|
||||
u32 addr = DSP_PADR;
|
||||
switch (DSP_PCFG & (7<<12)) // memory region select
|
||||
{
|
||||
case 0<<12: // data
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
r = TeakraCore->DataReadA32(addr);
|
||||
break;
|
||||
case 1<<12: // mmio
|
||||
r = TeakraCore->MMIORead(addr & 0x7FF);
|
||||
break;
|
||||
case 5<<12: // program
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
r = TeakraCore->ProgramRead(addr);
|
||||
break;
|
||||
case 7<<12:
|
||||
addr |= (u32)TeakraCore->DMAChan0GetDstHigh() << 16;
|
||||
// only do stuff when AHBM is configured correctly
|
||||
if (TeakraCore->AHBMGetDmaChannel(0) == 0 && TeakraCore->AHBMGetDirection(0) == 0/*R*/)
|
||||
{
|
||||
switch (TeakraCore->AHBMGetUnitSize(0))
|
||||
{
|
||||
case 0: /* 8bit */ r = DSi::ARM9Read8 (addr); break;
|
||||
case 1: /* 16 b */ r = TeakraCore->AHBMRead16(addr); break;
|
||||
case 2: /* 32 b */ r = (u16)TeakraCore->AHBMRead32(addr); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: return r;
|
||||
}
|
||||
|
||||
if (DSP_PCFG & (1<<1)) // auto-increment
|
||||
++DSP_PADR; // overflows and stays within a 64k 'page' // TODO: is this +1 or +2?
|
||||
|
||||
return r;
|
||||
}
|
||||
void PDataDMAFetch()
|
||||
{
|
||||
if (!PDataDMALen) return;
|
||||
|
||||
PDATAReadFifo.Write(PDataDMARead());
|
||||
|
||||
if (PDataDMALen > 0) --PDataDMALen;
|
||||
}
|
||||
void PDataDMAStart()
|
||||
{
|
||||
switch ((DSP_PSTS & (3<<2)) >> 2)
|
||||
{
|
||||
case 0: PDataDMALen = 1; break;
|
||||
case 1: PDataDMALen = 8; break;
|
||||
case 2: PDataDMALen =16; break;
|
||||
case 3: PDataDMALen =-1; break;
|
||||
}
|
||||
|
||||
// fill a single fifo
|
||||
int amt = PDataDMALen;
|
||||
if (amt < 0) amt = 16;
|
||||
for (int i = 0; i < amt; ++i)
|
||||
PDataDMAFetch();
|
||||
|
||||
NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
|
||||
}
|
||||
void PDataDMACancel()
|
||||
{
|
||||
PDataDMALen = 0;
|
||||
PDATAReadFifo.Clear();
|
||||
|
||||
}
|
||||
u16 PDataDMAReadMMIO()
|
||||
{
|
||||
u16 ret;
|
||||
|
||||
if (!PDATAReadFifo.IsEmpty())
|
||||
ret = PDATAReadFifo.Read();
|
||||
|
||||
// aha, there's more to come
|
||||
if (PDataDMALen != 0)
|
||||
{
|
||||
int left = 16 - PDATAReadFifo.Level();
|
||||
if (PDataDMALen > 0 && PDataDMALen < left)
|
||||
left = PDataDMALen;
|
||||
|
||||
for (int i = 0; i < left; ++i)
|
||||
PDataDMAFetch();
|
||||
|
||||
ret = PDATAReadFifo.Read();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ah, crap
|
||||
ret = 0; // TODO: is this actually 0, or just open bus?
|
||||
}
|
||||
|
||||
if (!PDATAReadFifo.IsEmpty() || PDATAReadFifo.IsFull())
|
||||
NDS::SetIRQ(0, NDS::IRQ_DSi_DSP);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u8 Read8(u32 addr)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18)))
|
||||
return 0;
|
||||
|
||||
if (!DSPCatchUp()) return 0;
|
||||
|
||||
addr &= 0x3F; // mirroring wheee
|
||||
|
||||
// ports are a bit weird, 16-bit regs in 32-bit spaces
|
||||
switch (addr)
|
||||
{
|
||||
// no 8-bit PDATA read
|
||||
// no DSP_PADR read
|
||||
case 0x08: return DSP_PCFG & 0xFF;
|
||||
case 0x09: return DSP_PCFG >> 8;
|
||||
case 0x0C: return GetPSTS() & 0xFF;
|
||||
case 0x0D: return GetPSTS() >> 8;
|
||||
case 0x10: return DSP_PSEM & 0xFF;
|
||||
case 0x11: return DSP_PSEM >> 8;
|
||||
case 0x14: return DSP_PMASK & 0xFF;
|
||||
case 0x15: return DSP_PMASK >> 8;
|
||||
// no DSP_PCLEAR read
|
||||
case 0x1C: return TeakraCore->GetSemaphore() & 0xFF; // SEM
|
||||
case 0x1D: return TeakraCore->GetSemaphore() >> 8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
u16 Read16(u32 addr)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18)))
|
||||
return 0;
|
||||
|
||||
if (!DSPCatchUp()) return 0;
|
||||
|
||||
addr &= 0x3E; // mirroring wheee
|
||||
|
||||
// ports are a bit weird, 16-bit regs in 32-bit spaces
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: return PDataDMAReadMMIO();
|
||||
// no DSP_PADR read
|
||||
case 0x08: return DSP_PCFG;
|
||||
case 0x0C: return GetPSTS();
|
||||
case 0x10: return DSP_PSEM;
|
||||
case 0x14: return DSP_PMASK;
|
||||
// no DSP_PCLEAR read
|
||||
case 0x1C: return TeakraCore->GetSemaphore(); // SEM
|
||||
|
||||
case 0x20: return DSP_CMD[0];
|
||||
case 0x28: return DSP_CMD[1];
|
||||
case 0x30: return DSP_CMD[2];
|
||||
|
||||
case 0x24:
|
||||
{
|
||||
u16 r = TeakraCore->RecvData(0);
|
||||
return r;
|
||||
}
|
||||
case 0x2C:
|
||||
{
|
||||
u16 r = TeakraCore->RecvData(1);
|
||||
return r;
|
||||
}
|
||||
case 0x34:
|
||||
{
|
||||
u16 r = TeakraCore->RecvData(2);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
u32 Read32(u32 addr)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18))) return 0;
|
||||
|
||||
addr &= 0x3C;
|
||||
return Read16(addr); // *shrug* (doesn't do anything unintended due to the
|
||||
// 4byte spacing between regs while they're all 16bit)
|
||||
}
|
||||
|
||||
void Write8(u32 addr, u8 val)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18))) return;
|
||||
|
||||
if (!DSPCatchUp()) return;
|
||||
|
||||
addr &= 0x3F;
|
||||
switch (addr)
|
||||
{
|
||||
// no 8-bit PDATA or PADR writes
|
||||
case 0x08:
|
||||
DSP_PCFG = (DSP_PCFG & 0xFF00) | (val << 0);
|
||||
break;
|
||||
case 0x09:
|
||||
DSP_PCFG = (DSP_PCFG & 0x00FF) | (val << 8);
|
||||
break;
|
||||
// no PSTS writes
|
||||
// no 8-bit semaphore writes
|
||||
// no 8-bit CMDx writes
|
||||
// no REPx writes
|
||||
}
|
||||
}
|
||||
void Write16(u32 addr, u16 val)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18))) return;
|
||||
|
||||
if (!DSPCatchUp()) return;
|
||||
|
||||
addr &= 0x3E;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: PDataDMAWrite(val); break;
|
||||
case 0x04: DSP_PADR = val; break;
|
||||
|
||||
case 0x08:
|
||||
DSP_PCFG = val;
|
||||
if (DSP_PCFG & (1<<4))
|
||||
PDataDMAStart();
|
||||
else
|
||||
PDataDMACancel();
|
||||
break;
|
||||
// no PSTS writes
|
||||
case 0x10:
|
||||
DSP_PSEM = val;
|
||||
TeakraCore->SetSemaphore(val);
|
||||
break;
|
||||
case 0x14:
|
||||
DSP_PMASK = val;
|
||||
TeakraCore->MaskSemaphore(val);
|
||||
break;
|
||||
case 0x18: // PCLEAR
|
||||
TeakraCore->ClearSemaphore(val);
|
||||
if (TeakraCore->GetSemaphore() == 0)
|
||||
DSP_PSTS &= ~(1<<9);
|
||||
|
||||
break;
|
||||
// SEM not writable
|
||||
|
||||
case 0x20: // CMD0
|
||||
DSP_CMD[0] = val;
|
||||
TeakraCore->SendData(0, val);
|
||||
break;
|
||||
case 0x28: // CMD1
|
||||
DSP_CMD[1] = val;
|
||||
TeakraCore->SendData(1, val);
|
||||
break;
|
||||
case 0x30: // CMD2
|
||||
DSP_CMD[2] = val;
|
||||
TeakraCore->SendData(2, val);
|
||||
break;
|
||||
|
||||
// no REPx writes
|
||||
}
|
||||
}
|
||||
|
||||
void Write32(u32 addr, u32 val)
|
||||
{
|
||||
if (!(DSi::SCFG_EXT[0] & (1<<18))) return;
|
||||
|
||||
addr &= 0x3C;
|
||||
Write16(addr, val & 0xFFFF);
|
||||
}
|
||||
|
||||
void Run(u32 cycles)
|
||||
{
|
||||
if (!IsDSPCoreEnabled())
|
||||
{
|
||||
DSPTimestamp += cycles;
|
||||
return;
|
||||
}
|
||||
|
||||
TeakraCore->Run(cycles);
|
||||
|
||||
DSPTimestamp += cycles;
|
||||
|
||||
NDS::ScheduleEvent(NDS::Event_DSi_DSP, false,
|
||||
16384/*from citra (TeakraSlice)*/, DSPCatchUpU32, 0);
|
||||
}
|
||||
|
||||
void DoSavestate(Savestate* file)
|
||||
{
|
||||
file->Section("DSPi");
|
||||
|
||||
PDATAReadFifo.DoSavestate(file);
|
||||
|
||||
file->Var64(&DSPTimestamp);
|
||||
file->Var32((u32*)&PDataDMALen);
|
||||
|
||||
file->Var16(&DSP_PADR);
|
||||
file->Var16(&DSP_PCFG);
|
||||
file->Var16(&DSP_PSTS);
|
||||
file->Var16(&DSP_PSEM);
|
||||
file->Var16(&DSP_PMASK);
|
||||
file->Var16(&DSP_PCLEAR);
|
||||
file->Var16(&DSP_CMD[0]);
|
||||
file->Var16(&DSP_CMD[1]);
|
||||
file->Var16(&DSP_CMD[2]);
|
||||
file->Var16(&DSP_REP[0]);
|
||||
file->Var16(&DSP_REP[1]);
|
||||
file->Var16(&DSP_REP[2]);
|
||||
file->Var8((u8*)&SCFG_RST);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2020 PoroCYon
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef DSI_DSP_H
|
||||
#define DSI_DSP_H
|
||||
|
||||
// TODO: for actual sound output
|
||||
// * audio callbacks
|
||||
// * SNDEXCNT
|
||||
|
||||
namespace DSi_DSP
|
||||
{
|
||||
|
||||
extern u16 DSP_PDATA;
|
||||
extern u16 DSP_PADR;
|
||||
extern u16 DSP_PCFG;
|
||||
extern u16 DSP_PSTS;
|
||||
extern u16 DSP_PSEM;
|
||||
extern u16 DSP_PMASK;
|
||||
extern u16 DSP_PCLEAR;
|
||||
extern u16 DSP_SEM;
|
||||
extern u16 DSP_CMD[3];
|
||||
extern u16 DSP_REP[3];
|
||||
|
||||
bool Init();
|
||||
void DeInit();
|
||||
void Reset();
|
||||
|
||||
// TODO: needs to be called!
|
||||
// however, no DSi savestate stuff seems to be actually implemented?!
|
||||
void DoSavestate(Savestate* file);
|
||||
|
||||
// SCFG_RST bit0
|
||||
bool IsRstReleased();
|
||||
void SetRstLine(bool release);
|
||||
|
||||
// apply NWRAM settings
|
||||
void OnMBKCfg(char bank, u32 slot, u8 oldcfg, u8 newcfg, u8* nwrambacking);
|
||||
|
||||
// DSP_* regs (0x040043xx) (NOTE: checks SCFG_EXT)
|
||||
u8 Read8(u32 addr);
|
||||
void Write8(u32 addr, u8 val);
|
||||
|
||||
u16 Read16(u32 addr);
|
||||
void Write16(u32 addr, u16 val);
|
||||
|
||||
u32 Read32(u32 addr);
|
||||
void Write32(u32 addr, u32 val);
|
||||
|
||||
// NOTE: checks SCFG_CLK9
|
||||
void Run(u32 cycles);
|
||||
|
||||
}
|
||||
|
||||
#endif // DSI_DSP_H
|
||||
|
|
@ -50,6 +50,7 @@ enum
|
|||
Event_DSi_CamTransfer,
|
||||
|
||||
Event_DSi_RAMSizeChange,
|
||||
Event_DSi_DSP,
|
||||
|
||||
Event_MAX
|
||||
};
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# Build directory
|
||||
[Bb]uild/
|
||||
cmake-build/
|
||||
doc-build/
|
||||
|
||||
test/
|
||||
|
||||
# Generated source files
|
||||
src/common/scm_rev.cpp
|
||||
.travis.descriptor.json
|
||||
|
||||
# Project/editor files
|
||||
*.swp
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
*~
|
||||
|
||||
# Visual Studio CMake settings
|
||||
CMakeSettings.json
|
||||
|
||||
# OSX global filetypes
|
||||
# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Windows global filetypes
|
||||
Thumbs.db
|
||||
|
||||
teakra.out
|
||||
teakra.out.*
|
||||
|
||||
# cmake and ctest temporary files
|
||||
/Testing
|
|
@ -0,0 +1,33 @@
|
|||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: NAME="Linux Build"
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/assets
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- gcc-7
|
||||
- g++-7
|
||||
script: ./.travis/linux-build.sh
|
||||
- env: NAME="macOS Build"
|
||||
os: osx
|
||||
sudo: false
|
||||
osx_image: xcode10
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/assets
|
||||
script: ./.travis/macos-build.sh
|
||||
- env: NAME="Windows Build"
|
||||
os: windows
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/assets
|
||||
script: ./.travis/windows-build.sh
|
|
@ -0,0 +1,89 @@
|
|||
cmake_minimum_required(VERSION 3.8)
|
||||
project(teakra CXX)
|
||||
|
||||
# Determine if we're built as a subproject (using add_subdirectory)
|
||||
# or if this is the master project.
|
||||
set(MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(MASTER_PROJECT ON)
|
||||
endif()
|
||||
|
||||
option(TEAKRA_WARNINGS_AS_ERRORS "Warnings as errors" ${MASTER_PROJECT})
|
||||
option(TEAKRA_BUILD_TOOLS "Build tools" ${MASTER_PROJECT})
|
||||
option(TEAKRA_BUILD_UNIT_TESTS "Build unit tests" ${MASTER_PROJECT})
|
||||
option(TEAKRA_RUN_TESTS "Run Teakra accuracy tests" OFF)
|
||||
|
||||
# Set hard requirements for C++
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Warn on CMake API deprecations
|
||||
set(CMAKE_WARN_DEPRECATED ON)
|
||||
|
||||
# Disable in-source builds
|
||||
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
|
||||
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
|
||||
if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
message(SEND_ERROR "In-source builds are not allowed.")
|
||||
endif()
|
||||
|
||||
# Add the module directory to the list of paths
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")
|
||||
|
||||
# Compiler flags
|
||||
if (MSVC)
|
||||
set(TEAKRA_CXX_FLAGS
|
||||
/std:c++latest # CMAKE_CXX_STANDARD as no effect on MSVC until CMake 3.10.
|
||||
/W3
|
||||
/permissive- # Stricter C++ standards conformance
|
||||
/MP
|
||||
/Zi
|
||||
/Zo
|
||||
/EHsc
|
||||
/Zc:throwingNew # Assumes new never returns null
|
||||
/Zc:inline # Omits inline functions from object-file output
|
||||
/DNOMINMAX
|
||||
/D_CRT_SECURE_NO_WARNINGS)
|
||||
|
||||
if (TEAKRA_WARNINGS_AS_ERRORS)
|
||||
list(APPEND TEAKRA_CXX_FLAGS
|
||||
/WX)
|
||||
endif()
|
||||
else()
|
||||
set(TEAKRA_CXX_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wcast-qual
|
||||
-pedantic
|
||||
-pedantic-errors
|
||||
-Wfatal-errors
|
||||
-Wno-missing-braces
|
||||
-Wno-unused-parameter)
|
||||
|
||||
if (TEAKRA_WARNINGS_AS_ERRORS)
|
||||
list(APPEND TEAKRA_CXX_FLAGS
|
||||
-Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Prefer the -pthread flag on Linux.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
|
||||
if (NOT TEAKRA_TEST_ASSETS_DIR)
|
||||
set(TEAKRA_TEST_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
# External libraries
|
||||
add_subdirectory(externals)
|
||||
|
||||
# Teakra project files
|
||||
add_subdirectory(src)
|
||||
|
||||
# Teakra tests
|
||||
if (TEAKRA_BUILD_UNIT_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
|
@ -0,0 +1,17 @@
|
|||
# This function should be passed a name of an existing target. It will automatically generate
|
||||
# file groups following the directory hierarchy, so that the layout of the files in IDEs matches the
|
||||
# one in the filesystem.
|
||||
function(create_target_directory_groups target_name)
|
||||
# Place any files that aren't in the source list in a separate group so that they don't get in
|
||||
# the way.
|
||||
source_group("Other Files" REGULAR_EXPRESSION ".")
|
||||
|
||||
get_target_property(target_sources "${target_name}" SOURCES)
|
||||
|
||||
foreach(file_name IN LISTS target_sources)
|
||||
get_filename_component(dir_name "${file_name}" PATH)
|
||||
# Group names use '\' as a separator even though the entire rest of CMake uses '/'...
|
||||
string(REPLACE "/" "\\" group_name "${dir_name}")
|
||||
source_group("${group_name}" FILES "${file_name}")
|
||||
endforeach()
|
||||
endfunction()
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Weiyi Wang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
# Teakra
|
||||
|
||||
[](https://travis-ci.com/wwylele/teakra)
|
||||
[](https://ci.appveyor.com/project/wwylele/teakra/branch/master)
|
||||
|
||||
Emulator, (dis-)assembler, tools and documentation for XpertTeak, the DSP used by DSi/3DS.
|
||||
|
||||
Many thanks to Martin Korth and many other contributers for their help and their [excellent GBATEK doc](http://problemkaputt.de/gbatek.htm#dsixpertteakdsp)!
|
||||
|
||||
## Contents
|
||||
Please refer to README.md in the following directories for their detail.
|
||||
- `src` contains main source code for compiling portable libraries/tools. [Detailed documentation](src/README.md) for the Teak architecture and for peripherals is also here.
|
||||
- `include` contains the header for the emulator and the disassembler libraries.
|
||||
- `dsptester` contains the source code of a 3DS tool that tests processor instructions and registers
|
||||
- `dspmemorytester` contains the source code of another 3DS tool that tests memory read/write, MMIO and DMA.
|
||||
|
||||
## General Information of the XpertTeak
|
||||
|
||||
The XpertTeak DSP consists of a Teak-family architecture processor, and peripheral components including DMA, interrupt controller and audio input/output ports etc. The exact architecture of the processor is still unclear. GBATEK states that the architecture is TeakLite II, the successor of the TeakLite architecture. Their evidence is the TeakLite II disassembler bundled in RealView Developer Suite. However, a Teak family debugger from [here](https://www.lauterbach.com) shows that the "TEAK(-REVA, -REVB, DEV-A0, -RTL2_0)" contains very similar registers and instruction set described in GBATEK, while the "TeakLite-II" contains very different registers and instructions. This shows that the architecture is likely the original Teak, introduced along with TeakLite as a "non-Lite" expansion to it.
|
||||
|
||||
DSi and 3DS both include XpertTeak. However, their uses of XpertTeak are pretty different. Most DSi games don't use it at all. It's used by the "Nintendo DSi Sound" and "Nintendo Zone" system utilities, and by the "Cooking Coach" cartridge (according to GBATEK), where it appears to be intended for audio/video decoding. On the contrary, 3DS games all use XpertTeak for audio decoding and output.
|
|
@ -0,0 +1,32 @@
|
|||
# shallow clone
|
||||
clone_depth: 5
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
cmake_generator: "Visual Studio 15 2017 Win64"
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake .. -G "%cmake_generator%" -DTEAKRA_TEST_ASSETS_DIR="%USERPROFILE%\assets" -DTEAKRA_RUN_TESTS=ON
|
||||
- cd ..
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\assets'
|
||||
|
||||
build:
|
||||
project: build/teakra.sln
|
||||
parallel: true
|
||||
|
||||
test_script:
|
||||
- cd build && ctest -VV -C Release && cd ..
|
|
@ -0,0 +1,5 @@
|
|||
if (TEAKRA_BUILD_UNIT_TESTS)
|
||||
add_library(catch INTERFACE)
|
||||
target_include_directories(catch INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/catch>)
|
||||
endif()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Teakra::Disassembler {
|
||||
|
||||
struct ArArpSettings {
|
||||
std::array<std::uint16_t, 2> ar;
|
||||
std::array<std::uint16_t, 4> arp;
|
||||
};
|
||||
|
||||
bool NeedExpansion(std::uint16_t opcode);
|
||||
bool NeedExpansion(std::uint16_t opcode);
|
||||
std::vector<std::string> GetTokenList(std::uint16_t opcode, std::uint16_t expansion = 0,
|
||||
std::optional<ArArpSettings> ar_arp = std::nullopt);
|
||||
std::string Do(std::uint16_t opcode, std::uint16_t expansion = 0,
|
||||
std::optional<ArArpSettings> ar_arp = std::nullopt);
|
||||
|
||||
} // namespace Teakra::Disassembler
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool Teakra_Disasm_NeedExpansion(uint16_t opcode);
|
||||
|
||||
size_t Teakra_Disasm_Do(char* dst, size_t dstlen,
|
||||
uint16_t opcode, uint16_t expansion /*= 0*/);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
struct AHBMCallback {
|
||||
std::function<std::uint8_t(std::uint32_t address)> read8;
|
||||
std::function<void(std::uint32_t address, std::uint8_t value)> write8;
|
||||
|
||||
std::function<std::uint16_t(std::uint32_t address)> read16;
|
||||
std::function<void(std::uint32_t address, std::uint16_t value)> write16;
|
||||
|
||||
std::function<std::uint32_t(std::uint32_t address)> read32;
|
||||
std::function<void(std::uint32_t address, std::uint32_t value)> write32;
|
||||
};
|
||||
|
||||
class Teakra {
|
||||
public:
|
||||
Teakra();
|
||||
~Teakra();
|
||||
|
||||
void Reset();
|
||||
|
||||
std::array<std::uint8_t, 0x80000>& GetDspMemory();
|
||||
const std::array<std::uint8_t, 0x80000>& GetDspMemory() const;
|
||||
|
||||
// APBP Data
|
||||
bool SendDataIsEmpty(std::uint8_t index) const;
|
||||
void SendData(std::uint8_t index, std::uint16_t value);
|
||||
bool RecvDataIsReady(std::uint8_t index) const;
|
||||
std::uint16_t RecvData(std::uint8_t index);
|
||||
std::uint16_t PeekRecvData(std::uint8_t index);
|
||||
void SetRecvDataHandler(std::uint8_t index, std::function<void()> handler);
|
||||
|
||||
// APBP Semaphore
|
||||
void SetSemaphore(std::uint16_t value);
|
||||
void ClearSemaphore(std::uint16_t value);
|
||||
void MaskSemaphore(std::uint16_t value);
|
||||
void SetSemaphoreHandler(std::function<void()> handler);
|
||||
std::uint16_t GetSemaphore() const;
|
||||
|
||||
// for implementing DSP_PDATA/PADR DMA transfers
|
||||
std::uint16_t ProgramRead(std::uint32_t address) const;
|
||||
void ProgramWrite(std::uint32_t address, std::uint16_t value);
|
||||
std::uint16_t DataRead(std::uint16_t address, bool bypass_mmio = false);
|
||||
void DataWrite(std::uint16_t address, std::uint16_t value, bool bypass_mmio = false);
|
||||
std::uint16_t DataReadA32(std::uint32_t address) const;
|
||||
void DataWriteA32(std::uint32_t address, std::uint16_t value);
|
||||
std::uint16_t MMIORead(std::uint16_t address);
|
||||
void MMIOWrite(std::uint16_t address, std::uint16_t value);
|
||||
|
||||
// DSP_PADR is only 16-bit, so this is where the DMA interface gets the
|
||||
// upper 16-bits from
|
||||
std::uint16_t DMAChan0GetSrcHigh();
|
||||
std::uint16_t DMAChan0GetDstHigh();
|
||||
|
||||
std::uint16_t AHBMGetUnitSize(std::uint16_t i) const;
|
||||
std::uint16_t AHBMGetDirection(std::uint16_t i) const;
|
||||
std::uint16_t AHBMGetDmaChannel(std::uint16_t i) const;
|
||||
// we need these as AHBM does some weird stuff on unaligned accesses internally
|
||||
std::uint16_t AHBMRead16(std::uint32_t addr);
|
||||
void AHBMWrite16(std::uint32_t addr, std::uint16_t value);
|
||||
std::uint16_t AHBMRead32(std::uint32_t addr);
|
||||
void AHBMWrite32(std::uint32_t addr, std::uint32_t value);
|
||||
|
||||
// core
|
||||
void Run(unsigned cycle);
|
||||
|
||||
void SetAHBMCallback(const AHBMCallback& callback);
|
||||
|
||||
void SetAudioCallback(std::function<void(std::array<std::int16_t, 2>)> callback);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct TeakraObject;
|
||||
typedef struct TeakraObject TeakraContext;
|
||||
|
||||
typedef void (*Teakra_InterruptCallback)(void* userdata);
|
||||
typedef void (*Teakra_AudioCallback)(void* userdata, int16_t samples[2]);
|
||||
|
||||
typedef uint8_t (*Teakra_AHBMReadCallback8)(void* userdata, uint32_t address);
|
||||
typedef void (*Teakra_AHBMWriteCallback8)(void* userdata, uint32_t address, uint8_t value);
|
||||
|
||||
typedef uint16_t (*Teakra_AHBMReadCallback16)(void* userdata, uint32_t address);
|
||||
typedef void (*Teakra_AHBMWriteCallback16)(void* userdata, uint32_t address, uint16_t value);
|
||||
|
||||
typedef uint32_t (*Teakra_AHBMReadCallback32)(void* userdata, uint32_t address);
|
||||
typedef void (*Teakra_AHBMWriteCallback32)(void* userdata, uint32_t address, uint32_t value);
|
||||
|
||||
TeakraContext* Teakra_Create();
|
||||
void Teakra_Destroy(TeakraContext* context);
|
||||
void Teakra_Reset(TeakraContext* context);
|
||||
uint8_t* Teakra_GetDspMemory(TeakraContext* context);
|
||||
|
||||
int Teakra_SendDataIsEmpty(const TeakraContext* context, uint8_t index);
|
||||
void Teakra_SendData(TeakraContext* context, uint8_t index, uint16_t value);
|
||||
int Teakra_RecvDataIsReady(const TeakraContext* context, uint8_t index);
|
||||
uint16_t Teakra_RecvData(TeakraContext* context, uint8_t index);
|
||||
uint16_t Teakra_PeekRecvData(TeakraContext* context, uint8_t index);
|
||||
void Teakra_SetRecvDataHandler(TeakraContext* context, uint8_t index,
|
||||
Teakra_InterruptCallback handler, void* userdata);
|
||||
|
||||
void Teakra_SetSemaphore(TeakraContext* context, uint16_t value);
|
||||
void Teakra_ClearSemaphore(TeakraContext* context, uint16_t value);
|
||||
void Teakra_MaskSemaphore(TeakraContext* context, uint16_t value);
|
||||
void Teakra_SetSemaphoreHandler(TeakraContext* context, Teakra_InterruptCallback handler,
|
||||
void* userdata);
|
||||
uint16_t Teakra_GetSemaphore(const TeakraContext* context);
|
||||
|
||||
uint16_t Teakra_ProgramRead(TeakraContext* context, uint32_t address);
|
||||
void Teakra_ProgramWrite(TeakraContext* context, uint32_t address, uint16_t value);
|
||||
uint16_t Teakra_DataRead(TeakraContext* context, uint16_t address, bool bypass_mmio);
|
||||
void Teakra_DataWrite(TeakraContext* context, uint16_t address, uint16_t value, bool bypass_mmio);
|
||||
uint16_t Teakra_DataReadA32(TeakraContext* context, uint32_t address);
|
||||
void Teakra_DataWriteA32(TeakraContext* context, uint32_t address, uint16_t value);
|
||||
uint16_t Teakra_MMIORead(TeakraContext* context, uint16_t address);
|
||||
void Teakra_MMIOWrite(TeakraContext* context, uint16_t address, uint16_t value);
|
||||
|
||||
uint16_t Teakra_DMAChan0GetSrcHigh(TeakraContext* context);
|
||||
uint16_t Teakra_DMAChan0GetDstHigh(TeakraContext* context);
|
||||
|
||||
uint16_t Teakra_AHBMGetUnitSize(TeakraContext* context, uint16_t i);
|
||||
uint16_t Teakra_AHBMGetDirection(TeakraContext* context, uint16_t i);
|
||||
uint16_t Teakra_AHBMGetDmaChannel(TeakraContext* context, uint16_t i);
|
||||
|
||||
uint16_t Teakra_AHBMRead16(TeakraContext* context, uint32_t addr);
|
||||
void Teakra_AHBMWrite16(TeakraContext* context, uint32_t addr, uint16_t value);
|
||||
uint16_t Teakra_AHBMRead32(TeakraContext* context, uint32_t addr);
|
||||
void Teakra_AHBMWrite32(TeakraContext* context, uint32_t addr, uint32_t value);
|
||||
|
||||
|
||||
void Teakra_Run(TeakraContext* context, unsigned cycle);
|
||||
|
||||
void Teakra_SetAHBMCallback(TeakraContext* context,
|
||||
Teakra_AHBMReadCallback8 read8 , Teakra_AHBMWriteCallback8 write8 ,
|
||||
Teakra_AHBMReadCallback16 read16, Teakra_AHBMWriteCallback16 write16,
|
||||
Teakra_AHBMReadCallback32 read32, Teakra_AHBMWriteCallback32 write32,
|
||||
void* userdata);
|
||||
|
||||
|
||||
void Teakra_SetAudioCallback(TeakraContext* context, Teakra_AudioCallback callback, void* userdata);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,64 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_library(teakra
|
||||
../include/teakra/disassembler.h
|
||||
../include/teakra/teakra.h
|
||||
ahbm.cpp
|
||||
ahbm.h
|
||||
apbp.cpp
|
||||
apbp.h
|
||||
bit.h
|
||||
btdmp.cpp
|
||||
btdmp.h
|
||||
common_types.h
|
||||
crash.h
|
||||
decoder.h
|
||||
disassembler.cpp
|
||||
dma.cpp
|
||||
dma.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
icu.h
|
||||
interpreter.h
|
||||
matcher.h
|
||||
memory_interface.cpp
|
||||
memory_interface.h
|
||||
mmio.cpp
|
||||
mmio.h
|
||||
operand.h
|
||||
parser.cpp
|
||||
processor.cpp
|
||||
processor.h
|
||||
register.h
|
||||
shared_memory.h
|
||||
teakra.cpp
|
||||
test.h
|
||||
test_generator.cpp
|
||||
test_generator.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(teakra)
|
||||
|
||||
target_link_libraries(teakra PRIVATE Threads::Threads)
|
||||
target_include_directories(teakra
|
||||
PUBLIC ../include
|
||||
PRIVATE .)
|
||||
target_compile_options(teakra PRIVATE ${TEAKRA_CXX_FLAGS})
|
||||
|
||||
add_library(teakra_c
|
||||
../include/teakra/disassembler_c.h
|
||||
../include/teakra/teakra_c.h
|
||||
disassembler_c.cpp
|
||||
teakra_c.cpp
|
||||
)
|
||||
target_link_libraries(teakra_c PRIVATE teakra)
|
||||
|
||||
if (TEAKRA_BUILD_TOOLS)
|
||||
add_subdirectory(coff_reader)
|
||||
add_subdirectory(dsp1_reader)
|
||||
add_subdirectory(test_generator)
|
||||
add_subdirectory(test_verifier)
|
||||
add_subdirectory(mod_test_generator)
|
||||
add_subdirectory(step2_test_generator)
|
||||
add_subdirectory(makedsp1)
|
||||
endif()
|
|
@ -0,0 +1,29 @@
|
|||
## Content
|
||||
|
||||
- main library
|
||||
- [processor related](processor_general.md)
|
||||
- operand: defines basic operand types used in instructions
|
||||
- matcher and decoder: decodes binary instructions into opcodes and operands
|
||||
- disassembler: translate binary instructions to (pseudo-)assembly.
|
||||
- parser: translate (pseudo-)assembly to binary instructions
|
||||
- interpreter: executes instructions
|
||||
- [register](register.md): defines all register states in the processor
|
||||
- processor: wrapper of interpreter and register as a processor emulator
|
||||
- test_generator: generates test cases information for the instruction set
|
||||
- peripherals
|
||||
- [AHBM](ahbm.md): interface for accessing external memory (DSi/3DS main memory)
|
||||
- [APBP](apbp.md): interface for communication with CPU (ARM in DSi/3DS)
|
||||
- [BTDMP](btdmp.md): audio input/output ports
|
||||
- [DMA](dma.md): engine for transferring large data between DSP memory and external memory
|
||||
- [ICU](icu.md): interrupt controller unit
|
||||
- [timer](timer.md)
|
||||
- [MMIO](mmio.md): I/O ports for all peripherals
|
||||
- shared_memory: the DSP working memory
|
||||
- [memory_interface](miu.md): the memory space exposed to the processor and related control
|
||||
- Tools
|
||||
- coff_reader: disassembles and parses symbols COFF files leaked by some DSi applications
|
||||
- dsp1_reader: disassembles DSP1 files, DSP binary for 3DS applications
|
||||
- makedsp1: assembles DSP1 files
|
||||
- test_generator: generate random test cases for processor instructions.
|
||||
- mod_test_generator & step2_test_generator: similar to test_generator, but dedicated for mod/step2 related instructions
|
||||
- test_verifier: verify test cases on the interpreter against the result generated from 3DS
|
|
@ -0,0 +1,159 @@
|
|||
#include <cstdio>
|
||||
#include "ahbm.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
void Ahbm::Reset() {
|
||||
busy_flag = 0;
|
||||
channels = {};
|
||||
}
|
||||
|
||||
unsigned Ahbm::Channel::GetBurstSize() {
|
||||
switch (burst_size) {
|
||||
case Ahbm::BurstSize::X1:
|
||||
return 1;
|
||||
case Ahbm::BurstSize::X4:
|
||||
return 4;
|
||||
case Ahbm::BurstSize::X8:
|
||||
return 8;
|
||||
default:
|
||||
std::printf("Unknown burst size %04X\n", static_cast<u16>(burst_size));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
u16 Ahbm::Read16(u16 channel, u32 address) {
|
||||
u32 value32 = Read32(channel, address);
|
||||
if ((address & 1) == 0) {
|
||||
return (u16)(value32 & 0xFFFF);
|
||||
} else {
|
||||
return (u16)(value32 >> 16);
|
||||
}
|
||||
}
|
||||
u32 Ahbm::Read32(u16 channel, u32 address) {
|
||||
if (channels[channel].direction != Direction::Read) {
|
||||
std::printf("Wrong direction!\n");
|
||||
}
|
||||
|
||||
if (channels[channel].burst_queue.empty()) {
|
||||
u32 current = address;
|
||||
unsigned size = channels[channel].GetBurstSize();
|
||||
for (unsigned i = 0; i < size; ++i) {
|
||||
u32 value = 0;
|
||||
switch (channels[channel].unit_size) {
|
||||
case UnitSize::U8:
|
||||
value = read_external8(current);
|
||||
if ((current & 1) == 1) {
|
||||
value <<= 8; // this weird bahiviour is hwtested
|
||||
}
|
||||
current += 1;
|
||||
break;
|
||||
case UnitSize::U16: {
|
||||
u32 current_masked = current & 0xFFFFFFFE;
|
||||
value = read_external16(current_masked);
|
||||
current += 2;
|
||||
break;
|
||||
}
|
||||
case UnitSize::U32: {
|
||||
u32 current_masked = current & 0xFFFFFFFC;
|
||||
value = read_external32(current_masked);
|
||||
current += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::printf("Unknown unit size %04X\n",
|
||||
static_cast<u16>(channels[channel].unit_size));
|
||||
break;
|
||||
}
|
||||
channels[channel].burst_queue.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
u32 value = channels[channel].burst_queue.front();
|
||||
channels[channel].burst_queue.pop();
|
||||
return value;
|
||||
}
|
||||
void Ahbm::Write16(u16 channel, u32 address, u16 value) {
|
||||
WriteInternal(channel, address, value);
|
||||
}
|
||||
void Ahbm::Write32(u16 channel, u32 address, u32 value) {
|
||||
if ((address & 1) == 1) {
|
||||
value >>= 16; // this weird behaviour is hwtested
|
||||
}
|
||||
WriteInternal(channel, address, value);
|
||||
}
|
||||
|
||||
void Ahbm::WriteInternal(u16 channel, u32 address, u32 value) {
|
||||
if (channels[channel].direction != Direction::Write) {
|
||||
std::printf("Wrong direction!\n");
|
||||
}
|
||||
|
||||
if (channels[channel].burst_queue.empty()) {
|
||||
channels[channel].write_burst_start = address;
|
||||
}
|
||||
|
||||
channels[channel].burst_queue.push(value);
|
||||
if (channels[channel].burst_queue.size() >= channels[channel].GetBurstSize()) {
|
||||
u32 current = channels[channel].write_burst_start;
|
||||
while (!channels[channel].burst_queue.empty()) {
|
||||
u32 value32 = channels[channel].burst_queue.front();
|
||||
channels[channel].burst_queue.pop();
|
||||
switch (channels[channel].unit_size) {
|
||||
case UnitSize::U8: {
|
||||
// this weird behaviour is hwtested
|
||||
u8 value8 = ((current & 1) == 1) ? (u8)(value32 >> 8) : (u8)value32;
|
||||
write_external8(current, value8);
|
||||
current += 1;
|
||||
break;
|
||||
}
|
||||
case UnitSize::U16: {
|
||||
u32 c0 = current & 0xFFFFFFFE;
|
||||
u32 c1 = c0 + 1;
|
||||
if (c0 >= current) {
|
||||
write_external16(c0, (u16)value32);
|
||||
} else {
|
||||
write_external8(c1, (u8)(value32 >> 8));
|
||||
}
|
||||
current += 2;
|
||||
break;
|
||||
}
|
||||
case UnitSize::U32: {
|
||||
u32 c0 = current & 0xFFFFFFFC;
|
||||
u32 c1 = c0 + 1;
|
||||
u32 c2 = c0 + 2;
|
||||
u32 c3 = c0 + 3;
|
||||
|
||||
if (c0 >= current && c1 >= current && c2 >= current) {
|
||||
write_external32(c0, value32);
|
||||
} else if (c2 >= current) {
|
||||
if (c1 >= current) {
|
||||
write_external8(c1, (u8)(value32 >> 8));
|
||||
}
|
||||
write_external16(c2, (u16)(value32 >> 16));
|
||||
} else {
|
||||
write_external8(c3, (u8)(value32 >> 24));
|
||||
}
|
||||
|
||||
current += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::printf("Unknown unit size %04X\n",
|
||||
static_cast<u16>(channels[channel].unit_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 Ahbm::GetChannelForDma(u16 dma_channel) const {
|
||||
for (u16 channel = 0; channel < channels.size(); ++channel) {
|
||||
if ((channels[channel].dma_channel >> dma_channel) & 1) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
std::printf("Could not find AHBM channel for DMA channel %04X\n", dma_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class Ahbm {
|
||||
public:
|
||||
enum class UnitSize : u16 {
|
||||
U8 = 0,
|
||||
U16 = 1,
|
||||
U32 = 2,
|
||||
};
|
||||
|
||||
enum class BurstSize : u16 {
|
||||
X1 = 0,
|
||||
X4 = 1,
|
||||
X8 = 2,
|
||||
};
|
||||
|
||||
enum class Direction : u16 {
|
||||
Read = 0,
|
||||
Write = 1,
|
||||
};
|
||||
|
||||
void Reset();
|
||||
|
||||
u16 GetBusyFlag() const {
|
||||
return busy_flag;
|
||||
}
|
||||
void SetUnitSize(u16 i, u16 value) {
|
||||
channels[i].unit_size = static_cast<UnitSize>(value);
|
||||
}
|
||||
u16 GetUnitSize(u16 i) const {
|
||||
return static_cast<u16>(channels[i].unit_size);
|
||||
}
|
||||
void SetBurstSize(u16 i, u16 value) {
|
||||
channels[i].burst_size = static_cast<BurstSize>(value);
|
||||
}
|
||||
u16 GetBurstSize(u16 i) const {
|
||||
return static_cast<u16>(channels[i].burst_size);
|
||||
}
|
||||
void SetDirection(u16 i, u16 value) {
|
||||
channels[i].direction = static_cast<Direction>(value);
|
||||
}
|
||||
u16 GetDirection(u16 i) const {
|
||||
return static_cast<u16>(channels[i].direction);
|
||||
}
|
||||
void SetDmaChannel(u16 i, u16 value) {
|
||||
channels[i].dma_channel = value;
|
||||
}
|
||||
u16 GetDmaChannel(u16 i) const {
|
||||
return channels[i].dma_channel;
|
||||
}
|
||||
|
||||
u16 Read16(u16 channel, u32 address);
|
||||
u32 Read32(u16 channel, u32 address);
|
||||
void Write16(u16 channel, u32 address, u16 value);
|
||||
void Write32(u16 channel, u32 address, u32 value);
|
||||
|
||||
u16 GetChannelForDma(u16 dma_channel) const;
|
||||
|
||||
void SetExternalMemoryCallback(
|
||||
std::function<u8 (u32)> read8 , std::function<void(u32, u8 )> write8 ,
|
||||
std::function<u16(u32)> read16, std::function<void(u32, u16)> write16,
|
||||
std::function<u32(u32)> read32, std::function<void(u32, u32)> write32) {
|
||||
|
||||
read_external8 = std::move(read8);
|
||||
write_external8 = std::move(write8);
|
||||
read_external16 = std::move(read16);
|
||||
write_external16 = std::move(write16);
|
||||
read_external32 = std::move(read32);
|
||||
write_external32 = std::move(write32);
|
||||
}
|
||||
|
||||
private:
|
||||
u16 busy_flag = 0;
|
||||
struct Channel {
|
||||
UnitSize unit_size = UnitSize::U8;
|
||||
BurstSize burst_size = BurstSize::X1;
|
||||
Direction direction = Direction::Read;
|
||||
u16 dma_channel = 0;
|
||||
|
||||
std::queue<u32> burst_queue;
|
||||
u32 write_burst_start = 0;
|
||||
unsigned GetBurstSize();
|
||||
};
|
||||
std::array<Channel, 3> channels;
|
||||
|
||||
std::function<u8(u32)> read_external8;
|
||||
std::function<void(u32, u8)> write_external8;
|
||||
std::function<u16(u32)> read_external16;
|
||||
std::function<void(u32, u16)> write_external16;
|
||||
std::function<u32(u32)> read_external32;
|
||||
std::function<void(u32, u32)> write_external32;
|
||||
|
||||
void WriteInternal(u16 channel, u32 address, u32 value);
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,37 @@
|
|||
# AHBM
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
Status register
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00E0 | | | | | | | | | | | | | |RNE| | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
Applications wait for all bits to be 0 before connecting AHBM to DMA
|
||||
RNE: 1 when the burst queue is not empty
|
||||
|
||||
Channel config (N = 0, 1, 2)
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00E2+N*6| - | | | | | | - | TYPE | | BURST | R |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00E4+N*6| - | E | W | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00E6+N*6| - | D7| D6| D5| D4| D3| D2| D1| D0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
R: Applications set this to 1 if BURST is non-zero
|
||||
BURST: burst type
|
||||
- 0: x1
|
||||
- 1: x4
|
||||
- 2: x8
|
||||
- 3: ?
|
||||
TYPE: data type
|
||||
- 0: 8 bit
|
||||
- 1: 16 bit
|
||||
- 2: 32 bit
|
||||
- 3: ?
|
||||
W: Transfer direction
|
||||
- 0: read from external memory
|
||||
- 1: write to external memory
|
||||
E: Applications always set this.
|
||||
D0..D7: Connects to DMA channel 0..7 if set to one
|
||||
```
|
|
@ -0,0 +1,149 @@
|
|||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include "apbp.h"
|
||||
|
||||
namespace Teakra {
|
||||
class DataChannel {
|
||||
public:
|
||||
void Reset() {
|
||||
ready = false;
|
||||
data = 0;
|
||||
}
|
||||
|
||||
void Send(u16 data) {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ready = true;
|
||||
this->data = data;
|
||||
if (disable_interrupt)
|
||||
return;
|
||||
}
|
||||
if (handler)
|
||||
handler();
|
||||
}
|
||||
u16 Recv() {
|
||||
std::lock_guard lock(mutex);
|
||||
ready = false;
|
||||
return data;
|
||||
}
|
||||
u16 Peek() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data;
|
||||
}
|
||||
bool IsReady() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return ready;
|
||||
}
|
||||
u16 GetDisableInterrupt() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return disable_interrupt;
|
||||
}
|
||||
void SetDisableInterrupt(u16 v) {
|
||||
disable_interrupt = v;
|
||||
}
|
||||
|
||||
std::function<void()> handler;
|
||||
|
||||
private:
|
||||
bool ready = false;
|
||||
u16 data = 0;
|
||||
u16 disable_interrupt = 0;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
class Apbp::Impl {
|
||||
public:
|
||||
std::array<DataChannel, 3> data_channels;
|
||||
u16 semaphore = 0;
|
||||
u16 semaphore_mask = 0;
|
||||
bool semaphore_master_signal = false;
|
||||
mutable std::recursive_mutex semaphore_mutex;
|
||||
std::function<void()> semaphore_handler;
|
||||
|
||||
void Reset() {
|
||||
for (auto& c : data_channels)
|
||||
c.Reset();
|
||||
semaphore = 0;
|
||||
semaphore_mask = 0;
|
||||
semaphore_master_signal = false;
|
||||
}
|
||||
};
|
||||
|
||||
Apbp::Apbp() : impl(new Impl) {}
|
||||
Apbp::~Apbp() = default;
|
||||
|
||||
void Apbp::Reset() {
|
||||
impl->Reset();
|
||||
}
|
||||
|
||||
void Apbp::SendData(unsigned channel, u16 data) {
|
||||
impl->data_channels[channel].Send(data);
|
||||
}
|
||||
|
||||
u16 Apbp::RecvData(unsigned channel) {
|
||||
return impl->data_channels[channel].Recv();
|
||||
}
|
||||
|
||||
u16 Apbp::PeekData(unsigned channel) const {
|
||||
return impl->data_channels[channel].Peek();
|
||||
}
|
||||
|
||||
bool Apbp::IsDataReady(unsigned channel) const {
|
||||
return impl->data_channels[channel].IsReady();
|
||||
}
|
||||
|
||||
u16 Apbp::GetDisableInterrupt(unsigned channel) const {
|
||||
return impl->data_channels[channel].GetDisableInterrupt();
|
||||
}
|
||||
|
||||
void Apbp::SetDisableInterrupt(unsigned channel, u16 v) {
|
||||
impl->data_channels[channel].SetDisableInterrupt(v);
|
||||
}
|
||||
|
||||
void Apbp::SetDataHandler(unsigned channel, std::function<void()> handler) {
|
||||
impl->data_channels[channel].handler = std::move(handler);
|
||||
}
|
||||
|
||||
void Apbp::SetSemaphore(u16 bits) {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
impl->semaphore |= bits;
|
||||
bool new_signal = (impl->semaphore & ~impl->semaphore_mask) != 0;
|
||||
if (new_signal && impl->semaphore_handler) {
|
||||
impl->semaphore_handler();
|
||||
}
|
||||
impl->semaphore_master_signal = impl->semaphore_master_signal || new_signal;
|
||||
}
|
||||
|
||||
void Apbp::ClearSemaphore(u16 bits) {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
impl->semaphore &= ~bits;
|
||||
impl->semaphore_master_signal = (impl->semaphore & ~impl->semaphore_mask) != 0;
|
||||
}
|
||||
|
||||
u16 Apbp::GetSemaphore() const {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
return impl->semaphore;
|
||||
}
|
||||
|
||||
void Apbp::MaskSemaphore(u16 bits) {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
impl->semaphore_mask = bits;
|
||||
}
|
||||
|
||||
u16 Apbp::GetSemaphoreMask() const {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
return impl->semaphore_mask;
|
||||
}
|
||||
|
||||
void Apbp::SetSemaphoreHandler(std::function<void()> handler) {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
impl->semaphore_handler = std::move(handler);
|
||||
}
|
||||
|
||||
bool Apbp::IsSemaphoreSignaled() const {
|
||||
std::lock_guard lock(impl->semaphore_mutex);
|
||||
return impl->semaphore_master_signal;
|
||||
}
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
class Apbp {
|
||||
public:
|
||||
Apbp();
|
||||
~Apbp();
|
||||
|
||||
void Reset();
|
||||
|
||||
void SendData(unsigned channel, u16 data);
|
||||
u16 RecvData(unsigned channel);
|
||||
u16 PeekData(unsigned channel) const;
|
||||
bool IsDataReady(unsigned channel) const;
|
||||
u16 GetDisableInterrupt(unsigned channel) const;
|
||||
void SetDisableInterrupt(unsigned channel, u16 v);
|
||||
void SetDataHandler(unsigned channel, std::function<void()> handler);
|
||||
|
||||
void SetSemaphore(u16 bits);
|
||||
void ClearSemaphore(u16 bits);
|
||||
u16 GetSemaphore() const;
|
||||
void MaskSemaphore(u16 bits);
|
||||
u16 GetSemaphoreMask() const;
|
||||
void SetSemaphoreHandler(std::function<void()> handler);
|
||||
|
||||
bool IsSemaphoreSignaled() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,110 @@
|
|||
# APBP
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
Command/Reply registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00C0 | REPLY0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00C2 | CMD0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00C4 | REPLY1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00C6 | CMD1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00C8 | REPLY2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00CA | CMD2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
REPLY0..2: data sent from DSP to CPU
|
||||
CMD0..2: data received from CPU to DSP
|
||||
|
||||
Semaphore registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00CC | SET_SEMAPHORE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00CE | MASK_SEMAPHORE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00D0 | ACK_SEMAPHORE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00D2 | GET_SEMAPHORE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
SET_SEMAPHORE: sets semaphore sent from DSP to CPU
|
||||
MASK_SEMAPHORE: masks semaphore interrupt received from CPU to DSP
|
||||
ACK_SEMAPHORE: acknowledges/clears semaphore received from CPU to DSP
|
||||
GET_SEMAPHORE: semaphore received from CPU to DSP
|
||||
|
||||
Config/status registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00D4 | |CI2|CI1| |CI0| |END| |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00D6 | | | C2| C1| | | S | C0| R2| R1| R0| | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00D8 | C2| C1| C0| R2| R1| R0| S'|WEM|WFL|RNE|RFL| |PRS|WTU|RTU|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
END: CPU-side registers endianness. 1 to swap byte order of CPU-side registers
|
||||
R0..R2: 1 when there is data in REPLY0..2
|
||||
C0..C2: 1 when there is data in CMD0..2
|
||||
CI0...CI2: 1 to disable interrupt when CMD0..2 is written by CPU
|
||||
S: 1 when (GET_SEMAPHORE & ~MASK_SEMAPHORE) is non-zero
|
||||
S': similar to S, but for CPU side
|
||||
RTU: CPU-side read transfer underway flag
|
||||
WTU: CPU-side write transfer underway flag
|
||||
PRS: peripheral reset flag
|
||||
RFL: CPU-side read FIFO full flag
|
||||
RNE: CPU-side read FIFO non-empty flag
|
||||
WFL: CPU-side write FIFO full flag
|
||||
WEM: CPU-side write FIFO empty flag
|
||||
* Note 0x00D8 is a mirror of CPU-side register DSP_PSTS
|
||||
```
|
||||
|
||||
## CPU-DSP communication
|
||||
|
||||
APBP is an important port for CPU and DSP to communicate with each other. It mainly contains 3 pairs of symmetrical data channels and a pair of 16-bit symmetrical semaphore channel.
|
||||
|
||||
### Data channels
|
||||
|
||||
When one side writes to a data channel, it sets the 1 to the "data ready" bit (`R0..R2` or `C0..C2`), and fires interrupt on the other side if enabled. When the othersides read from the channel register, it automatically clears the "data ready" bit. If new data is written before the previous one is read, the new data will overwrite the old data and fires another interrupt.
|
||||
|
||||
### Semaphore channels
|
||||
|
||||
There are two 16-bit semaphore channels for two directions, `CPU->DSP` and `DSP->CPU`. Writing to `SET_SEMAPHORE` from side A or `ACK_SEMAPHORE` from side B changes the semaphore value of direction `A->B`. Semaphore value change also changes the corresponding `S` bit (See the calculation above). Changing in mask is also reflected in the `S` bit immediately. Whenever a `0->1` transition for `S` is detected, interrupt is fired on B side.
|
||||
|
||||
## CPU side MMIO
|
||||
|
||||
```
|
||||
Command/Reply registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0000 | PDATA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0004 | PADDR |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0008 | DATA_SPACE |RI2|RI1|RI0|IWE|IWF|IRE|IRF|STR| BURST |INC|RST|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x000C | C2| C1| C0| R2| R1| R0| S'|WEM|WFL|RNE|RFL| |PRS|WTU|RTU|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0010 | SET_SEMAPHORE' |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0014 | MASK_SEMAPHORE' |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0018 | ACK_SEMAPHORE' |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x001C | GET_SEMAPHORE' |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0020 | CMD0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0024 | REPLY0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0028 | CMD1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x002C | REPLY1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0030 | CMD2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0034 | REPLY2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace std20 {
|
||||
|
||||
// A simple (and not very correct) implementation of C++20's std::log2p1
|
||||
template <class T>
|
||||
constexpr T log2p1(T x) noexcept {
|
||||
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
|
||||
if (x == 0)
|
||||
return 0;
|
||||
#ifdef _MSC_VER
|
||||
unsigned long index = 0;
|
||||
_BitScanReverse64(&index, x);
|
||||
return static_cast<T>(index) + 1;
|
||||
#else
|
||||
return static_cast<T>(std::numeric_limits<unsigned long long>::digits - __builtin_clzll(x));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace std20
|
|
@ -0,0 +1,97 @@
|
|||
#include <string>
|
||||
#include "btdmp.h"
|
||||
#include "crash.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
Btdmp::Btdmp(CoreTiming& core_timing) {
|
||||
core_timing.RegisterCallbacks(this);
|
||||
}
|
||||
|
||||
Btdmp::~Btdmp() = default;
|
||||
|
||||
void Btdmp::Reset() {
|
||||
transmit_clock_config = 0;
|
||||
transmit_period = 4096;
|
||||
transmit_timer = 0;
|
||||
transmit_enable = 0;
|
||||
transmit_empty = true;
|
||||
transmit_full = false;
|
||||
transmit_queue = {};
|
||||
}
|
||||
|
||||
void Btdmp::Tick() {
|
||||
if (transmit_enable) {
|
||||
++transmit_timer;
|
||||
if (transmit_timer >= transmit_period) {
|
||||
transmit_timer = 0;
|
||||
std::array<std::int16_t, 2> sample;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (transmit_queue.empty()) {
|
||||
std::printf("BTDMP: transmit buffer underrun\n");
|
||||
sample[i] = 0;
|
||||
} else {
|
||||
sample[i] = static_cast<s16>(transmit_queue.front());
|
||||
transmit_queue.pop();
|
||||
transmit_empty = transmit_queue.empty();
|
||||
transmit_full = false;
|
||||
if (transmit_empty) {
|
||||
interrupt_handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (audio_callback) {
|
||||
audio_callback(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u64 Btdmp::GetMaxSkip() const {
|
||||
if (!transmit_enable || transmit_queue.empty()) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
u64 ticks = 0;
|
||||
if (transmit_timer < transmit_period) {
|
||||
// number of ticks before the tick of the next transmit
|
||||
ticks += transmit_period - transmit_timer - 1;
|
||||
}
|
||||
|
||||
// number of ticks from the next transmit to the one just before the transmit that empties
|
||||
// the buffer
|
||||
ticks += ((transmit_queue.size() + 1) / 2 - 1) * transmit_period;
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void Btdmp::Skip(u64 ticks) {
|
||||
if (!transmit_enable)
|
||||
return;
|
||||
|
||||
if (transmit_timer >= transmit_period)
|
||||
transmit_timer = 0;
|
||||
|
||||
u64 future_timer = transmit_timer + ticks;
|
||||
u64 cycles = future_timer / transmit_period;
|
||||
transmit_timer = (u16)(future_timer % transmit_period);
|
||||
|
||||
for (u64 c = 0; c < cycles; ++c) {
|
||||
std::array<std::int16_t, 2> sample;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (transmit_queue.empty()) {
|
||||
sample[i] = 0;
|
||||
} else {
|
||||
sample[i] = static_cast<s16>(transmit_queue.front());
|
||||
transmit_queue.pop();
|
||||
ASSERT(!transmit_queue.empty());
|
||||
transmit_full = false;
|
||||
}
|
||||
}
|
||||
if (audio_callback) {
|
||||
audio_callback(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,99 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
#include "common_types.h"
|
||||
#include "core_timing.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class Btdmp : public CoreTiming::Callbacks {
|
||||
public:
|
||||
Btdmp(CoreTiming& core_timing);
|
||||
~Btdmp();
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetTransmitClockConfig(u16 value) {
|
||||
transmit_clock_config = value;
|
||||
}
|
||||
|
||||
u16 GetTransmitClockConfig() const {
|
||||
return transmit_clock_config;
|
||||
}
|
||||
|
||||
void SetTransmitPeriod(u16 value) {
|
||||
transmit_period = value;
|
||||
}
|
||||
|
||||
u16 GetTransmitPeriod() const {
|
||||
return transmit_period;
|
||||
}
|
||||
|
||||
void SetTransmitEnable(u16 value) {
|
||||
transmit_enable = value;
|
||||
}
|
||||
|
||||
u16 GetTransmitEnable() const {
|
||||
return transmit_enable;
|
||||
}
|
||||
|
||||
u16 GetTransmitEmpty() const {
|
||||
return transmit_empty;
|
||||
}
|
||||
|
||||
u16 GetTransmitFull() const {
|
||||
return transmit_full;
|
||||
}
|
||||
|
||||
void Send(u16 value) {
|
||||
if (transmit_queue.size() == 16) {
|
||||
std::printf("BTDMP: transmit buffer overrun\n");
|
||||
} else {
|
||||
transmit_queue.push(value);
|
||||
transmit_empty = false;
|
||||
transmit_full = transmit_queue.size() == 16;
|
||||
}
|
||||
}
|
||||
|
||||
void SetTransmitFlush(u16 value) {
|
||||
transmit_queue = {};
|
||||
transmit_empty = true;
|
||||
transmit_full = false;
|
||||
}
|
||||
|
||||
u16 GetTransmitFlush() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
u64 GetMaxSkip() const override;
|
||||
void Skip(u64 ticks) override;
|
||||
|
||||
void SetAudioCallback(std::function<void(std::array<std::int16_t, 2>)> callback) {
|
||||
audio_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void SetInterruptHandler(std::function<void()> handler) {
|
||||
interrupt_handler = std::move(handler);
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: figure out the relation between clock_config and period.
|
||||
// Default to period = 4096 for now which every game uses
|
||||
u16 transmit_clock_config = 0;
|
||||
u16 transmit_period = 4096;
|
||||
u16 transmit_timer = 0;
|
||||
u16 transmit_enable = 0;
|
||||
bool transmit_empty = true;
|
||||
bool transmit_full = false;
|
||||
std::queue<u16> transmit_queue;
|
||||
std::function<void(std::array<std::int16_t, 2>)> audio_callback;
|
||||
std::function<void()> interrupt_handler;
|
||||
|
||||
class BtdmpTimingCallbacks;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,82 @@
|
|||
# BTDMP
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
Receive config registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0280 | | | | | | |RIR| | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0282 | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0284 | "0004"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0286 | "0021"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0288 | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x028A | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x028C | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x028E | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0290 | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
...
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x029E |RE | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
RIR: enable IRQ for receive if 1
|
||||
RE: enable receive if 1
|
||||
|
||||
Transmit config registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02A0 | | | | | | |TIR| | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02A2 | | | | | | | <- clock related, "1004"
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02A4 | "0004"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02A6 | "0021"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02A8 | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02AA | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02AC | "0000"? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02AE | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02B0 | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
...
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02BE |RT | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
TIR: enable IRQ for transmit if 1
|
||||
TE: enable transmit if 1
|
||||
|
||||
Receive/transmit status and data registers
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02C0 | |RF | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02C2 | |TE |TF | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02C4 | FIFO_RECEIVE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02C6 | FIFO_TRANSMIT |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02C8 | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x02CA | |FL | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
RF: 1 if receive buffer is full
|
||||
TF: 1 if receive buffer is full
|
||||
TE: 1 if receive buffer is empty
|
||||
FL: Application spin waits on this flag
|
||||
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(coff_reader
|
||||
coff.h
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(coff_reader)
|
||||
target_link_libraries(coff_reader PRIVATE teakra)
|
||||
target_include_directories(coff_reader PRIVATE .)
|
||||
target_compile_options(coff_reader PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,367 @@
|
|||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
struct NameOrIndex {
|
||||
u8 value[8];
|
||||
std::variant<std::string, u32> Get() {
|
||||
u32 temp;
|
||||
std::memcpy(&temp, value, 4);
|
||||
if (temp == 0) {
|
||||
std::memcpy(&temp, value + 4, 4);
|
||||
return temp;
|
||||
}
|
||||
std::string ret(value, value + 8);
|
||||
size_t pos = ret.find('\0');
|
||||
if (pos != std::string::npos)
|
||||
ret.erase(pos);
|
||||
return ret;
|
||||
}
|
||||
std::string GetString(std::FILE* file, u32 offset) {
|
||||
auto v = Get();
|
||||
if (v.index() == 1) {
|
||||
std::fseek(file, offset + std::get<1>(v), SEEK_SET);
|
||||
char c;
|
||||
std::string ret;
|
||||
while (true) {
|
||||
if (std::fread(&c, 1, 1, file) != 1)
|
||||
throw "unexpected end";
|
||||
if (c == '\0')
|
||||
break;
|
||||
ret += c;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return std::get<0>(v);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u16 magic;
|
||||
u16 num_section;
|
||||
u32 time;
|
||||
u32 offset_symbol;
|
||||
u32 num_symbol;
|
||||
u16 optheader_size;
|
||||
u16 flags;
|
||||
};
|
||||
static_assert(sizeof(Header) == 20);
|
||||
|
||||
struct SectionHeader {
|
||||
NameOrIndex name;
|
||||
u32 prog_addr;
|
||||
u32 data_addr;
|
||||
u32 size;
|
||||
u32 offset_data;
|
||||
u32 offset_rel;
|
||||
u32 offset_line;
|
||||
u16 num_rel;
|
||||
u16 num_line;
|
||||
u32 flags;
|
||||
};
|
||||
static_assert(sizeof(SectionHeader) == 40);
|
||||
|
||||
namespace SFlag {
|
||||
constexpr u32 Exec = 0x0001;
|
||||
constexpr u32 Unk2 = 0x0002;
|
||||
constexpr u32 Prog = 0x0008;
|
||||
constexpr u32 Data = 0x0010;
|
||||
constexpr u32 RegionMask = Prog | Data;
|
||||
constexpr u32 Moni = 0x0020;
|
||||
constexpr u32 Un40 = 0x0040;
|
||||
constexpr u32 Dupl = 0x0080;
|
||||
constexpr u32 U200 = 0x0200;
|
||||
} // namespace SFlag
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Symbol {
|
||||
NameOrIndex name;
|
||||
u32 value;
|
||||
u16 section_index;
|
||||
u16 type;
|
||||
u8 storage;
|
||||
u8 num_aux;
|
||||
};
|
||||
static_assert(sizeof(Symbol) == 18);
|
||||
|
||||
inline const std::unordered_map<u32, const char*> storage_names{
|
||||
{0, "label"}, {1, "auto"}, {2, "external"}, {3, "static"},
|
||||
{4, "register"}, {8, "struct"}, {9, "arg"}, {10, "struct-tag"},
|
||||
{11, "union"}, {12, "union-tag"}, {13, "typedef"}, {15, "enum-tag"},
|
||||
{16, "enum"}, {18, "bitfield"}, {19, "auto-arg"}, {98, "start"},
|
||||
{99, "end"}, {100, "block"}, {101, "func"}, {102, "struct-size"},
|
||||
{103, "file"}, {107, "ar"}, {108, "ar"}, {109, "ar"},
|
||||
{110, "ar"}, {111, "ar"}, {112, "ar"}, {255, "physical-function-end"},
|
||||
};
|
||||
|
||||
/*
|
||||
Storage:
|
||||
0 labels? | section+address | type = 0 | no aux
|
||||
1 auto variable | address, rel to sp? | variable type | variable aux
|
||||
2 externals | section+address | variable type | variable aux
|
||||
3 statics | section+address | variable type | variable aux
|
||||
4 register variable | address, abs? | variable type | variable aux
|
||||
8 structure member | address | variable type | variable aux
|
||||
9 function argument | address, ? | variable type | variable aux
|
||||
10 structure tag | debug/no address | type = 8 (structure) | aux = 1
|
||||
11 union member | address = 0? | type = 5 (long) or 8 (structure) | aux = 0 (long) or 1 (structure)
|
||||
12 union tag | debug/no address | type = 9 (union) | aux = 1
|
||||
13 typedef | debug/no address | type = 5 (long) or 8 (structure) | aux = 0 (long) or 1 (structure)
|
||||
15 enum tag | debug/no address | type = 10 (enum) | aux = 1
|
||||
16 enum member | abs value | type = 11 (enum member) | aux = 0
|
||||
18 bitfield | address | type = 5 (long) or 15 ulong | aux = 1
|
||||
19 auto arg | section+address | type = 17 | aux = 0
|
||||
98 function start | section + address | type = 0 | aux = 0
|
||||
99 function end | section + address | type = 0 | aux = 0
|
||||
100 .eb/.bb | section + address | type = 0 | aux = 1
|
||||
101 .ef/.bf | section + address | type = 0 | aux = 1
|
||||
102 struct end | address | type = 0 | aux = 1
|
||||
103 .file | debug value | type = 1 or 2 | aux = 1
|
||||
107 .ar | section + address | type = => ar0 value | aux = 0
|
||||
108 .ar | section + address | type = => ar1 value | aux = 0
|
||||
109 .ar | section + address | type = => arp0 value | aux = 0
|
||||
110 .ar | section + address | type = => arp1 value | aux = 0
|
||||
111 .ar | section + address | type = => arp2 value | aux = 0
|
||||
112 .ar | section + address | type = => arp3 value | aux = 0
|
||||
255 physical function end | section + address | type = 0 | aux = 0
|
||||
*/
|
||||
|
||||
struct Line {
|
||||
u32 symbol_index_or_addr;
|
||||
u16 line_number;
|
||||
};
|
||||
static_assert(sizeof(Line) == 6);
|
||||
|
||||
struct Relocation {
|
||||
u32 addr;
|
||||
u32 symbol;
|
||||
u32 type;
|
||||
};
|
||||
static_assert(sizeof(Relocation) == 12); // not 10 bytes!
|
||||
#pragma pack(pop)
|
||||
|
||||
class Coff {
|
||||
|
||||
public:
|
||||
Coff(std::FILE* in) {
|
||||
fseek(in, 0, SEEK_SET);
|
||||
Header header;
|
||||
if (fread(&header, sizeof(header), 1, in) != 1)
|
||||
throw "failed to read header";
|
||||
u32 string_offset = header.offset_symbol + header.num_symbol * sizeof(Symbol);
|
||||
|
||||
sections.resize(header.num_section);
|
||||
u32 expected = 0;
|
||||
for (u32 i = 0; i < header.num_section; ++i) {
|
||||
fseek(in, sizeof(header) + header.optheader_size + i * sizeof(SectionHeader), SEEK_SET);
|
||||
SectionHeader sheader;
|
||||
if (fread(&sheader, sizeof(sheader), 1, in) != 1)
|
||||
throw "failed to read sheader";
|
||||
sections[i].name = sheader.name.GetString(in, string_offset);
|
||||
sections[i].prog_addr = sheader.prog_addr;
|
||||
sections[i].data_addr = sheader.data_addr;
|
||||
if (expected == 0)
|
||||
expected = sheader.offset_data;
|
||||
if (expected != sheader.offset_data) {
|
||||
throw "";
|
||||
}
|
||||
sections[i].data.resize(sheader.size);
|
||||
if ((sheader.flags & SFlag::Dupl) == 0) {
|
||||
fseek(in, sheader.offset_data, SEEK_SET);
|
||||
if (sheader.size != 0)
|
||||
if (fread(sections[i].data.data(), sheader.size, 1, in) != 1)
|
||||
throw "failed to read section";
|
||||
expected += sheader.size;
|
||||
}
|
||||
sections[i].num_rel = sheader.num_rel;
|
||||
sections[i].flags = sheader.flags;
|
||||
if ((sections[i].flags & SFlag::RegionMask) == 0)
|
||||
throw "no region type";
|
||||
if ((sections[i].flags & SFlag::RegionMask) == SFlag::Prog) {
|
||||
if (sections[i].data_addr != 0)
|
||||
throw "prog section has data addr";
|
||||
} else if ((sections[i].flags & SFlag::RegionMask) == SFlag::Data) {
|
||||
if (sections[i].prog_addr != 0)
|
||||
throw "data section has prog addr";
|
||||
}
|
||||
|
||||
fseek(in, sheader.offset_line, SEEK_SET);
|
||||
u32 current_symbol = 0;
|
||||
for (u16 j = 0; j < sheader.num_line; ++j) {
|
||||
Line line;
|
||||
if (fread(&line, sizeof(line), 1, in) != 1)
|
||||
throw "failed to read line";
|
||||
if (line.line_number == 0) {
|
||||
current_symbol = line.symbol_index_or_addr;
|
||||
} else {
|
||||
u32 addr = line.symbol_index_or_addr;
|
||||
if (addr < sections[i].prog_addr ||
|
||||
addr >= sections[i].prog_addr + sections[i].data.size() / 2) {
|
||||
if (addr != 0xFFFFFFFF && addr != 0xFFFFFFFE &&
|
||||
addr != 0xFFFFFFFD) // unknown magic
|
||||
throw "line out of range";
|
||||
}
|
||||
sections[i].line_numbers[addr].emplace_back(
|
||||
Section::LineNumber{current_symbol, line.line_number});
|
||||
}
|
||||
}
|
||||
|
||||
fseek(in, sheader.offset_rel, SEEK_SET);
|
||||
// printf("\n");
|
||||
for (u16 j = 0; j < sheader.num_rel; ++j) {
|
||||
Relocation relocation;
|
||||
if (fread(&relocation, sizeof(relocation), 1, in) != 1)
|
||||
throw "failed to read relocation";
|
||||
// printf("%08X, %08X, %08X\n", relocation.addr, relocation.symbol,
|
||||
// relocation.type);
|
||||
sections[i].relocations[relocation.addr].push_back(relocation);
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < header.num_symbol; ++i) {
|
||||
Symbol symbol;
|
||||
fseek(in, header.offset_symbol + i * sizeof(symbol), SEEK_SET);
|
||||
if (fread(&symbol, sizeof(symbol), 1, in) != 1)
|
||||
throw "failed to read symbol";
|
||||
i += symbol.num_aux;
|
||||
printf("%s\n", symbol.name.GetString(in, string_offset).c_str());
|
||||
printf("value = %08X, section = %04X, type = %04X, storage = %02X, aux = %02X\n",
|
||||
symbol.value, symbol.section_index, symbol.type, symbol.storage, symbol.num_aux);
|
||||
SymbolEx symbol_ex;
|
||||
symbol_ex.name = symbol.name.GetString(in, string_offset);
|
||||
if (symbol.section_index > 0 && symbol.section_index < 0x7FFF) {
|
||||
u16 section_index = symbol.section_index - 1;
|
||||
symbol_ex.region =
|
||||
sections[section_index].flags & SFlag::Prog ? SymbolEx::Prog : SymbolEx::Data;
|
||||
symbol_ex.value = symbol.value + (sections[section_index].flags & SFlag::Prog
|
||||
? sections[section_index].prog_addr
|
||||
: sections[section_index].data_addr);
|
||||
|
||||
} else {
|
||||
symbol_ex.region = SymbolEx::Absolute;
|
||||
symbol_ex.value = symbol.value;
|
||||
}
|
||||
|
||||
symbol_ex.storage = symbol.storage;
|
||||
symbol_ex.type = symbol.type;
|
||||
|
||||
symbols.push_back(symbol_ex);
|
||||
|
||||
if (symbols.back().region == SymbolEx::Prog ||
|
||||
symbols.back().region == SymbolEx::Data) {
|
||||
symbols_lut[symbols.back().value].push_back(symbols.size() - 1);
|
||||
}
|
||||
|
||||
symbol_ex.region = SymbolEx::Aux;
|
||||
for (u16 aux = 0; aux < symbol.num_aux; ++aux) {
|
||||
symbols.push_back(symbol_ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge duplicated sections
|
||||
std::sort(sections.begin(), sections.end(), [](const Section& left, const Section& right) {
|
||||
u32 lm = left.flags & SFlag::Dupl;
|
||||
u32 rm = right.flags & SFlag::Dupl;
|
||||
if (lm == rm) {
|
||||
return left.name < right.name;
|
||||
} else {
|
||||
return lm < rm;
|
||||
}
|
||||
});
|
||||
auto mid = sections.begin() + sections.size() / 2;
|
||||
for (auto a = sections.begin(), b = mid; a != mid; ++a, ++b) {
|
||||
if (a->name != b->name || a->prog_addr != b->prog_addr ||
|
||||
a->data_addr != b->data_addr || a->data.size() != b->data.size() ||
|
||||
(a->flags + SFlag::Dupl) != b->flags)
|
||||
throw "mismatch";
|
||||
if (!b->line_numbers.empty())
|
||||
throw "dup has line numbers";
|
||||
if (b->num_rel != 0)
|
||||
throw "dup has relocations";
|
||||
}
|
||||
sections.resize(sections.size() / 2);
|
||||
|
||||
// Break up multi-region section
|
||||
auto both_begin =
|
||||
std::partition(sections.begin(), sections.end(), [](const Section& section) {
|
||||
return (section.flags & SFlag::RegionMask) != SFlag::RegionMask;
|
||||
});
|
||||
std::vector<Section> copy(both_begin, sections.end());
|
||||
for (auto i = both_begin; i != sections.end(); ++i) {
|
||||
i->flags &= ~SFlag::Prog;
|
||||
i->prog_addr = 0;
|
||||
}
|
||||
for (auto& section : copy) {
|
||||
section.flags &= ~SFlag::Data;
|
||||
section.data_addr = 0;
|
||||
}
|
||||
sections.insert(sections.end(), std::make_move_iterator(copy.begin()),
|
||||
std::make_move_iterator(copy.end()));
|
||||
|
||||
// Sort according to address
|
||||
std::sort(sections.begin(), sections.end(), [](const Section& left, const Section& right) {
|
||||
if ((left.flags & SFlag::RegionMask) == SFlag::Prog) {
|
||||
if ((right.flags & SFlag::RegionMask) == SFlag::Prog) {
|
||||
if (left.prog_addr == right.prog_addr) {
|
||||
return left.data.size() < right.data.size();
|
||||
}
|
||||
return left.prog_addr < right.prog_addr;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ((right.flags & SFlag::RegionMask) == SFlag::Prog) {
|
||||
return false;
|
||||
} else {
|
||||
if (left.data_addr == right.data_addr) {
|
||||
return left.data.size() < right.data.size();
|
||||
}
|
||||
return left.data_addr < right.data_addr;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct Section {
|
||||
std::string name;
|
||||
u32 prog_addr;
|
||||
u32 data_addr;
|
||||
std::vector<u8> data;
|
||||
u16 num_rel;
|
||||
u32 flags;
|
||||
|
||||
struct LineNumber {
|
||||
u32 symbol;
|
||||
u16 line;
|
||||
};
|
||||
std::unordered_map<u32 /*abs addr*/, std::vector<LineNumber>> line_numbers;
|
||||
std::unordered_map<u32 /*rel addr*/, std::vector<Relocation>> relocations;
|
||||
};
|
||||
std::vector<Section> sections;
|
||||
|
||||
struct SymbolEx {
|
||||
std::string name;
|
||||
enum Region {
|
||||
Aux,
|
||||
Absolute,
|
||||
Prog,
|
||||
Data,
|
||||
} region;
|
||||
u32 value;
|
||||
u16 type;
|
||||
u8 storage;
|
||||
};
|
||||
std::vector<SymbolEx> symbols;
|
||||
std::unordered_map<u32 /*abs addr*/, std::vector<std::size_t /*index in symbols*/>> symbols_lut;
|
||||
};
|
|
@ -0,0 +1,132 @@
|
|||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <teakra/disassembler.h>
|
||||
#include "coff.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printf("Please input a file\n");
|
||||
return -1;
|
||||
}
|
||||
std::FILE* in;
|
||||
std::FILE* out;
|
||||
in = std::fopen(argv[1], "rb");
|
||||
if (!in) {
|
||||
printf("Failed to open input file\n");
|
||||
return -1;
|
||||
}
|
||||
out = std::fopen((argv[1] + std::string(".out")).c_str(), "wt");
|
||||
if (!out) {
|
||||
printf("Failed to open output file\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Coff coff(in);
|
||||
|
||||
u32 prog_grow = 0, data_grow = 0;
|
||||
for (const auto& section : coff.sections) {
|
||||
u32 addr;
|
||||
bool is_prog;
|
||||
if ((section.flags & SFlag::RegionMask) == SFlag::Prog) {
|
||||
if (section.prog_addr < prog_grow) {
|
||||
throw "overlap";
|
||||
}
|
||||
is_prog = true;
|
||||
addr = section.prog_addr;
|
||||
prog_grow = section.prog_addr + (u32)section.data.size() / 2;
|
||||
} else {
|
||||
if (section.data_addr < data_grow) {
|
||||
throw "overlap";
|
||||
}
|
||||
is_prog = false;
|
||||
addr = section.data_addr;
|
||||
data_grow = section.data_addr + (u32)section.data.size() / 2;
|
||||
}
|
||||
|
||||
fprintf(out, "\n===Section===\n");
|
||||
fprintf(out, "name: %s\n", section.name.c_str());
|
||||
fprintf(out, "addr: %s.%08X\n", is_prog ? "Prog" : "Data", addr);
|
||||
fprintf(out, "flag: %08X\n\n", section.flags);
|
||||
|
||||
std::vector<u16> data(section.data.size() / 2);
|
||||
std::memcpy(data.data(), section.data.data(), section.data.size());
|
||||
|
||||
for (auto iter = data.begin(); iter != data.end(); ++iter) {
|
||||
u32 current_rel_addr = (u32)(iter - data.begin());
|
||||
u32 current_addr = current_rel_addr + addr;
|
||||
|
||||
if (coff.symbols_lut.count(current_addr)) {
|
||||
const auto& symbol_list = coff.symbols_lut.at(current_addr);
|
||||
auto begin = symbol_list.begin();
|
||||
while (true) {
|
||||
auto symbol_index =
|
||||
std::find_if(begin, symbol_list.end(),
|
||||
[is_prog, current_addr, &coff](const auto symbol_index) {
|
||||
const auto& symbol = coff.symbols[symbol_index];
|
||||
if (symbol.region == Coff::SymbolEx::Absolute ||
|
||||
symbol.region == Coff::SymbolEx::Aux)
|
||||
throw "what";
|
||||
if (symbol.region == Coff::SymbolEx::Prog && !is_prog)
|
||||
return false;
|
||||
if (symbol.region == Coff::SymbolEx::Data && is_prog)
|
||||
return false;
|
||||
if (symbol.value != current_addr)
|
||||
throw "what";
|
||||
return true;
|
||||
});
|
||||
if (symbol_index != symbol_list.end()) {
|
||||
const auto& symbol = coff.symbols[*symbol_index];
|
||||
fprintf(out, "$[%s]%s\n", storage_names.at(symbol.storage),
|
||||
symbol.name.c_str());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
begin = symbol_index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (section.line_numbers.count(current_addr)) {
|
||||
for (const auto& line_number : section.line_numbers.at(current_addr)) {
|
||||
const auto& symbol = coff.symbols[line_number.symbol];
|
||||
fprintf(out, "#%d + $[%s]%s\n", line_number.line,
|
||||
storage_names.at(symbol.storage), symbol.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(out, ".%08X ", current_addr);
|
||||
fprintf(out, "%04X", *iter);
|
||||
|
||||
if (section.flags & SFlag::Exec) {
|
||||
u16 opcode = *iter;
|
||||
std::string dsm;
|
||||
if (Teakra::Disassembler::NeedExpansion(opcode)) {
|
||||
++iter;
|
||||
if (iter == data.end()) {
|
||||
fprintf(out, "[broken expansion]\n");
|
||||
break;
|
||||
}
|
||||
u16 exp = *iter;
|
||||
dsm = Teakra::Disassembler::Do(opcode, exp);
|
||||
fprintf(out, " %04X ", exp);
|
||||
} else {
|
||||
dsm = Teakra::Disassembler::Do(opcode);
|
||||
fprintf(out, " ");
|
||||
}
|
||||
fprintf(out, "%s ;", dsm.c_str());
|
||||
}
|
||||
|
||||
if (section.relocations.count(current_rel_addr)) {
|
||||
for (const auto& relocation : section.relocations.at(current_rel_addr)) {
|
||||
const auto& symbol = coff.symbols[relocation.symbol];
|
||||
fprintf(out, "{@%08X + $[%s]%s}", relocation.type,
|
||||
storage_names.at(symbol.storage), symbol.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::fclose(in);
|
||||
std::fclose(out);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using s8 = std::int8_t;
|
||||
using s16 = std::int16_t;
|
||||
using s32 = std::int32_t;
|
||||
using s64 = std::int64_t;
|
||||
|
||||
template <typename T>
|
||||
constexpr unsigned BitSize() {
|
||||
return sizeof(T) * 8; // yeah I know I shouldn't use 8 here.
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T SignExtend(const T value, unsigned bit_count) {
|
||||
const T mask = static_cast<T>(1ULL << bit_count) - 1;
|
||||
const bool sign_bit = ((value >> (bit_count - 1)) & 1) != 0;
|
||||
if (sign_bit) {
|
||||
return value | ~mask;
|
||||
}
|
||||
return value & mask;
|
||||
}
|
||||
|
||||
template <unsigned bit_count, typename T>
|
||||
constexpr T SignExtend(const T value) {
|
||||
static_assert(bit_count <= BitSize<T>(), "bit_count larger than bitsize of T");
|
||||
return SignExtend(value, bit_count);
|
||||
}
|
||||
|
||||
inline constexpr u16 BitReverse(u16 value) {
|
||||
u16 result = 0;
|
||||
for (u32 i = 0; i < 16; ++i) {
|
||||
result |= ((value >> i) & 1) << (15 - i);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class CoreTiming {
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
virtual ~Callbacks() = default;
|
||||
virtual void Tick() = 0;
|
||||
virtual u64 GetMaxSkip() const = 0;
|
||||
virtual void Skip(u64) = 0;
|
||||
static constexpr u64 Infinity = std::numeric_limits<u64>::max();
|
||||
};
|
||||
|
||||
void Tick() {
|
||||
for (const auto& callbacks : registered_callbacks) {
|
||||
callbacks->Tick();
|
||||
}
|
||||
}
|
||||
|
||||
u64 Skip(u64 maximum) {
|
||||
u64 ticks = maximum;
|
||||
for (const auto& callbacks : registered_callbacks) {
|
||||
ticks = std::min(ticks, callbacks->GetMaxSkip());
|
||||
}
|
||||
for (const auto& callbacks : registered_callbacks) {
|
||||
callbacks->Skip(ticks);
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void RegisterCallbacks(Callbacks* callbacks) {
|
||||
registered_callbacks.push_back(std::move(callbacks));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Callbacks*> registered_callbacks;
|
||||
};
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
[[noreturn]] inline void Assert(const char* expression, const char* file, int line) {
|
||||
std::fprintf(stderr, "Assertion '%s' failed, file '%s' line '%d'.", expression, file, line);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
#define ASSERT(EXPRESSION) ((EXPRESSION) ? (void)0 : Assert(#EXPRESSION, __FILE__, __LINE__))
|
||||
#define UNREACHABLE() Assert("UNREACHABLE", __FILE__, __LINE__)
|
|
@ -0,0 +1,18 @@
|
|||
# CRU
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
(N = 0..14)
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0140+N*4| OFFSET_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0142+N*4| | | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x017C | OFFSET_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x017E | | | | ? |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
OFFSET_L, OFFSET_H: address of the program to replace
|
||||
```
|
|
@ -0,0 +1,722 @@
|
|||
#pragma once
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "crash.h"
|
||||
#include "matcher.h"
|
||||
#include "operand.h"
|
||||
|
||||
template <typename... OperandAtT>
|
||||
struct OperandList {
|
||||
template <typename OperandAtT0>
|
||||
using prefix = OperandList<OperandAtT0, OperandAtT...>;
|
||||
};
|
||||
|
||||
template <typename... OperandAtT>
|
||||
struct FilterOperand;
|
||||
|
||||
template <>
|
||||
struct FilterOperand<> {
|
||||
using result = OperandList<>;
|
||||
};
|
||||
|
||||
template <bool keep, typename OperandAtT0, typename... OperandAtT>
|
||||
struct FilterOperandHelper;
|
||||
|
||||
template <typename OperandAtT0, typename... OperandAtT>
|
||||
struct FilterOperandHelper<false, OperandAtT0, OperandAtT...> {
|
||||
using result = typename FilterOperand<OperandAtT...>::result;
|
||||
};
|
||||
|
||||
template <typename OperandAtT0, typename... OperandAtT>
|
||||
struct FilterOperandHelper<true, OperandAtT0, OperandAtT...> {
|
||||
using result = typename FilterOperand<OperandAtT...>::result ::template prefix<OperandAtT0>;
|
||||
};
|
||||
|
||||
template <typename OperandAtT0, typename... OperandAtT>
|
||||
struct FilterOperand<OperandAtT0, OperandAtT...> {
|
||||
using result =
|
||||
typename FilterOperandHelper<OperandAtT0::PassAsParameter, OperandAtT0, OperandAtT...>::result;
|
||||
};
|
||||
|
||||
template <typename V, typename OperandListT>
|
||||
struct VisitorFunctionWithoutFilter;
|
||||
|
||||
template <typename V, typename... OperandAtT>
|
||||
struct VisitorFunctionWithoutFilter<V, OperandList<OperandAtT...>> {
|
||||
using type = typename V::instruction_return_type (V::*)(typename OperandAtT::FilterResult...);
|
||||
};
|
||||
|
||||
template <typename V, typename... OperandAtT>
|
||||
struct VisitorFunction {
|
||||
using type =
|
||||
typename VisitorFunctionWithoutFilter<V, typename FilterOperand<OperandAtT...>::result>::type;
|
||||
};
|
||||
|
||||
template <typename V, u16 expected, typename... OperandAtT>
|
||||
struct MatcherCreator {
|
||||
template <typename OperandListT>
|
||||
struct Proxy;
|
||||
|
||||
using F = typename VisitorFunction<V, OperandAtT...>::type;
|
||||
|
||||
template <typename... OperandAtTs>
|
||||
struct Proxy<OperandList<OperandAtTs...>> {
|
||||
F func;
|
||||
auto operator()(V& visitor, [[maybe_unused]] u16 opcode,
|
||||
[[maybe_unused]] u16 expansion) const {
|
||||
return (visitor.*func)(OperandAtTs::Extract(opcode, expansion)...);
|
||||
}
|
||||
};
|
||||
|
||||
static Matcher<V> Create(const char* name, F func) {
|
||||
// Operands shouldn't overlap each other, nor overlap with the expected ones
|
||||
static_assert(NoOverlap<u16, expected, OperandAtT::Mask...>, "Error");
|
||||
|
||||
Proxy<typename FilterOperand<OperandAtT...>::result> proxy{func};
|
||||
|
||||
constexpr u16 mask = (~OperandAtT::Mask & ... & 0xFFFF);
|
||||
constexpr bool expanded = (OperandAtT::NeedExpansion || ...);
|
||||
return Matcher<V>(name, mask, expected, expanded, proxy);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... OperandAtConstT>
|
||||
struct RejectorCreator {
|
||||
static constexpr Rejector rejector{(OperandAtConstT::Mask | ...), (OperandAtConstT::Pad | ...)};
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
template <typename V>
|
||||
std::vector<Matcher<V>> GetDecodeTable() {
|
||||
return {
|
||||
|
||||
#define INST(name, ...) MatcherCreator<V, __VA_ARGS__>::Create(#name, &V::name)
|
||||
#define EXCEPT(...) Except(RejectorCreator<__VA_ARGS__>::rejector)
|
||||
|
||||
// <<< Misc >>>
|
||||
INST(nop, 0x0000),
|
||||
INST(norm, 0x94C0, At<Ax, 8>, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(swap, 0x4980, At<SwapType, 0>),
|
||||
INST(trap, 0x0020),
|
||||
|
||||
// <<< ALM normal >>>
|
||||
INST(alm, 0xA000, At<Alm, 9>, At<MemImm8, 0>, At<Ax, 8>),
|
||||
INST(alm, 0x8080, At<Alm, 9>, At<Rn, 0>, At<StepZIDS, 3>, At<Ax, 8>),
|
||||
INST(alm, 0x80A0, At<Alm, 9>, At<Register, 0>, At<Ax, 8>),
|
||||
|
||||
// <<< ALM r6 >>>
|
||||
INST(alm_r6, 0xD388, Const<Alm, 0>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD389, Const<Alm, 1>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38A, Const<Alm, 2>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38B, Const<Alm, 3>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38C, Const<Alm, 4>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38D, Const<Alm, 5>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38E, Const<Alm, 6>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD38F, Const<Alm, 7>, At<Ax, 4>),
|
||||
INST(alm_r6, 0x9462, Const<Alm, 8>, At<Ax, 0>),
|
||||
INST(alm_r6, 0x9464, Const<Alm, 9>, At<Ax, 0>),
|
||||
INST(alm_r6, 0x9466, Const<Alm, 10>, At<Ax, 0>),
|
||||
INST(alm_r6, 0x5E23, Const<Alm, 11>, At<Ax, 8>),
|
||||
INST(alm_r6, 0x5E22, Const<Alm, 12>, At<Ax, 8>),
|
||||
INST(alm_r6, 0x5F41, Const<Alm, 13>, Const<Ax, 0>),
|
||||
INST(alm_r6, 0x9062, Const<Alm, 14>, At<Ax, 8>, Unused<0>),
|
||||
INST(alm_r6, 0x8A63, Const<Alm, 15>, At<Ax, 3>),
|
||||
|
||||
// <<< ALU normal >>>
|
||||
INST(alu, 0xD4F8, At<Alu, 0>, At<MemImm16, 16>, At<Ax, 8>)
|
||||
.EXCEPT(AtConst<Alu, 0, 4>).EXCEPT(AtConst<Alu, 0, 5>),
|
||||
INST(alu, 0xD4D8, At<Alu, 0>, At<MemR7Imm16, 16>, At<Ax, 8>)
|
||||
.EXCEPT(AtConst<Alu, 0, 4>).EXCEPT(AtConst<Alu, 0, 5>),
|
||||
INST(alu, 0x80C0, At<Alu, 9>, At<Imm16, 16>, At<Ax, 8>)
|
||||
.EXCEPT(AtConst<Alu, 9, 4>).EXCEPT(AtConst<Alu, 9, 5>),
|
||||
INST(alu, 0xC000, At<Alu, 9>, At<Imm8, 0>, At<Ax, 8>)
|
||||
.EXCEPT(AtConst<Alu, 9, 4>).EXCEPT(AtConst<Alu, 9, 5>),
|
||||
INST(alu, 0x4000, At<Alu, 9>, At<MemR7Imm7s, 0>, At<Ax, 8>)
|
||||
.EXCEPT(AtConst<Alu, 9, 4>).EXCEPT(AtConst<Alu, 9, 5>),
|
||||
|
||||
// <<< OR Extra >>>
|
||||
INST(or_, 0xD291, At<Ab, 10>, At<Ax, 6>, At<Ax, 5>),
|
||||
INST(or_, 0xD4A4, At<Ax, 8>, At<Bx, 1>, At<Ax, 0>),
|
||||
INST(or_, 0xD3C4, At<Bx, 10>, At<Bx, 1>, At<Ax, 0>),
|
||||
|
||||
// <<< ALB normal >>>
|
||||
INST(alb, 0xE100, At<Alb, 9>, At<Imm16, 16>, At<MemImm8, 0>),
|
||||
INST(alb, 0x80E0, At<Alb, 9>, At<Imm16, 16>, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(alb, 0x81E0, At<Alb, 9>, At<Imm16, 16>, At<Register, 0>),
|
||||
INST(alb_r6, 0x47B8, At<Alb, 0>, At<Imm16, 16>),
|
||||
|
||||
// <<< ALB SttMod >>>
|
||||
INST(alb, 0x43C8, Const<Alb, 0>, At<Imm16, 16>, At<SttMod, 0>),
|
||||
INST(alb, 0x4388, Const<Alb, 1>, At<Imm16, 16>, At<SttMod, 0>),
|
||||
INST(alb, 0x0038, Const<Alb, 2>, At<Imm16, 16>, At<SttMod, 0>),
|
||||
//INST(alb, 0x????, Const<Alb, 3>, At<Imm,16, 16>, At<SttMod, 0>),
|
||||
INST(alb, 0x9470, Const<Alb, 4>, At<Imm16, 16>, At<SttMod, 0>),
|
||||
INST(alb, 0x9478, Const<Alb, 5>, At<Imm16, 16>, At<SttMod, 0>),
|
||||
//INST(alb, 0x????, Const<Alb, 6>, At<Imm,16, 16>, At<SttMod, 0>),
|
||||
//INST(alb, 0x????, Const<Alb, 7>, At<Imm,16, 16>, At<SttMod, 0>),
|
||||
|
||||
// <<< Add extra >>>
|
||||
INST(add, 0xD2DA, At<Ab, 10>, At<Bx, 0>),
|
||||
INST(add, 0x5DF0, At<Bx, 1>, At<Ax, 0>),
|
||||
INST(add_p1, 0xD782, At<Ax, 0>),
|
||||
INST(add, 0x5DF8, At<Px, 1>, At<Bx, 0>),
|
||||
|
||||
// <<< Sub extra >>>
|
||||
INST(sub, 0x8A61, At<Ab, 3>, At<Bx, 8>),
|
||||
INST(sub, 0x8861, At<Bx, 4>, At<Ax, 3>),
|
||||
INST(sub_p1, 0xD4B9, At<Ax, 8>),
|
||||
INST(sub, 0x8FD0, At<Px, 1>, At<Bx, 0>),
|
||||
|
||||
/// <<< addsub p0 p1 >>>
|
||||
INST(app, 0x5DC0, At<Ab, 2>, BZr, Add, PP, Add, PP),
|
||||
INST(app, 0x5DC1, At<Ab, 2>, BZr, Add, PP, Add, PA),
|
||||
INST(app, 0x4590, At<Ab, 2>, BAc, Add, PP, Add, PP),
|
||||
INST(app, 0x4592, At<Ab, 2>, BAc, Add, PP, Add, PA),
|
||||
INST(app, 0x4593, At<Ab, 2>, BAc, Add, PA, Add, PA),
|
||||
INST(app, 0x5DC2, At<Ab, 2>, BZr, Add, PP, Sub, PP),
|
||||
INST(app, 0x5DC3, At<Ab, 2>, BZr, Add, PP, Sub, PA),
|
||||
INST(app, 0x80C6, At<Ab, 10>, BAc, Sub, PP, Sub, PP),
|
||||
INST(app, 0x82C6, At<Ab, 10>, BAc, Sub, PP, Sub, PA),
|
||||
INST(app, 0x83C6, At<Ab, 10>, BAc, Sub, PA, Sub, PA),
|
||||
INST(app, 0x906C, At<Ab, 0>, BAc, Add, PP, Sub, PP),
|
||||
INST(app, 0x49C2, At<Ab, 4>, BAc, Sub, PP, Add, PP),
|
||||
INST(app, 0x916C, At<Ab, 0>, BAc, Add, PP, Sub, PA),
|
||||
INST(app, 0x49C3, At<Ab, 4>, BAc, Sub, PP, Add, PA),
|
||||
|
||||
/// <<< add||sub >>>
|
||||
INST(add_add, 0x6F80, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
INST(add_sub, 0x6FA0, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
INST(sub_add, 0x6FC0, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
INST(sub_sub, 0x6FE0, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
|
||||
/// <<< add||sub sv >>>
|
||||
INST(add_sub_sv, 0x5DB0, At<ArRn1, 1>, At<ArStep1, 0>, At<Ab, 2>),
|
||||
INST(sub_add_sv, 0x5DE0, At<ArRn1, 1>, At<ArStep1, 0>, At<Ab, 2>),
|
||||
|
||||
/// <<< add||sub||mov sv >>>
|
||||
INST(sub_add_i_mov_j_sv, 0x8064, At<ArpRn1, 8>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
INST(sub_add_j_mov_i_sv, 0x5D80, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 3>),
|
||||
INST(add_sub_i_mov_j, 0x9070, At<ArpRn1, 8>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 2>),
|
||||
INST(add_sub_j_mov_i, 0x5E30, At<ArpRn1, 8>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 2>),
|
||||
|
||||
// <<< Mul >>>
|
||||
INST(mul, 0x8000, At<Mul3, 8>, At<Rn, 0>, At<StepZIDS, 3>, At<Imm16, 16>, At<Ax, 11>),
|
||||
INST(mul_y0, 0x8020, At<Mul3, 8>, At<Rn, 0>, At<StepZIDS, 3>, At<Ax, 11>),
|
||||
INST(mul_y0, 0x8040, At<Mul3, 8>, At<Register, 0>, At<Ax, 11>),
|
||||
INST(mul, 0xD000, At<Mul3, 8>, At<R45, 2>, At<StepZIDS, 5>, At<R0123, 0>, At<StepZIDS, 3>, At<Ax, 11>),
|
||||
INST(mul_y0_r6, 0x5EA0, At<Mul3, 1>, At<Ax, 0>),
|
||||
INST(mul_y0, 0xE000, At<Mul2, 9>, At<MemImm8, 0>, At<Ax, 11>),
|
||||
|
||||
// <<< Mul Extra >>>
|
||||
INST(mpyi, 0x0800, At<Imm8s, 0>),
|
||||
INST(msu, 0xD080, At<R45, 2>, At<StepZIDS, 5>, At<R0123, 0>, At<StepZIDS, 3>, At<Ax, 8>),
|
||||
INST(msu, 0x90C0, At<Rn, 0>, At<StepZIDS, 3>, At<Imm16, 16>, At<Ax, 8>),
|
||||
INST(msusu, 0x8264, At<ArRn2, 3>, At<ArStep2, 0>, At<Ax, 8>),
|
||||
INST(mac_x1to0, 0x4D84, At<Ax, 1>, Unused<0>),
|
||||
INST(mac1, 0x5E28, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ax, 8>),
|
||||
|
||||
// <<< MODA >>>
|
||||
INST(moda4, 0x6700, At<Moda4, 4>, At<Ax, 12>, At<Cond, 0>)
|
||||
.EXCEPT(AtConst<Moda4, 4, 7>),
|
||||
INST(moda3, 0x6F00, At<Moda3, 4>, At<Bx, 12>, At<Cond, 0>),
|
||||
INST(pacr1, 0xD7C2, At<Ax, 0>),
|
||||
INST(clr, 0x8ED0, At<Ab, 2>, At<Ab, 0>),
|
||||
INST(clrr, 0x8DD0, At<Ab, 2>, At<Ab, 0>),
|
||||
|
||||
// <<< Block repeat >>>
|
||||
INST(bkrep, 0x5C00, At<Imm8, 0>, At<Address16, 16>),
|
||||
INST(bkrep, 0x5D00, At<Register, 0>, At<Address18_16, 16>, At<Address18_2, 5>),
|
||||
INST(bkrep_r6, 0x8FDC, At<Address18_16, 16>, At<Address18_2, 0>),
|
||||
INST(bkreprst, 0xDA9C, At<ArRn2, 0>),
|
||||
INST(bkreprst_memsp, 0x5F48, Unused<0>, Unused<1>),
|
||||
INST(bkrepsto, 0xDADC, At<ArRn2, 0>, Unused<10>),
|
||||
INST(bkrepsto_memsp, 0x9468, Unused<0>, Unused<1>, Unused<2>),
|
||||
|
||||
// <<< Bank >>>
|
||||
INST(banke, 0x4B80, At<BankFlags, 0>),
|
||||
INST(bankr, 0x8CDF),
|
||||
INST(bankr, 0x8CDC, At<Ar, 0>),
|
||||
INST(bankr, 0x8CD0, At<Ar, 2>, At<Arp, 0>),
|
||||
INST(bankr, 0x8CD8, At<Arp, 0>),
|
||||
|
||||
// <<< Bitrev >>>
|
||||
INST(bitrev, 0x5EB8, At<Rn, 0>),
|
||||
INST(bitrev_dbrv, 0xD7E8, At<Rn, 0>),
|
||||
INST(bitrev_ebrv, 0xD7E0, At<Rn, 0>),
|
||||
|
||||
// <<< Branching >>>
|
||||
INST(br, 0x4180, At<Address18_16, 16>, At<Address18_2, 4>, At<Cond, 0>),
|
||||
INST(brr, 0x5000, At<RelAddr7, 4>, At<Cond, 0>),
|
||||
|
||||
// <<< Break >>>
|
||||
INST(break_, 0xD3C0),
|
||||
|
||||
// <<< Call >>>
|
||||
INST(call, 0x41C0, At<Address18_16, 16>, At<Address18_2, 4>, At<Cond, 0>),
|
||||
INST(calla, 0xD480, At<Axl, 8>),
|
||||
INST(calla, 0xD381, At<Ax, 4>),
|
||||
INST(callr, 0x1000, At<RelAddr7, 4>, At<Cond, 0>),
|
||||
|
||||
// <<< Context >>>
|
||||
INST(cntx_s, 0xD380),
|
||||
INST(cntx_r, 0xD390),
|
||||
|
||||
// <<< Return >>>
|
||||
INST(ret, 0x4580, At<Cond, 0>),
|
||||
INST(retd, 0xD780),
|
||||
INST(reti, 0x45C0, At<Cond, 0>),
|
||||
INST(retic, 0x45D0, At<Cond, 0>),
|
||||
INST(retid, 0xD7C0),
|
||||
INST(retidc, 0xD3C3),
|
||||
INST(rets, 0x0900, At<Imm8, 0>),
|
||||
|
||||
// <<< Load >>>
|
||||
INST(load_ps, 0x4D80, At<Imm2, 0>),
|
||||
INST(load_stepi, 0xDB80, At<Imm7s, 0>),
|
||||
INST(load_stepj, 0xDF80, At<Imm7s, 0>),
|
||||
INST(load_page, 0x0400, At<Imm8, 0>),
|
||||
INST(load_modi, 0x0200, At<Imm9, 0>),
|
||||
INST(load_modj, 0x0A00, At<Imm9, 0>),
|
||||
INST(load_movpd, 0xD7D8, At<Imm2, 1>, Unused<0>),
|
||||
INST(load_ps01, 0x0010, At<Imm4, 0>),
|
||||
|
||||
// <<< Push >>>
|
||||
INST(push, 0x5F40, At<Imm16, 16>),
|
||||
INST(push, 0x5E40, At<Register, 0>),
|
||||
INST(push, 0xD7C8, At<Abe, 1>, Unused<0>),
|
||||
INST(push, 0xD3D0, At<ArArpSttMod, 0>),
|
||||
INST(push_prpage, 0xD7FC, Unused<0>, Unused<1>),
|
||||
INST(push, 0xD78C, At<Px, 1>, Unused<0>),
|
||||
INST(push_r6, 0xD4D7, Unused<5>),
|
||||
INST(push_repc, 0xD7F8, Unused<0>, Unused<1>),
|
||||
INST(push_x0, 0xD4D4, Unused<5>),
|
||||
INST(push_x1, 0xD4D5, Unused<5>),
|
||||
INST(push_y1, 0xD4D6, Unused<5>),
|
||||
INST(pusha, 0x4384, At<Ax, 6>, Unused<0>, Unused<1>),
|
||||
INST(pusha, 0xD788, At<Bx, 1>, Unused<0>),
|
||||
|
||||
// <<< Pop >>>
|
||||
INST(pop, 0x5E60, At<Register, 0>),
|
||||
INST(pop, 0x47B4, At<Abe, 0>),
|
||||
INST(pop, 0x80C7, At<ArArpSttMod, 8>),
|
||||
INST(pop, 0x0006, At<Bx, 5>, Unused<0>),
|
||||
INST(pop_prpage, 0xD7F4, Unused<0>, Unused<1>),
|
||||
INST(pop, 0xD496, At<Px, 0>),
|
||||
INST(pop_r6, 0x0024, Unused<0>),
|
||||
INST(pop_repc, 0xD7F0, Unused<0>, Unused<1>),
|
||||
INST(pop_x0, 0xD494),
|
||||
INST(pop_x1, 0xD495),
|
||||
INST(pop_y1, 0x0004, Unused<0>),
|
||||
INST(popa, 0x47B0, At<Ab, 0>),
|
||||
|
||||
// <<< Repeat >>>
|
||||
INST(rep, 0x0C00, At<Imm8, 0>),
|
||||
INST(rep, 0x0D00, At<Register, 0>),
|
||||
INST(rep_r6, 0x0002, Unused<0>),
|
||||
|
||||
// <<< Shift >>>
|
||||
INST(shfc, 0xD280, At<Ab, 10>, At<Ab, 5>, At<Cond, 0>),
|
||||
INST(shfi, 0x9240, At<Ab, 10>, At<Ab, 7>, At<Imm6s, 0>),
|
||||
|
||||
// <<< TSTB >>>
|
||||
INST(tst4b, 0x80C1, At<ArRn2, 10>, At<ArStep2, 8>),
|
||||
INST(tst4b, 0x4780, At<ArRn2, 2>, At<ArStep2, 0>, At<Ax, 4>),
|
||||
INST(tstb, 0xF000, At<MemImm8, 0>, At<Imm4, 8>),
|
||||
INST(tstb, 0x9020, At<Rn, 0>, At<StepZIDS, 3>, At<Imm4, 8>),
|
||||
INST(tstb, 0x9000, At<Register, 0>, At<Imm4, 8>)
|
||||
.EXCEPT(AtConst<Register, 0, 24>), // override by tstb_r6
|
||||
INST(tstb_r6, 0x9018, At<Imm4, 8>),
|
||||
INST(tstb, 0x0028, At<SttMod, 0>, At<Imm16, 16>), // unused12@20
|
||||
|
||||
// <<< AND Extra >>>
|
||||
INST(and_, 0x6770, At<Ab, 2>, At<Ab, 0>, At<Ax, 12>),
|
||||
|
||||
// <<< Interrupt >>>
|
||||
INST(dint, 0x43C0),
|
||||
INST(eint, 0x4380),
|
||||
|
||||
// <<< EXP >>>
|
||||
INST(exp, 0x9460, At<Bx, 0>),
|
||||
INST(exp, 0x9060, At<Bx, 0>, At<Ax, 8>),
|
||||
INST(exp, 0x9C40, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(exp, 0x9840, At<Rn, 0>, At<StepZIDS, 3>, At<Ax, 8>),
|
||||
INST(exp, 0x9440, At<Register, 0>),
|
||||
INST(exp, 0x9040, At<Register, 0>, At<Ax, 8>),
|
||||
INST(exp_r6, 0xD7C1),
|
||||
INST(exp_r6, 0xD382, At<Ax, 4>),
|
||||
|
||||
// <<< MODR >>>
|
||||
INST(modr, 0x0080, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(modr_dmod, 0x00A0, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(modr_i2, 0x4990, At<Rn, 0>),
|
||||
INST(modr_i2_dmod, 0x4998, At<Rn, 0>),
|
||||
INST(modr_d2, 0x5DA0, At<Rn, 0>),
|
||||
INST(modr_d2_dmod, 0x5DA8, At<Rn, 0>),
|
||||
INST(modr_eemod, 0xD294, At<ArpRn2, 10>, At<ArpStep2, 0>, At<ArpStep2, 5>),
|
||||
INST(modr_edmod, 0x0D80, At<ArpRn2, 5>, At<ArpStep2, 1>, At<ArpStep2, 3>),
|
||||
INST(modr_demod, 0x8464, At<ArpRn2, 8>, At<ArpStep2, 0>, At<ArpStep2, 3>),
|
||||
INST(modr_ddmod, 0x0D81, At<ArpRn2, 5>, At<ArpStep2, 1>, At<ArpStep2, 3>),
|
||||
|
||||
// <<< MOV >>>
|
||||
INST(mov, 0xD290, At<Ab, 10>, At<Ab, 5>),
|
||||
INST(mov_dvm, 0xD298, At<Abl, 10>),
|
||||
INST(mov_x0, 0xD2D8, At<Abl, 10>),
|
||||
INST(mov_x1, 0xD394, At<Abl, 0>),
|
||||
INST(mov_y1, 0xD384, At<Abl, 0>),
|
||||
|
||||
INST(mov, 0x3000, At<Ablh, 9>, At<MemImm8, 0>),
|
||||
INST(mov, 0xD4BC, At<Axl, 8>, At<MemImm16, 16>),
|
||||
INST(mov, 0xD49C, At<Axl, 8>, At<MemR7Imm16, 16>),
|
||||
INST(mov, 0xDC80, At<Axl, 8>, At<MemR7Imm7s, 0>),
|
||||
|
||||
INST(mov, 0xD4B8, At<MemImm16, 16>, At<Ax, 8>),
|
||||
INST(mov, 0x6100, At<MemImm8, 0>, At<Ab, 11>),
|
||||
INST(mov, 0x6200, At<MemImm8, 0>, At<Ablh, 10>),
|
||||
INST(mov_eu, 0x6500, At<MemImm8, 0>, At<Axh, 12>),
|
||||
INST(mov, 0x6000, At<MemImm8, 0>, At<RnOld, 10>),
|
||||
INST(mov_sv, 0x6D00, At<MemImm8, 0>),
|
||||
|
||||
INST(mov_dvm_to, 0xD491, At<Ab, 5>),
|
||||
INST(mov_icr_to, 0xD492, At<Ab, 5>),
|
||||
|
||||
INST(mov, 0x5E20, At<Imm16, 16>, At<Bx, 8>),
|
||||
INST(mov, 0x5E00, At<Imm16, 16>, At<Register, 0>),
|
||||
INST(mov_icr, 0x4F80, At<Imm5, 0>),
|
||||
INST(mov, 0x2500, At<Imm8s, 0>, At<Axh, 12>),
|
||||
INST(mov_ext0, 0x2900, At<Imm8s, 0>),
|
||||
INST(mov_ext1, 0x2D00, At<Imm8s, 0>),
|
||||
INST(mov_ext2, 0x3900, At<Imm8s, 0>),
|
||||
INST(mov_ext3, 0x3D00, At<Imm8s, 0>),
|
||||
INST(mov, 0x2300, At<Imm8s, 0>, At<RnOld, 10>),
|
||||
INST(mov_sv, 0x0500, At<Imm8s, 0>),
|
||||
INST(mov, 0x2100, At<Imm8, 0>, At<Axl, 12>),
|
||||
|
||||
INST(mov, 0xD498, At<MemR7Imm16, 16>, At<Ax, 8>),
|
||||
INST(mov, 0xD880, At<MemR7Imm7s, 0>, At<Ax, 8>),
|
||||
INST(mov, 0x98C0, At<Rn, 0>, At<StepZIDS, 3>, At<Bx, 8>),
|
||||
INST(mov, 0x1C00, At<Rn, 0>, At<StepZIDS, 3>, At<Register, 5>),
|
||||
|
||||
INST(mov_memsp_to, 0x47E0, At<Register, 0>),
|
||||
INST(mov_mixp_to, 0x47C0, At<Register, 0>),
|
||||
INST(mov, 0x2000, At<RnOld, 9>, At<MemImm8, 0>),
|
||||
INST(mov_icr, 0x4FC0, At<Register, 0>),
|
||||
INST(mov_mixp, 0x5E80, At<Register, 0>),
|
||||
INST(mov, 0x1800, At<Register, 5>, At<Rn, 0>, At<StepZIDS, 3>)
|
||||
.EXCEPT(AtConst<Register, 5, 24>).EXCEPT(AtConst<Register, 5, 25>), // override by mov_r6(_to)
|
||||
INST(mov, 0x5EC0, At<Register, 0>, At<Bx, 5>),
|
||||
INST(mov, 0x5800, At<Register, 0>, At<Register, 5>)
|
||||
.EXCEPT(AtConst<Register, 0, 24>).EXCEPT(AtConst<Register, 0, 25>), // override by mma_mov
|
||||
INST(mov_repc_to, 0xD490, At<Ab, 5>),
|
||||
INST(mov_sv_to, 0x7D00, At<MemImm8, 0>),
|
||||
INST(mov_x0_to, 0xD493, At<Ab, 5>),
|
||||
INST(mov_x1_to, 0x49C1, At<Ab, 4>),
|
||||
INST(mov_y1_to, 0xD299, At<Ab, 10>),
|
||||
|
||||
// <<< MOV load >>>
|
||||
INST(mov, 0x0008, At<Imm16, 16>, At<ArArp, 0>),
|
||||
INST(mov_r6, 0x0023, At<Imm16, 16>),
|
||||
INST(mov_repc, 0x0001, At<Imm16, 16>),
|
||||
INST(mov_stepi0, 0x8971, At<Imm16, 16>),
|
||||
INST(mov_stepj0, 0x8979, At<Imm16, 16>),
|
||||
INST(mov, 0x0030, At<Imm16, 16>, At<SttMod, 0>),
|
||||
INST(mov_prpage, 0x5DD0, At<Imm4, 0>),
|
||||
|
||||
// <<< <<< MOV p/d >>>
|
||||
INST(movd, 0x5F80, At<R0123, 0>, At<StepZIDS, 3>, At<R45, 2>, At<StepZIDS, 5>),
|
||||
INST(movp, 0x0040, At<Axl, 5>, At<Register, 0>),
|
||||
INST(movp, 0x0D40, At<Ax, 5>, At<Register, 0>),
|
||||
INST(movp, 0x0600, At<Rn, 0>, At<StepZIDS, 3>, At<R0123, 5>, At<StepZIDS, 7>),
|
||||
INST(movpdw, 0xD499, At<Ax, 8>),
|
||||
|
||||
// <<< MOV 2 >>>
|
||||
INST(mov_a0h_stepi0, 0xD49B),
|
||||
INST(mov_a0h_stepj0, 0xD59B),
|
||||
INST(mov_stepi0_a0h, 0xD482),
|
||||
INST(mov_stepj0_a0h, 0xD582),
|
||||
|
||||
INST(mov_prpage, 0x9164, At<Abl, 0>),
|
||||
INST(mov_repc, 0x9064, At<Abl, 0>),
|
||||
INST(mov, 0x9540, At<Abl, 3>, At<ArArp, 0>),
|
||||
INST(mov, 0x9C60, At<Abl, 3>, At<SttMod, 0>),
|
||||
|
||||
INST(mov_prpage_to, 0x5EB0, At<Abl, 0>),
|
||||
INST(mov_repc_to, 0xD2D9, At<Abl, 10>),
|
||||
INST(mov, 0x9560, At<ArArp, 0>, At<Abl, 3>),
|
||||
INST(mov, 0xD2F8, At<SttMod, 0>, At<Abl, 10>),
|
||||
|
||||
INST(mov_repc_to, 0xD7D0, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(mov, 0xD488, At<ArArp, 0>, At<ArRn1, 8>, At<ArStep1, 5>),
|
||||
INST(mov, 0x49A0, At<SttMod, 0>, At<ArRn1, 4>, At<ArStep1, 3>),
|
||||
|
||||
INST(mov_repc, 0xD7D4, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(mov, 0x8062, At<ArRn1, 4>, At<ArStep1, 3>, At<ArArp, 8>),
|
||||
INST(mov, 0x8063, At<ArRn1, 4>, At<ArStep1, 3>, At<SttMod, 8>),
|
||||
|
||||
INST(mov_repc_to, 0xD3C8, At<MemR7Imm16, 16>, Unused<0>, Unused<1>, Unused<2>),
|
||||
INST(mov, 0x5F50, At<ArArpSttMod, 0>, At<MemR7Imm16, 16>),
|
||||
|
||||
INST(mov_repc, 0xD2DC, At<MemR7Imm16, 16>, Unused<0>, Unused<1>, Unused<10>),
|
||||
INST(mov, 0x4D90, At<MemR7Imm16, 16>, At<ArArpSttMod, 0>),
|
||||
|
||||
INST(mov_pc, 0x886B, At<Ax, 8>),
|
||||
INST(mov_pc, 0x8863, At<Bx, 8>),
|
||||
|
||||
INST(mov_mixp_to, 0x8A73, At<Bx, 3>),
|
||||
INST(mov_mixp_r6, 0x4381),
|
||||
INST(mov_p0h_to, 0x4382, At<Bx, 0>),
|
||||
INST(mov_p0h_r6, 0xD3C2),
|
||||
INST(mov_p0h_to, 0x4B60, At<Register, 0>),
|
||||
INST(mov_p0, 0x8FD4, At<Ab, 0>),
|
||||
INST(mov_p1_to, 0x8FD8, At<Ab, 0>),
|
||||
|
||||
INST(mov2, 0x88D0, At<Px, 1>, At<ArRn2, 8>, At<ArStep2, 2>),
|
||||
INST(mov2s, 0x88D1, At<Px, 1>, At<ArRn2, 8>, At<ArStep2, 2>),
|
||||
INST(mov2, 0xD292, At<ArRn2, 10>, At<ArStep2, 5>, At<Px, 0>),
|
||||
INST(mova, 0x4DC0, At<Ab, 4>, At<ArRn2, 2>, At<ArStep2, 0>),
|
||||
INST(mova, 0x4BC0, At<ArRn2, 2>, At<ArStep2, 0>, At<Ab, 4>),
|
||||
|
||||
INST(mov_r6_to, 0xD481, At<Bx, 8>),
|
||||
INST(mov_r6_mixp, 0x43C1),
|
||||
INST(mov_r6_to, 0x5F00, At<Register, 0>),
|
||||
INST(mov_r6, 0x5F60, At<Register, 0>),
|
||||
INST(mov_memsp_r6, 0xD29C, Unused<0>, Unused<1>, Unused<10>),
|
||||
INST(mov_r6_to, 0x1B00, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
INST(mov_r6, 0x1B20, At<Rn, 0>, At<StepZIDS, 3>),
|
||||
|
||||
INST(movs, 0x6300, At<MemImm8, 0>, At<Ab, 11>),
|
||||
INST(movs, 0x0180, At<Rn, 0>, At<StepZIDS, 3>, At<Ab, 5>),
|
||||
INST(movs, 0x0100, At<Register, 0>, At<Ab, 5>),
|
||||
INST(movs_r6_to, 0x5F42, At<Ax, 0>),
|
||||
INST(movsi, 0x4080, At<RnOld, 9>, At<Ab, 5>, At<Imm5s, 0>),
|
||||
|
||||
// <<< MOV MOV >>>
|
||||
INST(mov2_axh_m_y0_m, 0x4390, At<Axh, 6>, At<ArRn2, 2>, At<ArStep2, 0>),
|
||||
INST(mov2_ax_mij, 0x43A0, At<Ab, 3>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
INST(mov2_ax_mji, 0x43E0, At<Ab, 3>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
INST(mov2_mij_ax, 0x80C4, At<ArpRn1, 9>, At<ArpStep1, 0>, At<ArpStep1, 8>, At<Ab, 10>),
|
||||
INST(mov2_mji_ax, 0xD4C0, At<ArpRn1, 5>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<Ab, 2>),
|
||||
INST(mov2_abh_m, 0x9D40, At<Abh, 4>, At<Abh, 2>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(exchange_iaj, 0x8C60, At<Axh, 4>, At<ArpRn2, 8>, At<ArpStep2, 0>, At<ArpStep2, 2>),
|
||||
INST(exchange_riaj, 0x7F80, At<Axh, 6>, At<ArpRn2, 4>, At<ArpStep2, 0>, At<ArpStep2, 2>),
|
||||
INST(exchange_jai, 0x4900, At<Axh, 6>, At<ArpRn2, 4>, At<ArpStep2, 0>, At<ArpStep2, 2>),
|
||||
INST(exchange_rjai, 0x4800, At<Axh, 6>, At<ArpRn2, 4>, At<ArpStep2, 0>, At<ArpStep2, 2>),
|
||||
|
||||
// <<< MOVR >>>
|
||||
INST(movr, 0x8864, At<ArRn2, 3>, At<ArStep2, 0>, At<Abh, 8>),
|
||||
INST(movr, 0x9CE0, At<Rn, 0>, At<StepZIDS, 3>, At<Ax, 8>),
|
||||
INST(movr, 0x9CC0, At<Register, 0>, At<Ax, 8>),
|
||||
INST(movr, 0x5DF4, At<Bx, 1>, At<Ax, 0>),
|
||||
INST(movr_r6_to, 0x8961, At<Ax, 3>),
|
||||
|
||||
// <<< LIM >>>
|
||||
INST(lim, 0x49C0, At<Ax, 5>, At<Ax, 4>),
|
||||
|
||||
// <<< Viterbi >>>
|
||||
INST(vtrclr0, 0x5F45),
|
||||
INST(vtrclr1, 0x5F46),
|
||||
INST(vtrclr, 0x5F47),
|
||||
INST(vtrmov0, 0xD29A, At<Axl, 0>),
|
||||
INST(vtrmov1, 0xD69A, At<Axl, 0>),
|
||||
INST(vtrmov, 0xD383, At<Axl, 4>),
|
||||
INST(vtrshr, 0xD781),
|
||||
|
||||
// <<< CLRP >>>
|
||||
INST(clrp0, 0x5DFE),
|
||||
INST(clrp1, 0x5DFD),
|
||||
INST(clrp, 0x5DFF),
|
||||
|
||||
// <<< min/max >>>
|
||||
INST(max_ge, 0x8460, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(max_gt, 0x8660, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(min_le, 0x8860, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(min_lt, 0x8A60, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(max_ge_r0, 0x8060, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(max_gt_r0, 0x8260, At<Ax, 8>, At<StepZIDS, 3>),
|
||||
INST(min_le_r0, 0x47A0, At<Ax, 3>, At<StepZIDS, 0>),
|
||||
INST(min_lt_r0, 0x47A4, At<Ax, 3>, At<StepZIDS, 0>),
|
||||
|
||||
// <<< Division Step >>>
|
||||
INST(divs, 0x0E00, At<MemImm8, 0>, At<Ax, 8>),
|
||||
|
||||
// <<< Sqr >>>
|
||||
INST(sqr_sqr_add3, 0xD790, At<Ab, 2>, At<Ab, 0>),
|
||||
INST(sqr_sqr_add3, 0x4B00, At<ArRn2, 4>, At<ArStep2, 2>, At<Ab, 0>),
|
||||
INST(sqr_mpysu_add3a, 0x49C4, At<Ab, 4>, At<Ab, 0>),
|
||||
|
||||
// <<< CMP Extra >>>
|
||||
INST(cmp, 0x4D8C, At<Ax, 1>, At<Bx, 0>),
|
||||
INST(cmp_b0_b1, 0xD483),
|
||||
INST(cmp_b1_b0, 0xD583),
|
||||
INST(cmp, 0xDA9A, At<Bx, 10>, At<Ax, 0>),
|
||||
INST(cmp_p1_to, 0x8B63, At<Ax, 4>),
|
||||
|
||||
// <<< min||max||vtrshr >>>
|
||||
INST(max2_vtr, 0x5E21, At<Ax, 8>),
|
||||
INST(min2_vtr, 0x43C2, At<Ax, 0>),
|
||||
INST(max2_vtr, 0xD784, At<Ax, 1>, At<Bx, 0>),
|
||||
INST(min2_vtr, 0xD4BA, At<Ax, 8>, At<Bx, 0>),
|
||||
INST(max2_vtr_movl, 0x4A40, At<Ax, 3>, At<Bx, 4>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(max2_vtr_movh, 0x4A44, At<Ax, 3>, At<Bx, 4>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(max2_vtr_movl, 0x4A60, At<Bx, 4>, At<Ax, 3>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(max2_vtr_movh, 0x4A64, At<Bx, 4>, At<Ax, 3>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(min2_vtr_movl, 0x4A00, At<Ax, 3>, At<Bx, 4>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(min2_vtr_movh, 0x4A04, At<Ax, 3>, At<Bx, 4>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(min2_vtr_movl, 0x4A20, At<Bx, 4>, At<Ax, 3>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(min2_vtr_movh, 0x4A24, At<Bx, 4>, At<Ax, 3>, At<ArRn1, 1>, At<ArStep1, 0>),
|
||||
INST(max2_vtr_movij, 0xD590, At<Ax, 6>, At<Bx, 5>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
INST(max2_vtr_movji, 0x45A0, At<Ax, 4>, At<Bx, 3>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
INST(min2_vtr_movij, 0xD2B8, At<Ax, 11>, At<Bx, 10>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
INST(min2_vtr_movji, 0x45E0, At<Ax, 4>, At<Bx, 3>, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>),
|
||||
|
||||
// <<< MOV ADDSUB >>>
|
||||
INST(mov_sv_app, 0x4B40, At<ArRn1, 3>, At<ArStep1, 2>, At<Bx, 0>, BSv, Sub, PP, Add, PP),
|
||||
INST(mov_sv_app, 0x9960, At<ArRn1, 4>, At<ArStep1Alt, 3>, At<Bx, 2>, BSv, Sub, PP, Add, PP),
|
||||
INST(mov_sv_app, 0x4B42, At<ArRn1, 3>, At<ArStep1, 2>, At<Bx, 0>, BSr, Sub, PP, Add, PP),
|
||||
INST(mov_sv_app, 0x99E0, At<ArRn1, 4>, At<ArStep1Alt, 3>, At<Bx, 2>, BSr, Sub, PP, Add, PP),
|
||||
INST(mov_sv_app, 0x5F4C, At<ArRn1, 1>, At<ArStep1, 0>, Const<Bx, 0>, BSv, Sub, PP, Sub, PP),
|
||||
INST(mov_sv_app, 0x8873, At<ArRn1, 8>, At<ArStep1, 3>, Const<Bx, 1>, BSv, Sub, PP, Sub, PP),
|
||||
INST(mov_sv_app, 0x9860, At<ArRn1, 4>, At<ArStep1Alt, 3>, At<Bx, 2>, BSv, Sub, PP, Sub, PP),
|
||||
INST(mov_sv_app, 0xDE9C, At<ArRn1, 1>, At<ArStep1, 0>, Const<Bx, 0>, BSr, Sub, PP, Sub, PP),
|
||||
INST(mov_sv_app, 0xD4B4, At<ArRn1, 1>, At<ArStep1, 0>, Const<Bx, 1>, BSr, Sub, PP, Sub, PP),
|
||||
INST(mov_sv_app, 0x98E0, At<ArRn1, 4>, At<ArStep1Alt, 3>, At<Bx, 2>, BSr, Sub, PP, Sub, PP),
|
||||
|
||||
// <<< CBS >>>
|
||||
INST(cbs, 0x9068, At<Axh, 0>, At<CbsCond, 8>),
|
||||
INST(cbs, 0xD49E, At<Axh, 8>, At<Bxh, 5>, At<CbsCond, 0>),
|
||||
INST(cbs, 0xD5C0, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, At<CbsCond, 3>),
|
||||
|
||||
// [[[XXX_xy_XXX_xy_XXX]]]
|
||||
INST(mma, 0x4D88, AtNamed<Ax, 1>, SX, SY, SX, SY, BZr, Add, PP, Sub, PP),
|
||||
INST(mma, 0xD49D, AtNamed<Bx, 5>, SX, SY, SX, SY, BZr, Add, PP, Sub, PP),
|
||||
INST(mma, 0x5E24, AtNamed<Ab, 0>, SX, SY, SX, SY, BZr, Add, PP, Add, PP),
|
||||
INST(mma, 0x8061, AtNamed<Ab, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0x8071, AtNamed<Ab, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x8461, AtNamed<Ab, 8>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma, 0x8471, AtNamed<Ab, 8>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xD484, AtNamed<Ab, 0>, SX, SY, SX, SY, BAc, Add, PA, Add, PA),
|
||||
INST(mma, 0xD4A0, AtNamed<Ab, 0>, SX, SY, SX, SY, BAc, Add, PP, Sub, PP),
|
||||
INST(mma, 0x4D89, AtNamed<Ax, 1>, SX, SY, SX, UY, BZr, Add, PP, Sub, PP),
|
||||
INST(mma, 0xD59D, AtNamed<Bx, 5>, SX, SY, SX, UY, BZr, Add, PP, Sub, PP),
|
||||
INST(mma, 0x5F24, AtNamed<Ab, 0>, SX, SY, SX, UY, BZr, Add, PP, Add, PP),
|
||||
INST(mma, 0x8069, AtNamed<Ab, 8>, SX, SY, SX, UY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0x8079, AtNamed<Ab, 8>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x8469, AtNamed<Ab, 8>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma, 0x8479, AtNamed<Ab, 8>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xD584, AtNamed<Ab, 0>, SX, SY, SX, UY, BAc, Add, PA, Add, PA),
|
||||
INST(mma, 0xD5A0, AtNamed<Ab, 0>, SX, SY, SX, UY, BAc, Add, PP, Sub, PP),
|
||||
|
||||
// [[[XXX_mm_XXX_mm_XXX]]]
|
||||
INST(mma, 0xCA00, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, UX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xCA01, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, SX, UY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xCA02, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, UX, SY, BAc, Sub, PA, Sub, PA),
|
||||
INST(mma, 0xCA03, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, SX, UY, BAc, Sub, PA, Sub, PA),
|
||||
INST(mma, 0xCA04, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, UX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0xCA05, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0xCA06, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, UX, SY, BAc, Add, PA, Add, PA),
|
||||
INST(mma, 0xCA07, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, UX, SY, SX, UY, BAc, Add, PA, Add, PA),
|
||||
INST(mma, 0xCB00, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, UX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma, 0xCB01, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma, 0xCB02, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, UX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xCB03, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0xCB04, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, UX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0xCB05, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, UY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0xCB06, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, UX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0xCB07, At<ArpRn1, 5>, At<ArpStep1, 3>, At<ArpStep1, 4>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
|
||||
INST(mma, 0x0D30, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, EMod, DMod, AtNamed<Ax, 0>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x0D20, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, DMod, EMod, AtNamed<Ax, 0>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x4B50, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, DMod, DMod, AtNamed<Ax, 0>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
|
||||
INST(mma, 0x9861, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, EMod, DMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0x9862, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, DMod, EMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0x9863, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, DMod, DMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
|
||||
INST(mma, 0x98E1, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, EMod, DMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x98E2, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, DMod, EMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x98E3, At<ArpRn1, 4>, At<ArpStep1, 2>, At<ArpStep1, 3>, DMod, DMod, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
|
||||
INST(mma, 0x80C8, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, EMod, EMod, AtNamed<Ab, 10>, SX, SY, SX, SY, BAc, Add, PP, Sub, PP),
|
||||
INST(mma, 0x81C8, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, EMod, EMod, AtNamed<Ab, 10>, SX, SY, SX, SY, BAc, Add, PP, Sub, PA),
|
||||
INST(mma, 0x82C8, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, EMod, EMod, AtNamed<Ab, 10>, SX, SY, SX, SY, BZr, Add, PP, Add, PP),
|
||||
INST(mma, 0x83C8, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, EMod, EMod, AtNamed<Ab, 10>, SX, SY, SX, SY, BZr, Add, PP, Add, PA),
|
||||
|
||||
INST(mma, 0x80C2, At<ArpRn1, 0>, At<ArpStep1, 8>, At<ArpStep1, 9>, EMod, EMod, AtNamed<Ab, 10>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma, 0x49C8, At<ArpRn1, 2>, At<ArpStep1, 0>, At<ArpStep1, 1>, EMod, EMod, AtNamed<Ab, 4>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma, 0x00C0, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, EMod, EMod, AtNamed<Ab, 4>, SX, SY, SX, SY, BZr, Add, PP, Sub, PP),
|
||||
INST(mma, 0x00C1, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, EMod, EMod, AtNamed<Ab, 4>, SX, SY, SX, SY, BZr, Add, PP, Sub, PA),
|
||||
INST(mma, 0xD7A0, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, EMod, EMod, AtNamed<Ax, 4>, SX, SY, SX, SY, BSv, Add, PP, Add, PP),
|
||||
INST(mma, 0xD7A1, At<ArpRn1, 3>, At<ArpStep1, 1>, At<ArpStep1, 2>, EMod, EMod, AtNamed<Ax, 4>, SX, SY, SX, SY, BSr, Add, PP, Add, PP),
|
||||
|
||||
INST(mma, 0xC800, At<ArpRn2, 4>, At<ArpStep2, 0>, At<ArpStep2, 2>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma, 0xC900, At<ArpRn2, 4>, At<ArpStep2, 0>, At<ArpStep2, 2>, EMod, EMod, AtNamed<Ab, 6>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PP),
|
||||
|
||||
// [[[XXX_mx_XXX_xy_XXX]]]
|
||||
INST(mma_mx_xy, 0xD5E0, At<ArRn1, 1>, At<ArStep1, 0>, AtNamed<Ax, 3>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma_mx_xy, 0xD5E4, At<ArRn1, 1>, At<ArStep1, 0>, AtNamed<Ax, 3>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
|
||||
// [[[XXX_xy_XXX_mx_XXX]]]
|
||||
INST(mma_xy_mx, 0x8862, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma_xy_mx, 0x8A62, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
|
||||
// [[[XXX_my_XXX_my_XXX]]]
|
||||
INST(mma_my_my, 0x4DA0, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 4>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma_my_my, 0x4DA1, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 4>, SX, SY, SX, UY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma_my_my, 0x4DA2, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 4>, SX, SY, SX, UY, BAc, Add, PP, Add, PP),
|
||||
INST(mma_my_my, 0x4DA3, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 4>, SX, SY, SX, UY, BAc, Add, PP, Add, PA),
|
||||
|
||||
INST(mma_my_my, 0x94E0, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma_my_my, 0x94E1, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, UX, SY, BAc, Sub, PP, Sub, PP),
|
||||
INST(mma_my_my, 0x94E2, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma_my_my, 0x94E3, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, UX, SY, BAc, Sub, PP, Sub, PA),
|
||||
INST(mma_my_my, 0x94E4, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma_my_my, 0x94E5, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, UX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma_my_my, 0x94E6, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, SX, SY, BAc, Add, PP, Add, PA),
|
||||
INST(mma_my_my, 0x94E7, At<ArRn1, 4>, At<ArStep1, 3>, AtNamed<Ax, 8>, SX, SY, UX, SY, BAc, Add, PP, Add, PA),
|
||||
|
||||
// [[[XXX_xy_XXX_xy_XXX_mov]]]
|
||||
INST(mma_mov, 0x4FA0, At<Axh, 6>, At<Bxh, 2>, At<ArRn1, 1>, At<ArStep1, 0>, AtNamed<Ab, 3>, SX, SY, SX, SY, BAc, Add, PP, Add, PP),
|
||||
INST(mma_mov, 0xD3A0, At<Axh, 6>, At<Bxh, 2>, At<ArRn1, 1>, At<ArStep1, 0>, AtNamed<Ab, 3>, SX, SY, SX, SY, BAc, Add, PP, Sub, PP),
|
||||
INST(mma_mov, 0x80D0, At<Axh, 9>, At<Bxh, 8>, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 10>, SX, SY, SX, SY, BSv, Add, PP, Sub, PP),
|
||||
INST(mma_mov, 0x80D1, At<Axh, 9>, At<Bxh, 8>, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 10>, SX, SY, SX, SY, BSr, Add, PP, Sub, PP),
|
||||
INST(mma_mov, 0x80D2, At<Axh, 9>, At<Bxh, 8>, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 10>, SX, SY, SX, SY, BSv, Add, PP, Add, PP),
|
||||
INST(mma_mov, 0x80D3, At<Axh, 9>, At<Bxh, 8>, At<ArRn1, 3>, At<ArStep1, 2>, AtNamed<Ax, 10>, SX, SY, SX, SY, BSr, Add, PP, Add, PP),
|
||||
INST(mma_mov, 0x5818, At<ArRn2, 7>, At<ArStep1, 6>, AtNamed<Ax, 0>, SX, SY, SX, SY, BSv, Add, PP, Sub, PP),
|
||||
INST(mma_mov, 0x5838, At<ArRn2, 7>, At<ArStep1, 6>, AtNamed<Ax, 0>, SX, SY, SX, SY, BSr, Add, PP, Sub, PP),
|
||||
|
||||
INST(addhp, 0x90E0, At<ArRn2, 2>, At<ArStep2, 0>, At<Px, 4>, At<Ax, 8>),
|
||||
};
|
||||
|
||||
#undef INST
|
||||
#undef EXCEPT
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
||||
template <typename V>
|
||||
Matcher<V> Decode(u16 instruction) {
|
||||
static const auto table = GetDecodeTable<V>();
|
||||
|
||||
const auto matches_instruction = [instruction](const auto& matcher) {
|
||||
return matcher.Matches(instruction);
|
||||
};
|
||||
|
||||
auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
|
||||
if (iter == table.end()) {
|
||||
return Matcher<V>::AllMatcher([](V& v, u16 opcode, u16) { return v.undefined(opcode); });
|
||||
} else {
|
||||
auto other = std::find_if(iter + 1, table.end(), matches_instruction);
|
||||
ASSERT(other == table.end());
|
||||
return *iter;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
std::vector<Matcher<V>> GetDecoderTable() {
|
||||
std::vector<Matcher<V>> table;
|
||||
table.reserve(0x10000);
|
||||
for (u32 i = 0; i < 0x10000; ++i) {
|
||||
table.push_back(Decode<V>((u16)i));
|
||||
}
|
||||
return table;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
# Teak Instruction Set Encoding
|
||||
|
||||
Each opcode is one- or two-word wide. In this code base, two-word wide opcodes are generally referred as "expanded". The optional second word is used purely for parameters, so the opcode family can always be determined from the first word (which then determine whether there is a second word). The encoding is very messy. The general theme of the encoding are
|
||||
- The encoding seems first designed for TeakLite (which is already a mess), then additional opcodes from Teak squeezed in formerly unused locations.
|
||||
- While registers `r0`~`r7` are generally at equal status in the computation model, they are not always in the opcodes. Specifically, as `r6` doesn't present in TeakLite, and because of the opcode squeezing, many opcodes involving `r6` are encoded separately with seemingly random pattern.
|
||||
- Similarly, there are also opcodes that are in the same family but have different pattern for different arguments.
|
||||
- Some specific combination of arguments can be invalid for one opcode, and this specific pattern can be used for a totally different opcode.
|
||||
- Some opcodes have unused bits, where setting either 0 or 1 has no effect.
|
||||
|
||||
## Comparison between `decoder.h` and gbatek
|
||||
|
||||
`decoder.h` contains the full table of the instruction set. It is derived from the table in gbatek, but rearranged a little according to the opcode families and patterns. The notation of the two table are comparable. For example:
|
||||
|
||||
```
|
||||
teakra: INST(push, 0xD7C8, At<Abe, 1>, Unused<0>)
|
||||
gbatek: D7C8h TL2 push Abe@1, Unused1@0
|
||||
```
|
||||
This means that the opcode has a parameter `Abe` at bit 1 (which is 2-bit long, defined by operand `Abe`), and an unused bit at bit 0. The rest bit of the opcode (bit 3~15) should match the pattern `0xD7C8`. The assembly of this opcode would be like `push <Abe>`.
|
||||
|
||||
Sometimes there is a 18-bit address parameter that is split into two parts:
|
||||
|
||||
```
|
||||
teakra: INST(br, 0x4180, At<Address18_16, 16>, At<Address18_2, 4>, At<Cond, 0>)
|
||||
gbatek: 4180h TL br Address18@16and4, Cond@0
|
||||
```
|
||||
Note that the existence of `At<..., 16>` also indicates that this opcode is 2-word long. The pattern `0x4180` is only matched against the first word.
|
||||
|
||||
Some opcodes that have the same pattern are merged into one opcode in teakra. For example
|
||||
```
|
||||
teakra: INST(alm, 0xA000, At<Alm, 9>, At<MemImm8, 0>, At<Ax, 8>),
|
||||
gbatek: {
|
||||
A000h TL or MemImm8@0, Ax@8
|
||||
A200h TL and MemImm8@0, Ax@8
|
||||
...
|
||||
BE00h TL cmpu MemImm8@0, Ax@8
|
||||
}
|
||||
```
|
||||
Here the operation name is also treated as an operand `Alm` in teakra.
|
||||
|
||||
Opcodes with constants that has special encoding are marked in their opcode names:
|
||||
```
|
||||
teakra: INST(sub_p1, 0xD4B9, At<Ax, 8>),
|
||||
gbatek: D4B9h TL2 sub p1, Ax@8
|
||||
```
|
||||
|
||||
However, if several opcodes with constants have different encoding but similar semantics, the constants are placed in the parameter list and delegate to the same opcode
|
||||
```
|
||||
teakra: {
|
||||
INST(alm_r6, 0xD388, Const<Alm, 0>, At<Ax, 4>),
|
||||
INST(alm_r6, 0xD389, Const<Alm, 1>, At<Ax, 4>),
|
||||
...
|
||||
INST(alm_r6, 0x9462, Const<Alm, 8>, At<Ax, 0>),
|
||||
...
|
||||
INST(alm_r6, 0x5F41, Const<Alm, 13>, Const<Ax, 0>),
|
||||
INST(alm_r6, 0x9062, Const<Alm, 14>, At<Ax, 8>, Unused<0>),
|
||||
INST(alm_r6, 0x8A63, Const<Alm, 15>, At<Ax, 3>),
|
||||
}
|
||||
gbatek: {
|
||||
D388h TL2 or r6, Ax@4
|
||||
D389h TL2 and r6, Ax@4
|
||||
...
|
||||
9462h TL2 msu y0, r6, Ax@0 // y0 is implied by msu
|
||||
...
|
||||
5F41h TL2 sqr r6 // Ax is implied/unused for sqr
|
||||
9062h TL2 sqra r6, Ax@8, Unused1@0
|
||||
8A63h TL2 cmpu r6, Ax@3
|
||||
}
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
#include "teakra/disassembler.h"
|
||||
#include "teakra/disassembler_c.h"
|
||||
|
||||
extern "C" {
|
||||
bool Teakra_Disasm_NeedExpansion(uint16_t opcode) {
|
||||
return Teakra::Disassembler::NeedExpansion(opcode);
|
||||
}
|
||||
|
||||
size_t Teakra_Disasm_Do(char* dst, size_t dstlen,
|
||||
uint16_t opcode, uint16_t expansion /*= 0*/) {
|
||||
std::string r = Teakra::Disassembler::Do(opcode, expansion);
|
||||
|
||||
if (dst) {
|
||||
size_t i = 0;
|
||||
for (; i < (dstlen-1) && i < r.length(); ++i) {
|
||||
dst[i] = r[i];
|
||||
}
|
||||
dst[dstlen-1] = '\0';
|
||||
}
|
||||
|
||||
return r.length();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "ahbm.h"
|
||||
#include "dma.h"
|
||||
#include "shared_memory.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
void Dma::Reset() {
|
||||
enable_channel = 0;
|
||||
active_channel = 0;
|
||||
channels = {};
|
||||
}
|
||||
|
||||
void Dma::DoDma(u16 channel) {
|
||||
channels[channel].Start();
|
||||
|
||||
channels[channel].ahbm_channel = ahbm.GetChannelForDma(channel);
|
||||
|
||||
// TODO: actually Tick this according to global Tick;
|
||||
while (channels[channel].running)
|
||||
channels[channel].Tick(*this);
|
||||
|
||||
interrupt_handler();
|
||||
}
|
||||
|
||||
void Dma::Channel::Start() {
|
||||
running = 1;
|
||||
current_src = addr_src_low | ((u32)addr_src_high << 16);
|
||||
current_dst = addr_dst_low | ((u32)addr_dst_high << 16);
|
||||
counter0 = 0;
|
||||
counter1 = 0;
|
||||
counter2 = 0;
|
||||
}
|
||||
|
||||
void Dma::Channel::Tick(Dma& parent) {
|
||||
static constexpr u32 DataMemoryOffset = 0x20000;
|
||||
if (dword_mode) {
|
||||
u32 value = 0;
|
||||
switch (src_space) {
|
||||
case 0: {
|
||||
u32 l = current_src & 0xFFFFFFFE;
|
||||
u32 h = current_src | 1;
|
||||
value = parent.shared_memory.ReadWord(DataMemoryOffset + l) |
|
||||
((u32)parent.shared_memory.ReadWord(DataMemoryOffset + h) << 16);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
std::printf("Unimplemented MMIO space");
|
||||
value = 0;
|
||||
break;
|
||||
case 7:
|
||||
value = parent.ahbm.Read32(ahbm_channel, current_src);
|
||||
break;
|
||||
default:
|
||||
std::printf("Unknown SrcSpace %04X\n", src_space);
|
||||
}
|
||||
|
||||
switch (dst_space) {
|
||||
case 0: {
|
||||
u32 l = current_dst & 0xFFFFFFFE;
|
||||
u32 h = current_dst | 1;
|
||||
parent.shared_memory.WriteWord(DataMemoryOffset + l, (u16)value);
|
||||
parent.shared_memory.WriteWord(DataMemoryOffset + h, (u16)(value >> 16));
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
std::printf("Unimplemented MMIO space");
|
||||
value = 0;
|
||||
break;
|
||||
case 7:
|
||||
parent.ahbm.Write32(ahbm_channel, current_dst, value);
|
||||
break;
|
||||
default:
|
||||
std::printf("Unknown DstSpace %04X\n", dst_space);
|
||||
}
|
||||
|
||||
counter0 += 2;
|
||||
} else {
|
||||
u16 value = 0;
|
||||
switch (src_space) {
|
||||
case 0:
|
||||
value = parent.shared_memory.ReadWord(DataMemoryOffset + current_src);
|
||||
break;
|
||||
case 1:
|
||||
std::printf("Unimplemented MMIO space");
|
||||
value = 0;
|
||||
break;
|
||||
case 7:
|
||||
value = parent.ahbm.Read16(ahbm_channel, current_src);
|
||||
break;
|
||||
default:
|
||||
std::printf("Unknown SrcSpace %04X\n", src_space);
|
||||
}
|
||||
|
||||
switch (dst_space) {
|
||||
case 0:
|
||||
parent.shared_memory.WriteWord(DataMemoryOffset + current_dst, value);
|
||||
break;
|
||||
case 1:
|
||||
std::printf("Unimplemented MMIO space");
|
||||
value = 0;
|
||||
break;
|
||||
case 7:
|
||||
parent.ahbm.Write16(ahbm_channel, current_dst, value);
|
||||
break;
|
||||
default:
|
||||
std::printf("Unknown DstSpace %04X\n", dst_space);
|
||||
}
|
||||
|
||||
counter0 += 1;
|
||||
}
|
||||
|
||||
if (counter0 >= size0) {
|
||||
counter0 = 0;
|
||||
counter1 += 1;
|
||||
if (counter1 >= size1) {
|
||||
counter1 = 0;
|
||||
counter2 += 1;
|
||||
if (counter2 >= size2) {
|
||||
running = 0;
|
||||
return;
|
||||
} else {
|
||||
current_src += src_step2;
|
||||
current_dst += dst_step2;
|
||||
}
|
||||
} else {
|
||||
current_src += src_step1;
|
||||
current_dst += dst_step1;
|
||||
}
|
||||
} else {
|
||||
current_src += src_step0;
|
||||
current_dst += dst_step0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,201 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
struct SharedMemory;
|
||||
class Ahbm;
|
||||
|
||||
class Dma {
|
||||
public:
|
||||
Dma(SharedMemory& shared_memory, Ahbm& ahbm) : shared_memory(shared_memory), ahbm(ahbm) {}
|
||||
|
||||
void Reset();
|
||||
|
||||
void EnableChannel(u16 value) {
|
||||
enable_channel = value;
|
||||
}
|
||||
u16 GetChannelEnabled() const {
|
||||
return enable_channel;
|
||||
}
|
||||
|
||||
void ActivateChannel(u16 value) {
|
||||
active_channel = value;
|
||||
}
|
||||
u16 GetActiveChannel() const {
|
||||
return active_channel;
|
||||
}
|
||||
|
||||
void SetAddrSrcLow(u16 value) {
|
||||
channels[active_channel].addr_src_low = value;
|
||||
}
|
||||
u16 GetAddrSrcLow() const {
|
||||
return channels[active_channel].addr_src_low;
|
||||
}
|
||||
|
||||
void SetAddrSrcHigh(u16 value) {
|
||||
channels[active_channel].addr_src_high = value;
|
||||
}
|
||||
u16 GetAddrSrcHigh() const {
|
||||
return channels[active_channel].addr_src_high;
|
||||
}
|
||||
|
||||
void SetAddrDstLow(u16 value) {
|
||||
channels[active_channel].addr_dst_low = value;
|
||||
}
|
||||
u16 GetAddrDstLow() const {
|
||||
return channels[active_channel].addr_dst_low;
|
||||
}
|
||||
|
||||
void SetAddrDstHigh(u16 value) {
|
||||
channels[active_channel].addr_dst_high = value;
|
||||
}
|
||||
u16 GetAddrDstHigh() const {
|
||||
return channels[active_channel].addr_dst_high;
|
||||
}
|
||||
|
||||
void SetSize0(u16 value) {
|
||||
channels[active_channel].size0 = value;
|
||||
}
|
||||
u16 GetSize0() const {
|
||||
return channels[active_channel].size0;
|
||||
}
|
||||
|
||||
void SetSize1(u16 value) {
|
||||
channels[active_channel].size1 = value;
|
||||
}
|
||||
u16 GetSize1() const {
|
||||
return channels[active_channel].size1;
|
||||
}
|
||||
|
||||
void SetSize2(u16 value) {
|
||||
channels[active_channel].size2 = value;
|
||||
}
|
||||
u16 GetSize2() const {
|
||||
return channels[active_channel].size2;
|
||||
}
|
||||
|
||||
void SetSrcStep0(u16 value) {
|
||||
channels[active_channel].src_step0 = value;
|
||||
}
|
||||
u16 GetSrcStep0() const {
|
||||
return channels[active_channel].src_step0;
|
||||
}
|
||||
|
||||
void SetDstStep0(u16 value) {
|
||||
channels[active_channel].dst_step0 = value;
|
||||
}
|
||||
u16 GetDstStep0() const {
|
||||
return channels[active_channel].dst_step0;
|
||||
}
|
||||
|
||||
void SetSrcStep1(u16 value) {
|
||||
channels[active_channel].src_step1 = value;
|
||||
}
|
||||
u16 GetSrcStep1() const {
|
||||
return channels[active_channel].src_step1;
|
||||
}
|
||||
|
||||
void SetDstStep1(u16 value) {
|
||||
channels[active_channel].dst_step1 = value;
|
||||
}
|
||||
u16 GetDstStep1() const {
|
||||
return channels[active_channel].dst_step1;
|
||||
}
|
||||
|
||||
void SetSrcStep2(u16 value) {
|
||||
channels[active_channel].src_step2 = value;
|
||||
}
|
||||
u16 GetSrcStep2() const {
|
||||
return channels[active_channel].src_step2;
|
||||
}
|
||||
|
||||
void SetDstStep2(u16 value) {
|
||||
channels[active_channel].dst_step2 = value;
|
||||
}
|
||||
u16 GetDstStep2() const {
|
||||
return channels[active_channel].dst_step2;
|
||||
}
|
||||
|
||||
void SetSrcSpace(u16 value) {
|
||||
channels[active_channel].src_space = value;
|
||||
}
|
||||
u16 GetSrcSpace() const {
|
||||
return channels[active_channel].src_space;
|
||||
}
|
||||
|
||||
void SetDstSpace(u16 value) {
|
||||
channels[active_channel].dst_space = value;
|
||||
}
|
||||
u16 GetDstSpace() const {
|
||||
return channels[active_channel].dst_space;
|
||||
}
|
||||
|
||||
void SetDwordMode(u16 value) {
|
||||
channels[active_channel].dword_mode = value;
|
||||
}
|
||||
u16 GetDwordMode() const {
|
||||
return channels[active_channel].dword_mode;
|
||||
}
|
||||
|
||||
void SetY(u16 value) {
|
||||
channels[active_channel].y = value;
|
||||
}
|
||||
u16 GetY() const {
|
||||
return channels[active_channel].y;
|
||||
}
|
||||
|
||||
void SetZ(u16 value) {
|
||||
channels[active_channel].z = value;
|
||||
|
||||
if (value == 0x40C0) {
|
||||
DoDma(active_channel);
|
||||
}
|
||||
}
|
||||
u16 GetZ() const {
|
||||
return channels[active_channel].z;
|
||||
}
|
||||
|
||||
void DoDma(u16 channel);
|
||||
|
||||
void SetInterruptHandler(std::function<void()> handler) {
|
||||
interrupt_handler = std::move(handler);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> interrupt_handler;
|
||||
|
||||
u16 enable_channel = 0;
|
||||
u16 active_channel = 0;
|
||||
|
||||
struct Channel {
|
||||
u16 addr_src_low = 0, addr_src_high = 0;
|
||||
u16 addr_dst_low = 0, addr_dst_high = 0;
|
||||
u16 size0 = 0, size1 = 0, size2 = 0;
|
||||
u16 src_step0 = 0, dst_step0 = 0;
|
||||
u16 src_step1 = 0, dst_step1 = 0;
|
||||
u16 src_step2 = 0, dst_step2 = 0;
|
||||
u16 src_space = 0, dst_space = 0;
|
||||
u16 dword_mode = 0;
|
||||
u16 y = 0;
|
||||
u16 z = 0;
|
||||
|
||||
u32 current_src = 0, current_dst = 0;
|
||||
u16 counter0 = 0, counter1 = 0, counter2 = 0;
|
||||
u16 running = 0;
|
||||
u16 ahbm_channel = 0;
|
||||
|
||||
void Start();
|
||||
void Tick(Dma& parent);
|
||||
};
|
||||
|
||||
std::array<Channel, 8> channels;
|
||||
|
||||
SharedMemory& shared_memory;
|
||||
Ahbm& ahbm;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,99 @@
|
|||
# DMA
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0180 | | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0182 | | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0184 | | | | | | | | |C7 |C6 |C5 |C4 |C3 |C2 |C1 |C0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0186 | | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0188 | | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x018A | | | | | | | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x018C | |E7 |E6 |E5 |E4 |E3 |E2 |E1 |E0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x018E | | S3 | | S2 | | S1 | | S0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0190 | | S7 | | S6 | | S5 | | S4 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
C0..C7: enable channel 0..7?
|
||||
E0..E7: end of transfer flag for channel 0..7?
|
||||
S0..S7: some sort of slots for channel 0..7? are initialized as value 0..7
|
||||
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01BE | | CHANNEL |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01C0 | SRC_ADDR_LOW |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01C2 | SRC_ADDR_HIGH |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01C4 | DST_ADDR_LOW |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01C6 | DST_ADDR_HIGH |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01C8 | SIZE0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01CA | SIZE1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01CC | SIZE2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01CE | SRC_STEP0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01D0 | DST_STEP0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01D2 | SRC_STEP1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01D4 | DST_STEP1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01D6 | SRC_STEP2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01D8 | DST_STEP2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01DA | | - |DWM| ? | | DST_SPACE | SRC_SPACE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01DC | - | | | ? | ? | ? | | | | | - | | | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x01DE |RST|STR| | ? | ? | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
CHANNEL: set the channel to bind with register +0x01C0..+0x01DE
|
||||
SRC_SPACE / DST_SPACE: the memory space associated with SRC_ADDR and DST_ADDR
|
||||
- 0: DSP main memory
|
||||
- 1: MMIO
|
||||
- 5: Program memory (only for DST_SPACE) (untested)
|
||||
- 7: connect to AHBM / external memory
|
||||
|
||||
```
|
||||
|
||||
## Data Transfer Engine
|
||||
|
||||
The engine is used for transferring data between memory locations. One transfer session is like copying a three-dimensional array, with configurable strides, from the specified source and destination. `SRC_ADDR` and `DST_ADDR` specify the starting address of data source and destination. `SIZE0`, `SIZE1` and `SIZE2` specify the element count for each dimension, where `SIZE0` specifies the finest dimension in the linear memory space. (`SRC_`/`DST_`)`STEP0`, `STEP1` and `STEP2` specify the address stepping between elements for each dimension. Below is an example showing how it works.
|
||||
|
||||
With the source address configuration:
|
||||
|
||||
```
|
||||
SRC_ADDR = 0
|
||||
SIZE0 = 3
|
||||
SIZE1 = 5
|
||||
SIZE2 = 2
|
||||
SRC_STEP0 = 2
|
||||
SRC_STEP1 = 1
|
||||
SRC_STEP2 = 7
|
||||
```
|
||||
The data transfer copies data from the following addresses, where `,` represents dimension 0 stepping, `/` for dimension 1 and `||` for dimension 2.
|
||||
```
|
||||
0, 2, 4/ 5, 7, 9/ 10, 12, 14/ 15, 17, 19/ 20, 22, 24||
|
||||
31, 33, 35/ 36, 38, 40/ 41, 43, 45/ 46, 48, 50/ 51, 53, 55
|
||||
```
|
||||
The same rule applies to the destination address as well. Note that `SIZEx` can be 0, in which case they have the same effect as being 1.
|
||||
|
||||
One element can be either a 16-bit(`DWM = 0`) word or a 32-bit double word (`DWM = 1`). In the 32-bit double word mode, the address stepping method is the same as described above, except for `SIZE0`, where one 32-bit element is counted as 2 for the dimension 0 counter, so effectively `SIZE0` value is twice as the actual count of 32-bit elements copied in a dimension 0 stride. The double word mode also automatically aligns down the addresses to 32-bit boundaries.
|
||||
|
||||
When the memory space is specified as 7 (AHBM), it performs data transfer from/to external memory. Take 3DS for example, the external memory is FCRAM, and the address is specified as FCRAM's physical address as-is. Because the external memory has 8-bit addressing, the address resolution mismatch between DSP memory and FCRAM can make it unintuitive. Just keep in mind that the `STEP` value is also added to the address as-is, so `STEP = 1` means stepping 8-bit for FCRAM, while stepping 16-bit for DSP memory. There are also more alignment requirement on the external memory address, but it is handled by AHBM. See [ahbm.md](ahbm.md) for detail.
|
|
@ -0,0 +1,9 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(dsp1_reader
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(dsp1_reader)
|
||||
target_link_libraries(dsp1_reader PRIVATE teakra)
|
||||
target_include_directories(dsp1_reader PRIVATE .)
|
||||
target_compile_options(dsp1_reader PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,125 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <teakra/disassembler.h>
|
||||
#include "../common_types.h"
|
||||
|
||||
class Dsp1 {
|
||||
public:
|
||||
Dsp1(std::vector<u8> raw);
|
||||
|
||||
struct Header {
|
||||
u8 signature[0x100];
|
||||
u8 magic[4];
|
||||
u32 binary_size;
|
||||
u16 memory_layout;
|
||||
u16 padding;
|
||||
u8 unknown;
|
||||
u8 filter_segment_type;
|
||||
u8 num_segments;
|
||||
u8 flags;
|
||||
u32 filter_segment_address;
|
||||
u32 filter_segment_size;
|
||||
u64 zero;
|
||||
struct Segment {
|
||||
u32 offset;
|
||||
u32 address;
|
||||
u32 size;
|
||||
u8 pa, pb, pc;
|
||||
u8 memory_type;
|
||||
u8 sha256[0x20];
|
||||
} segments[10];
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x300);
|
||||
|
||||
struct Segment {
|
||||
std::vector<u8> data;
|
||||
u8 memory_type;
|
||||
u32 target;
|
||||
};
|
||||
|
||||
std::vector<Segment> segments;
|
||||
bool recv_data_on_start;
|
||||
};
|
||||
|
||||
Dsp1::Dsp1(std::vector<u8> raw) {
|
||||
Header header;
|
||||
std::memcpy(&header, raw.data(), sizeof(header));
|
||||
|
||||
recv_data_on_start = (header.flags & 1) != 0;
|
||||
|
||||
printf("Memory layout = %04X\n", header.memory_layout);
|
||||
printf("Unk = %02X\n", header.unknown);
|
||||
printf("Filter segment type = %d\n", header.filter_segment_type);
|
||||
printf("Num segments = %d\n", header.num_segments);
|
||||
printf("Flags = %d\n", header.flags);
|
||||
printf("Filter address = 2 * %08X\n", header.filter_segment_address);
|
||||
printf("Filter size = %08X\n", header.filter_segment_size);
|
||||
|
||||
for (u32 i = 0; i < header.num_segments; ++i) {
|
||||
Segment segment;
|
||||
segment.data =
|
||||
std::vector<u8>(raw.begin() + header.segments[i].offset,
|
||||
raw.begin() + header.segments[i].offset + header.segments[i].size);
|
||||
segment.memory_type = header.segments[i].memory_type;
|
||||
segment.target = header.segments[i].address; /*header.segments[i].address * 2 +
|
||||
(segment.memory_type == 2 ? 0x1FF40000 : 0x1FF00000);*/
|
||||
segments.push_back(segment);
|
||||
|
||||
printf("[Segment %d]\n", i);
|
||||
printf("memory_type = %d\n", segment.memory_type);
|
||||
printf("target = %08X\n", segment.target);
|
||||
printf("size = %08X\n", (u32)segment.data.size());
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3)
|
||||
return -1;
|
||||
|
||||
FILE* file = fopen(argv[1], "rb");
|
||||
std::vector<u8> raw;
|
||||
u8 ch;
|
||||
while (fread(&ch, 1, 1, file) == 1) {
|
||||
raw.push_back(ch);
|
||||
}
|
||||
fclose(file);
|
||||
Dsp1 dsp(raw);
|
||||
|
||||
file = fopen(argv[2], "wt");
|
||||
|
||||
for (const auto& segment : dsp.segments) {
|
||||
if (segment.memory_type == 0 || segment.memory_type == 1) {
|
||||
fprintf(file, "\n>>>>>>>> Segment <<<<<<<<\n\n");
|
||||
for (unsigned pos = 0; pos < segment.data.size(); pos += 2) {
|
||||
u16 opcode = segment.data[pos] | (segment.data[pos + 1] << 8);
|
||||
fprintf(file, "%08X %04X ", segment.target + pos / 2, opcode);
|
||||
bool expand = false;
|
||||
u16 expand_value = 0;
|
||||
if (Teakra::Disassembler::NeedExpansion(opcode)) {
|
||||
expand = true;
|
||||
pos += 2;
|
||||
expand_value = segment.data[pos] | (segment.data[pos + 1] << 8);
|
||||
}
|
||||
std::string result = Teakra::Disassembler::Do(opcode, expand_value);
|
||||
fprintf(file, "%s\n", result.c_str());
|
||||
if (expand) {
|
||||
fprintf(file, "%08X %04X ^^^\n", segment.target + pos / 2, expand_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.memory_type == 2) {
|
||||
fprintf(file, "\n>>>>>>>> Data Segment <<<<<<<<\n\n");
|
||||
for (unsigned pos = 0; pos < segment.data.size(); pos += 2) {
|
||||
u16 opcode = segment.data[pos] | (segment.data[pos + 1] << 8);
|
||||
fprintf(file, "%08X %04X\n", segment.target + pos / 2, opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class ICU {
|
||||
public:
|
||||
using IrqBits = std::bitset<16>;
|
||||
u16 GetRequest() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return (u16)request.to_ulong();
|
||||
}
|
||||
void Acknowledge(u16 irq_bits) {
|
||||
std::lock_guard lock(mutex);
|
||||
request &= ~IrqBits(irq_bits);
|
||||
}
|
||||
u16 GetAcknowledge() {
|
||||
return 0;
|
||||
}
|
||||
void Trigger(u16 irq_bits) {
|
||||
std::lock_guard lock(mutex);
|
||||
IrqBits bits(irq_bits);
|
||||
request |= bits;
|
||||
for (u32 irq = 0; irq < 16; ++irq) {
|
||||
if (bits[irq]) {
|
||||
for (u32 interrupt = 0; interrupt < enabled.size(); ++interrupt) {
|
||||
if (enabled[interrupt][irq]) {
|
||||
on_interrupt(interrupt);
|
||||
}
|
||||
}
|
||||
if (vectored_enabled[irq]) {
|
||||
on_vectored_interrupt(GetVector(irq), vector_context_switch[irq] != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
u16 GetTrigger() {
|
||||
return 0;
|
||||
}
|
||||
void TriggerSingle(u32 irq) {
|
||||
Trigger(1 << irq);
|
||||
}
|
||||
void SetEnable(u32 interrupt_index, u16 irq_bits) {
|
||||
std::lock_guard lock(mutex);
|
||||
enabled[interrupt_index] = IrqBits(irq_bits);
|
||||
}
|
||||
void SetEnableVectored(u16 irq_bits) {
|
||||
std::lock_guard lock(mutex);
|
||||
vectored_enabled = IrqBits(irq_bits);
|
||||
}
|
||||
u16 GetEnable(u32 interrupt_index) const {
|
||||
std::lock_guard lock(mutex);
|
||||
return (u16)enabled[interrupt_index].to_ulong();
|
||||
}
|
||||
u16 GetEnableVectored() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return (u16)vectored_enabled.to_ulong();
|
||||
}
|
||||
|
||||
u32 GetVector(u32 irq) const {
|
||||
return vector_low[irq] | ((u32)vector_high[irq] << 16);
|
||||
}
|
||||
|
||||
void SetInterruptHandler(std::function<void(u32)> interrupt,
|
||||
std::function<void(u32, bool)> vectored_interrupt) {
|
||||
on_interrupt = std::move(interrupt);
|
||||
on_vectored_interrupt = std::move(vectored_interrupt);
|
||||
}
|
||||
|
||||
std::array<u16, 16> vector_low, vector_high;
|
||||
std::array<u16, 16> vector_context_switch;
|
||||
|
||||
private:
|
||||
std::function<void(u32)> on_interrupt;
|
||||
std::function<void(u32, bool)> on_vectored_interrupt;
|
||||
|
||||
IrqBits request;
|
||||
std::array<IrqBits, 3> enabled;
|
||||
IrqBits vectored_enabled;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,70 @@
|
|||
# ICU
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0200 |IPF|IPE|IPD|IPC|IPB|IPA|IP9|IP8|IP7|IP6|IP5|IP4|IP3|IP2|IP1|IP0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0202 |IAF|IAE|IAD|IAC|IAB|IAA|IA9|IA8|IA7|IA6|IA5|IA4|IA3|IA2|IA1|IA0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0204 |ITF|ITE|ITD|ITC|ITB|ITA|IT9|IT8|IT7|IT6|IT5|IT4|IT3|IT2|IT1|IT0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0206 |I0F|I0E|I0D|I0C|I0B|I0A|I09|I08|I07|I06|I05|I04|I03|I02|I01|I00|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0208 |I1F|I1E|I1D|I1C|I1B|I1A|I19|I18|I17|I16|I15|I14|I13|I12|I11|I10|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x020A |I2F|I2E|I2D|I2C|I2B|I2A|I29|I28|I27|I26|I25|I24|I23|I22|I21|I20|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x020C |IVF|IVE|IVD|IVC|IVB|IVA|IV9|IV8|IV7|IV6|IV5|IV4|IV3|IV2|IV1|IV0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x020E |TPF|TPE|TPD|TPC|TPB|TPA|TP9|TP8|TP7|TP6|TP5|TP4|TP3|TP2|TP1|TP0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0210 |PLF|PLE|PLD|PLC|PLB|PLA|PL9|PL8|PL7|PL6|PL5|PL4|PL3|PL2|PL1|PL0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
N = 0..15
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0212+N*4|VIC| |VADDR_H|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0214+N*4| VADDR_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
IP0..IPF: IRQ pending flag
|
||||
IA0..IAF: IRQ acknowledge, 1 to clears pending flag
|
||||
IT0..ITF: triggers IRQ manually
|
||||
I00..I0F: connects IRQ to core interrupt 0
|
||||
I10..I1F: connects IRQ to core interrupt 1
|
||||
I20..I2F: connects IRQ to core interrupt 2
|
||||
IV0..IVF: connects IRQ to core vectored interrupt
|
||||
TP0..TPF: IRQ trigger mode? 0: pulse, 1: sticky
|
||||
PL0..PLF: polarity of IRQ signal?
|
||||
* I might have got PLx and TPx swapped
|
||||
VADDR_H, VADDR_L: address of interrupt handler for vectored interrupt
|
||||
VIC: 1 to enable context switch on vectored interrupt
|
||||
```
|
||||
|
||||
## IRQ signal
|
||||
|
||||
All bit fields in MMIO correspond to 16 IRQ signal. Some of them are known to connect to other peripherals as shown below. There might be more undiscovered components that have associated IRQ
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
| | *F| *E| *D| *C| *B| *A| *9| *8| *7| *6| *5| *4| *3| *2| *1| *0|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
| | | | | | |
|
||||
DMA --------* | | | | | |
|
||||
APBP ------------* | | | | |
|
||||
ICU? ----------------* | | | |
|
||||
--------------------* | | |
|
||||
BTDMP ------------------------* | |
|
||||
TIMER0----------------------------* |
|
||||
TIMER1--------------------------------*
|
||||
```
|
||||
|
||||
## IRQ-to-interrupt translator
|
||||
|
||||
The main job of ICU is to translate 16 IRQ signals to 4 processor interrupt signals (int0, int1, int2 and vint), specified by `I0x`, `I1x`, `I2x` and `IVx` registers. When an IRQ is signaled, ICU will generate interrupt signal and the processor starts to execute interrupt handling procedure if the core interrupt is enabled. The procedure is supposed check `IPx` reigster to know the exact IRQ index, and to set `IAx` registers to clear the IRQ signal.
|
||||
|
||||
## Software interrupt
|
||||
|
||||
The processor can writes to `ITx` to generate a software interrupt manually. A use case for this in many 3DS games is that IRQ 0 is used as the reschedule procedure for multithreading. When a thread wants to yield, it triggers IRQ 0, which switches thread context and resume another thread.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(makedsp1
|
||||
main.cpp
|
||||
sha256.cpp
|
||||
sha256.h
|
||||
)
|
||||
create_target_directory_groups(makedsp1)
|
||||
target_link_libraries(makedsp1 PRIVATE teakra)
|
||||
target_include_directories(makedsp1 PRIVATE .)
|
||||
target_compile_options(makedsp1 PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,172 @@
|
|||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "../common_types.h"
|
||||
#include "../parser.h"
|
||||
#include "sha256.h"
|
||||
|
||||
template <typename T>
|
||||
std::vector<u8> Sha256(const std::vector<T>& data) {
|
||||
SHA256_CTX ctx;
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, (const BYTE*)data.data(), data.size() * sizeof(T));
|
||||
std::vector<u8> result(0x20);
|
||||
sha256_final(&ctx, result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Segment {
|
||||
std::vector<u16> data;
|
||||
u8 memory_type;
|
||||
u32 target;
|
||||
};
|
||||
|
||||
std::vector<std::string> StringToTokens(const std::string& in) {
|
||||
std::vector<std::string> out;
|
||||
bool need_new = true;
|
||||
for (char c : in) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
need_new = true;
|
||||
} else {
|
||||
if (need_new) {
|
||||
need_new = false;
|
||||
out.push_back("");
|
||||
}
|
||||
out.back() += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
auto parser = Teakra::GenerateParser();
|
||||
|
||||
if (argc < 3) {
|
||||
printf("Not enough parameters\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::ifstream in(argv[1]);
|
||||
if (!in.is_open()) {
|
||||
printf("Failed to open input file\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::vector<Segment> segments;
|
||||
int line_number = 0;
|
||||
while (std::getline(in, line)) {
|
||||
++line_number;
|
||||
auto comment_pos = line.find("//");
|
||||
if (comment_pos != std::string::npos) {
|
||||
line.erase(comment_pos);
|
||||
}
|
||||
|
||||
auto expansion_pos = line.find("$");
|
||||
bool has_expansion = expansion_pos != std::string::npos;
|
||||
u16 expansion_data;
|
||||
if (has_expansion) {
|
||||
if (line.size() - expansion_pos < 5) {
|
||||
printf("%d: unexpected line break in expansion data\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
expansion_data = (u16)std::stoi(line.substr(expansion_pos + 1, 4), 0, 16);
|
||||
line = line.substr(0, expansion_pos) + "0000" + line.substr(expansion_pos + 5);
|
||||
}
|
||||
|
||||
auto tokens = StringToTokens(line);
|
||||
if (tokens.size() == 0)
|
||||
continue;
|
||||
|
||||
if (tokens[0] == "segment") {
|
||||
if (tokens.size() != 3) {
|
||||
printf("%d: Wrong parameter count for 'segment'\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
Segment s;
|
||||
if (tokens[1] == "p") {
|
||||
s.memory_type = 0;
|
||||
} else if (tokens[1] == "d") {
|
||||
s.memory_type = 2;
|
||||
} else {
|
||||
printf("%d: Unknown segment type %s\n", line_number, tokens[1].c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
s.target = std::stoi(tokens[2], 0, 16);
|
||||
segments.push_back(s);
|
||||
} else {
|
||||
u16 v;
|
||||
if (tokens[0] == "data") {
|
||||
if (tokens.size() != 2) {
|
||||
printf("%d: Wrong parameter count for 'data'\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
v = (u16)std::stoi(tokens[1], 0, 16);
|
||||
segments.back().data.push_back(v);
|
||||
} else {
|
||||
auto maybe_v = parser->Parse(tokens);
|
||||
if (maybe_v.status == Teakra::Parser::Opcode::Invalid) {
|
||||
printf("%d: could not parse\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
v = maybe_v.opcode;
|
||||
segments.back().data.push_back(v);
|
||||
|
||||
if (maybe_v.status == Teakra::Parser::Opcode::ValidWithExpansion) {
|
||||
if (!has_expansion) {
|
||||
printf("%d: needs expansion\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
segments.back().data.push_back(expansion_data);
|
||||
} else {
|
||||
if (has_expansion) {
|
||||
printf("%d: unexpected expansion\n", line_number);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
|
||||
FILE* out = fopen(argv[2], "wb");
|
||||
if (!out) {
|
||||
printf("Failed to open output file\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 data_ptr = 0x300;
|
||||
|
||||
for (unsigned i = 0; i < segments.size(); ++i) {
|
||||
fseek(out, 0x120 + i * 0x30, SEEK_SET);
|
||||
fwrite(&data_ptr, 4, 1, out);
|
||||
fwrite(&segments[i].target, 4, 1, out);
|
||||
u32 size = (u32)segments[i].data.size() * 2;
|
||||
fwrite(&size, 4, 1, out);
|
||||
u32 memory_type = segments[i].memory_type << 24;
|
||||
fwrite(&memory_type, 4, 1, out);
|
||||
auto sha = Sha256(segments[i].data);
|
||||
fwrite(sha.data(), 0x20, 1, out);
|
||||
|
||||
fseek(out, data_ptr, SEEK_SET);
|
||||
fwrite(segments[i].data.data(), size, 1, out);
|
||||
data_ptr += size;
|
||||
}
|
||||
|
||||
fseek(out, 0x100, SEEK_SET);
|
||||
fwrite("DSP1", 4, 1, out);
|
||||
fwrite(&data_ptr, 4, 1, out);
|
||||
u32 memory_layout = 0x0000FFFF;
|
||||
fwrite(&memory_layout, 4, 1, out);
|
||||
u32 misc = (u32)segments.size() << 16;
|
||||
fwrite(&misc, 4, 1, out);
|
||||
u32 zero = 0;
|
||||
fwrite(&zero, 4, 1, out);
|
||||
fwrite(&zero, 4, 1, out);
|
||||
fwrite(&zero, 4, 1, out);
|
||||
fwrite(&zero, 4, 1, out);
|
||||
|
||||
fclose(out);
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/*********************************************************************
|
||||
* Filename: sha256.c
|
||||
* Author: Brad Conte (brad AT bradconte.com)
|
||||
* Copyright:
|
||||
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||
* Details: Implementation of the SHA-256 hashing algorithm.
|
||||
SHA-256 is one of the three algorithms in the SHA2
|
||||
specification. The others, SHA-384 and SHA-512, are not
|
||||
offered in this implementation.
|
||||
Algorithm specification can be found here:
|
||||
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
|
||||
This implementation uses little endian byte order.
|
||||
*********************************************************************/
|
||||
|
||||
/*************************** HEADER FILES ***************************/
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include "sha256.h"
|
||||
|
||||
/****************************** MACROS ******************************/
|
||||
#define ROTLEFT(a, b) (((a) << (b)) | ((a) >> (32 - (b))))
|
||||
#define ROTRIGHT(a, b) (((a) >> (b)) | ((a) << (32 - (b))))
|
||||
|
||||
#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
|
||||
#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||
#define EP0(x) (ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22))
|
||||
#define EP1(x) (ROTRIGHT(x, 6) ^ ROTRIGHT(x, 11) ^ ROTRIGHT(x, 25))
|
||||
#define SIG0(x) (ROTRIGHT(x, 7) ^ ROTRIGHT(x, 18) ^ ((x) >> 3))
|
||||
#define SIG1(x) (ROTRIGHT(x, 17) ^ ROTRIGHT(x, 19) ^ ((x) >> 10))
|
||||
|
||||
/**************************** VARIABLES *****************************/
|
||||
static const WORD k[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
|
||||
|
||||
/*********************** FUNCTION DEFINITIONS ***********************/
|
||||
void sha256_transform(SHA256_CTX* ctx, const BYTE data[]) {
|
||||
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
|
||||
|
||||
for (i = 0, j = 0; i < 16; ++i, j += 4)
|
||||
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
|
||||
for (; i < 64; ++i)
|
||||
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
|
||||
|
||||
a = ctx->state[0];
|
||||
b = ctx->state[1];
|
||||
c = ctx->state[2];
|
||||
d = ctx->state[3];
|
||||
e = ctx->state[4];
|
||||
f = ctx->state[5];
|
||||
g = ctx->state[6];
|
||||
h = ctx->state[7];
|
||||
|
||||
for (i = 0; i < 64; ++i) {
|
||||
t1 = h + EP1(e) + CH(e, f, g) + k[i] + m[i];
|
||||
t2 = EP0(a) + MAJ(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
|
||||
ctx->state[0] += a;
|
||||
ctx->state[1] += b;
|
||||
ctx->state[2] += c;
|
||||
ctx->state[3] += d;
|
||||
ctx->state[4] += e;
|
||||
ctx->state[5] += f;
|
||||
ctx->state[6] += g;
|
||||
ctx->state[7] += h;
|
||||
}
|
||||
|
||||
void sha256_init(SHA256_CTX* ctx) {
|
||||
ctx->datalen = 0;
|
||||
ctx->bitlen = 0;
|
||||
ctx->state[0] = 0x6a09e667;
|
||||
ctx->state[1] = 0xbb67ae85;
|
||||
ctx->state[2] = 0x3c6ef372;
|
||||
ctx->state[3] = 0xa54ff53a;
|
||||
ctx->state[4] = 0x510e527f;
|
||||
ctx->state[5] = 0x9b05688c;
|
||||
ctx->state[6] = 0x1f83d9ab;
|
||||
ctx->state[7] = 0x5be0cd19;
|
||||
}
|
||||
|
||||
void sha256_update(SHA256_CTX* ctx, const BYTE data[], size_t len) {
|
||||
WORD i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
ctx->data[ctx->datalen] = data[i];
|
||||
ctx->datalen++;
|
||||
if (ctx->datalen == 64) {
|
||||
sha256_transform(ctx, ctx->data);
|
||||
ctx->bitlen += 512;
|
||||
ctx->datalen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sha256_final(SHA256_CTX* ctx, BYTE hash[]) {
|
||||
WORD i;
|
||||
|
||||
i = ctx->datalen;
|
||||
|
||||
// Pad whatever data is left in the buffer.
|
||||
if (ctx->datalen < 56) {
|
||||
ctx->data[i++] = 0x80;
|
||||
while (i < 56)
|
||||
ctx->data[i++] = 0x00;
|
||||
} else {
|
||||
ctx->data[i++] = 0x80;
|
||||
while (i < 64)
|
||||
ctx->data[i++] = 0x00;
|
||||
sha256_transform(ctx, ctx->data);
|
||||
memset(ctx->data, 0, 56);
|
||||
}
|
||||
|
||||
// Append to the padding the total message's length in bits and transform.
|
||||
ctx->bitlen += ctx->datalen * 8;
|
||||
ctx->data[63] = (BYTE)ctx->bitlen;
|
||||
ctx->data[62] = (BYTE)(ctx->bitlen >> 8);
|
||||
ctx->data[61] = (BYTE)(ctx->bitlen >> 16);
|
||||
ctx->data[60] = (BYTE)(ctx->bitlen >> 24);
|
||||
ctx->data[59] = (BYTE)(ctx->bitlen >> 32);
|
||||
ctx->data[58] = (BYTE)(ctx->bitlen >> 40);
|
||||
ctx->data[57] = (BYTE)(ctx->bitlen >> 48);
|
||||
ctx->data[56] = (BYTE)(ctx->bitlen >> 56);
|
||||
sha256_transform(ctx, ctx->data);
|
||||
|
||||
// Since this implementation uses little endian byte ordering and SHA uses big endian,
|
||||
// reverse all the bytes when copying the final state to the output hash.
|
||||
for (i = 0; i < 4; ++i) {
|
||||
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*********************************************************************
|
||||
* Filename: sha256.h
|
||||
* Author: Brad Conte (brad AT bradconte.com)
|
||||
* Copyright:
|
||||
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||
* Details: Defines the API for the corresponding SHA1 implementation.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef SHA256_H
|
||||
#define SHA256_H
|
||||
|
||||
/*************************** HEADER FILES ***************************/
|
||||
#include <stddef.h>
|
||||
|
||||
/****************************** MACROS ******************************/
|
||||
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
|
||||
|
||||
/**************************** DATA TYPES ****************************/
|
||||
typedef unsigned char BYTE; // 8-bit byte
|
||||
typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines
|
||||
|
||||
typedef struct {
|
||||
BYTE data[64];
|
||||
WORD datalen;
|
||||
unsigned long long bitlen;
|
||||
WORD state[8];
|
||||
} SHA256_CTX;
|
||||
|
||||
/*********************** FUNCTION DECLARATIONS **********************/
|
||||
void sha256_init(SHA256_CTX* ctx);
|
||||
void sha256_update(SHA256_CTX* ctx, const BYTE data[], size_t len);
|
||||
void sha256_final(SHA256_CTX* ctx, BYTE hash[]);
|
||||
|
||||
#endif // SHA256_H
|
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "common_types.h"
|
||||
#include "crash.h"
|
||||
|
||||
struct Rejector {
|
||||
u16 mask;
|
||||
u16 unexpected;
|
||||
bool Rejects(u16 instruction) const {
|
||||
return (instruction & mask) == unexpected;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Visitor>
|
||||
class Matcher {
|
||||
public:
|
||||
using visitor_type = Visitor;
|
||||
using handler_return_type = typename Visitor::instruction_return_type;
|
||||
using handler_function = std::function<handler_return_type(Visitor&, u16, u16)>;
|
||||
|
||||
Matcher(const char* const name, u16 mask, u16 expected, bool expanded, handler_function func)
|
||||
: name{name}, mask{mask}, expected{expected}, expanded{expanded}, fn{std::move(func)} {}
|
||||
|
||||
static Matcher AllMatcher(handler_function func) {
|
||||
return Matcher("*", 0, 0, false, std::move(func));
|
||||
}
|
||||
|
||||
const char* GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
bool NeedExpansion() const {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
bool Matches(u16 instruction) const {
|
||||
return (instruction & mask) == expected &&
|
||||
std::none_of(rejectors.begin(), rejectors.end(),
|
||||
[instruction](const Rejector& rejector) {
|
||||
return rejector.Rejects(instruction);
|
||||
});
|
||||
}
|
||||
|
||||
Matcher Except(Rejector rejector) const {
|
||||
Matcher new_matcher(*this);
|
||||
new_matcher.rejectors.push_back(rejector);
|
||||
return new_matcher;
|
||||
}
|
||||
|
||||
handler_return_type call(Visitor& v, u16 instruction, u16 instruction_expansion = 0) const {
|
||||
ASSERT(Matches(instruction));
|
||||
return fn(v, instruction, instruction_expansion);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name;
|
||||
u16 mask;
|
||||
u16 expected;
|
||||
bool expanded;
|
||||
handler_function fn;
|
||||
std::vector<Rejector> rejectors;
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
#include "memory_interface.h"
|
||||
#include "mmio.h"
|
||||
#include "shared_memory.h"
|
||||
|
||||
namespace Teakra {
|
||||
MemoryInterface::MemoryInterface(SharedMemory& shared_memory,
|
||||
MemoryInterfaceUnit& memory_interface_unit)
|
||||
: shared_memory(shared_memory), memory_interface_unit(memory_interface_unit) {}
|
||||
|
||||
void MemoryInterface::SetMMIO(MMIORegion& mmio) {
|
||||
this->mmio = &mmio;
|
||||
}
|
||||
|
||||
u16 MemoryInterface::ProgramRead(u32 address) const {
|
||||
return shared_memory.ReadWord(address);
|
||||
}
|
||||
void MemoryInterface::ProgramWrite(u32 address, u16 value) {
|
||||
shared_memory.WriteWord(address, value);
|
||||
}
|
||||
u16 MemoryInterface::DataRead(u16 address, bool bypass_mmio) {
|
||||
if (memory_interface_unit.InMMIO(address) && !bypass_mmio) {
|
||||
ASSERT(mmio != nullptr);
|
||||
return mmio->Read(memory_interface_unit.ToMMIO(address));
|
||||
}
|
||||
u32 converted = memory_interface_unit.ConvertDataAddress(address);
|
||||
u16 value = shared_memory.ReadWord(converted);
|
||||
return value;
|
||||
}
|
||||
void MemoryInterface::DataWrite(u16 address, u16 value, bool bypass_mmio) {
|
||||
if (memory_interface_unit.InMMIO(address) && !bypass_mmio) {
|
||||
ASSERT(mmio != nullptr);
|
||||
return mmio->Write(memory_interface_unit.ToMMIO(address), value);
|
||||
}
|
||||
u32 converted = memory_interface_unit.ConvertDataAddress(address);
|
||||
shared_memory.WriteWord(converted, value);
|
||||
}
|
||||
u16 MemoryInterface::DataReadA32(u32 address) const {
|
||||
u32 converted = (address & ((MemoryInterfaceUnit::DataMemoryBankSize*2)-1))
|
||||
+ MemoryInterfaceUnit::DataMemoryOffset;
|
||||
return shared_memory.ReadWord(converted);
|
||||
}
|
||||
void MemoryInterface::DataWriteA32(u32 address, u16 value) {
|
||||
u32 converted = (address & ((MemoryInterfaceUnit::DataMemoryBankSize*2)-1))
|
||||
+ MemoryInterfaceUnit::DataMemoryOffset;
|
||||
shared_memory.WriteWord(converted, value);
|
||||
}
|
||||
u16 MemoryInterface::MMIORead(u16 address) {
|
||||
ASSERT(mmio != nullptr);
|
||||
// according to GBATek ("DSi Teak I/O Ports (on ARM9 Side)"), these are mirrored
|
||||
return mmio->Read(address & (MemoryInterfaceUnit::MMIOSize - 1));
|
||||
}
|
||||
void MemoryInterface::MMIOWrite(u16 address, u16 value) {
|
||||
ASSERT(mmio != nullptr);
|
||||
mmio->Write(address & (MemoryInterfaceUnit::MMIOSize - 1), value);
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common_types.h"
|
||||
#include "crash.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class MemoryInterfaceUnit {
|
||||
public:
|
||||
u16 x_page = 0, y_page = 0, z_page = 0;
|
||||
static constexpr u16 XYSizeResolution = 0x400;
|
||||
std::array<u16, 2> x_size{{0x20, 0x20}};
|
||||
std::array<u16, 2> y_size{{0x1E, 0x1E}};
|
||||
u16 page_mode = 0;
|
||||
u16 mmio_base = 0x8000;
|
||||
|
||||
static constexpr u16 MMIOSize = 0x0800;
|
||||
static constexpr u32 DataMemoryOffset = 0x20000;
|
||||
static constexpr u32 DataMemoryBankSize = 0x10000;
|
||||
|
||||
void Reset() {
|
||||
*this = MemoryInterfaceUnit();
|
||||
}
|
||||
|
||||
bool InMMIO(u16 addr) const {
|
||||
return addr >= mmio_base && addr < mmio_base + MMIOSize;
|
||||
}
|
||||
u16 ToMMIO(u16 addr) const {
|
||||
ASSERT(z_page == 0);
|
||||
// according to GBATek ("DSi Teak I/O Ports (on ARM9 Side)"), these are mirrored
|
||||
return (addr - mmio_base) & (MMIOSize - 1);
|
||||
}
|
||||
|
||||
u32 ConvertDataAddress(u16 addr) const {
|
||||
if (page_mode == 0) {
|
||||
ASSERT(z_page < 2);
|
||||
return DataMemoryOffset + addr + z_page * DataMemoryBankSize;
|
||||
} else {
|
||||
if (addr <= x_size[0] * XYSizeResolution) {
|
||||
ASSERT(x_page < 2);
|
||||
return DataMemoryOffset + addr + x_page * DataMemoryBankSize;
|
||||
} else {
|
||||
ASSERT(y_page < 2);
|
||||
return DataMemoryOffset + addr + y_page * DataMemoryBankSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SharedMemory;
|
||||
class MMIORegion;
|
||||
|
||||
class MemoryInterface {
|
||||
public:
|
||||
MemoryInterface(SharedMemory& shared_memory, MemoryInterfaceUnit& memory_interface_unit);
|
||||
void SetMMIO(MMIORegion& mmio);
|
||||
u16 ProgramRead(u32 address) const;
|
||||
void ProgramWrite(u32 address, u16 value);
|
||||
u16 DataRead(u16 address, bool bypass_mmio = false); // not const because it can be a FIFO register
|
||||
void DataWrite(u16 address, u16 value, bool bypass_mmio = false);
|
||||
u16 DataReadA32(u32 address) const;
|
||||
void DataWriteA32(u32 address, u16 value);
|
||||
u16 MMIORead(u16 address);
|
||||
void MMIOWrite(u16 address, u16 value);
|
||||
|
||||
private:
|
||||
SharedMemory& shared_memory;
|
||||
MemoryInterfaceUnit& memory_interface_unit;
|
||||
MMIORegion* mmio;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,97 @@
|
|||
# MIU
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
The following MMIO definition is extracted from Lauterbach's Teak debugger. Some of them are untested.
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0100 | Z3WS | Z2WS | Z1WS | Z0WS |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0102 | | PRMWS | XYWS | ZDEFAULTWS |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0104 | Z0WSPAGE | Z0WSEA | Z0WSSA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0106 | Z1WSPAGE | Z1WSEA | Z1WSSA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0108 | Z2WSPAGE | Z2WSEA | Z2WSSA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x010A | Z3WSPAGE | Z3WSEA | Z3WSSA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x010C | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x010E | | XPAGE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0110 | | YPAGE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0112 | | ZPAGE |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0114 | | YPAGE0CFG | | XPAGE0CFG |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0116 | | YPAGE1CFG | | XPAGE1CFG |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0118 | | YOFFPAGECFG | | XOFFPAGECFG |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x011A | |PGM| |ZSP| |INP|TSP|PP |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x011C | |PDP| PDLPAGE |SDL|DLP|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x011E | MMIOBASE | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0120 | | OBSMOD |OBS|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0122 | |PRD|PZS|PXS|PXT|PZT|PZW|PZR|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
ZxWS: the number of wait-states for the x-th off-chip Z block
|
||||
ZDEFAULTWS: the default number of wait-states for Z off-chip transactions
|
||||
XYWS: the number of wait-states for X/Y off-chip memory transactions
|
||||
PRMWS: the number of wait-states for off-chip program-memory transactions
|
||||
ZxWSSA: the start address of the Z wait-states block #0 with a resolution of 1K-word.
|
||||
ZxWSEA: the end address of the Z wait-states block #0 with a resolution of 1K-word
|
||||
ZxWSPACE: the four LSBs of the Z wait-states block #0 page
|
||||
|
||||
XPAGE: the X memory page when working in paging mode 1
|
||||
YPAGE: the Y memory page when working in paging mode 1
|
||||
ZPAGE:
|
||||
- in paging mode 0: the absolute data memory page
|
||||
- in paging mode 1: the Z memory page
|
||||
XPAGE0CFG:
|
||||
- in paging mode 0: the X memory size for page #0
|
||||
- in paging mode 1: the X memory size for all pages
|
||||
YPAGE0CFG:
|
||||
- in paging mode 0: the Y memory size for page #0
|
||||
- in paging mode 1: the Y memory size for all pages
|
||||
XPAGE1CFG: in paging mode 0, the X memory size for page #1
|
||||
YPAGE1CFG: in paging mode 0, the Y memory size for page #1
|
||||
XOFFPAGECFG: in paging mode 0: the X memory size for all off-chip pages
|
||||
YOFFPAGECFG: in paging mode 0: the Y memory size for all off-chip pages
|
||||
|
||||
PP: 1 to enable the program protection mechanism
|
||||
TSP: "Test Program", 1 to latch the value of the DAZXE[1]/TESTP strap pin during reset. It configures the entire program space as off-chip or based on the INTP bit
|
||||
INP: "Internal Program", when a breakpoint interrupt occurs, the MIU forces a jump to page #1 (off-chip page) (when set to 0) or to page #0 (on-chip page) (when set to 1) and reads the Monitor program from this page
|
||||
ZSP: 1 to enable sngle access mode, where the Z data memory space's Core/DMA addresses use only the Z-even address bus
|
||||
PGM: paging mode
|
||||
|
||||
DLP: "Download Memory Select", this bit is used to select between two, parallel Z-space data memories, including Regular and Download memories. 0 - ZRDEN, 1- ZBRDN
|
||||
SDL: "Sticky Download Select", this bit is set one cycle after DLP is set. It is not cleared when clearing the DLP bit. It remains set until specifically writing low to it during a Trap routine
|
||||
PDLPAGE: "Alternative Program Page", this field is an alternative paging register for program write (movd) and program read (movp) transactions
|
||||
PDP: 1 to enable alternative program page
|
||||
|
||||
MMIOBASE: MMIO base address, with resolution of 512-word.
|
||||
|
||||
OBS: 1 to enable observability mode
|
||||
OBSMOD: observability mode, defines which Core/DMA address/data buses are reflected on the off-chip XZ buses
|
||||
- 0: Core XZ address/data buses
|
||||
- 1: Core Y address/data buses
|
||||
- 2: Core P address/data buses
|
||||
- 3: DMA DST address/data buses
|
||||
- 4: DMA SRC address/data buses
|
||||
|
||||
PRD: Signal Polarity Bit - This bit defines the polarity of RD_WR
|
||||
PZS: Z Select Polarity Bit - This bit defines the polarity of ZS
|
||||
PXS: X Select Polarity Bit - This bit defines the polarity of XS
|
||||
PXT: X Strobe Polarity Bit - This bit defines the polarity of XSTRB
|
||||
PZT: Z Strobe Polarity Bit - This bit defines the polarity of ZSTRB
|
||||
PZW: Z Write Polarity Bit - This bit defines the polarity of DWZON/DWZEN
|
||||
PZR: Z Read Polarity Bit - This bit defines the polarity of DRZON/DRZEN
|
||||
```
|
|
@ -0,0 +1,361 @@
|
|||
#include <functional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "ahbm.h"
|
||||
#include "apbp.h"
|
||||
#include "btdmp.h"
|
||||
#include "dma.h"
|
||||
#include "memory_interface.h"
|
||||
#include "mmio.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
auto NoSet(const std::string& debug_string) {
|
||||
return [debug_string](u16) { printf("Warning: NoSet on %s\n", debug_string.data()); };
|
||||
}
|
||||
auto NoGet(const std::string& debug_string) {
|
||||
return [debug_string]() -> u16 {
|
||||
printf("Warning: NoGet on %s\n", debug_string.data());
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
struct BitFieldSlot {
|
||||
unsigned pos;
|
||||
unsigned length;
|
||||
std::function<void(u16)> set;
|
||||
std::function<u16(void)> get;
|
||||
|
||||
template <typename T>
|
||||
static BitFieldSlot RefSlot(unsigned pos, unsigned length, T& var) {
|
||||
static_assert(
|
||||
std::is_same_v<u16,
|
||||
typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
|
||||
std::enable_if<true, T>>::type>);
|
||||
BitFieldSlot slot{pos, length, {}, {}};
|
||||
slot.set = [&var](u16 value) { var = static_cast<T>(value); };
|
||||
slot.get = [&var]() -> u16 { return static_cast<u16>(var); };
|
||||
return slot;
|
||||
}
|
||||
};
|
||||
|
||||
struct Cell {
|
||||
std::function<void(u16)> set;
|
||||
std::function<u16(void)> get;
|
||||
u16 index = 0;
|
||||
|
||||
Cell(std::function<void(u16)> set, std::function<u16(void)> get)
|
||||
: set(std::move(set)), get(std::move(get)) {}
|
||||
Cell() {
|
||||
std::shared_ptr<u16> storage = std::make_shared<u16>(0);
|
||||
set = [storage, this](u16 value) {
|
||||
*storage = value;
|
||||
std::printf("MMIO: cell %04X set = %04X\n", index, value);
|
||||
};
|
||||
get = [storage, this]() -> u16 {
|
||||
std::printf("MMIO: cell %04X get\n", index);
|
||||
return *storage;
|
||||
};
|
||||
}
|
||||
static Cell ConstCell(u16 constant) {
|
||||
Cell cell({}, {});
|
||||
cell.set = NoSet("");
|
||||
cell.get = [constant]() -> u16 { return constant; };
|
||||
return cell;
|
||||
}
|
||||
static Cell RefCell(u16& var) {
|
||||
Cell cell({}, {});
|
||||
cell.set = [&var](u16 value) { var = value; };
|
||||
cell.get = [&var]() -> u16 { return var; };
|
||||
return cell;
|
||||
}
|
||||
|
||||
static Cell MirrorCell(Cell* mirror) {
|
||||
Cell cell({}, {});
|
||||
cell.set = [mirror](u16 value) { mirror->set(value); };
|
||||
cell.get = [mirror]() -> u16 { return mirror->get(); };
|
||||
return cell;
|
||||
}
|
||||
|
||||
static Cell BitFieldCell(const std::vector<BitFieldSlot>& slots) {
|
||||
Cell cell({}, {});
|
||||
std::shared_ptr<u16> storage = std::make_shared<u16>(0);
|
||||
cell.set = [storage, slots](u16 value) {
|
||||
for (const auto& slot : slots) {
|
||||
if (slot.set) {
|
||||
slot.set((value >> slot.pos) & ((1 << slot.length) - 1));
|
||||
}
|
||||
}
|
||||
*storage = value;
|
||||
};
|
||||
cell.get = [storage, slots]() -> u16 {
|
||||
u16 value = *storage;
|
||||
for (const auto& slot : slots) {
|
||||
if (slot.get) {
|
||||
value &= ~(((1 << slot.length) - 1) << slot.pos);
|
||||
value |= slot.get() << slot.pos;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return cell;
|
||||
}
|
||||
};
|
||||
|
||||
class MMIORegion::Impl {
|
||||
public:
|
||||
std::array<Cell, 0x800> cells{};
|
||||
Impl() {
|
||||
for (std::size_t i = 0; i < cells.size(); ++i) {
|
||||
cells[i].index = (u16)i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MMIORegion::MMIORegion(MemoryInterfaceUnit& miu, ICU& icu, Apbp& apbp_from_cpu, Apbp& apbp_from_dsp,
|
||||
std::array<Timer, 2>& timer, Dma& dma, Ahbm& ahbm,
|
||||
std::array<Btdmp, 2>& btdmp)
|
||||
: impl(new Impl) {
|
||||
using namespace std::placeholders;
|
||||
|
||||
impl->cells[0x01A] = Cell::ConstCell(0xC902); // chip detect
|
||||
|
||||
// Timer
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
impl->cells[0x20 + i * 0x10] = Cell::BitFieldCell({
|
||||
// TIMERx_CFG
|
||||
BitFieldSlot::RefSlot(0, 2, timer[i].scale), // TS
|
||||
BitFieldSlot::RefSlot(2, 3, timer[i].count_mode), // CM
|
||||
BitFieldSlot{6, 1, {}, {}}, // TP
|
||||
BitFieldSlot{7, 1, {}, {}}, // CT
|
||||
BitFieldSlot::RefSlot(8, 1, timer[i].pause), // PC
|
||||
BitFieldSlot::RefSlot(9, 1, timer[i].update_mmio), // MU
|
||||
BitFieldSlot{10, 1,
|
||||
[&timer, i](u16 v) {
|
||||
if (v)
|
||||
timer[i].Restart();
|
||||
},
|
||||
[]() -> u16 { return 0; }}, // RES
|
||||
BitFieldSlot{11, 1, {}, {}}, // BP
|
||||
BitFieldSlot{12, 1, {}, {}}, // CS
|
||||
BitFieldSlot{13, 1, {}, {}}, // GP
|
||||
BitFieldSlot{14, 2, {}, {}}, // TM
|
||||
});
|
||||
|
||||
impl->cells[0x22 + i * 0x10].set = [&timer, i](u16 v) {
|
||||
if (v)
|
||||
timer[i].TickEvent();
|
||||
}; // TIMERx_EW
|
||||
impl->cells[0x22 + i * 0x10].get = []() -> u16 { return 0; };
|
||||
impl->cells[0x24 + i * 0x10] = Cell::RefCell(timer[i].start_low); // TIMERx_SCL
|
||||
impl->cells[0x26 + i * 0x10] = Cell::RefCell(timer[i].start_high); // TIMERx_SCH
|
||||
impl->cells[0x28 + i * 0x10] = Cell::RefCell(timer[i].counter_low); // TIMERx_CCL
|
||||
impl->cells[0x2A + i * 0x10] = Cell::RefCell(timer[i].counter_high); // TIMERx_CCH
|
||||
impl->cells[0x2C + i * 0x10] = Cell(); // TIMERx_SPWMCL
|
||||
impl->cells[0x2E + i * 0x10] = Cell(); // TIMERx_SPWMCH
|
||||
}
|
||||
|
||||
// APBP
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
impl->cells[0x0C0 + i * 4].set = std::bind(&Apbp::SendData, &apbp_from_dsp, i, _1);
|
||||
impl->cells[0x0C0 + i * 4].get = std::bind(&Apbp::PeekData, &apbp_from_dsp, i);
|
||||
impl->cells[0x0C2 + i * 4].set = [](u16) {};
|
||||
impl->cells[0x0C2 + i * 4].get = std::bind(&Apbp::RecvData, &apbp_from_cpu, i);
|
||||
}
|
||||
impl->cells[0x0CC].set = std::bind(&Apbp::SetSemaphore, &apbp_from_dsp, _1);
|
||||
impl->cells[0x0CC].get = std::bind(&Apbp::GetSemaphore, &apbp_from_dsp);
|
||||
impl->cells[0x0CE].set = std::bind(&Apbp::MaskSemaphore, &apbp_from_cpu, _1);
|
||||
impl->cells[0x0CE].get = std::bind(&Apbp::GetSemaphoreMask, &apbp_from_cpu);
|
||||
impl->cells[0x0D0].set = std::bind(&Apbp::ClearSemaphore, &apbp_from_cpu, _1);
|
||||
impl->cells[0x0D0].get = []() -> u16 { return 0; };
|
||||
impl->cells[0x0D2].set = [](u16) {};
|
||||
impl->cells[0x0D2].get = std::bind(&Apbp::GetSemaphore, &apbp_from_cpu);
|
||||
impl->cells[0x0D4] = Cell::BitFieldCell({
|
||||
BitFieldSlot{2, 1, {}, {}}, // ARM side endianness flag
|
||||
BitFieldSlot{8, 1,
|
||||
[&apbp_from_cpu](u16 v) { return apbp_from_cpu.SetDisableInterrupt(0, v); },
|
||||
[&apbp_from_cpu]() -> u16 { return apbp_from_cpu.GetDisableInterrupt(0); }},
|
||||
BitFieldSlot{12, 1,
|
||||
[&apbp_from_cpu](u16 v) { return apbp_from_cpu.SetDisableInterrupt(1, v); },
|
||||
[&apbp_from_cpu]() -> u16 { return apbp_from_cpu.GetDisableInterrupt(1); }},
|
||||
BitFieldSlot{13, 1,
|
||||
[&apbp_from_cpu](u16 v) { return apbp_from_cpu.SetDisableInterrupt(2, v); },
|
||||
[&apbp_from_cpu]() -> u16 { return apbp_from_cpu.GetDisableInterrupt(2); }},
|
||||
});
|
||||
impl->cells[0x0D6] = Cell::BitFieldCell({
|
||||
BitFieldSlot{5, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(0); }},
|
||||
BitFieldSlot{6, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(1); }},
|
||||
BitFieldSlot{7, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(2); }},
|
||||
BitFieldSlot{8, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(0); }},
|
||||
BitFieldSlot{
|
||||
9, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsSemaphoreSignaled(); }},
|
||||
BitFieldSlot{12, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(1); }},
|
||||
BitFieldSlot{13, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(2); }},
|
||||
});
|
||||
|
||||
// This register is a mirror of CPU side register DSP_PSTS
|
||||
impl->cells[0x0D8] = Cell::BitFieldCell({
|
||||
BitFieldSlot{
|
||||
9, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsSemaphoreSignaled(); }},
|
||||
BitFieldSlot{10, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(0); }},
|
||||
BitFieldSlot{11, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(1); }},
|
||||
BitFieldSlot{12, 1, {}, [&apbp_from_dsp]() -> u16 { return apbp_from_dsp.IsDataReady(2); }},
|
||||
BitFieldSlot{13, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(0); }},
|
||||
BitFieldSlot{14, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(1); }},
|
||||
BitFieldSlot{15, 1, {}, [&apbp_from_cpu]() -> u16 { return apbp_from_cpu.IsDataReady(2); }},
|
||||
});
|
||||
|
||||
// AHBM
|
||||
impl->cells[0x0E0].set = NoSet("AHBM::BusyFlag");
|
||||
impl->cells[0x0E0].get = std::bind(&Ahbm::GetBusyFlag, &ahbm);
|
||||
for (u16 i = 0; i < 3; ++i) {
|
||||
impl->cells[0x0E2 + i * 6] = Cell::BitFieldCell({
|
||||
// BitFieldSlot{0, 1, ?, ?},
|
||||
BitFieldSlot{1, 2, std::bind(&Ahbm::SetBurstSize, &ahbm, i, _1),
|
||||
std::bind(&Ahbm::GetBurstSize, &ahbm, i)},
|
||||
BitFieldSlot{4, 2, std::bind(&Ahbm::SetUnitSize, &ahbm, i, _1),
|
||||
std::bind(&Ahbm::GetUnitSize, &ahbm, i)},
|
||||
});
|
||||
impl->cells[0x0E4 + i * 6] = Cell::BitFieldCell({
|
||||
BitFieldSlot{8, 1, std::bind(&Ahbm::SetDirection, &ahbm, i, _1),
|
||||
std::bind(&Ahbm::GetDirection, &ahbm, i)},
|
||||
// BitFieldSlot{9, 1, ?, ?},
|
||||
});
|
||||
impl->cells[0x0E6 + i * 6].set = std::bind(&Ahbm::SetDmaChannel, &ahbm, i, _1);
|
||||
impl->cells[0x0E6 + i * 6].get = std::bind(&Ahbm::GetDmaChannel, &ahbm, i);
|
||||
}
|
||||
|
||||
// MIU
|
||||
// impl->cells[0x100]; // MIU_WSCFG0
|
||||
// impl->cells[0x102]; // MIU_WSCFG1
|
||||
// impl->cells[0x104]; // MIU_Z0WSCFG
|
||||
// impl->cells[0x106]; // MIU_Z1WSCFG
|
||||
// impl->cells[0x108]; // MIU_Z2WSCFG
|
||||
// impl->cells[0x10C]; // MIU_Z3WSCFG
|
||||
impl->cells[0x10E] = Cell::RefCell(miu.x_page); // MIU_XPAGE
|
||||
impl->cells[0x110] = Cell::RefCell(miu.y_page); // MIU_YPAGE
|
||||
impl->cells[0x112] = Cell::RefCell(miu.z_page); // MIU_ZPAGE
|
||||
impl->cells[0x114] = Cell::BitFieldCell({
|
||||
// MIU_PAGE0CFG
|
||||
BitFieldSlot::RefSlot(0, 6, miu.x_size[0]),
|
||||
BitFieldSlot::RefSlot(8, 6, miu.y_size[0]),
|
||||
});
|
||||
impl->cells[0x116] = Cell::BitFieldCell({
|
||||
// MIU_PAGE1CFG
|
||||
BitFieldSlot::RefSlot(0, 6, miu.x_size[1]),
|
||||
BitFieldSlot::RefSlot(8, 6, miu.y_size[1]),
|
||||
});
|
||||
// impl->cells[0x118]; // MIU_OFFPAGECFG
|
||||
impl->cells[0x11A] = Cell::BitFieldCell({
|
||||
BitFieldSlot{0, 1, {}, {}}, // PP
|
||||
BitFieldSlot{1, 1, {}, {}}, // TESTP
|
||||
BitFieldSlot{2, 1, {}, {}}, // INTP
|
||||
BitFieldSlot{4, 1, {}, {}}, // ZSINGLEP
|
||||
BitFieldSlot::RefSlot(6, 1, miu.page_mode), // PAGEMODE
|
||||
});
|
||||
// impl->cells[0x11C]; // MIU_DLCFG
|
||||
impl->cells[0x11E] = Cell::RefCell(miu.mmio_base); // MIU_MMIOBASE
|
||||
// impl->cells[0x120]; // MIU_OBSCFG
|
||||
// impl->cells[0x122]; // MIU_POLARITY
|
||||
|
||||
// DMA
|
||||
impl->cells[0x184].set = std::bind(&Dma::EnableChannel, &dma, _1);
|
||||
impl->cells[0x184].get = std::bind(&Dma::GetChannelEnabled, &dma);
|
||||
|
||||
impl->cells[0x18C].get = []() -> u16 { return 0xFFFF; }; // SEOX ?
|
||||
|
||||
impl->cells[0x1BE].set = std::bind(&Dma::ActivateChannel, &dma, _1);
|
||||
impl->cells[0x1BE].get = std::bind(&Dma::GetActiveChannel, &dma);
|
||||
impl->cells[0x1C0].set = std::bind(&Dma::SetAddrSrcLow, &dma, _1);
|
||||
impl->cells[0x1C0].get = std::bind(&Dma::GetAddrSrcLow, &dma);
|
||||
impl->cells[0x1C2].set = std::bind(&Dma::SetAddrSrcHigh, &dma, _1);
|
||||
impl->cells[0x1C2].get = std::bind(&Dma::GetAddrSrcHigh, &dma);
|
||||
impl->cells[0x1C4].set = std::bind(&Dma::SetAddrDstLow, &dma, _1);
|
||||
impl->cells[0x1C4].get = std::bind(&Dma::GetAddrDstLow, &dma);
|
||||
impl->cells[0x1C6].set = std::bind(&Dma::SetAddrDstHigh, &dma, _1);
|
||||
impl->cells[0x1C6].get = std::bind(&Dma::GetAddrDstHigh, &dma);
|
||||
impl->cells[0x1C8].set = std::bind(&Dma::SetSize0, &dma, _1);
|
||||
impl->cells[0x1C8].get = std::bind(&Dma::GetSize0, &dma);
|
||||
impl->cells[0x1CA].set = std::bind(&Dma::SetSize1, &dma, _1);
|
||||
impl->cells[0x1CA].get = std::bind(&Dma::GetSize1, &dma);
|
||||
impl->cells[0x1CC].set = std::bind(&Dma::SetSize2, &dma, _1);
|
||||
impl->cells[0x1CC].get = std::bind(&Dma::GetSize2, &dma);
|
||||
impl->cells[0x1CE].set = std::bind(&Dma::SetSrcStep0, &dma, _1);
|
||||
impl->cells[0x1CE].get = std::bind(&Dma::GetSrcStep0, &dma);
|
||||
impl->cells[0x1D0].set = std::bind(&Dma::SetDstStep0, &dma, _1);
|
||||
impl->cells[0x1D0].get = std::bind(&Dma::GetDstStep0, &dma);
|
||||
impl->cells[0x1D2].set = std::bind(&Dma::SetSrcStep1, &dma, _1);
|
||||
impl->cells[0x1D2].get = std::bind(&Dma::GetSrcStep1, &dma);
|
||||
impl->cells[0x1D4].set = std::bind(&Dma::SetDstStep1, &dma, _1);
|
||||
impl->cells[0x1D4].get = std::bind(&Dma::GetDstStep1, &dma);
|
||||
impl->cells[0x1D6].set = std::bind(&Dma::SetSrcStep2, &dma, _1);
|
||||
impl->cells[0x1D6].get = std::bind(&Dma::GetSrcStep2, &dma);
|
||||
impl->cells[0x1D8].set = std::bind(&Dma::SetDstStep2, &dma, _1);
|
||||
impl->cells[0x1D8].get = std::bind(&Dma::GetDstStep2, &dma);
|
||||
impl->cells[0x1DA] = Cell::BitFieldCell({
|
||||
BitFieldSlot{0, 4, std::bind(&Dma::SetSrcSpace, &dma, _1),
|
||||
std::bind(&Dma::GetSrcSpace, &dma)},
|
||||
BitFieldSlot{4, 4, std::bind(&Dma::SetDstSpace, &dma, _1),
|
||||
std::bind(&Dma::GetDstSpace, &dma)},
|
||||
// BitFieldSlot{9, 1, ?, ?},
|
||||
BitFieldSlot{10, 1, std::bind(&Dma::SetDwordMode, &dma, _1),
|
||||
std::bind(&Dma::GetDwordMode, &dma)},
|
||||
});
|
||||
impl->cells[0x1DC].set = std::bind(&Dma::SetY, &dma, _1);
|
||||
impl->cells[0x1DC].get = std::bind(&Dma::GetY, &dma);
|
||||
impl->cells[0x1DE].set = std::bind(&Dma::SetZ, &dma, _1);
|
||||
impl->cells[0x1DE].get = std::bind(&Dma::GetZ, &dma);
|
||||
|
||||
// ICU
|
||||
impl->cells[0x200].set = NoSet("ICU::GetRequest");
|
||||
impl->cells[0x200].get = std::bind(&ICU::GetRequest, &icu);
|
||||
impl->cells[0x202].set = std::bind(&ICU::Acknowledge, &icu, _1);
|
||||
impl->cells[0x202].get = std::bind(&ICU::GetAcknowledge, &icu);
|
||||
impl->cells[0x204].set = std::bind(&ICU::Trigger, &icu, _1);
|
||||
impl->cells[0x204].get = std::bind(&ICU::GetTrigger, &icu);
|
||||
impl->cells[0x206].set = std::bind(&ICU::SetEnable, &icu, 0, _1);
|
||||
impl->cells[0x206].get = std::bind(&ICU::GetEnable, &icu, 0);
|
||||
impl->cells[0x208].set = std::bind(&ICU::SetEnable, &icu, 1, _1);
|
||||
impl->cells[0x208].get = std::bind(&ICU::GetEnable, &icu, 1);
|
||||
impl->cells[0x20A].set = std::bind(&ICU::SetEnable, &icu, 2, _1);
|
||||
impl->cells[0x20A].get = std::bind(&ICU::GetEnable, &icu, 2);
|
||||
impl->cells[0x20C].set = std::bind(&ICU::SetEnableVectored, &icu, _1);
|
||||
impl->cells[0x20C].get = std::bind(&ICU::GetEnableVectored, &icu);
|
||||
// impl->cells[0x20E]; // polarity for each interrupt?
|
||||
// impl->cells[0x210]; // source type for each interrupt?
|
||||
for (unsigned i = 0; i < 16; ++i) {
|
||||
impl->cells[0x212 + i * 4] = Cell::BitFieldCell({
|
||||
BitFieldSlot::RefSlot(0, 2, icu.vector_high[i]),
|
||||
BitFieldSlot::RefSlot(15, 1, icu.vector_context_switch[i]),
|
||||
});
|
||||
impl->cells[0x214 + i * 4] = Cell::RefCell(icu.vector_low[i]);
|
||||
}
|
||||
|
||||
// BTDMP
|
||||
for (u16 i = 0; i < 2; ++i) {
|
||||
impl->cells[0x2A2 + i * 0x80].set = std::bind(&Btdmp::SetTransmitClockConfig, &btdmp[i], _1);
|
||||
impl->cells[0x2A2 + i * 0x80].get = std::bind(&Btdmp::GetTransmitClockConfig, &btdmp[i]);
|
||||
impl->cells[0x2BE + i * 0x80].set = std::bind(&Btdmp::SetTransmitEnable, &btdmp[i], _1);
|
||||
impl->cells[0x2BE + i * 0x80].get = std::bind(&Btdmp::GetTransmitEnable, &btdmp[i]);
|
||||
impl->cells[0x2C2 + i * 0x80] = Cell::BitFieldCell({
|
||||
BitFieldSlot{3, 1, {}, std::bind(&Btdmp::GetTransmitFull, &btdmp[i])},
|
||||
BitFieldSlot{4, 1, {}, std::bind(&Btdmp::GetTransmitEmpty, &btdmp[i])},
|
||||
});
|
||||
impl->cells[0x2C6 + i * 0x80].set = std::bind(&Btdmp::Send, &btdmp[i], _1);
|
||||
impl->cells[0x2CA + i * 0x80].set = std::bind(&Btdmp::SetTransmitFlush, &btdmp[i], _1);
|
||||
impl->cells[0x2CA + i * 0x80].get = std::bind(&Btdmp::GetTransmitFlush, &btdmp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
MMIORegion::~MMIORegion() = default;
|
||||
|
||||
u16 MMIORegion::Read(u16 addr) {
|
||||
u16 value = impl->cells[addr].get();
|
||||
return value;
|
||||
}
|
||||
void MMIORegion::Write(u16 addr, u16 value) {
|
||||
impl->cells[addr].set(value);
|
||||
}
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "common_types.h"
|
||||
#include "icu.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class MemoryInterfaceUnit;
|
||||
class Apbp;
|
||||
class Timer;
|
||||
class Dma;
|
||||
class Ahbm;
|
||||
class Btdmp;
|
||||
|
||||
class MMIORegion {
|
||||
public:
|
||||
MMIORegion(MemoryInterfaceUnit& miu, ICU& icu, Apbp& apbp_from_cpu, Apbp& apbp_from_dsp,
|
||||
std::array<Timer, 2>& timer, Dma& dma, Ahbm& ahbm, std::array<Btdmp, 2>& btdmp);
|
||||
~MMIORegion();
|
||||
u16 Read(u16 addr); // not const because it can be a FIFO register
|
||||
void Write(u16 addr, u16 value);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,18 @@
|
|||
# MMIO
|
||||
|
||||
The core processor communicates with peripherals (and indirectly external hardware such as CPU) via MMIO. The MMIO region occupies 0x0800-word region in the 16-bit address space, initially starting at 0x8000. The location of MMIO region can be configured in MIU (...yes, via MMIO). MMIO registers usually use even addresses only, which seems to be a hardware optimization as there is a register in MIU that enables forcing this rule. Each peripheral corresponds to a sub-region in MMIO, listed below. The detail of registers are in their corresponding peripheral documents. The register addresses in these documents are offsets to the start of the MMIO region.
|
||||
|
||||
- `+0x0000` ?
|
||||
- `+0x0004` JAM
|
||||
- `+0x0010` GLUE
|
||||
- `+0x0020` [Timer](timer.md)
|
||||
- `+0x0050` [SIO](sio.md), Serial IO
|
||||
- `+0x0060` [OCEM](ocem.md), On-chip Emulation Module
|
||||
- `+0x0080` [PMU](pmu.md), Power Management Unit
|
||||
- `+0x00C0` [APBP](apbp.md), Advanced Peripheral Bus Port?
|
||||
- `+0x00E0` [AHBM](ahbm.md), Advanced High Performance Bus Master
|
||||
- `+0x0100` [MIU](miu.md), Memory Interface Unit
|
||||
- `+0x0140` [CRU](cru.md), Code Replacement Unit
|
||||
- `+0x0180` [DMA](dma.md), Direct Memory Access
|
||||
- `+0x0200` [ICU](icu.md), Interrupt Control Unit
|
||||
- `+0x0280` [BTDMP](btdmp.md), Buffered Time Division Multiplexing Port
|
|
@ -0,0 +1,9 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(mod_test_generator
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(mod_test_generator)
|
||||
target_link_libraries(mod_test_generator PRIVATE teakra)
|
||||
target_include_directories(mod_test_generator PRIVATE .)
|
||||
target_compile_options(mod_test_generator PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,57 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include "../test.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::fprintf(stderr, "A file path argument must be provided. Exiting...\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::FILE, decltype(&std::fclose)> f{std::fopen(argv[1], "wb"), std::fclose};
|
||||
if (!f) {
|
||||
std::fprintf(stderr, "Unable to open file %s. Exiting...\n", argv[1]);
|
||||
return -2;
|
||||
}
|
||||
|
||||
TestCase test_case{};
|
||||
test_case.opcode = 0x4DA0; // mpy y0, MemR04@3 || mpyus y1, MemR04@3offsZI@2 || sub3 p0, p1,
|
||||
// Ax@4 || R04@3stepII2@2
|
||||
test_case.expand = 0;
|
||||
test_case.before.mod2 = 1; // enable mod for r0; disable brv
|
||||
for (u16 i = 0; i < TestSpaceSize; ++i) {
|
||||
test_case.before.test_space_x[i] = TestSpaceX + i;
|
||||
}
|
||||
for (u16 i = 0; i < 0x20; ++i) {
|
||||
test_case.before.r[0] = TestSpaceX + i + 0xF0;
|
||||
for (u16 legacy = 0; legacy < 2; ++legacy) {
|
||||
test_case.before.mod1 = legacy << 13;
|
||||
for (u16 offset_mode = 0; offset_mode < 4; ++offset_mode) {
|
||||
for (u16 step_mode = 0; step_mode < 8; ++step_mode) {
|
||||
/*!!!*/ if (step_mode == 3)
|
||||
continue;
|
||||
test_case.before.ar[0] = (step_mode << 5) | (offset_mode << 8);
|
||||
u16 step_min = 0, step_max = 0x20;
|
||||
if (step_mode != 3) {
|
||||
step_min = step_max = std::rand() % 0x20;
|
||||
++step_max;
|
||||
}
|
||||
for (u16 step = step_min; step < step_max; ++step) {
|
||||
u16 step_true = SignExtend<5>(step) & 0x7F;
|
||||
for (u16 mod = 0; mod < 0x10; ++mod) {
|
||||
test_case.before.cfgi = step_true | (mod << 7);
|
||||
if (std::fwrite(&test_case, sizeof(test_case), 1, f.get()) == 0) {
|
||||
std::fprintf(stderr,
|
||||
"Unable to completely write test case. Exiting...\n");
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
# OCEM
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
The following MMIO definition is derived from Lauterbach's Teak debugger with modification according to the Teak architecutre. The different layout around program address breakpoint is tested. Note that this is the only MMIO region that uses odd-address registers
|
||||
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0060 | PFT |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0061 | |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0062 | PAB1_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0063 | | PAP1 | |PAB1_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0064 | PAB2_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0065 | | PAP2 | |PAB2_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0066 | PAB3_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0067 | | PAP3 | |PAB3_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0068 | | PACNT1 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0069 | | PACNT2 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006A | | PACNT3 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006B | DAM |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006C | DAB |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006D |SSE|ILE|BRE|TBE|INE|BRE|P3E|P2E|P1E|EXR|EXW|CDE|DAR|DAW|DVR|DVW|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006E |DBG|BOT|ERR|MVD| |TRE|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x006F |SFT|ILL|TBF|INT|BR | |PA3|PA2|PA1|ABT|ERG|CD |DA |DV |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
PFT: Program Flow Trace
|
||||
PAB1_L, PAB1_H: Program Address Break Point #1
|
||||
PAB2_L, PAB2_H: Program Address Break Point #2
|
||||
PAB3_L, PAB3_H: Program Address Break Point #3
|
||||
PAP1, PAP2, PAP3: Program page for Program Address Break Point #1/#2/#3 (?)
|
||||
PACNT1, PACNT2, PACNT3: Program Address Break Point Counter #1/#2/#3
|
||||
DAM: Data Address Mask
|
||||
DAB: Data Address Break Point
|
||||
|
||||
DVW: 1 to enable data value break point on data write transaction
|
||||
DVR: 1 to enable data value break point on data read transaction
|
||||
DAW: 1 to enable data address break point as a result on data write transaction
|
||||
DAR: 1 to enable data address break point as a result on data read transaction
|
||||
CDE: 1 to enable break point as a result of simultaneous data address and data value match
|
||||
EXW: 1 to enable break point as a result of external register write transaction
|
||||
EXR: 1 to enable break point as a result of external register read transaction
|
||||
P1E: 1 to enable program break point #1
|
||||
P2E: 1 to enable program break point #2
|
||||
P3E: 1 to enable program break point #3
|
||||
BRE: 1 to enable break point every time the program jumps instead of executing the next address
|
||||
INE: 1 to enable break point upon detection of interrupt service routine
|
||||
TBE: 1 to enable break point as a result of program flow trace buffer full
|
||||
BRE: 1 to enable break point when returning to the beginning of block repeat loop
|
||||
ILE: 1 to enable break point on illegal condition
|
||||
SSE: 1 to enable single step
|
||||
|
||||
DBG: 1 indicates the debug mode
|
||||
BOT: 1 indicates the boot mode
|
||||
ERR: 1 on detection of user reset that is being activated during execution of break point service routine
|
||||
MVD: 1 on detection of MOVD instruction
|
||||
TRE: 1 indicates that the current TRACE entry has to be combined with the next TRACE entry
|
||||
|
||||
SFT: 1 on detection of software trap
|
||||
ILL: 1 on detection of illegal break point
|
||||
TBF: 1 indicates program flow Trace Buffer Full
|
||||
INT: 1 on detection of interrupt break point
|
||||
BR: 1 on detection of branch break point
|
||||
PA3: 1 on detection of program address break point #3
|
||||
PA2: 1 on detection of program address break point #2
|
||||
PA1: 1 on detection of program address break point #1
|
||||
ABT: 1 on detection of break point due to an external event
|
||||
ERG: 1 on detection of break point due to user defined register transaction
|
||||
CD: 1 on detection of break point due to matched data value and matched data address
|
||||
DA: 1 on detection of break point due to matched data address
|
||||
DV: 1 on detection of break point due to matched data value
|
||||
```
|
|
@ -0,0 +1,698 @@
|
|||
#pragma once
|
||||
#include "common_types.h"
|
||||
|
||||
template <typename T, T... values>
|
||||
inline constexpr bool NoOverlap = (values + ...) == (values | ...);
|
||||
|
||||
template <unsigned bits>
|
||||
struct Operand {
|
||||
static_assert(bits > 0 && bits <= 16);
|
||||
static constexpr unsigned Bits = bits;
|
||||
|
||||
protected:
|
||||
u16 storage{};
|
||||
|
||||
template <typename OperandT, unsigned pos>
|
||||
friend struct At;
|
||||
|
||||
template <typename OperandT, u16 value>
|
||||
friend struct Const;
|
||||
};
|
||||
|
||||
template <typename OperandT, unsigned pos>
|
||||
struct At {
|
||||
static constexpr unsigned Bits = OperandT::Bits;
|
||||
static_assert((Bits < 16 && pos < 16 && Bits + pos <= 16) || (Bits == 16 && pos == 16));
|
||||
static constexpr u16 Mask = (((1 << Bits) - 1) << pos) & 0xFFFF;
|
||||
static constexpr bool NeedExpansion = pos == 16;
|
||||
static constexpr bool PassAsParameter = true;
|
||||
using FilterResult = OperandT;
|
||||
static constexpr OperandT Extract(u16 opcode, u16 expansion) {
|
||||
OperandT operand{};
|
||||
if (NeedExpansion)
|
||||
operand.storage = expansion;
|
||||
else
|
||||
operand.storage = (u16)((opcode & Mask) >> pos);
|
||||
return operand;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OperandT, unsigned pos>
|
||||
struct AtNamed {
|
||||
using BaseType = At<OperandT, pos>;
|
||||
static constexpr unsigned Bits = BaseType::Bits;
|
||||
static constexpr u16 Mask = BaseType::Mask;
|
||||
static constexpr bool NeedExpansion = BaseType::NeedExpansion;
|
||||
static constexpr bool PassAsParameter = BaseType::PassAsParameter;
|
||||
using FilterResult = typename BaseType::FilterResult::NameType;
|
||||
static constexpr auto Extract(u16 opcode, u16 expansion) {
|
||||
return BaseType::Extract(opcode, expansion).GetName();
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned pos>
|
||||
struct Unused {
|
||||
static_assert(pos < 16);
|
||||
static constexpr u16 Mask = 1 << pos;
|
||||
static constexpr bool NeedExpansion = false;
|
||||
static constexpr bool PassAsParameter = false;
|
||||
};
|
||||
|
||||
template <typename OperandT, u16 value>
|
||||
struct Const {
|
||||
static constexpr u16 Mask = 0;
|
||||
static constexpr bool NeedExpansion = false;
|
||||
static constexpr bool PassAsParameter = true;
|
||||
using FilterResult = OperandT;
|
||||
static constexpr OperandT Extract(u16, u16) {
|
||||
OperandT operand{};
|
||||
operand.storage = value;
|
||||
return operand;
|
||||
}
|
||||
};
|
||||
|
||||
enum class SumBase {
|
||||
Zero,
|
||||
Acc,
|
||||
Sv,
|
||||
SvRnd,
|
||||
};
|
||||
|
||||
template <typename T, T value>
|
||||
struct Cn {
|
||||
static constexpr u16 Mask = 0;
|
||||
static constexpr bool NeedExpansion = false;
|
||||
static constexpr bool PassAsParameter = true;
|
||||
using FilterResult = T;
|
||||
static constexpr T Extract(u16, u16) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
using SX = Cn<bool, true>;
|
||||
using UX = Cn<bool, false>;
|
||||
using SY = Cn<bool, true>;
|
||||
using UY = Cn<bool, false>;
|
||||
using BZr = Cn<SumBase, SumBase::Zero>;
|
||||
using BAc = Cn<SumBase, SumBase::Acc>;
|
||||
using BSv = Cn<SumBase, SumBase::Sv>;
|
||||
using BSr = Cn<SumBase, SumBase::SvRnd>;
|
||||
using PA = Cn<bool, true>;
|
||||
using PP = Cn<bool, false>;
|
||||
using Sub = Cn<bool, true>;
|
||||
using Add = Cn<bool, false>;
|
||||
using EMod = Cn<bool, false>;
|
||||
using DMod = Cn<bool, true>;
|
||||
|
||||
template <typename OperandT, unsigned pos, u16 value>
|
||||
struct AtConst {
|
||||
using Base = At<OperandT, pos>;
|
||||
static_assert(Base::NeedExpansion == false, "");
|
||||
static constexpr u16 Mask = Base::Mask;
|
||||
static constexpr u16 Pad = value << pos;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
constexpr unsigned intlog2(unsigned n) {
|
||||
if (n % 2 != 0)
|
||||
throw "wtf";
|
||||
return (n == 2) ? 1 : 1 + intlog2(n / 2);
|
||||
}
|
||||
|
||||
template <typename EnumT, EnumT... names>
|
||||
struct EnumOperand : Operand<intlog2(sizeof...(names))> {
|
||||
using NameType = EnumT;
|
||||
static constexpr EnumT values[] = {names...};
|
||||
constexpr EnumT GetName() const {
|
||||
return values[this->storage];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename EnumT>
|
||||
struct EnumAllOperand : Operand<intlog2((unsigned)EnumT::EnumEnd)> {
|
||||
using NameType = EnumT;
|
||||
constexpr EnumT GetName() const {
|
||||
return (EnumT)this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
enum class RegName {
|
||||
a0, a0l, a0h, a0e,
|
||||
a1, a1l, a1h, a1e,
|
||||
b0, b0l, b0h, b0e,
|
||||
b1, b1l, b1h, b1e,
|
||||
|
||||
r0, r1, r2, r3, r4, r5, r6, r7,
|
||||
|
||||
y0, p,
|
||||
|
||||
pc, sp, sv, lc,
|
||||
|
||||
ar0, ar1,
|
||||
arp0, arp1, arp2, arp3,
|
||||
|
||||
ext0, ext1, ext2, ext3,
|
||||
|
||||
stt0, stt1, stt2,
|
||||
st0, st1, st2,
|
||||
cfgi, cfgj,
|
||||
mod0, mod1, mod2, mod3,
|
||||
|
||||
undefine,
|
||||
};
|
||||
|
||||
template <RegName ... reg_names>
|
||||
using RegOperand = EnumOperand <RegName, reg_names...>;
|
||||
|
||||
struct Register : RegOperand<
|
||||
RegName::r0,
|
||||
RegName::r1,
|
||||
RegName::r2,
|
||||
RegName::r3,
|
||||
RegName::r4,
|
||||
RegName::r5,
|
||||
RegName::r7,
|
||||
RegName::y0,
|
||||
RegName::st0,
|
||||
RegName::st1,
|
||||
RegName::st2,
|
||||
RegName::p, // take special care of this as src operand
|
||||
RegName::pc,
|
||||
RegName::sp,
|
||||
RegName::cfgi,
|
||||
RegName::cfgj,
|
||||
RegName::b0h,
|
||||
RegName::b1h,
|
||||
RegName::b0l,
|
||||
RegName::b1l,
|
||||
RegName::ext0,
|
||||
RegName::ext1,
|
||||
RegName::ext2,
|
||||
RegName::ext3,
|
||||
RegName::a0, // take special care of this as src operand
|
||||
RegName::a1, // take special care of this as src operand
|
||||
RegName::a0l,
|
||||
RegName::a1l,
|
||||
RegName::a0h,
|
||||
RegName::a1h,
|
||||
RegName::lc,
|
||||
RegName::sv
|
||||
> {
|
||||
// only used in mov(Register, Register)
|
||||
constexpr RegName GetNameForMovFromP() {
|
||||
return (this->storage & 1) ? RegName::a1 : RegName::a0;
|
||||
}
|
||||
};
|
||||
struct Ax : RegOperand<
|
||||
RegName::a0,
|
||||
RegName::a1
|
||||
> {};
|
||||
struct Axl : RegOperand<
|
||||
RegName::a0l,
|
||||
RegName::a1l
|
||||
> {};
|
||||
struct Axh : RegOperand<
|
||||
RegName::a0h,
|
||||
RegName::a1h
|
||||
> {};
|
||||
struct Bx : RegOperand<
|
||||
RegName::b0,
|
||||
RegName::b1
|
||||
> {};
|
||||
struct Bxl : RegOperand<
|
||||
RegName::b0l,
|
||||
RegName::b1l
|
||||
> {};
|
||||
struct Bxh : RegOperand<
|
||||
RegName::b0h,
|
||||
RegName::b1h
|
||||
> {};
|
||||
struct Px : Operand<1> {
|
||||
constexpr Px() = default;
|
||||
constexpr Px(u16 index) {
|
||||
this->storage = index;
|
||||
}
|
||||
constexpr u16 Index() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
struct Ab : RegOperand<
|
||||
RegName::b0,
|
||||
RegName::b1,
|
||||
RegName::a0,
|
||||
RegName::a1
|
||||
> {};
|
||||
struct Abl : RegOperand<
|
||||
RegName::b0l,
|
||||
RegName::b1l,
|
||||
RegName::a0l,
|
||||
RegName::a1l
|
||||
> {};
|
||||
struct Abh : RegOperand<
|
||||
RegName::b0h,
|
||||
RegName::b1h,
|
||||
RegName::a0h,
|
||||
RegName::a1h
|
||||
> {};
|
||||
struct Abe : RegOperand<
|
||||
RegName::b0e,
|
||||
RegName::b1e,
|
||||
RegName::a0e,
|
||||
RegName::a1e
|
||||
> {};
|
||||
struct Ablh : RegOperand<
|
||||
RegName::b0l,
|
||||
RegName::b0h,
|
||||
RegName::b1l,
|
||||
RegName::b1h,
|
||||
RegName::a0l,
|
||||
RegName::a0h,
|
||||
RegName::a1l,
|
||||
RegName::a1h
|
||||
> {};
|
||||
struct RnOld : RegOperand<
|
||||
RegName::r0,
|
||||
RegName::r1,
|
||||
RegName::r2,
|
||||
RegName::r3,
|
||||
RegName::r4,
|
||||
RegName::r5,
|
||||
RegName::r7,
|
||||
RegName::y0
|
||||
> {};
|
||||
struct Rn : RegOperand<
|
||||
RegName::r0,
|
||||
RegName::r1,
|
||||
RegName::r2,
|
||||
RegName::r3,
|
||||
RegName::r4,
|
||||
RegName::r5,
|
||||
RegName::r6,
|
||||
RegName::r7
|
||||
> {
|
||||
constexpr Rn() = default;
|
||||
constexpr Rn(u16 index) {
|
||||
this->storage = index;
|
||||
}
|
||||
constexpr u16 Index() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
struct R45 : RegOperand<
|
||||
RegName::r4,
|
||||
RegName::r5
|
||||
> {
|
||||
constexpr u16 Index() const {
|
||||
return this->storage + 4;
|
||||
}
|
||||
};
|
||||
|
||||
struct R0123 : RegOperand<
|
||||
RegName::r0,
|
||||
RegName::r1,
|
||||
RegName::r2,
|
||||
RegName::r3
|
||||
> {
|
||||
constexpr u16 Index() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
struct ArArpSttMod : RegOperand<
|
||||
RegName::ar0,
|
||||
RegName::ar1,
|
||||
RegName::arp0,
|
||||
RegName::arp1,
|
||||
RegName::arp2,
|
||||
RegName::arp3,
|
||||
RegName::undefine,
|
||||
RegName::undefine,
|
||||
RegName::stt0,
|
||||
RegName::stt1,
|
||||
RegName::stt2,
|
||||
RegName::undefine,
|
||||
RegName::mod0,
|
||||
RegName::mod1,
|
||||
RegName::mod2,
|
||||
RegName::mod3
|
||||
> {};
|
||||
struct ArArp : RegOperand<
|
||||
RegName::ar0,
|
||||
RegName::ar1,
|
||||
RegName::arp0,
|
||||
RegName::arp1,
|
||||
RegName::arp2,
|
||||
RegName::arp3,
|
||||
RegName::undefine,
|
||||
RegName::undefine
|
||||
> {};
|
||||
struct SttMod : RegOperand<
|
||||
RegName::stt0,
|
||||
RegName::stt1,
|
||||
RegName::stt2,
|
||||
RegName::undefine,
|
||||
RegName::mod0,
|
||||
RegName::mod1,
|
||||
RegName::mod2,
|
||||
RegName::mod3
|
||||
> {};
|
||||
|
||||
struct Ar : RegOperand<
|
||||
RegName::ar0,
|
||||
RegName::ar1
|
||||
> {
|
||||
constexpr u16 Index() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
struct Arp : RegOperand<
|
||||
RegName::arp0,
|
||||
RegName::arp1,
|
||||
RegName::arp2,
|
||||
RegName::arp3
|
||||
> {
|
||||
constexpr u16 Index() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
enum SwapTypeValue {
|
||||
a0b0,
|
||||
a0b1,
|
||||
a1b0,
|
||||
a1b1,
|
||||
a0b0a1b1,
|
||||
a0b1a1b0,
|
||||
a0b0a1,
|
||||
a0b1a1,
|
||||
a1b0a0,
|
||||
a1b1a0,
|
||||
b0a0b1,
|
||||
b0a1b1,
|
||||
b1a0b0,
|
||||
b1a1b0,
|
||||
reserved0,
|
||||
reserved1,
|
||||
|
||||
EnumEnd,
|
||||
};
|
||||
|
||||
using SwapType = EnumAllOperand<SwapTypeValue>;
|
||||
|
||||
enum class StepValue {
|
||||
Zero,
|
||||
Increase,
|
||||
Decrease,
|
||||
PlusStep,
|
||||
Increase2Mode1,
|
||||
Decrease2Mode1,
|
||||
Increase2Mode2,
|
||||
Decrease2Mode2,
|
||||
};
|
||||
|
||||
using StepZIDS = EnumOperand<StepValue,
|
||||
StepValue::Zero,
|
||||
StepValue::Increase,
|
||||
StepValue::Decrease,
|
||||
StepValue::PlusStep
|
||||
>;
|
||||
|
||||
template<unsigned bits, u16 offset = 0>
|
||||
struct ArIndex : Operand<bits>{
|
||||
constexpr u16 Index() const {
|
||||
return this->storage + offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct ArRn1 : ArIndex<1> {};
|
||||
struct ArRn2 : ArIndex<2> {};
|
||||
struct ArStep1 : ArIndex<1> {};
|
||||
struct ArStep1Alt : ArIndex<1, 2> {};
|
||||
struct ArStep2 : ArIndex<2> {};
|
||||
struct ArpRn1 : ArIndex<1> {};
|
||||
struct ArpRn2 : ArIndex<2> {};
|
||||
struct ArpStep1 : ArIndex<1> {};
|
||||
struct ArpStep2 : ArIndex<2> {};
|
||||
|
||||
struct Address18_2 : Operand<2> {
|
||||
constexpr u32 Address32() const {
|
||||
return (u32)(this->storage) << 16;
|
||||
}
|
||||
};
|
||||
struct Address18_16 : Operand<16> {
|
||||
constexpr u32 Address32() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr u32 Address32(Address18_16 low, Address18_2 high) {
|
||||
return low.Address32() | high.Address32();
|
||||
}
|
||||
|
||||
struct Address16 : Operand<16> {
|
||||
constexpr u32 Address32() {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
struct RelAddr7 : Operand<7> {
|
||||
constexpr u32 Relative32() {
|
||||
return SignExtend<7, u32>(this->storage);
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned bits>
|
||||
struct Imm : Operand<bits> {
|
||||
constexpr u16 Unsigned16() const {
|
||||
return this->storage;
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned bits>
|
||||
struct Imms : Operand<bits> {
|
||||
constexpr u16 Signed16() const {
|
||||
return SignExtend<bits, u16>(this->storage);
|
||||
}
|
||||
};
|
||||
|
||||
struct Imm2 : Imm<2> {};
|
||||
struct Imm4 : Imm<4> {};
|
||||
struct Imm5 : Imm<5> {};
|
||||
struct Imm5s : Imms<5> {};
|
||||
struct Imm6s : Imms<6> {};
|
||||
struct Imm7s : Imms<7> {};
|
||||
struct Imm8 : Imm<8> {};
|
||||
struct Imm8s : Imms<8> {};
|
||||
struct Imm9 : Imm<9> {};
|
||||
struct Imm16 : Imm<16> {};
|
||||
|
||||
struct MemImm8 : Imm8 {};
|
||||
struct MemImm16 : Imm16 {};
|
||||
struct MemR7Imm7s : Imm7s {};
|
||||
struct MemR7Imm16 : Imm16 {};
|
||||
|
||||
|
||||
enum class AlmOp {
|
||||
Or,
|
||||
And,
|
||||
Xor,
|
||||
Add,
|
||||
Tst0,
|
||||
Tst1,
|
||||
Cmp,
|
||||
Sub,
|
||||
Msu,
|
||||
Addh,
|
||||
Addl,
|
||||
Subh,
|
||||
Subl,
|
||||
Sqr,
|
||||
Sqra,
|
||||
Cmpu,
|
||||
|
||||
Reserved
|
||||
};
|
||||
|
||||
using Alm = EnumOperand<AlmOp,
|
||||
AlmOp::Or,
|
||||
AlmOp::And,
|
||||
AlmOp::Xor,
|
||||
AlmOp::Add,
|
||||
AlmOp::Tst0,
|
||||
AlmOp::Tst1,
|
||||
AlmOp::Cmp,
|
||||
AlmOp::Sub,
|
||||
AlmOp::Msu,
|
||||
AlmOp::Addh,
|
||||
AlmOp::Addl,
|
||||
AlmOp::Subh,
|
||||
AlmOp::Subl,
|
||||
AlmOp::Sqr,
|
||||
AlmOp::Sqra,
|
||||
AlmOp::Cmpu
|
||||
>;
|
||||
|
||||
using Alu = EnumOperand<AlmOp,
|
||||
AlmOp::Or,
|
||||
AlmOp::And,
|
||||
AlmOp::Xor,
|
||||
AlmOp::Add,
|
||||
AlmOp::Reserved,
|
||||
AlmOp::Reserved,
|
||||
AlmOp::Cmp,
|
||||
AlmOp::Sub
|
||||
>;
|
||||
|
||||
enum class AlbOp {
|
||||
Set,
|
||||
Rst,
|
||||
Chng,
|
||||
Addv,
|
||||
Tst0,
|
||||
Tst1,
|
||||
Cmpv,
|
||||
Subv,
|
||||
|
||||
EnumEnd
|
||||
};
|
||||
|
||||
using Alb = EnumAllOperand<AlbOp>;
|
||||
|
||||
enum class MulOp {
|
||||
Mpy,
|
||||
Mpysu,
|
||||
Mac,
|
||||
Macus,
|
||||
Maa,
|
||||
Macuu,
|
||||
Macsu,
|
||||
Maasu,
|
||||
};
|
||||
|
||||
using Mul3 = EnumOperand<MulOp,
|
||||
MulOp::Mpy,
|
||||
MulOp::Mpysu,
|
||||
MulOp::Mac,
|
||||
MulOp::Macus,
|
||||
MulOp::Maa,
|
||||
MulOp::Macuu,
|
||||
MulOp::Macsu,
|
||||
MulOp::Maasu
|
||||
>;
|
||||
|
||||
using Mul2 = EnumOperand<MulOp,
|
||||
MulOp::Mpy,
|
||||
MulOp::Mac,
|
||||
MulOp::Maa,
|
||||
MulOp::Macsu
|
||||
>;
|
||||
|
||||
enum class ModaOp {
|
||||
Shr,
|
||||
Shr4,
|
||||
Shl,
|
||||
Shl4,
|
||||
Ror,
|
||||
Rol,
|
||||
Clr,
|
||||
Reserved,
|
||||
Not,
|
||||
Neg,
|
||||
Rnd,
|
||||
Pacr,
|
||||
Clrr,
|
||||
Inc,
|
||||
Dec,
|
||||
Copy,
|
||||
|
||||
};
|
||||
|
||||
using Moda4 = EnumOperand<ModaOp,
|
||||
ModaOp::Shr,
|
||||
ModaOp::Shr4,
|
||||
ModaOp::Shl,
|
||||
ModaOp::Shl4,
|
||||
ModaOp::Ror,
|
||||
ModaOp::Rol,
|
||||
ModaOp::Clr,
|
||||
ModaOp::Reserved,
|
||||
ModaOp::Not,
|
||||
ModaOp::Neg,
|
||||
ModaOp::Rnd,
|
||||
ModaOp::Pacr,
|
||||
ModaOp::Clrr,
|
||||
ModaOp::Inc,
|
||||
ModaOp::Dec,
|
||||
ModaOp::Copy
|
||||
>;
|
||||
|
||||
using Moda3 = EnumOperand<ModaOp,
|
||||
ModaOp::Shr,
|
||||
ModaOp::Shr4,
|
||||
ModaOp::Shl,
|
||||
ModaOp::Shl4,
|
||||
ModaOp::Ror,
|
||||
ModaOp::Rol,
|
||||
ModaOp::Clr,
|
||||
ModaOp::Clrr
|
||||
>;
|
||||
|
||||
enum class CondValue {
|
||||
True,
|
||||
Eq,
|
||||
Neq,
|
||||
Gt,
|
||||
Ge,
|
||||
Lt,
|
||||
Le,
|
||||
Nn,
|
||||
C,
|
||||
V,
|
||||
E,
|
||||
L,
|
||||
Nr,
|
||||
Niu0,
|
||||
Iu0,
|
||||
Iu1,
|
||||
|
||||
EnumEnd
|
||||
};
|
||||
|
||||
using Cond = EnumAllOperand<CondValue>;
|
||||
|
||||
struct BankFlags : Operand<6>{
|
||||
constexpr bool Cfgi() const {
|
||||
return (this->storage & 1) != 0;
|
||||
}
|
||||
constexpr bool R4() const {
|
||||
return (this->storage & 2) != 0;
|
||||
}
|
||||
constexpr bool R1() const {
|
||||
return (this->storage & 4) != 0;
|
||||
}
|
||||
constexpr bool R0() const {
|
||||
return (this->storage & 8) != 0;
|
||||
}
|
||||
constexpr bool R7() const {
|
||||
return (this->storage & 16) != 0;
|
||||
}
|
||||
constexpr bool Cfgj() const {
|
||||
return (this->storage & 32) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
enum class CbsCondValue {
|
||||
Ge,
|
||||
Gt,
|
||||
|
||||
EnumEnd
|
||||
};
|
||||
|
||||
using CbsCond = EnumAllOperand<CbsCondValue>;
|
||||
|
||||
// clang-format on
|
|
@ -0,0 +1,92 @@
|
|||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include "../include/teakra/disassembler.h"
|
||||
#include "common_types.h"
|
||||
#include "crash.h"
|
||||
#include "parser.h"
|
||||
|
||||
using NodeAsConst = std::string;
|
||||
struct NodeAsExpansion {};
|
||||
|
||||
bool operator==(NodeAsExpansion, NodeAsExpansion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<NodeAsExpansion> {
|
||||
typedef NodeAsExpansion argument_type;
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(argument_type const& s) const {
|
||||
return 0x12345678;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace Teakra {
|
||||
class ParserImpl : public Parser {
|
||||
public:
|
||||
using NodeKey = std::variant<NodeAsConst, NodeAsExpansion>;
|
||||
|
||||
struct Node {
|
||||
bool end = false;
|
||||
u16 opcode = 0;
|
||||
bool expansion = false;
|
||||
std::unordered_map<NodeKey, std::unique_ptr<Node>> children;
|
||||
};
|
||||
|
||||
Node root;
|
||||
|
||||
Opcode Parse(const std::vector<std::string>& tokens) override {
|
||||
Node* current = &root;
|
||||
for (auto& token : tokens) {
|
||||
auto const_find = current->children.find(token);
|
||||
if (const_find != current->children.end()) {
|
||||
current = const_find->second.get();
|
||||
} else {
|
||||
return Opcode{Opcode::Invalid};
|
||||
}
|
||||
}
|
||||
if (!current->end) {
|
||||
return Opcode{Opcode::Invalid};
|
||||
}
|
||||
return Opcode{current->expansion ? Opcode::ValidWithExpansion : Opcode::Valid,
|
||||
current->opcode};
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Parser> GenerateParser() {
|
||||
std::unique_ptr<ParserImpl> parser = std::make_unique<ParserImpl>();
|
||||
for (u32 opcode = 0; opcode < 0x10000; ++opcode) {
|
||||
u16 o = (u16)opcode;
|
||||
bool expansion = Disassembler::NeedExpansion(o);
|
||||
auto tokens = Disassembler::GetTokenList(o);
|
||||
|
||||
if (std::any_of(tokens.begin(), tokens.end(), [](const auto& token) {
|
||||
return token.find("[ERROR]") != std::string::npos;
|
||||
}))
|
||||
continue;
|
||||
|
||||
ParserImpl::Node* current = &parser->root;
|
||||
for (const auto& token : tokens) {
|
||||
auto& next = current->children[token];
|
||||
if (!next)
|
||||
next = std::make_unique<ParserImpl::Node>();
|
||||
current = next.get();
|
||||
}
|
||||
|
||||
if (current->end) {
|
||||
ASSERT((current->opcode & (u16)(~o)) == 0);
|
||||
continue;
|
||||
}
|
||||
current->end = true;
|
||||
current->opcode = o;
|
||||
current->expansion = expansion;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
virtual ~Parser() = default;
|
||||
struct Opcode {
|
||||
enum {
|
||||
Invalid,
|
||||
Valid,
|
||||
ValidWithExpansion,
|
||||
} status = Invalid;
|
||||
u16 opcode = 0;
|
||||
};
|
||||
|
||||
virtual Opcode Parse(const std::vector<std::string>& tokens) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Parser> GenerateParser();
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,78 @@
|
|||
# PMU
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
The following MMIO definition is extracted from Lauterbach's Teak debugger and is not tested at all.
|
||||
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0080 | PLLMUL |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0082 | |PLO|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0084 |PLB| | CLKD |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0086 | |SDJ|SD1|SD0|SDO| |SDA|SDH|SDG|SDS|SDD|SDC|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0088 | |I0J|I01|I00|I0O| |I0A|I0H|I0G|I0S|I0D|I0C|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x008A | |I1J|I11|I10|I1O| |I1A|I1H|I1G|I1S|I1D|I1C|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x008C | |I2J|I21|I20|I2O| |I2A|I2H|I2G|I2S|I2D|I2C|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x008E | |IVJ|IV1|IV0|IVO| |IVA|IVH|IVG|IVS|IVD|IVC|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0090 | |T0J|T01| |T0O| |T0A|T0H|T0G|T0S|T0D|T0C|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0092 | |T1J| |T10|T1O| |T1A|T1H|T1G|T1S|T1D|T1C|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0094 | |NMJ|NM1|NM0|NMO| |NMA|NMH|NMG|NMS|NMD|NMC|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0096 | | | | | | | | | | |EXD| |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0098 | | |BM1|BM0| | | | | | |BMD| |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x009A | | SDB |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x009C | | I0B |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x009E | | I1B |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00A0 | | I2B |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00A2 | | IVB |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00A4 | | T0B |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x00A6 | | T1B |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
PLLMUL: Configuration of the PLL clock multiplication
|
||||
PLO: PLL power-on configuration value for PLL use
|
||||
CLKD: Clock Division Factor. When writing a value of 0 or 1 to CLKD, then the clock frequency is divided by 1
|
||||
PLB: 1 to bypass PLL
|
||||
|
||||
SDx: 1 to shut down module x
|
||||
I0x: 1 to enable module x recovery on interrupt 0
|
||||
I1x: 1 to enable module x recovery on interrupt 1
|
||||
I2x: 1 to enable module x recovery on interrupt 2
|
||||
IVx: 1 to enable module x recovery on vectored interrupt
|
||||
T0x: 1 to enable module x recovery on timer 0
|
||||
T1x: 1 to enable module x recovery on timer 1
|
||||
NMx: 1 to enable module x recovery on non-maskable interrupt
|
||||
EXx: 1 to enable module x recovery on external signal
|
||||
BMx: 1 to enable module x breakpoint mask
|
||||
|
||||
The module "x" in registers above means
|
||||
- C: core
|
||||
- D: DMA
|
||||
- S: SIO
|
||||
- G: GLUE
|
||||
- H: APBP/HPI
|
||||
- A: AHBM
|
||||
- O: OCEM
|
||||
- 0: Timer 0
|
||||
- 1: Timer 1
|
||||
- J: JAM
|
||||
- B: BTDMP 0/1/2/3, for bit 0/1/2/3 respectively
|
||||
```
|
|
@ -0,0 +1,34 @@
|
|||
#include "interpreter.h"
|
||||
#include "processor.h"
|
||||
#include "register.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
struct Processor::Impl {
|
||||
Impl(CoreTiming& core_timing, MemoryInterface& memory_interface)
|
||||
: core_timing(core_timing), interpreter(core_timing, regs, memory_interface) {}
|
||||
CoreTiming& core_timing;
|
||||
RegisterState regs;
|
||||
Interpreter interpreter;
|
||||
};
|
||||
|
||||
Processor::Processor(CoreTiming& core_timing, MemoryInterface& memory_interface)
|
||||
: impl(new Impl(core_timing, memory_interface)) {}
|
||||
Processor::~Processor() = default;
|
||||
|
||||
void Processor::Reset() {
|
||||
impl->regs = RegisterState();
|
||||
}
|
||||
|
||||
void Processor::Run(unsigned cycles) {
|
||||
impl->interpreter.Run(cycles);
|
||||
}
|
||||
|
||||
void Processor::SignalInterrupt(u32 i) {
|
||||
impl->interpreter.SignalInterrupt(i);
|
||||
}
|
||||
void Processor::SignalVectoredInterrupt(u32 address, bool context_switch) {
|
||||
impl->interpreter.SignalVectoredInterrupt(address, context_switch);
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common_types.h"
|
||||
#include "core_timing.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class MemoryInterface;
|
||||
|
||||
class Processor {
|
||||
public:
|
||||
Processor(CoreTiming& core_timing, MemoryInterface& memory_interface);
|
||||
~Processor();
|
||||
void Reset();
|
||||
void Run(unsigned cycles);
|
||||
void SignalInterrupt(u32 i);
|
||||
void SignalVectoredInterrupt(u32 address, bool context_switch);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,11 @@
|
|||
## Features of the Architecture
|
||||
|
||||
Most things in the processor is 16-bit. There is no 8-bit byte, and the smallest addressable memory unit is 16-bit. Many registers and data buses are also 16-bit. Exceptions are 40-bit accumulators, 33-bit multiplication results and 18-bit program counter. From now on, we call 16-bit "a word".
|
||||
|
||||
Teak separate instruction and data address space. The instruction address is (18 + 4)-bit long, where 18 being the lower bits directly addressable by the program counter, and 4 being the higher bits specified by the `prpage` register. i.e. the program memory can be 16 pages with each page up to 0x40000 words. However, DSi/3DS seem to only support half of one program page (0x20000 words), and the register `prpage` is always 0. The data address is 16-bit long, meaning the data memory space contains 0x10000 words. However, DSi/3DS provides 0x20000-words long data memory to DSP. Accessing the larger space of data memory is achieved by memory bank switching. See [memory interface](miu.md) for details
|
||||
|
||||
Although the program/data memory is viewed as arrays of 16-bit words from the Teak processor with no byte-order concept, it can be also accessed from the CPU (ARM) side and viewed as byte arrays. It appears that the 16-bit words is little-endian when represented in byte arrays.
|
||||
|
||||
Each instruction is either one word or two words. The encoding of the instruction set is very messy. See [decoder](decoder.md) for details.
|
||||
|
||||
The clock rate of the processor is unclear. Disassembly of 3DS DSP binary shows that it is likely about 134MHz (134,060,000 being the number used in the code). This also matches claimed sample rate from 3dbrew (32728Hz ≈ 134MHz / 4100, where 4100 is also a number for configuring audio output timer found in disassembly). Most instructions can complete in a single clock cycle, except for double-word instructions, multiple data read/write instructions and flow control instructions.
|
|
@ -0,0 +1,687 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common_types.h"
|
||||
#include "crash.h"
|
||||
#include "operand.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
struct RegisterState {
|
||||
void Reset() {
|
||||
*this = RegisterState();
|
||||
}
|
||||
|
||||
/** Program control unit **/
|
||||
|
||||
u32 pc = 0; // 18-bit, program counter
|
||||
u16 prpage = 0; // 4-bit, program page
|
||||
u16 cpc = 1; // 1-bit, change word order when push/pop pc
|
||||
|
||||
u16 repc = 0; // 16-bit rep loop counter
|
||||
u16 repcs = 0; // repc shadow
|
||||
bool rep = false; // true when in rep loop
|
||||
u16 crep = 1; // 1-bit. If clear, store/restore repc to shadows on context switch
|
||||
|
||||
u16 bcn = 0; // 3-bit, nest loop counter
|
||||
u16 lp = 0; // 1-bit, set when in a loop
|
||||
|
||||
struct BlockRepeatFrame {
|
||||
u32 start = 0;
|
||||
u32 end = 0;
|
||||
u16 lc = 0;
|
||||
};
|
||||
|
||||
std::array<BlockRepeatFrame, 4> bkrep_stack;
|
||||
u16& Lc() {
|
||||
if (lp)
|
||||
return bkrep_stack[bcn - 1].lc;
|
||||
return bkrep_stack[0].lc;
|
||||
}
|
||||
|
||||
/** Computation unit **/
|
||||
|
||||
// 40-bit 2's comp accumulators.
|
||||
// Use 64-bit 2's comp here. The upper 24 bits are always sign extension
|
||||
std::array<u64, 2> a{};
|
||||
std::array<u64, 2> b{};
|
||||
|
||||
u64 a1s = 0, b1s = 0; // shadows for a1 and b1
|
||||
u16 ccnta = 1; // 1-bit. If clear, store/restore a1/b1 to shadows on context switch
|
||||
|
||||
u16 sat = 0; // 1-bit, disable saturation when moving from acc
|
||||
u16 sata = 1; // 1-bit, disable saturation when moving to acc
|
||||
u16 s = 0; // 1-bit, shift mode. 0 - arithmetic, 1 - logic
|
||||
u16 sv = 0; // 16-bit two's complement shift value
|
||||
|
||||
// 1-bit flags
|
||||
u16 fz = 0; // zero flag
|
||||
u16 fm = 0; // negative flag
|
||||
u16 fn = 0; // normalized flag
|
||||
u16 fv = 0; // overflow flag
|
||||
u16 fe = 0; // extension flag
|
||||
u16 fc0 = 0; // carry flag
|
||||
u16 fc1 = 0; // another carry flag
|
||||
u16 flm = 0; // set on saturation
|
||||
u16 fvl = 0; // latching fv
|
||||
u16 fr = 0; // Rn zero flag
|
||||
|
||||
// Viterbi
|
||||
u16 vtr0 = 0;
|
||||
u16 vtr1 = 0;
|
||||
|
||||
/** Multiplication unit **/
|
||||
|
||||
std::array<u16, 2> x{}; // factor
|
||||
std::array<u16, 2> y{}; // factor
|
||||
u16 hwm = 0; // 2-bit, half word mode, modify y on multiplication
|
||||
std::array<u32, 2> p{}; // product
|
||||
std::array<u16, 2> pe{}; // 1-bit product extension
|
||||
std::array<u16, 2> ps{}; // 2-bit, product shift mode
|
||||
u16 p0h_cbs = 0; // 16-bit hidden state for codebook search (CBS) opcode
|
||||
|
||||
/** Address unit **/
|
||||
|
||||
std::array<u16, 8> r{}; // 16-bit general and address registers
|
||||
u16 mixp = 0; // 16-bit, stores result of min/max instructions
|
||||
u16 sp = 0; // 16-bit stack pointer
|
||||
u16 page = 0; // 8-bit, higher part of MemImm8 address
|
||||
u16 pcmhi = 0; // 2-bit, higher part of program address for movp/movd
|
||||
|
||||
// shadows for bank exchange;
|
||||
u16 r0b = 0, r1b = 0, r4b = 0, r7b = 0;
|
||||
|
||||
/** Address step/mod unit **/
|
||||
|
||||
// step/modulo
|
||||
u16 stepi = 0, stepj = 0; // 7-bit step
|
||||
u16 modi = 0, modj = 0; // 9-bit mod
|
||||
u16 stepi0 = 0, stepj0 = 0; // 16-bit step
|
||||
|
||||
// shadows for bank exchange
|
||||
u16 stepib = 0, stepjb = 0;
|
||||
u16 modib = 0, modjb = 0;
|
||||
u16 stepi0b = 0, stepj0b = 0;
|
||||
|
||||
std::array<u16, 8> m{}; // 1-bit each, enable modulo arithmetic for Rn
|
||||
std::array<u16, 8> br{}; // 1-bit each, use bit-reversed value from Rn as address
|
||||
u16 stp16 = 0; // 1 bit. If set, stepi0/j0 will be exchanged along with cfgi/j in banke, and use
|
||||
// stepi0/j0 for steping
|
||||
u16 cmd = 1; // 1-bit, step/mod method. 0 - Teak; 1 - TeakLite
|
||||
u16 epi = 0; // 1-bit. If set, cause r3 = 0 when steping r3
|
||||
u16 epj = 0; // 1-bit. If set, cause r7 = 0 when steping r7
|
||||
|
||||
/** Indirect address unit **/
|
||||
|
||||
// 3 bits each
|
||||
// 0: +0
|
||||
// 1: +1
|
||||
// 2: -1
|
||||
// 3: +s
|
||||
// 4: +2
|
||||
// 5: -2
|
||||
// 6: +2*
|
||||
// 7: -2*
|
||||
std::array<u16, 4> arstep{{1, 4, 5, 3}}, arpstepi{{1, 4, 5, 3}}, arpstepj{{1, 4, 5, 3}};
|
||||
|
||||
// 2 bits each
|
||||
// 0: +0
|
||||
// 1: +1
|
||||
// 2: -1
|
||||
// 3: -1*
|
||||
std::array<u16, 4> aroffset{{0, 1, 2, 0}}, arpoffseti{{0, 1, 2, 0}}, arpoffsetj{{0, 1, 2, 0}};
|
||||
|
||||
// 3 bits each, represent r0~r7
|
||||
std::array<u16, 4> arrn{{0, 4, 2, 6}};
|
||||
|
||||
// 2 bits each. for i represent r0~r3, for j represents r4~r7
|
||||
std::array<u16, 4> arprni{{0, 1, 2, 3}}, arprnj{{0, 1, 2, 3}};
|
||||
|
||||
/** Interrupt unit **/
|
||||
|
||||
// interrupt pending bit
|
||||
std::array<u16, 3> ip{};
|
||||
u16 ipv = 0;
|
||||
|
||||
// interrupt enable bit
|
||||
std::array<u16, 3> im{};
|
||||
u16 imv = 0;
|
||||
|
||||
// interrupt context switching bit
|
||||
std::array<u16, 3> ic{};
|
||||
u16 nimc = 0;
|
||||
|
||||
// interrupt enable master bit
|
||||
u16 ie = 0;
|
||||
|
||||
/** Extension unit **/
|
||||
|
||||
std::array<u16, 5> ou{}; // user output pins
|
||||
std::array<u16, 2> iu{}; // user input pins
|
||||
std::array<u16, 4> ext{};
|
||||
|
||||
u16 mod0_unk_const = 1; // 3-bit
|
||||
|
||||
/** Shadow registers **/
|
||||
|
||||
template <u16 RegisterState::*origin>
|
||||
class ShadowRegister {
|
||||
public:
|
||||
void Store(RegisterState* self) {
|
||||
shadow = self->*origin;
|
||||
}
|
||||
void Restore(RegisterState* self) {
|
||||
self->*origin = shadow;
|
||||
}
|
||||
|
||||
private:
|
||||
u16 shadow = 0;
|
||||
};
|
||||
|
||||
template <std::size_t size, std::array<u16, size> RegisterState::*origin>
|
||||
class ShadowArrayRegister {
|
||||
public:
|
||||
void Store(RegisterState* self) {
|
||||
shadow = self->*origin;
|
||||
}
|
||||
void Restore(RegisterState* self) {
|
||||
self->*origin = shadow;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<u16, size> shadow{};
|
||||
};
|
||||
|
||||
template <typename... ShadowRegisters>
|
||||
class ShadowRegisterList : private ShadowRegisters... {
|
||||
public:
|
||||
void Store(RegisterState* self) {
|
||||
(ShadowRegisters::Store(self), ...);
|
||||
}
|
||||
void Restore(RegisterState* self) {
|
||||
(ShadowRegisters::Restore(self), ...);
|
||||
}
|
||||
};
|
||||
|
||||
template <u16 RegisterState::*origin>
|
||||
class ShadowSwapRegister {
|
||||
public:
|
||||
void Swap(RegisterState* self) {
|
||||
std::swap(self->*origin, shadow);
|
||||
}
|
||||
|
||||
private:
|
||||
u16 shadow = 0;
|
||||
};
|
||||
|
||||
template <std::size_t size, std::array<u16, size> RegisterState::*origin>
|
||||
class ShadowSwapArrayRegister {
|
||||
public:
|
||||
void Swap(RegisterState* self) {
|
||||
std::swap(self->*origin, shadow);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<u16, size> shadow{};
|
||||
};
|
||||
|
||||
template <typename... ShadowSwapRegisters>
|
||||
class ShadowSwapRegisterList : private ShadowSwapRegisters... {
|
||||
public:
|
||||
void Swap(RegisterState* self) {
|
||||
(ShadowSwapRegisters::Swap(self), ...);
|
||||
}
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
ShadowRegisterList<
|
||||
ShadowRegister<&RegisterState::flm>,
|
||||
ShadowRegister<&RegisterState::fvl>,
|
||||
ShadowRegister<&RegisterState::fe>,
|
||||
ShadowRegister<&RegisterState::fc0>,
|
||||
ShadowRegister<&RegisterState::fc1>,
|
||||
ShadowRegister<&RegisterState::fv>,
|
||||
ShadowRegister<&RegisterState::fn>,
|
||||
ShadowRegister<&RegisterState::fm>,
|
||||
ShadowRegister<&RegisterState::fz>,
|
||||
ShadowRegister<&RegisterState::fr>
|
||||
> shadow_registers;
|
||||
|
||||
ShadowSwapRegisterList<
|
||||
ShadowSwapRegister<&RegisterState::pcmhi>,
|
||||
ShadowSwapRegister<&RegisterState::sat>,
|
||||
ShadowSwapRegister<&RegisterState::sata>,
|
||||
ShadowSwapRegister<&RegisterState::hwm>,
|
||||
ShadowSwapRegister<&RegisterState::s>,
|
||||
ShadowSwapArrayRegister<2, &RegisterState::ps>,
|
||||
ShadowSwapRegister<&RegisterState::page>,
|
||||
ShadowSwapRegister<&RegisterState::stp16>,
|
||||
ShadowSwapRegister<&RegisterState::cmd>,
|
||||
ShadowSwapArrayRegister<8, &RegisterState::m>,
|
||||
ShadowSwapArrayRegister<8, &RegisterState::br>,
|
||||
ShadowSwapArrayRegister<3, &RegisterState::im>, // ?
|
||||
ShadowSwapRegister<&RegisterState::imv>, // ?
|
||||
ShadowSwapRegister<&RegisterState::epi>,
|
||||
ShadowSwapRegister<&RegisterState::epj>
|
||||
> shadow_swap_registers;
|
||||
|
||||
// clang-format on
|
||||
|
||||
template <unsigned index>
|
||||
class ShadowSwapAr {
|
||||
public:
|
||||
void Swap(RegisterState* self) {
|
||||
std::swap(self->arrn[index * 2], rni);
|
||||
std::swap(self->arrn[index * 2 + 1], rnj);
|
||||
std::swap(self->arstep[index * 2], stepi);
|
||||
std::swap(self->arstep[index * 2 + 1], stepj);
|
||||
std::swap(self->aroffset[index * 2], offseti);
|
||||
std::swap(self->aroffset[index * 2 + 1], offsetj);
|
||||
}
|
||||
|
||||
private:
|
||||
u16 rni, rnj, stepi, stepj, offseti, offsetj;
|
||||
};
|
||||
|
||||
template <unsigned index>
|
||||
class ShadowSwapArp {
|
||||
public:
|
||||
void Swap(RegisterState* self) {
|
||||
std::swap(self->arprni[index], rni);
|
||||
std::swap(self->arprnj[index], rnj);
|
||||
std::swap(self->arpstepi[index], stepi);
|
||||
std::swap(self->arpstepj[index], stepj);
|
||||
std::swap(self->arpoffseti[index], offseti);
|
||||
std::swap(self->arpoffsetj[index], offsetj);
|
||||
}
|
||||
|
||||
private:
|
||||
u16 rni, rnj, stepi, stepj, offseti, offsetj;
|
||||
};
|
||||
|
||||
ShadowSwapAr<0> shadow_swap_ar0;
|
||||
ShadowSwapAr<1> shadow_swap_ar1;
|
||||
ShadowSwapArp<0> shadow_swap_arp0;
|
||||
ShadowSwapArp<1> shadow_swap_arp1;
|
||||
ShadowSwapArp<2> shadow_swap_arp2;
|
||||
ShadowSwapArp<3> shadow_swap_arp3;
|
||||
|
||||
void ShadowStore() {
|
||||
shadow_registers.Store(this);
|
||||
}
|
||||
|
||||
void ShadowRestore() {
|
||||
shadow_registers.Restore(this);
|
||||
}
|
||||
|
||||
void SwapAllArArp() {
|
||||
shadow_swap_ar0.Swap(this);
|
||||
shadow_swap_ar1.Swap(this);
|
||||
shadow_swap_arp0.Swap(this);
|
||||
shadow_swap_arp1.Swap(this);
|
||||
shadow_swap_arp2.Swap(this);
|
||||
shadow_swap_arp3.Swap(this);
|
||||
}
|
||||
|
||||
void ShadowSwap() {
|
||||
shadow_swap_registers.Swap(this);
|
||||
SwapAllArArp();
|
||||
}
|
||||
|
||||
void SwapAr(u16 index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
shadow_swap_ar0.Swap(this);
|
||||
break;
|
||||
case 1:
|
||||
shadow_swap_ar1.Swap(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SwapArp(u16 index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
shadow_swap_arp0.Swap(this);
|
||||
break;
|
||||
case 1:
|
||||
shadow_swap_arp1.Swap(this);
|
||||
break;
|
||||
case 2:
|
||||
shadow_swap_arp2.Swap(this);
|
||||
break;
|
||||
case 3:
|
||||
shadow_swap_arp3.Swap(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConditionPass(Cond cond) const {
|
||||
switch (cond.GetName()) {
|
||||
case CondValue::True:
|
||||
return true;
|
||||
case CondValue::Eq:
|
||||
return fz == 1;
|
||||
case CondValue::Neq:
|
||||
return fz == 0;
|
||||
case CondValue::Gt:
|
||||
return fz == 0 && fm == 0;
|
||||
case CondValue::Ge:
|
||||
return fm == 0;
|
||||
case CondValue::Lt:
|
||||
return fm == 1;
|
||||
case CondValue::Le:
|
||||
return fm == 1 || fz == 1;
|
||||
case CondValue::Nn:
|
||||
return fn == 0;
|
||||
case CondValue::C:
|
||||
return fc0 == 1;
|
||||
case CondValue::V:
|
||||
return fv == 1;
|
||||
case CondValue::E:
|
||||
return fe == 1;
|
||||
case CondValue::L:
|
||||
return flm == 1 || fvl == 1;
|
||||
case CondValue::Nr:
|
||||
return fr == 0;
|
||||
case CondValue::Niu0:
|
||||
return iu[0] == 0;
|
||||
case CondValue::Iu0:
|
||||
return iu[0] == 1;
|
||||
case CondValue::Iu1:
|
||||
return iu[1] == 1;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename PseudoRegisterT>
|
||||
u16 Get() const {
|
||||
return PseudoRegisterT::Get(this);
|
||||
}
|
||||
|
||||
template <typename PseudoRegisterT>
|
||||
void Set(u16 value) {
|
||||
PseudoRegisterT::Set(this, value);
|
||||
}
|
||||
};
|
||||
|
||||
template <u16 RegisterState::*target>
|
||||
struct Redirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return self->*target;
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
self->*target = value;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t size, std::array<u16, size> RegisterState::*target, std::size_t index>
|
||||
struct ArrayRedirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return (self->*target)[index];
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
(self->*target)[index] = value;
|
||||
}
|
||||
};
|
||||
|
||||
template <u16 RegisterState::*target0, u16 RegisterState::*target1>
|
||||
struct DoubleRedirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return self->*target0 | self->*target1;
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
self->*target0 = self->*target1 = value;
|
||||
}
|
||||
};
|
||||
|
||||
template <u16 RegisterState::*target>
|
||||
struct RORedirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return self->*target;
|
||||
}
|
||||
static void Set(RegisterState*, u16) {
|
||||
// no
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t size, std::array<u16, size> RegisterState::*target, std::size_t index>
|
||||
struct ArrayRORedirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return (self->*target)[index];
|
||||
}
|
||||
static void Set(RegisterState*, u16) {
|
||||
// no
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned index>
|
||||
struct AccEProxy {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return (u16)((self->a[index] >> 32) & 0xF);
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
u32 value32 = SignExtend<4>((u32)value);
|
||||
self->a[index] &= 0xFFFFFFFF;
|
||||
self->a[index] |= (u64)value32 << 32;
|
||||
}
|
||||
};
|
||||
|
||||
struct LPRedirector {
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return self->lp;
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
if (value != 0) {
|
||||
self->lp = 0;
|
||||
self->bcn = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Proxy, unsigned position, unsigned length>
|
||||
struct ProxySlot {
|
||||
using proxy = Proxy;
|
||||
static constexpr unsigned pos = position;
|
||||
static constexpr unsigned len = length;
|
||||
static_assert(length < 16, "Error");
|
||||
static_assert(position + length <= 16, "Error");
|
||||
static constexpr u16 mask = ((1 << length) - 1) << position;
|
||||
};
|
||||
|
||||
template <typename... ProxySlots>
|
||||
struct PseudoRegister {
|
||||
static_assert(NoOverlap<u16, ProxySlots::mask...>, "Error");
|
||||
static u16 Get(const RegisterState* self) {
|
||||
return ((ProxySlots::proxy::Get(self) << ProxySlots::pos) | ...);
|
||||
}
|
||||
static void Set(RegisterState* self, u16 value) {
|
||||
(ProxySlots::proxy::Set(self, (value >> ProxySlots::pos) & ((1 << ProxySlots::len) - 1)),
|
||||
...);
|
||||
}
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
using cfgi = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::stepi>, 0, 7>,
|
||||
ProxySlot<Redirector<&RegisterState::modi>, 7, 9>
|
||||
>;
|
||||
|
||||
using cfgj = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::stepj>, 0, 7>,
|
||||
ProxySlot<Redirector<&RegisterState::modj>, 7, 9>
|
||||
>;
|
||||
|
||||
using stt0 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::flm>, 0, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fvl>, 1, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fe>, 2, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fc0>, 3, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fv>, 4, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fn>, 5, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fm>, 6, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fz>, 7, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fc1>, 11, 1>
|
||||
>;
|
||||
using stt1 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::fr>, 4, 1>,
|
||||
ProxySlot<ArrayRORedirector<2, &RegisterState::iu, 0>, 10, 1>,
|
||||
ProxySlot<ArrayRORedirector<2, &RegisterState::iu, 1>, 11, 1>,
|
||||
ProxySlot<ArrayRedirector<2, &RegisterState::pe, 0>, 14, 1>,
|
||||
ProxySlot<ArrayRedirector<2, &RegisterState::pe, 1>, 15, 1>
|
||||
>;
|
||||
using stt2 = PseudoRegister<
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 0>, 0, 1>,
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 1>, 1, 1>,
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 2>, 2, 1>,
|
||||
ProxySlot<RORedirector<&RegisterState::ipv>, 3, 1>,
|
||||
|
||||
ProxySlot<Redirector<&RegisterState::pcmhi>, 6, 2>,
|
||||
|
||||
ProxySlot<RORedirector<&RegisterState::bcn>, 12, 3>,
|
||||
ProxySlot<LPRedirector, 15, 1>
|
||||
>;
|
||||
using mod0 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::sat>, 0, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::sata>, 1, 1>,
|
||||
ProxySlot<RORedirector<&RegisterState::mod0_unk_const>, 2, 3>,
|
||||
ProxySlot<Redirector<&RegisterState::hwm>, 5, 2>,
|
||||
ProxySlot<Redirector<&RegisterState::s>, 7, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 0>, 8, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 1>, 9, 1>,
|
||||
ProxySlot<ArrayRedirector<2, &RegisterState::ps, 0>, 10, 2>,
|
||||
|
||||
ProxySlot<ArrayRedirector<2, &RegisterState::ps, 1>, 13, 2>
|
||||
>;
|
||||
using mod1 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::page>, 0, 8>,
|
||||
|
||||
ProxySlot<Redirector<&RegisterState::stp16>, 12, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::cmd>, 13, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::epi>, 14, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::epj>, 15, 1>
|
||||
>;
|
||||
using mod2 = PseudoRegister<
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 0>, 0, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 1>, 1, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 2>, 2, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 3>, 3, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 4>, 4, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 5>, 5, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 6>, 6, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 7>, 7, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 0>, 8, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 1>, 9, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 2>, 10, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 3>, 11, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 4>, 12, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 5>, 13, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 6>, 14, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::br, 7>, 15, 1>
|
||||
>;
|
||||
using mod3 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::nimc>, 0, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 0>, 1, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 1>, 2, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 2>, 3, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 2>, 4, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 3>, 5, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 4>, 6, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::ie>, 7, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 0>, 8, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 1>, 9, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 2>, 10, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::imv>, 11, 1>,
|
||||
|
||||
ProxySlot<Redirector<&RegisterState::ccnta>, 13, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::cpc>, 14, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::crep>, 15, 1>
|
||||
>;
|
||||
|
||||
using st0 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::sat>, 0, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::ie>, 1, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 0>, 2, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 1>, 3, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fr>, 4, 1>,
|
||||
ProxySlot<DoubleRedirector<&RegisterState::flm, &RegisterState::fvl>, 5, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fe>, 6, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fc0>, 7, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fv>, 8, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fn>, 9, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fm>, 10, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::fz>, 11, 1>,
|
||||
ProxySlot<AccEProxy<0>, 12, 4>
|
||||
>;
|
||||
using st1 = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::page>, 0, 8>,
|
||||
// 8, 9: reserved
|
||||
ProxySlot<ArrayRedirector<2, &RegisterState::ps, 0>, 10, 2>,
|
||||
ProxySlot<AccEProxy<1>, 12, 4>
|
||||
>;
|
||||
using st2 = PseudoRegister<
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 0>, 0, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 1>, 1, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 2>, 2, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 3>, 3, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 4>, 4, 1>,
|
||||
ProxySlot<ArrayRedirector<8, &RegisterState::m, 5>, 5, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::im, 2>, 6, 1>,
|
||||
ProxySlot<Redirector<&RegisterState::s>, 7, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 0>, 8, 1>,
|
||||
ProxySlot<ArrayRedirector<5, &RegisterState::ou, 1>, 9, 1>,
|
||||
ProxySlot<ArrayRORedirector<2, &RegisterState::iu, 0>, 10, 1>,
|
||||
ProxySlot<ArrayRORedirector<2, &RegisterState::iu, 1>, 11, 1>,
|
||||
// 12: reserved
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 2>, 13, 1>, // Note the index order!
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 0>, 14, 1>,
|
||||
ProxySlot<ArrayRORedirector<3, &RegisterState::ip, 1>, 15, 1>
|
||||
>;
|
||||
using icr = PseudoRegister<
|
||||
ProxySlot<Redirector<&RegisterState::nimc>, 0, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 0>, 1, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 1>, 2, 1>,
|
||||
ProxySlot<ArrayRedirector<3, &RegisterState::ic, 2>, 3, 1>,
|
||||
ProxySlot<LPRedirector, 4, 1>,
|
||||
ProxySlot<RORedirector<&RegisterState::bcn>, 5, 3>
|
||||
>;
|
||||
|
||||
template<unsigned index>
|
||||
using ar = PseudoRegister<
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arstep, index * 2 + 1>, 0, 3>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::aroffset, index * 2 + 1>, 3, 2>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arstep, index * 2>, 5, 3>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::aroffset, index * 2>, 8, 2>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arrn, index * 2 + 1>, 10, 3>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arrn, index * 2>, 13, 3>
|
||||
>;
|
||||
|
||||
using ar0 = ar<0>;
|
||||
using ar1 = ar<1>;
|
||||
|
||||
template<unsigned index>
|
||||
using arp = PseudoRegister<
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arpstepi, index>, 0, 3>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arpoffseti, index>, 3, 2>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arpstepj, index>, 5, 3>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arpoffsetj, index>, 8, 2>,
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arprni, index>, 10, 2>,
|
||||
// bit 12 reserved
|
||||
ProxySlot<ArrayRedirector<4, &RegisterState::arprnj, index>, 13, 2>
|
||||
// bit 15 reserved
|
||||
>;
|
||||
|
||||
using arp0 = arp<0>;
|
||||
using arp1 = arp<1>;
|
||||
using arp2 = arp<2>;
|
||||
using arp3 = arp<3>;
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,131 @@
|
|||
# Registers
|
||||
|
||||
Related code: [register.h](register.h)
|
||||
|
||||
## Basic Registers
|
||||
|
||||
The `RegisterState` class includes registers that program can directly access individually, such as `r0`~`r7` and accumulators, and registers that are not directly exposed to the program but affect execution. These registers have various bit length in hardware, but they are all represented as `u16` for consistency if possible(which makes it easier to define pseudo-registers, explained below). All extra bits are zero-padded. Exceptions are
|
||||
- program counter `pc`, as well as other program address registers, are 18-bit on hardware. they are zero-padded to `u32` here.
|
||||
- accumulators `a0`, `a1`, `b0` and `b1` are 40-bit on hardware. They are sign-extended to `u64` here.
|
||||
- Multiplication results `p0` and `p1` are 33-bit on hardware. They are stored in separate `u32` fields `p[*]` for lower bits and zero-padded one-bit `u16` fields `pe[*]` for the highest bit.
|
||||
|
||||
Note that many registers have signed-integer semantics with two's complement representation, but we still store them as unsigned integer. This is to cooperate with the interpreter policy where signed integer arithmetic is avoided in order to avoid undefined or implementation-defined behaviours.
|
||||
|
||||
|
||||
## Shadow Registers
|
||||
|
||||
Many registers have "shadow" conterpart that are not directly accessible, but can be swapped with the main registers during context change. These shadow registers are classified into 4 groups:
|
||||
- bank exchange registers
|
||||
- Batch one-side shadow registers
|
||||
- Batch two-side shadow registers
|
||||
- shadow ar/arp
|
||||
|
||||
Bank exchange registers swap with there counterparts by the `banke` opcode. In the `banke` opcode, program can specify which registers to swap. These bank exchange registers are suffixed with `b` in `RegisterState`.
|
||||
|
||||
Batch shadow registers (both one-side and two-side) swap with coutnerparts by the `cntx r/s` opcode. They are always swapped together, so there is no need to provide ability to swap then individually. The difference of one-side and two-side is that for one-side registers, only main -> shadow transfer happens in `cntx s` and shadow -> main in `cntx r`, while the full swap between main <-> shadow happens in both `cntx s` and `cntx r` for two-side registers. In `RegisterState`, batch shadow registers are created using template `Shadow(Swap)(Array)Register`, where `Swap` templates are for two-side registers and `Array` templates are for register arrays. They are then added to the master templates `Shadow(Swap)RegisterList`, which includes all shadow register states by inheritance and handles value swapping together.
|
||||
|
||||
Shadow ar/arp registers behaves much like Batch two-side shadow registers for ar/arp registers, but they can be also swapped with smaller batch pieces by opcode `bankr`, so they are defined separately from `ShadowSwapRegisterList` as `ShadowSwapAr(p)`.
|
||||
|
||||
## Block Repeat Stack
|
||||
|
||||
Teak supports block repeat nested in 4 levels. It has a small 4 frame stack for this, with each frame storing the loop counter, the start and the end address. The `lc` (loop counter) register visible to the program is always the one in the stack top. It is implemented as a function `Lc()` instead of a normal `u16 lc` registers in order to emulate this behaviour.
|
||||
|
||||
## Pseudo-registers
|
||||
|
||||
These registers works like bit fields and are combination of basic registers. Program can read and write them in 16-bit words, while each bit of them affects the corresponding basic register. However, they are not implemented as bit fields here for the following reasons:
|
||||
- There are two sets of control/status pseudo-registers, many fields of which maps to the same basic registers but with different bit layout. One set is TeakLite-compatible and the other includes all Teak-exclusive registers.
|
||||
- Some bits have special behaviour other than simply store the states.
|
||||
|
||||
|
||||
These pseudo-registers are defined as some C++ types with the same names, such as `cfgi`, `stt0` and `arp2`, and `RegisterState` provides two template functions `u16 Get<T>()` and `void Set<T>(u16)`, where `T` should be one of the predefined type names, for read from and write to pseudo-registers. Here is an example
|
||||
```
|
||||
u16 stt1_value = registers.Get<stt1>();
|
||||
```
|
||||
Internally, these predefined pseudo-register-types, using template, store pointers to basic registers and their bit position and length.
|
||||
|
||||
## Details of all registers
|
||||
|
||||
### Program control registers
|
||||
|
||||
#### `pc`, program counter
|
||||
|
||||
18-bit program counter, address for loading instructions from the program memory. It is increamented by 1 or 2, depending on the instruction length, on every instruction. It is post-incrementing from instructions' view, i.e. when referencing the `pc` value in an instruction, the value is always the address of the *next* instruction.
|
||||
|
||||
The value of `pc` can theoretically be accessed by all opcodes that accept operand `Register` as source. However, most of these opcodes and other registers in `Register` use 16-bit data bus, so it is unclear how the convertion works, or whether it works at all. Only the opcode `mov pc a0/a1` is well tested and implemented: it is full 18-bit transfer. Other code path that access `pc` via `Register` is currently marked as unreachable. On a side node, the TeakLite architecture only has a 16-bit `pc` register, where transferring `pc` value via 16-bit data base makes more sense.
|
||||
|
||||
`pc` value can be pushed to / popped from the stack as two words. The word order is specified by the `cpc` register.
|
||||
|
||||
Aside from increamenting on every instruction, `pc` value can also be modified by the following instructions and circumstances. Please refer to their sections for detail.
|
||||
- `mov a0/a1/b0/b1 pc` opcodes.
|
||||
- subroutine call: `call` opcodes family.
|
||||
- subroutine return: `ret` opcodes family.
|
||||
- branching: `br` opcodes family.
|
||||
- `movpdw [a0/a1] pc` opcode.
|
||||
- hitting the end of a repeating block indicated by opcode `bkrep`.
|
||||
- repeating instruction indicated by opcode `rep`.
|
||||
- on interrupt.
|
||||
|
||||
#### `prpage`, program page
|
||||
|
||||
4-bit register to select program page in the program memory, extending the entire possible program memory space to `4 + 18 = 22` bit. However XpertTeak/DSi/3DS only support single program page, so this register should remain 0.
|
||||
|
||||
#### `repc` repeat counter
|
||||
|
||||
16-bit counter that is set to `repeat count - 1` on `rep` opcodes (i.e. exactly the value passed in the opcodes) and decrements on every repeat until 0 (inclusive). During the repeat, program can read the register value by `mov` opcodes. Out side repeat loop, this register can be used as a general register by using `mov` opcodes to read and write. It can also be `push`ed to or `pop`ed from the stack, and swap with its shadow counterpart `repcs` on context store / restore if the configuration register `crep` is clear.
|
||||
|
||||
#### TODO: block repeat
|
||||
|
||||
|
||||
### Arithmetic registers
|
||||
|
||||
#### `a0`/`a1`/`b0`/`b1`, accumulators
|
||||
|
||||
40-bit accumulators, stored as sign-extended 64-bit integer in Teakra. Accumulators each has three parts from the most significant bit to the least one: `8:16:16` as `e:h:l` (extension, high, and low). The `h` and `l` part are visible to many operations, while the `e` part can be only access by opcode `push/pop Abe`, and for `a0`/`a1`, the lower 4 bits of their `e` part is exposed in pseduo-registers `st0`/`st1`.
|
||||
|
||||
#### Saturation
|
||||
|
||||
#### Shifting
|
||||
|
||||
#### Flags
|
||||
|
||||
#### `vtr0`/`vtr1`, Viterbi registers
|
||||
|
||||
These two 16-bit registers are dedicated for [Viterbi decoding](https://en.wikipedia.org/wiki/Viterbi_decoder),and for path recovery to be specific. For every opcodes that contain a `vtrshr` part, status bits `fc0` and `fc1` are pushed into `vtr0` and `vtr1`, respectively. `vtrmov` opcodes can move the value from these registers to accumulators. `vtrclr` can clear these registers to zero. There is no way to directly access these registers.
|
||||
|
||||
### Multiplication registers
|
||||
|
||||
#### `x0`/`x1`/`y0`/`y1`, factor registers
|
||||
|
||||
16 bits each, used as factors for calculating multiplication, but can also be used for general purpose.
|
||||
|
||||
#### `p0`/`p1`, product registers
|
||||
|
||||
33 bits each, stores the result of multiplication operation when the instruction initiates one. `p0` stores `x0 * y0` and `p1` stores `x1 * y1`. The registers are split in to 32-bit parts `p[0]/p[1]` and sign bits `pe[0]/pe[1]` in the code.
|
||||
|
||||
### Address registers
|
||||
|
||||
#### `r0`..`r7`, general address registers
|
||||
|
||||
These 16-bit registers can be used for general purpose or as memory pointer. Most opcodes that support indirect data access uses these registers as address operand (e.g. `[r0]`). Specifically `r7` also supports supplying an additional immediate address offset in some opcodes, and is often used as stack frame pointer.
|
||||
|
||||
#### `sp`, stack pointer
|
||||
|
||||
16-bit register as a pointer to data memory, decrements on `push` / `call` / `bksto [sp]` opcodes and increments on `pop` / `ret` / `bkrst [sp]` opcodes. Can be also read and modified as a general register via all opcodes that accept `Register` operand.
|
||||
|
||||
#### `page`, data memory page
|
||||
|
||||
Used to specify the higher 8-bits for opcodes that use 8-bit immediate data address.
|
||||
|
||||
#### `pcmhi`, program memory transfer page
|
||||
|
||||
Used to specify the higher 2-bits for opcodes that use 16-bit program address.
|
||||
|
||||
### Advanced address registers
|
||||
|
||||
#### Step/mod configuration registers
|
||||
|
||||
#### Indirect address registers
|
||||
|
||||
### Interrupt registers
|
||||
|
||||
### Extension registers
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Teakra {
|
||||
struct SharedMemory {
|
||||
std::array<u8, 0x80000> raw{};
|
||||
u16 ReadWord(u32 word_address) const {
|
||||
u32 byte_address = word_address * 2;
|
||||
u8 low = raw[byte_address];
|
||||
u8 high = raw[byte_address + 1];
|
||||
return low | ((u16)high << 8);
|
||||
}
|
||||
void WriteWord(u32 word_address, u16 value) {
|
||||
u8 low = value & 0xFF;
|
||||
u8 high = value >> 8;
|
||||
u32 byte_address = word_address * 2;
|
||||
raw[byte_address] = low;
|
||||
raw[byte_address + 1] = high;
|
||||
}
|
||||
};
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,40 @@
|
|||
# SIO
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
The following MMIO definition is extracted from Lauterbach's Teak debugger and is not tested at all.
|
||||
|
||||
```
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0050 | SIOSHIFT | | IM| PH| CP| MS|CSO|CSP|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0052 | | CLKD2SIO | | CLKD1SIO |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0054 | SIODATA |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0056 | | SE|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0058 | |ERR|STS|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
CSP: Chip Select Polarity
|
||||
CSO: Chip Select Output
|
||||
MS: Defines the operation mode of the SIO (Master or Slave).
|
||||
- 0: Master mode, the SIO clock is the Core clock frequency (with or without division)
|
||||
- 1: Slave mode, the clock is external
|
||||
CP: Clock Polarity
|
||||
PH: Phase, Determines whether SIODI/SIODO are sampled at the rising edge or at the falling edge
|
||||
- 0: SIODI on rising edge, SIODO on falling edge
|
||||
- 1: SIODI on falling edge, SIODO on rising edge
|
||||
IM: Interrupt Mask Enable - Masks the SIO interrupt to the ICU. When set to 1, the SIO does not issue an interrupt at the end of the transfer
|
||||
SIOSHIFT: Determines the length of bit shifts for every transfer (2 to 16). The value in the register is (shift - 1)
|
||||
|
||||
CLKD1SIO: Clock Division #1 Factor - Defines the division value of divider #1, which can range from 2 to 128. Writing 0 or 1 to this field causes the divider to be bypassed, and no division is performed.
|
||||
CLKD2SIO: Clock Division #2 Factor, similar behaviour to CLKD1SIO.
|
||||
|
||||
SIODATA: The data register of the SIO. Writing to this register initiates a shift of SIOSHIFT + 1 bits into it and its data is output via SIODO. Data can be written to this register via the ZSI only, and only when no shift operation occurs.
|
||||
|
||||
SE: 1 to enable SIO operation
|
||||
ERR: SIO Error, 1 when an error occurred. A shift overwrites a previous shift value before the Core has read it. The indication is automatically cleared when the Core reads the SIO_STS register
|
||||
STS: SIO Status, sticky indication set at the end of a transfer and cleared automatically when read.
|
||||
```
|
|
@ -0,0 +1,9 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(step2_test_generator
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(step2_test_generator)
|
||||
target_link_libraries(step2_test_generator PRIVATE teakra)
|
||||
target_include_directories(step2_test_generator PRIVATE .)
|
||||
target_compile_options(step2_test_generator PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,41 @@
|
|||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include "../test.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::fprintf(stderr, "A file path argument must be specified. Exiting...\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::FILE, decltype(&std::fclose)> f{std::fopen(argv[1], "wb"), std::fclose};
|
||||
if (!f) {
|
||||
std::fprintf(stderr, "Unable to open file %s. Exiting...\n", argv[1]);
|
||||
return -2;
|
||||
}
|
||||
|
||||
TestCase test_case{};
|
||||
u16 r0base = 0x4839;
|
||||
test_case.opcode = 0xDFE9; // modr r0+arps0
|
||||
test_case.expand = 0;
|
||||
test_case.before.mod2 = 1; // enable mod for r0;
|
||||
|
||||
for (u16 legacy = 0; legacy < 2; ++legacy) {
|
||||
test_case.before.mod1 = legacy << 13;
|
||||
for (u16 step = 4; step < 8; ++step) {
|
||||
test_case.before.arp[0] = step;
|
||||
for (u16 mod = 0; mod < 0x200; ++mod) {
|
||||
test_case.before.cfgi = mod << 7;
|
||||
for (u16 r = 0; r < 0x20; ++r) {
|
||||
test_case.before.r[0] = r + r0base;
|
||||
if (std::fwrite(&test_case, sizeof(test_case), 1, f.get()) == 0) {
|
||||
std::fprintf(stderr, "Unable to completely write test case. Exiting...\n");
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
#include <array>
|
||||
#include <atomic>
|
||||
#include "ahbm.h"
|
||||
#include "apbp.h"
|
||||
#include "btdmp.h"
|
||||
#include "core_timing.h"
|
||||
#include "dma.h"
|
||||
#include "icu.h"
|
||||
#include "memory_interface.h"
|
||||
#include "mmio.h"
|
||||
#include "processor.h"
|
||||
#include "shared_memory.h"
|
||||
#include "teakra/teakra.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
struct Teakra::Impl {
|
||||
CoreTiming core_timing;
|
||||
SharedMemory shared_memory;
|
||||
MemoryInterfaceUnit miu;
|
||||
ICU icu;
|
||||
Apbp apbp_from_cpu, apbp_from_dsp;
|
||||
std::array<Timer, 2> timer{{{core_timing}, {core_timing}}};
|
||||
Ahbm ahbm;
|
||||
Dma dma{shared_memory, ahbm};
|
||||
std::array<Btdmp, 2> btdmp{{{core_timing}, {core_timing}}};
|
||||
MMIORegion mmio{miu, icu, apbp_from_cpu, apbp_from_dsp, timer, dma, ahbm, btdmp};
|
||||
MemoryInterface memory_interface{shared_memory, miu};
|
||||
Processor processor{core_timing, memory_interface};
|
||||
|
||||
Impl() {
|
||||
memory_interface.SetMMIO(mmio);
|
||||
using namespace std::placeholders;
|
||||
icu.SetInterruptHandler(std::bind(&Processor::SignalInterrupt, &processor, _1),
|
||||
std::bind(&Processor::SignalVectoredInterrupt, &processor, _1, _2));
|
||||
|
||||
timer[0].SetInterruptHandler([this]() { icu.TriggerSingle(0xA); });
|
||||
timer[1].SetInterruptHandler([this]() { icu.TriggerSingle(0x9); });
|
||||
|
||||
apbp_from_cpu.SetDataHandler(0, [this]() { icu.TriggerSingle(0xE); });
|
||||
apbp_from_cpu.SetDataHandler(1, [this]() { icu.TriggerSingle(0xE); });
|
||||
apbp_from_cpu.SetDataHandler(2, [this]() { icu.TriggerSingle(0xE); });
|
||||
apbp_from_cpu.SetSemaphoreHandler([this]() { icu.TriggerSingle(0xE); });
|
||||
|
||||
btdmp[0].SetInterruptHandler([this]() { icu.TriggerSingle(0xB); });
|
||||
btdmp[1].SetInterruptHandler([this]() { icu.TriggerSingle(0xB); });
|
||||
|
||||
dma.SetInterruptHandler([this]() { icu.TriggerSingle(0xF); });
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
shared_memory.raw.fill(0);
|
||||
miu.Reset();
|
||||
apbp_from_cpu.Reset();
|
||||
apbp_from_dsp.Reset();
|
||||
timer[0].Reset();
|
||||
timer[1].Reset();
|
||||
ahbm.Reset();
|
||||
dma.Reset();
|
||||
btdmp[0].Reset();
|
||||
btdmp[1].Reset();
|
||||
processor.Reset();
|
||||
}
|
||||
};
|
||||
|
||||
Teakra::Teakra() : impl(new Impl) {}
|
||||
Teakra::~Teakra() = default;
|
||||
|
||||
void Teakra::Reset() {
|
||||
impl->Reset();
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 0x80000>& Teakra::GetDspMemory() {
|
||||
return impl->shared_memory.raw;
|
||||
}
|
||||
|
||||
const std::array<std::uint8_t, 0x80000>& Teakra::GetDspMemory() const {
|
||||
return impl->shared_memory.raw;
|
||||
}
|
||||
|
||||
void Teakra::Run(unsigned cycle) {
|
||||
impl->processor.Run(cycle);
|
||||
}
|
||||
|
||||
bool Teakra::SendDataIsEmpty(std::uint8_t index) const {
|
||||
return !impl->apbp_from_cpu.IsDataReady(index);
|
||||
}
|
||||
void Teakra::SendData(std::uint8_t index, std::uint16_t value) {
|
||||
impl->apbp_from_cpu.SendData(index, value);
|
||||
}
|
||||
bool Teakra::RecvDataIsReady(std::uint8_t index) const {
|
||||
return impl->apbp_from_dsp.IsDataReady(index);
|
||||
}
|
||||
std::uint16_t Teakra::RecvData(std::uint8_t index) {
|
||||
return impl->apbp_from_dsp.RecvData(index);
|
||||
}
|
||||
std::uint16_t Teakra::PeekRecvData(std::uint8_t index) {
|
||||
return impl->apbp_from_dsp.PeekData(index);
|
||||
}
|
||||
void Teakra::SetRecvDataHandler(std::uint8_t index, std::function<void()> handler) {
|
||||
impl->apbp_from_dsp.SetDataHandler(index, std::move(handler));
|
||||
}
|
||||
|
||||
void Teakra::SetSemaphore(std::uint16_t value) {
|
||||
impl->apbp_from_cpu.SetSemaphore(value);
|
||||
}
|
||||
void Teakra::SetSemaphoreHandler(std::function<void()> handler) {
|
||||
impl->apbp_from_dsp.SetSemaphoreHandler(std::move(handler));
|
||||
}
|
||||
std::uint16_t Teakra::GetSemaphore() const {
|
||||
return impl->apbp_from_dsp.GetSemaphore();
|
||||
}
|
||||
void Teakra::ClearSemaphore(std::uint16_t value) {
|
||||
impl->apbp_from_dsp.ClearSemaphore(value);
|
||||
}
|
||||
void Teakra::MaskSemaphore(std::uint16_t value) {
|
||||
impl->apbp_from_dsp.MaskSemaphore(value);
|
||||
}
|
||||
void Teakra::SetAHBMCallback(const AHBMCallback& callback) {
|
||||
impl->ahbm.SetExternalMemoryCallback(callback.read8, callback.write8,
|
||||
callback.read16, callback.write16,
|
||||
callback.read32, callback.write32);
|
||||
}
|
||||
|
||||
std::uint16_t Teakra::AHBMGetUnitSize(std::uint16_t i) const {
|
||||
return impl->ahbm.GetUnitSize(i);
|
||||
}
|
||||
std::uint16_t Teakra::AHBMGetDirection(std::uint16_t i) const {
|
||||
return impl->ahbm.GetDirection(i);
|
||||
}
|
||||
std::uint16_t Teakra::AHBMGetDmaChannel(std::uint16_t i) const {
|
||||
return impl->ahbm.GetDmaChannel(i);
|
||||
}
|
||||
|
||||
std::uint16_t Teakra::AHBMRead16(std::uint32_t addr) {
|
||||
return impl->ahbm.Read16(0, addr);
|
||||
}
|
||||
void Teakra::AHBMWrite16(std::uint32_t addr, std::uint16_t value) {
|
||||
impl->ahbm.Write16(0, addr, value);
|
||||
}
|
||||
std::uint16_t Teakra::AHBMRead32(std::uint32_t addr) {
|
||||
return impl->ahbm.Read32(0, addr);
|
||||
}
|
||||
void Teakra::AHBMWrite32(std::uint32_t addr, std::uint32_t value) {
|
||||
impl->ahbm.Write32(0, addr, value);
|
||||
}
|
||||
|
||||
void Teakra::SetAudioCallback(std::function<void(std::array<s16, 2>)> callback) {
|
||||
impl->btdmp[0].SetAudioCallback(std::move(callback));
|
||||
}
|
||||
|
||||
std::uint16_t Teakra::ProgramRead(std::uint32_t address) const {
|
||||
return impl->memory_interface.ProgramRead(address);
|
||||
}
|
||||
void Teakra::ProgramWrite(std::uint32_t address, std::uint16_t value) {
|
||||
impl->memory_interface.ProgramWrite(address, value);
|
||||
}
|
||||
std::uint16_t Teakra::DataRead(std::uint16_t address, bool bypass_mmio) {
|
||||
return impl->memory_interface.DataRead(address, bypass_mmio);
|
||||
}
|
||||
void Teakra::DataWrite(std::uint16_t address, std::uint16_t value, bool bypass_mmio) {
|
||||
impl->memory_interface.DataWrite(address, value, bypass_mmio);
|
||||
}
|
||||
std::uint16_t Teakra::DataReadA32(std::uint32_t address) const {
|
||||
return impl->memory_interface.DataReadA32(address);
|
||||
}
|
||||
void Teakra::DataWriteA32(std::uint32_t address, std::uint16_t value) {
|
||||
impl->memory_interface.DataWriteA32(address, value);
|
||||
}
|
||||
std::uint16_t Teakra::MMIORead(std::uint16_t address) {
|
||||
return impl->memory_interface.MMIORead(address);
|
||||
}
|
||||
void Teakra::MMIOWrite(std::uint16_t address, std::uint16_t value) {
|
||||
impl->memory_interface.MMIOWrite(address, value);
|
||||
}
|
||||
|
||||
std::uint16_t Teakra::DMAChan0GetSrcHigh() {
|
||||
u16 active_bak = impl->dma.GetActiveChannel();
|
||||
impl->dma.ActivateChannel(0);
|
||||
u16 ret = impl->dma.GetAddrSrcHigh();
|
||||
impl->dma.ActivateChannel(active_bak);
|
||||
return ret;
|
||||
}
|
||||
std::uint16_t Teakra::DMAChan0GetDstHigh() {
|
||||
u16 active_bak = impl->dma.GetActiveChannel();
|
||||
impl->dma.ActivateChannel(0);
|
||||
u16 ret = impl->dma.GetAddrDstHigh();
|
||||
impl->dma.ActivateChannel(active_bak);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,149 @@
|
|||
#include "teakra/teakra.h"
|
||||
#include "teakra/teakra_c.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct TeakraObject {
|
||||
Teakra::Teakra teakra;
|
||||
};
|
||||
|
||||
TeakraContext* Teakra_Create() {
|
||||
return new TeakraContext;
|
||||
}
|
||||
|
||||
void Teakra_Destroy(TeakraContext* context) {
|
||||
delete context;
|
||||
}
|
||||
|
||||
void Teakra_Reset(TeakraContext* context) {
|
||||
context->teakra.Reset();
|
||||
}
|
||||
|
||||
uint8_t* Teakra_GetDspMemory(TeakraContext* context) {
|
||||
return context->teakra.GetDspMemory().data();
|
||||
}
|
||||
|
||||
int Teakra_SendDataIsEmpty(const TeakraContext* context, uint8_t index) {
|
||||
return context->teakra.SendDataIsEmpty(index);
|
||||
}
|
||||
|
||||
void Teakra_SendData(TeakraContext* context, uint8_t index, uint16_t value) {
|
||||
context->teakra.SendData(index, value);
|
||||
}
|
||||
|
||||
int Teakra_RecvDataIsReady(const TeakraContext* context, uint8_t index) {
|
||||
return context->teakra.RecvDataIsReady(index);
|
||||
}
|
||||
|
||||
uint16_t Teakra_RecvData(TeakraContext* context, uint8_t index) {
|
||||
return context->teakra.RecvData(index);
|
||||
}
|
||||
uint16_t Teakra_PeekRecvData(TeakraContext* context, uint8_t index) {
|
||||
return context->teakra.PeekRecvData(index);
|
||||
}
|
||||
|
||||
void Teakra_SetRecvDataHandler(TeakraContext* context, uint8_t index,
|
||||
Teakra_InterruptCallback handler, void* userdata) {
|
||||
context->teakra.SetRecvDataHandler(index, [=]() { handler(userdata); });
|
||||
}
|
||||
|
||||
void Teakra_SetSemaphore(TeakraContext* context, uint16_t value) {
|
||||
context->teakra.SetSemaphore(value);
|
||||
}
|
||||
void Teakra_ClearSemaphore(TeakraContext* context, uint16_t value) {
|
||||
context->teakra.ClearSemaphore(value);
|
||||
}
|
||||
void Teakra_MaskSemaphore(TeakraContext* context, uint16_t value) {
|
||||
context->teakra.MaskSemaphore(value);
|
||||
}
|
||||
|
||||
void Teakra_SetSemaphoreHandler(TeakraContext* context, Teakra_InterruptCallback handler,
|
||||
void* userdata) {
|
||||
context->teakra.SetSemaphoreHandler([=]() { handler(userdata); });
|
||||
}
|
||||
|
||||
uint16_t Teakra_GetSemaphore(const TeakraContext* context) {
|
||||
return context->teakra.GetSemaphore();
|
||||
}
|
||||
|
||||
uint16_t Teakra_ProgramRead(TeakraContext* context, uint32_t address) {
|
||||
return context->teakra.ProgramRead(address);
|
||||
}
|
||||
void Teakra_ProgramWrite(TeakraContext* context, uint32_t address, uint16_t value) {
|
||||
context->teakra.ProgramWrite(address, value);
|
||||
}
|
||||
uint16_t Teakra_DataRead(TeakraContext* context, uint16_t address, bool bypass_mmio) {
|
||||
return context->teakra.DataRead(address, bypass_mmio);
|
||||
}
|
||||
void Teakra_DataWrite(TeakraContext* context, uint16_t address, uint16_t value, bool bypass_mmio) {
|
||||
context->teakra.DataWrite(address, value, bypass_mmio);
|
||||
}
|
||||
uint16_t Teakra_DataReadA32(TeakraContext* context, uint32_t address) {
|
||||
return context->teakra.DataReadA32(address);
|
||||
}
|
||||
void Teakra_DataWriteA32(TeakraContext* context, uint32_t address, uint16_t value) {
|
||||
context->teakra.DataWriteA32(address, value);
|
||||
}
|
||||
uint16_t Teakra_MMIORead(TeakraContext* context, uint16_t address) {
|
||||
return context->teakra.MMIORead(address);
|
||||
}
|
||||
void Teakra_MMIOWrite(TeakraContext* context, uint16_t address, uint16_t value) {
|
||||
context->teakra.MMIOWrite(address, value);
|
||||
}
|
||||
|
||||
uint16_t Teakra_DMAChan0GetSrcHigh(TeakraContext* context) {
|
||||
return context->teakra.DMAChan0GetSrcHigh();
|
||||
}
|
||||
uint16_t Teakra_DMAChan0GetDstHigh(TeakraContext* context){
|
||||
return context->teakra.DMAChan0GetDstHigh();
|
||||
}
|
||||
|
||||
uint16_t Teakra_AHBMGetUnitSize(TeakraContext* context, uint16_t i) {
|
||||
return context->teakra.AHBMGetUnitSize(i);
|
||||
}
|
||||
uint16_t Teakra_AHBMGetDirection(TeakraContext* context, uint16_t i) {
|
||||
return context->teakra.AHBMGetDirection(i);
|
||||
}
|
||||
uint16_t Teakra_AHBMGetDmaChannel(TeakraContext* context, uint16_t i) {
|
||||
return context->teakra.AHBMGetDmaChannel(i);
|
||||
}
|
||||
|
||||
uint16_t Teakra_AHBMRead16(TeakraContext* context, uint32_t addr) {
|
||||
return context->teakra.AHBMRead16(addr);
|
||||
}
|
||||
void Teakra_AHBMWrite16(TeakraContext* context, uint32_t addr, uint16_t value) {
|
||||
context->teakra.AHBMWrite16(addr, value);
|
||||
}
|
||||
uint16_t Teakra_AHBMRead32(TeakraContext* context, uint32_t addr) {
|
||||
return context->teakra.AHBMRead32(addr);
|
||||
}
|
||||
void Teakra_AHBMWrite32(TeakraContext* context, uint32_t addr, uint32_t value) {
|
||||
context->teakra.AHBMWrite32(addr, value);
|
||||
}
|
||||
|
||||
void Teakra_Run(TeakraContext* context, unsigned cycle) {
|
||||
context->teakra.Run(cycle);
|
||||
}
|
||||
|
||||
void Teakra_SetAHBMCallback(TeakraContext* context,
|
||||
Teakra_AHBMReadCallback8 read8 , Teakra_AHBMWriteCallback8 write8 ,
|
||||
Teakra_AHBMReadCallback16 read16, Teakra_AHBMWriteCallback16 write16,
|
||||
Teakra_AHBMReadCallback32 read32, Teakra_AHBMWriteCallback32 write32,
|
||||
void* userdata) {
|
||||
Teakra::AHBMCallback callback;
|
||||
callback.read8 = [=](uint32_t address) { return read8(userdata, address); };
|
||||
callback.write8 = [=](uint32_t address, uint8_t value) { write8(userdata, address, value); };
|
||||
callback.read16 = [=](uint32_t address) { return read16(userdata, address); };
|
||||
callback.write16 = [=](uint32_t address, uint16_t value) { write16(userdata, address, value); };
|
||||
callback.read32 = [=](uint32_t address) { return read32(userdata, address); };
|
||||
callback.write32 = [=](uint32_t address, uint32_t value) { write32(userdata, address, value); };
|
||||
context->teakra.SetAHBMCallback(callback);
|
||||
}
|
||||
|
||||
|
||||
void Teakra_SetAudioCallback(TeakraContext* context, Teakra_AudioCallback callback,
|
||||
void* userdata) {
|
||||
context->teakra.SetAudioCallback(
|
||||
[=](std::array<std::int16_t, 2> samples) { callback(userdata, samples.data()); });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef COMMON_TYPE_3DS
|
||||
#include "common_types.h"
|
||||
#endif
|
||||
|
||||
constexpr u16 TestSpaceX = 0x6400;
|
||||
constexpr u16 TestSpaceY = 0xCC00;
|
||||
constexpr u16 TestSpaceSize = 0x0200;
|
||||
|
||||
struct State {
|
||||
std::array<u64, 2> a, b;
|
||||
std::array<u32, 2> p;
|
||||
std::array<u16, 8> r;
|
||||
std::array<u16, 2> x, y;
|
||||
u16 stepi0, stepj0, mixp, sv, repc, lc;
|
||||
u16 cfgi, cfgj;
|
||||
u16 stt0, stt1, stt2;
|
||||
u16 mod0, mod1, mod2;
|
||||
std::array<u16, 2> ar;
|
||||
std::array<u16, 4> arp;
|
||||
|
||||
std::array<u16, TestSpaceSize> test_space_x;
|
||||
std::array<u16, TestSpaceSize> test_space_y;
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<State>);
|
||||
|
||||
struct TestCase {
|
||||
State before, after;
|
||||
u16 opcode, expand;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TestCase) == 4312);
|
||||
static_assert(std::is_trivially_copyable_v<TestCase>);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
namespace Teakra::Test {
|
||||
bool GenerateTestCasesToFile(const char* path);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(test_generator
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(test_generator)
|
||||
target_link_libraries(test_generator PRIVATE teakra)
|
||||
target_include_directories(test_generator PRIVATE .)
|
||||
target_compile_options(test_generator PRIVATE ${TEAKRA_CXX_FLAGS})
|
|
@ -0,0 +1,15 @@
|
|||
#include <cstdio>
|
||||
#include "../test_generator.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!Teakra::Test::GenerateTestCasesToFile(argv[1])) {
|
||||
std::fprintf(stderr, "Unable to successfully generate all tests.\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
include(CreateDirectoryGroups)
|
||||
|
||||
add_executable(test_verifier
|
||||
main.cpp
|
||||
)
|
||||
create_target_directory_groups(test_verifier)
|
||||
target_link_libraries(test_verifier PRIVATE teakra)
|
||||
target_include_directories(test_verifier PRIVATE .)
|
||||
target_compile_options(test_verifier PRIVATE ${TEAKRA_CXX_FLAGS})
|
||||
|
||||
|
||||
# Test related stuff
|
||||
set(ASSET_SHA256SUM "baffcd4f805a7480d969401792443a34aa39f813b4f0ae49c6365f1d1f3ce120")
|
||||
if(TEAKRA_RUN_TESTS)
|
||||
message(STATUS "Will run Teakra accuracy tests")
|
||||
# download fixtures if there is none
|
||||
if(NOT EXISTS "${TEAKRA_TEST_ASSETS_DIR}/teaklite2_tests_result")
|
||||
message(STATUS "Downloading required samples...")
|
||||
file(DOWNLOAD
|
||||
"https://liushuyu.b-cdn.net/teaklite2_tests_result_20181208"
|
||||
"${TEAKRA_TEST_ASSETS_DIR}/teaklite2_tests_result"
|
||||
EXPECTED_HASH SHA256=${ASSET_SHA256SUM}
|
||||
SHOW_PROGRESS
|
||||
)
|
||||
else()
|
||||
# check if provided fixtures are good
|
||||
file(SHA256 "${TEAKRA_TEST_ASSETS_DIR}/teaklite2_tests_result" ASSET_CHECKSUM)
|
||||
if(ASSET_SHA256SUM STREQUAL ASSET_CHECKSUM)
|
||||
message(STATUS "Unit test sample looks good.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unit test sample broken. Please remove the file and re-run CMake.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test(NAME tests COMMAND test_verifier "${TEAKRA_TEST_ASSETS_DIR}/teaklite2_tests_result")
|
||||
endif(TEAKRA_RUN_TESTS)
|
|
@ -0,0 +1,253 @@
|
|||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <teakra/disassembler.h>
|
||||
#include "../core_timing.h"
|
||||
#include "../interpreter.h"
|
||||
#include "../memory_interface.h"
|
||||
#include "../shared_memory.h"
|
||||
#include "../test.h"
|
||||
|
||||
std::string Flag16ToString(u16 value, const char* symbols) {
|
||||
std::string result = symbols;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if ((value >> i & 1) == 0)
|
||||
result[15 - i] = '-';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::fprintf(stderr, "A filename argument must be provided. Exiting...\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::FILE, decltype(&std::fclose)> file{std::fopen(argv[1], "rb"), std::fclose};
|
||||
if (!file) {
|
||||
std::fprintf(stderr, "Unable to open file %s. Exiting...\n", argv[1]);
|
||||
return -2;
|
||||
}
|
||||
|
||||
Teakra::CoreTiming core_timing;
|
||||
Teakra::SharedMemory shared_memory;
|
||||
Teakra::MemoryInterfaceUnit miu;
|
||||
Teakra::MemoryInterface memory_interface{shared_memory, miu};
|
||||
Teakra::RegisterState regs;
|
||||
Teakra::Interpreter interpreter(core_timing, regs, memory_interface);
|
||||
|
||||
int i = 0;
|
||||
int passed = 0;
|
||||
int total = 0;
|
||||
int skipped = 0;
|
||||
while (true) {
|
||||
TestCase test_case;
|
||||
if (std::fread(&test_case, sizeof(test_case), 1, file.get()) == 0) {
|
||||
break;
|
||||
}
|
||||
regs.Reset();
|
||||
regs.a = test_case.before.a;
|
||||
regs.b = test_case.before.b;
|
||||
regs.p = test_case.before.p;
|
||||
regs.r = test_case.before.r;
|
||||
regs.x = test_case.before.x;
|
||||
regs.y = test_case.before.y;
|
||||
regs.stepi0 = test_case.before.stepi0;
|
||||
regs.stepj0 = test_case.before.stepj0;
|
||||
regs.mixp = test_case.before.mixp;
|
||||
regs.sv = test_case.before.sv;
|
||||
regs.repc = test_case.before.repc;
|
||||
regs.Lc() = test_case.before.lc;
|
||||
regs.Set<Teakra::cfgi>(test_case.before.cfgi);
|
||||
regs.Set<Teakra::cfgj>(test_case.before.cfgj);
|
||||
regs.Set<Teakra::stt0>(test_case.before.stt0);
|
||||
regs.Set<Teakra::stt1>(test_case.before.stt1);
|
||||
regs.Set<Teakra::stt2>(test_case.before.stt2);
|
||||
regs.Set<Teakra::mod0>(test_case.before.mod0);
|
||||
regs.Set<Teakra::mod1>(test_case.before.mod1);
|
||||
regs.Set<Teakra::mod2>(test_case.before.mod2);
|
||||
regs.Set<Teakra::ar0>(test_case.before.ar[0]);
|
||||
regs.Set<Teakra::ar1>(test_case.before.ar[1]);
|
||||
regs.Set<Teakra::arp0>(test_case.before.arp[0]);
|
||||
regs.Set<Teakra::arp1>(test_case.before.arp[1]);
|
||||
regs.Set<Teakra::arp2>(test_case.before.arp[2]);
|
||||
regs.Set<Teakra::arp3>(test_case.before.arp[3]);
|
||||
|
||||
for (u16 offset = 0; offset < TestSpaceSize; ++offset) {
|
||||
memory_interface.DataWrite(TestSpaceX + offset, test_case.before.test_space_x[offset]);
|
||||
memory_interface.DataWrite(TestSpaceY + offset, test_case.before.test_space_y[offset]);
|
||||
}
|
||||
|
||||
memory_interface.ProgramWrite(0, test_case.opcode);
|
||||
memory_interface.ProgramWrite(1, test_case.expand);
|
||||
|
||||
bool pass = true;
|
||||
bool skip = false;
|
||||
try {
|
||||
interpreter.Run(1);
|
||||
auto Check40 = [&](const char* name, u64 expected, u64 actual) {
|
||||
if (expected != actual) {
|
||||
std::printf("Mismatch: %s: %010" PRIx64 " != %010" PRIx64 "\n", name,
|
||||
expected & 0xFF'FFFF'FFFF, actual & 0xFF'FFFF'FFFF);
|
||||
pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
auto Check32 = [&](const char* name, u32 expected, u32 actual) {
|
||||
if (expected != actual) {
|
||||
std::printf("Mismatch: %s: %08X != %08X\n", name, expected, actual);
|
||||
pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
auto Check = [&](const char* name, u16 expected, u16 actual) {
|
||||
if (expected != actual) {
|
||||
std::printf("Mismatch: %s: %04X != %04X\n", name, expected, actual);
|
||||
pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
auto CheckAddress = [&](const char* name, u16 address, u16 expected, u16 actual) {
|
||||
if (expected != actual) {
|
||||
std::printf("Mismatch: %s%04X: %04X != %04X\n", name, address, expected,
|
||||
actual);
|
||||
pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
auto CheckFlag = [&](const char* name, u16 expected, u16 actual, const char* symbols) {
|
||||
if (expected != actual) {
|
||||
std::printf("Mismatch: %s: %s != %s\n", name,
|
||||
Flag16ToString(expected, symbols).c_str(),
|
||||
Flag16ToString(actual, symbols).c_str());
|
||||
pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
Check40("a0", SignExtend<40>(test_case.after.a[0]), regs.a[0]);
|
||||
Check40("a1", SignExtend<40>(test_case.after.a[1]), regs.a[1]);
|
||||
Check40("b0", SignExtend<40>(test_case.after.b[0]), regs.b[0]);
|
||||
Check40("b1", SignExtend<40>(test_case.after.b[1]), regs.b[1]);
|
||||
Check32("p0", test_case.after.p[0], regs.p[0]);
|
||||
Check32("p1", test_case.after.p[1], regs.p[1]);
|
||||
Check("r0", test_case.after.r[0], regs.r[0]);
|
||||
Check("r1", test_case.after.r[1], regs.r[1]);
|
||||
Check("r2", test_case.after.r[2], regs.r[2]);
|
||||
Check("r3", test_case.after.r[3], regs.r[3]);
|
||||
Check("r4", test_case.after.r[4], regs.r[4]);
|
||||
Check("r5", test_case.after.r[5], regs.r[5]);
|
||||
Check("r6", test_case.after.r[6], regs.r[6]);
|
||||
Check("r7", test_case.after.r[7], regs.r[7]);
|
||||
Check("x0", test_case.after.x[0], regs.x[0]);
|
||||
Check("x1", test_case.after.x[1], regs.x[1]);
|
||||
Check("y0", test_case.after.y[0], regs.y[0]);
|
||||
Check("y1", test_case.after.y[1], regs.y[1]);
|
||||
Check("stepi0", test_case.after.stepi0, regs.stepi0);
|
||||
Check("stepj0", test_case.after.stepj0, regs.stepj0);
|
||||
Check("mixp", test_case.after.mixp, regs.mixp);
|
||||
Check("sv", test_case.after.sv, regs.sv);
|
||||
Check("repc", test_case.after.repc, regs.repc);
|
||||
Check("lc", test_case.after.lc, regs.Lc());
|
||||
CheckFlag("cfgi", test_case.after.cfgi, regs.Get<Teakra::cfgi>(), "mmmmmmmmmsssssss");
|
||||
CheckFlag("cfgj", test_case.after.cfgj, regs.Get<Teakra::cfgj>(), "mmmmmmmmmsssssss");
|
||||
CheckFlag("stt0", test_case.after.stt0, regs.Get<Teakra::stt0>(), "####C###ZMNVCELL");
|
||||
CheckFlag("stt1", test_case.after.stt1, regs.Get<Teakra::stt1>(), "QP#########R####");
|
||||
CheckFlag("stt2", test_case.after.stt2, regs.Get<Teakra::stt2>(), "LBBB####mm##V21I");
|
||||
CheckFlag("mod0", test_case.after.mod0, regs.Get<Teakra::mod0>(), "#QQ#PPooSYY###SS");
|
||||
CheckFlag("mod1", test_case.after.mod1, regs.Get<Teakra::mod1>(), "???B####pppppppp");
|
||||
CheckFlag("mod2", test_case.after.mod2, regs.Get<Teakra::mod2>(), "7654321m7654321M");
|
||||
CheckFlag("ar0", test_case.after.ar[0], regs.Get<Teakra::ar0>(), "RRRRRRoosssoosss");
|
||||
CheckFlag("ar1", test_case.after.ar[1], regs.Get<Teakra::ar1>(), "RRRRRRoosssoosss");
|
||||
CheckFlag("arp0", test_case.after.arp[0], regs.Get<Teakra::arp0>(), "#RR#RRjjjjjiiiii");
|
||||
CheckFlag("arp1", test_case.after.arp[1], regs.Get<Teakra::arp1>(), "#RR#RRjjjjjiiiii");
|
||||
CheckFlag("arp2", test_case.after.arp[2], regs.Get<Teakra::arp2>(), "#RR#RRjjjjjiiiii");
|
||||
CheckFlag("arp3", test_case.after.arp[3], regs.Get<Teakra::arp3>(), "#RR#RRjjjjjiiiii");
|
||||
|
||||
for (u16 offset = 0; offset < TestSpaceSize; ++offset) {
|
||||
CheckAddress("memory_", (TestSpaceX + offset), test_case.after.test_space_x[offset],
|
||||
memory_interface.DataRead(TestSpaceX + offset));
|
||||
CheckAddress("memory_", (TestSpaceY + offset), test_case.after.test_space_y[offset],
|
||||
memory_interface.DataRead(TestSpaceY + offset));
|
||||
}
|
||||
++total;
|
||||
} catch (const Teakra::UnimplementedException&) {
|
||||
std::printf("Skipped one unimplemented case\n");
|
||||
pass = false;
|
||||
skip = true;
|
||||
++skipped;
|
||||
}
|
||||
|
||||
if (pass) {
|
||||
++passed;
|
||||
} else {
|
||||
Teakra::Disassembler::ArArpSettings ar_arp;
|
||||
ar_arp.ar = test_case.before.ar;
|
||||
ar_arp.arp = test_case.before.arp;
|
||||
std::printf(
|
||||
"Test case %d: %04X %04X %s\n", i, test_case.opcode, test_case.expand,
|
||||
Teakra::Disassembler::Do(test_case.opcode, test_case.expand, ar_arp).c_str());
|
||||
if (!skip) {
|
||||
std::printf("before:\n");
|
||||
std::printf("a0 = %010" PRIx64 "; a1 = %010" PRIx64 "\n",
|
||||
test_case.before.a[0] & 0xFF'FFFF'FFFF,
|
||||
test_case.before.a[1] & 0xFF'FFFF'FFFF);
|
||||
std::printf("b0 = %010" PRIx64 "; b1 = %010" PRIx64 "\n",
|
||||
test_case.before.b[0] & 0xFF'FFFF'FFFF,
|
||||
test_case.before.b[1] & 0xFF'FFFF'FFFF);
|
||||
std::printf("p0 = %08X; p1 = %08X\n", test_case.before.p[0], test_case.before.p[1]);
|
||||
std::printf("x0 = %04X; x1 = %04X\n", test_case.before.x[0], test_case.before.x[1]);
|
||||
std::printf("y0 = %04X; y1 = %04X\n", test_case.before.y[0], test_case.before.y[1]);
|
||||
std::printf("r0 = %04X; r1 = %04X; r2 = %04X; r3 = %04X\n", test_case.before.r[0],
|
||||
test_case.before.r[1], test_case.before.r[2], test_case.before.r[3]);
|
||||
std::printf("r4 = %04X; r5 = %04X; r6 = %04X; r7 = %04X\n", test_case.before.r[4],
|
||||
test_case.before.r[5], test_case.before.r[6], test_case.before.r[7]);
|
||||
std::printf("stepi0 = %04X\n", test_case.before.stepi0);
|
||||
std::printf("stepj0 = %04X\n", test_case.before.stepj0);
|
||||
std::printf("mixp = %04X\n", test_case.before.mixp);
|
||||
std::printf("sv = %04X\n", test_case.before.sv);
|
||||
std::printf("repc = %04X\n", test_case.before.repc);
|
||||
std::printf("lc = %04X\n", test_case.before.lc);
|
||||
std::printf("cfgi = %s\n",
|
||||
Flag16ToString(test_case.before.cfgi, "mmmmmmmmmsssssss").c_str());
|
||||
std::printf("cfgj = %s\n",
|
||||
Flag16ToString(test_case.before.cfgj, "mmmmmmmmmsssssss").c_str());
|
||||
std::printf("stt0 = %s\n",
|
||||
Flag16ToString(test_case.before.stt0, "####C###ZMNVCELL").c_str());
|
||||
std::printf("stt1 = %s\n",
|
||||
Flag16ToString(test_case.before.stt1, "QP#########R####").c_str());
|
||||
std::printf("stt2 = %s\n",
|
||||
Flag16ToString(test_case.before.stt2, "LBBB####mm##V21I").c_str());
|
||||
std::printf("mod0 = %s\n",
|
||||
Flag16ToString(test_case.before.mod0, "#QQ#PPooSYY###SS").c_str());
|
||||
std::printf("mod1 = %s\n",
|
||||
Flag16ToString(test_case.before.mod1, "jicB####pppppppp").c_str());
|
||||
std::printf("mod2 = %s\n",
|
||||
Flag16ToString(test_case.before.mod2, "7654321m7654321M").c_str());
|
||||
std::printf("ar0 = %s\n",
|
||||
Flag16ToString(test_case.before.ar[0], "RRRRRRoosssoosss").c_str());
|
||||
std::printf("ar1 = %s\n",
|
||||
Flag16ToString(test_case.before.ar[1], "RRRRRRoosssoosss").c_str());
|
||||
std::printf("arp0 = %s\n",
|
||||
Flag16ToString(test_case.before.arp[0], "#RR#RRiiiiijjjjj").c_str());
|
||||
std::printf("arp1 = %s\n",
|
||||
Flag16ToString(test_case.before.arp[1], "#RR#RRiiiiijjjjj").c_str());
|
||||
std::printf("arp2 = %s\n",
|
||||
Flag16ToString(test_case.before.arp[2], "#RR#RRiiiiijjjjj").c_str());
|
||||
std::printf("arp3 = %s\n",
|
||||
Flag16ToString(test_case.before.arp[3], "#RR#RRiiiiijjjjj").c_str());
|
||||
std::printf("FAILED\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
std::printf("%d / %d passed, %d skipped\n", passed, total, skipped);
|
||||
|
||||
if (passed < total) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
#include "crash.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
void Timer::Reset() {
|
||||
update_mmio = 0;
|
||||
pause = 0;
|
||||
count_mode = CountMode::Single;
|
||||
scale = 0;
|
||||
|
||||
start_high = 0;
|
||||
start_low = 0;
|
||||
counter = 0;
|
||||
counter_high = 0;
|
||||
counter_low = 0;
|
||||
}
|
||||
|
||||
void Timer::Restart() {
|
||||
ASSERT(static_cast<u16>(count_mode) < 4);
|
||||
if (count_mode != CountMode::FreeRunning) {
|
||||
counter = ((u32)start_high << 16) | start_low;
|
||||
UpdateMMIO();
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::Tick() {
|
||||
ASSERT(static_cast<u16>(count_mode) < 4);
|
||||
ASSERT(scale == 0);
|
||||
if (pause)
|
||||
return;
|
||||
if (count_mode == CountMode::EventCount)
|
||||
return;
|
||||
if (counter == 0) {
|
||||
if (count_mode == CountMode::AutoRestart) {
|
||||
Restart();
|
||||
} else if (count_mode == CountMode::FreeRunning) {
|
||||
counter = 0xFFFFFFFF;
|
||||
UpdateMMIO();
|
||||
}
|
||||
} else {
|
||||
--counter;
|
||||
UpdateMMIO();
|
||||
if (counter == 0)
|
||||
interrupt_handler();
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::TickEvent() {
|
||||
if (pause)
|
||||
return;
|
||||
if (count_mode != CountMode::EventCount)
|
||||
return;
|
||||
if (counter == 0)
|
||||
return;
|
||||
--counter;
|
||||
UpdateMMIO();
|
||||
if (counter == 0)
|
||||
interrupt_handler();
|
||||
}
|
||||
|
||||
void Timer::UpdateMMIO() {
|
||||
if (!update_mmio)
|
||||
return;
|
||||
counter_high = counter >> 16;
|
||||
counter_low = counter & 0xFFFF;
|
||||
}
|
||||
|
||||
u64 Timer::GetMaxSkip() const {
|
||||
if (pause || count_mode == CountMode::EventCount)
|
||||
return Infinity;
|
||||
|
||||
if (counter == 0) {
|
||||
if (count_mode == CountMode::AutoRestart) {
|
||||
return ((u32)start_high << 16) | start_low;
|
||||
} else if (count_mode == CountMode::FreeRunning) {
|
||||
return 0xFFFFFFFF;
|
||||
} else /*Single*/ {
|
||||
return Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
return counter - 1;
|
||||
}
|
||||
|
||||
void Timer::Skip(u64 ticks) {
|
||||
if (pause || count_mode == CountMode::EventCount)
|
||||
return;
|
||||
|
||||
if (counter == 0) {
|
||||
u32 reset;
|
||||
if (count_mode == CountMode::AutoRestart) {
|
||||
reset = ((u32)start_high << 16) | start_low;
|
||||
} else if (count_mode == CountMode::FreeRunning) {
|
||||
reset = 0xFFFFFFFF;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
ASSERT(reset >= ticks);
|
||||
counter = reset - ((u32)ticks - 1);
|
||||
} else {
|
||||
ASSERT(counter > ticks);
|
||||
counter -= (u32)ticks;
|
||||
}
|
||||
|
||||
UpdateMMIO();
|
||||
}
|
||||
|
||||
Timer::Timer(CoreTiming& core_timing) {
|
||||
core_timing.RegisterCallbacks(this);
|
||||
}
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "common_types.h"
|
||||
#include "core_timing.h"
|
||||
|
||||
namespace Teakra {
|
||||
|
||||
class Timer : public CoreTiming::Callbacks {
|
||||
public:
|
||||
Timer(CoreTiming& core_timing);
|
||||
|
||||
enum class CountMode : u16 {
|
||||
Single = 0,
|
||||
AutoRestart = 1,
|
||||
FreeRunning = 2,
|
||||
EventCount = 3,
|
||||
};
|
||||
|
||||
void Reset();
|
||||
|
||||
void Restart();
|
||||
void Tick() override;
|
||||
void TickEvent();
|
||||
u64 GetMaxSkip() const override;
|
||||
void Skip(u64 ticks) override;
|
||||
|
||||
u16 update_mmio = 0;
|
||||
u16 pause = 0;
|
||||
CountMode count_mode = CountMode::Single;
|
||||
u16 scale = 0;
|
||||
|
||||
u16 start_high = 0;
|
||||
u16 start_low = 0;
|
||||
u32 counter = 0;
|
||||
u16 counter_high = 0;
|
||||
u16 counter_low = 0;
|
||||
|
||||
void SetInterruptHandler(std::function<void()> handler) {
|
||||
interrupt_handler = std::move(handler);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> interrupt_handler;
|
||||
|
||||
void UpdateMMIO();
|
||||
|
||||
class TimerTimingCallbacks;
|
||||
};
|
||||
|
||||
} // namespace Teakra
|
|
@ -0,0 +1,66 @@
|
|||
# Timer
|
||||
|
||||
## MMIO Layout
|
||||
|
||||
The following MMIO definition is extracted from Lauterbach's Teak debugger. Some of them are untested.
|
||||
```
|
||||
Timer 0
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0020 | TM | GP| CS| BP|RES| MU| PC| CT| TP| | CM | TS |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0022 | | EW|
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0024 | START_COUNT_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0026 | START_COUNT_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x0028 | COUNTER_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x002A | COUNTER_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x002C | PWM_COUNTER_L |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|+0x002E | PWM_COUNTER_H |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
|
||||
TS: time scale
|
||||
- 0: /1
|
||||
- 1: /2
|
||||
- 2: /4
|
||||
- 3: /16
|
||||
CM: count mode
|
||||
- 0: single count
|
||||
- 1: auto restart
|
||||
- 2: free running
|
||||
- 3: event count
|
||||
- 4: watchdog mode 1
|
||||
- 5: watchdog mode 2
|
||||
- 6: watchdog mode 3
|
||||
TP: output signal polarity
|
||||
CT: 1 to clear output signal
|
||||
PC: 1 to pause the counter
|
||||
MU: 1 to enable COUNTER_L/_H register update
|
||||
RES: 1 to restart the counter
|
||||
BP: 1 to enable breakpoint requests
|
||||
CS: clock source
|
||||
- 0: internal clock
|
||||
- 1: external clock
|
||||
GP: ?
|
||||
TM: output signal clearing method
|
||||
- 0: by setting CT manually
|
||||
- 1: after two cycles
|
||||
- 2: after four cycles
|
||||
- 3: after eight cycles
|
||||
|
||||
EW: 1 to decrement counter in event count mode / to reload watchdog in watch dog mode
|
||||
START_COUNT_L, START_COUNT_H: the restart value for counter. Loaded to the counter on restarting
|
||||
COUNTER_L, COUNTER_H: the value of the counter
|
||||
PWM_COUNTER_L, PWM_COUNTER_H: the restart value for PWM counter. Loaded to the PWM counter on restarting
|
||||
|
||||
Timer 1
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
| +0x0030 | Same layout as Timer 0 |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
| ... |
|
||||
+-----------#---+---+---+---#---+---+---+---#---+---+---+---#---+---+---+---#
|
||||
```
|
Loading…
Reference in New Issue