Merge pull request #10766 from xperia64/dsp_format_fun

DSP accelerator improvements
This commit is contained in:
JMC47
2025-05-09 19:04:23 -04:00
committed by GitHub
18 changed files with 567 additions and 275 deletions

View File

@ -11,86 +11,152 @@
namespace DSP
{
u16 Accelerator::ReadD3()
u16 Accelerator::GetCurrentSample()
{
u16 val = 0;
switch (m_sample_format)
// The lower two bits of the sample format indicate the access size
switch (m_sample_format.size)
{
case 0x5: // u8 reads
case FormatSize::Size4Bit:
val = ReadMemory(m_current_address >> 1);
if (m_current_address & 1)
val &= 0xf;
else
val >>= 4;
break;
case FormatSize::Size8Bit:
val = ReadMemory(m_current_address);
m_current_address++;
break;
case 0x6: // u16 reads
case FormatSize::Size16Bit:
val = (ReadMemory(m_current_address * 2) << 8) | ReadMemory(m_current_address * 2 + 1);
m_current_address++;
break;
default:
ERROR_LOG_FMT(DSPLLE, "dsp_read_aram_d3() - unknown format {:#x}", m_sample_format);
default: // produces garbage, but affects the current address
ERROR_LOG_FMT(DSPLLE, "GetCurrentSample() - bad format {:#x}", m_sample_format.hex);
break;
}
if (m_current_address >= m_end_address)
{
// Set address back to start address. (never seen this here!)
m_current_address = m_start_address;
}
return val;
}
void Accelerator::WriteD3(u16 value)
u16 Accelerator::ReadRaw()
{
u16 val = GetCurrentSample();
if (m_sample_format.size != FormatSize::SizeInvalid)
{
m_current_address++;
}
else
{
m_current_address = (m_current_address & ~3) | ((m_current_address + 1) & 3);
}
// There are edge cases that are currently not handled here in u4 and u8 mode
// In u8 mode, if ea & 1 == 0 and ca == ea + 1, the accelerator can be read twice. Upon the second
// read, the data returned appears to be the other half of the u16 from the first read, and the
// DSP resets the current address and throws exception 3
// During these reads, ca is not incremented normally.
// Instead, incrementing ca works like this: ca = (ca & ~ 3) | ((ca + 1) & 3)
// u4 mode extends this further.
// When ea & 3 == 0, and ca in [ea + 1, ea + 3], the accelerator can be read (4 - (ca - ea - 1))
// times. On the last read, the data returned appears to be the remaining nibble of the u16 from
// the first read, and the DSP resets the current address and throws exception 3
// When ea & 3 == 1, and ca in [ea + 1, ea + 2], the accelerator can be read (4 - (ca - ea - 1))
// times. On the last read, the data returned appears to be the remaining nibble of the u16 from
// the first read, and the DSP resets the current address and throws exception 3
// When ea & 3 == 2, and ca == ea + 1, the accelerator can be read 4 times. On the last read, the
// data returned appears to be the remaining nibble of the u16 from the first read, and the DSP
// resets the current address and throws exception 3
// There are extra extra edge cases if ca, ea, and potentially other registers are adjusted during
// this pre-reset phase
// The cleanest way to emulate the normal non-edge behavior is to only reset things if we just
// read the end address. If the current address is larger than the end address (and not in the
// edge range), it ignores the end address
if (m_current_address - 1 == m_end_address)
{
// Set address back to start address (confirmed on hardware)
m_current_address = m_start_address;
OnRawReadEndException();
}
SetCurrentAddress(m_current_address);
return val;
}
void Accelerator::WriteRaw(u16 value)
{
// Zelda ucode writes a bunch of zeros to ARAM through d3 during
// initialization. Don't know if it ever does it later, too.
// Pikmin 2 Wii writes non-stop to 0x10008000-0x1000801f (non-zero values too)
// Zelda TP Wii writes non-stop to 0x10000000-0x1000001f (non-zero values too)
switch (m_sample_format)
// Writes only seem to be accepted when the upper most bit of the address is set
if (m_current_address & 0x80000000)
{
case 0xA: // u16 writes
// The format doesn't matter for raw writes; all writes are u16 and the address is treated as if
// we are in a 16-bit format
WriteMemory(m_current_address * 2, value >> 8);
WriteMemory(m_current_address * 2 + 1, value & 0xFF);
m_current_address++;
break;
default:
ERROR_LOG_FMT(DSPLLE, "dsp_write_aram_d3() - unknown format {:#x}", m_sample_format);
break;
OnRawWriteEndException();
}
else
{
ERROR_LOG_FMT(DSPLLE, "WriteRaw() - tried to write to address {:#x} without high bit set",
m_current_address);
}
}
u16 Accelerator::Read(const s16* coefs)
u16 Accelerator::ReadSample(const s16* coefs)
{
if (m_reads_stopped)
return 0x0000;
u16 val;
u8 step_size_bytes = 0;
if (m_sample_format.unk != 0)
{
WARN_LOG_FMT(DSPLLE, "ReadSample() format {:#x} has unknown upper bits set",
m_sample_format.hex);
}
// let's do the "hardware" decode DSP_FORMAT is interesting - the Zelda
// ucode seems to indicate that the bottom two bits specify the "read size"
// and the address multiplier. The bits above that may be things like sign
// extension and do/do not use ADPCM. It also remains to be figured out
// whether there's a difference between the usual accelerator "read
// address" and 0xd3.
switch (m_sample_format)
u16 val = 0;
u8 step_size = 0;
s16 raw_sample;
if (m_sample_format.decode == FormatDecode::MMIOPCMNoInc ||
m_sample_format.decode == FormatDecode::MMIOPCMInc)
{
case 0x00: // ADPCM audio
// The addresses can be complete nonsense in either of these modes
raw_sample = m_input;
}
else
{
raw_sample = GetCurrentSample();
}
int coef_idx = (m_pred_scale >> 4) & 0x7;
s32 coef1 = coefs[coef_idx * 2 + 0];
s32 coef2 = coefs[coef_idx * 2 + 1];
switch (m_sample_format.decode)
{
case FormatDecode::ADPCM: // ADPCM audio
{
// ADPCM really only supports 4-bit decoding, but for larger values on hardware, it just ignores
// the upper 12 bits
raw_sample &= 0xF;
int scale = 1 << (m_pred_scale & 0xF);
int coef_idx = (m_pred_scale >> 4) & 0x7;
s32 coef1 = coefs[coef_idx * 2 + 0];
s32 coef2 = coefs[coef_idx * 2 + 1];
if (raw_sample >= 8)
raw_sample -= 16;
int temp = (m_current_address & 1) ? (ReadMemory(m_current_address >> 1) & 0xF) :
(ReadMemory(m_current_address >> 1) >> 4);
if (temp >= 8)
temp -= 16;
s32 val32 = (scale * temp) + ((0x400 + coef1 * m_yn1 + coef2 * m_yn2) >> 11);
s32 val32 = (scale * raw_sample) + ((0x400 + coef1 * m_yn1 + coef2 * m_yn2) >> 11);
val = static_cast<s16>(std::clamp<s32>(val32, -0x7FFF, 0x7FFF));
step_size_bytes = 2;
step_size = 2;
m_yn2 = m_yn1;
m_yn1 = val;
@ -100,6 +166,7 @@ u16 Accelerator::Read(const s16* coefs)
// the ACCOV exception does not fire at all, the predscale register is not updated,
// and if the end address is 16-byte aligned, the DSP loops to start_address + 1
// instead of start_address.
// TODO: This probably needs to be adjusted when using 8 or 16-bit accesses.
if ((m_end_address & 0xf) == 0x0 && m_current_address == m_end_address)
{
m_current_address = m_start_address + 1;
@ -113,47 +180,54 @@ u16 Accelerator::Read(const s16* coefs)
{
m_pred_scale = ReadMemory((m_current_address & ~15) >> 1);
m_current_address += 2;
step_size_bytes += 2;
step_size += 2;
}
break;
}
case 0x0A: // 16-bit PCM audio
val = (ReadMemory(m_current_address * 2) << 8) | ReadMemory(m_current_address * 2 + 1);
case FormatDecode::MMIOPCMNoInc:
case FormatDecode::PCM: // 16-bit PCM audio
case FormatDecode::MMIOPCMInc:
{
// Gain seems to only apply for PCM decoding
u8 gain_shift = 0;
switch (m_sample_format.gain_scale)
{
case FormatGainScale::GainScale2048:
gain_shift = 11; // x / 2048 = x >> 11
break;
case FormatGainScale::GainScale1:
gain_shift = 0; // x / 1 = x >> 0
break;
case FormatGainScale::GainScale65536:
gain_shift = 16; // x / 65536 = x >> 16
break;
default:
ERROR_LOG_FMT(DSPLLE, "ReadSample() invalid gain mode in format {:#x}", m_sample_format.hex);
break;
}
s32 val32 = ((static_cast<s32>(m_gain) * raw_sample) >> gain_shift) +
(((coef1 * m_yn1) >> gain_shift) + ((coef2 * m_yn2) >> gain_shift));
val = static_cast<s16>(val32);
m_yn2 = m_yn1;
m_yn1 = val;
step_size_bytes = 2;
m_current_address += 1;
break;
case 0x19: // 8-bit PCM audio
val = ReadMemory(m_current_address) << 8;
m_yn2 = m_yn1;
m_yn1 = val;
step_size_bytes = 2;
m_current_address += 1;
break;
default:
ERROR_LOG_FMT(DSPLLE, "dsp_read_accelerator() - unknown format {:#x}", m_sample_format);
step_size_bytes = 2;
m_current_address += 1;
val = 0;
step_size = 2;
if (m_sample_format.decode != FormatDecode::MMIOPCMNoInc)
{
m_current_address += 1;
}
break;
}
// TODO: Take GAIN into account
// adpcm = 0, pcm8 = 0x100, pcm16 = 0x800
// games using pcm8 : Phoenix Wright Ace Attorney (WiiWare), Megaman 9-10 (WiiWare)
// games using pcm16: GC Sega games, ...
}
// Check for loop.
// Somehow, YN1 and YN2 must be initialized with their "loop" values,
// so yeah, it seems likely that we should raise an exception to let
// the DSP program do that, at least if DSP_FORMAT == 0x0A.
if (m_current_address == (m_end_address + step_size_bytes - 1))
// YN1 and YN2 need to be initialized with their "loop" values,
// which is usually done upon this exception.
if (m_current_address == (m_end_address + step_size - 1))
{
// Set address back to start address.
m_current_address = m_start_address;
m_reads_stopped = true;
OnEndException();
OnSampleReadEndException();
}
SetCurrentAddress(m_current_address);
@ -192,7 +266,12 @@ void Accelerator::SetCurrentAddress(u32 address)
void Accelerator::SetSampleFormat(u16 format)
{
m_sample_format = format;
m_sample_format.hex = format;
}
void Accelerator::SetGain(s16 gain)
{
m_gain = gain;
}
void Accelerator::SetYn1(s16 yn1)
@ -210,4 +289,10 @@ void Accelerator::SetPredScale(u16 pred_scale)
{
m_pred_scale = pred_scale & 0x7f;
}
void Accelerator::SetInput(u16 input)
{
m_input = input;
}
} // namespace DSP

View File

@ -3,6 +3,7 @@
#pragma once
#include "Common/BitField.h"
#include "Common/CommonTypes.h"
class PointerWrap;
@ -14,41 +15,85 @@ class Accelerator
public:
virtual ~Accelerator() = default;
u16 Read(const s16* coefs);
u16 ReadSample(const s16* coefs);
// Zelda ucode reads ARAM through 0xffd3.
u16 ReadD3();
void WriteD3(u16 value);
u16 ReadRaw();
void WriteRaw(u16 value);
u32 GetStartAddress() const { return m_start_address; }
u32 GetEndAddress() const { return m_end_address; }
u32 GetCurrentAddress() const { return m_current_address; }
u16 GetSampleFormat() const { return m_sample_format; }
u16 GetSampleFormat() const { return m_sample_format.hex; }
s16 GetGain() const { return m_gain; }
s16 GetYn1() const { return m_yn1; }
s16 GetYn2() const { return m_yn2; }
u16 GetPredScale() const { return m_pred_scale; }
u16 GetInput() const { return m_input; }
void SetStartAddress(u32 address);
void SetEndAddress(u32 address);
void SetCurrentAddress(u32 address);
void SetSampleFormat(u16 format);
void SetGain(s16 gain);
void SetYn1(s16 yn1);
void SetYn2(s16 yn2);
void SetPredScale(u16 pred_scale);
void SetInput(u16 input);
void DoState(PointerWrap& p);
protected:
virtual void OnEndException() = 0;
virtual void OnRawReadEndException() = 0;
virtual void OnRawWriteEndException() = 0;
virtual void OnSampleReadEndException() = 0;
virtual u8 ReadMemory(u32 address) = 0;
virtual void WriteMemory(u32 address, u8 value) = 0;
u16 GetCurrentSample();
// DSP accelerator registers.
u32 m_start_address = 0;
u32 m_end_address = 0;
u32 m_current_address = 0;
u16 m_sample_format = 0;
enum class FormatSize : u16
{
Size4Bit = 0,
Size8Bit = 1,
Size16Bit = 2,
SizeInvalid = 3
};
enum class FormatDecode : u16
{
ADPCM = 0, // ADPCM reads from ARAM, ACCA increments
MMIOPCMNoInc = 1, // PCM Reads from ACIN, ACCA doesn't increment
PCM = 2, // PCM reads from ARAM, ACCA increments
MMIOPCMInc = 3 // PCM reads from ACIN, ACCA increments
};
// When reading samples (at least in PCM mode), they are multiplied by the gain, then divided by
// the value specified here
enum class FormatGainScale : u16
{
GainScale2048 = 0,
GainScale1 = 1,
GainScale65536 = 2,
GainScaleInvalid = 3
};
union SampleFormat
{
u16 hex;
BitField<0, 2, FormatSize> size;
BitField<2, 2, FormatDecode> decode;
BitField<4, 2, FormatGainScale> gain_scale;
BitField<6, 10, u16> unk;
} m_sample_format{0};
s16 m_gain = 0;
s16 m_yn1 = 0;
s16 m_yn2 = 0;
u16 m_pred_scale = 0;
u16 m_input = 0;
// When an ACCOV is triggered, the accelerator stops reading back anything
// and updating the current address register, unless the YN2 register is written to.

View File

@ -109,7 +109,18 @@ public:
protected:
u8 ReadMemory(u32 address) override { return Host::ReadHostMemory(address); }
void WriteMemory(u32 address, u8 value) override { Host::WriteHostMemory(value, address); }
void OnEndException() override { m_dsp.SetException(ExceptionType::AcceleratorOverflow); }
void OnRawReadEndException() override
{
m_dsp.SetException(ExceptionType::AcceleratorRawReadOverflow);
}
void OnRawWriteEndException() override
{
m_dsp.SetException(ExceptionType::AcceleratorRawWriteOverflow);
}
void OnSampleReadEndException() override
{
m_dsp.SetException(ExceptionType::AcceleratorSampleReadOverflow);
}
private:
SDSP& m_dsp;

View File

@ -152,10 +152,10 @@ enum : u32
DSP_DSMAH = 0xce, // DSP DMA Address High (External)
DSP_DSMAL = 0xcf, // DSP DMA Address Low (External)
DSP_FORMAT = 0xd1, // Sample format
DSP_ACUNK = 0xd2, // Set to 3 on my dumps
DSP_ACDATA1 = 0xd3, // Used only by Zelda ucodes
DSP_ACSAH = 0xd4, // Start of loop
DSP_FORMAT = 0xd1, // Sample format
DSP_ACUNK = 0xd2, // Set to 3 on my dumps
DSP_ACDRAW = 0xd3, // Raw accelerator accesses
DSP_ACSAH = 0xd4, // Start of loop
DSP_ACSAL = 0xd5,
DSP_ACEAH = 0xd6, // End of sample (and loop)
DSP_ACEAL = 0xd7,
@ -164,9 +164,9 @@ enum : u32
DSP_PRED_SCALE = 0xda, // ADPCM predictor and scale
DSP_YN1 = 0xdb,
DSP_YN2 = 0xdc,
DSP_ACCELERATOR = 0xdd, // ADPCM accelerator read. Used by AX.
DSP_ACDSAMP = 0xdd, // Accelerator sample reads, processed differently depending on FORMAT
DSP_GAIN = 0xde,
DSP_ACUNK2 = 0xdf, // Set to 0xc on my dumps
DSP_ACIN = 0xdf, // Feeds PCM samples written here
DSP_AMDM = 0xef, // ARAM DMA Request Mask 0: DMA with ARAM unmasked 1: masked
@ -226,13 +226,13 @@ enum : u16
// Exception vectors
enum class ExceptionType
{
StackOverflow = 1, // 0x0002 stack under/over flow
EXP_2 = 2, // 0x0004
EXP_3 = 3, // 0x0006
EXP_4 = 4, // 0x0008
AcceleratorOverflow = 5, // 0x000a accelerator address overflow
EXP_6 = 6, // 0x000c
ExternalInterrupt = 7 // 0x000e external int (message from CPU)
StackOverflow = 1, // 0x0002 stack under/over flow
EXP_2 = 2, // 0x0004
AcceleratorRawReadOverflow = 3, // 0x0006 accelerator raw read address overflow
AcceleratorRawWriteOverflow = 4, // 0x0008 accelerator raw write address overflow
AcceleratorSampleReadOverflow = 5, // 0x000a accelerator sample reads address overflow
EXP_6 = 6, // 0x000c
ExternalInterrupt = 7 // 0x000e external int (message from CPU)
};
enum class Mailbox

View File

@ -122,12 +122,6 @@ void SDSP::WriteIFX(u32 address, u16 value)
m_ifx_regs[DSP_DSBL] = 0;
break;
case DSP_GAIN:
if (value != 0)
{
DEBUG_LOG_FMT(DSPLLE, "Gain Written: {:#06x}", value);
}
[[fallthrough]];
case DSP_DSPA:
case DSP_DSMAH:
case DSP_DSMAL:
@ -161,6 +155,9 @@ void SDSP::WriteIFX(u32 address, u16 value)
case DSP_FORMAT:
m_accelerator->SetSampleFormat(value);
break;
case DSP_GAIN:
m_accelerator->SetGain(value);
break;
case DSP_YN1:
m_accelerator->SetYn1(value);
break;
@ -170,8 +167,11 @@ void SDSP::WriteIFX(u32 address, u16 value)
case DSP_PRED_SCALE:
m_accelerator->SetPredScale(value);
break;
case DSP_ACDATA1: // Accelerator write (Zelda type) - "UnkZelda"
m_accelerator->WriteD3(value);
case DSP_ACDRAW: // Raw accelerator write
m_accelerator->WriteRaw(value);
break;
case DSP_ACIN:
m_accelerator->SetInput(value);
break;
default:
@ -231,16 +231,20 @@ u16 SDSP::ReadIFXImpl(u16 address)
return static_cast<u16>(m_accelerator->GetCurrentAddress());
case DSP_FORMAT:
return m_accelerator->GetSampleFormat();
case DSP_GAIN:
return m_accelerator->GetGain();
case DSP_YN1:
return m_accelerator->GetYn1();
case DSP_YN2:
return m_accelerator->GetYn2();
case DSP_PRED_SCALE:
return m_accelerator->GetPredScale();
case DSP_ACCELERATOR: // ADPCM Accelerator reads
return m_accelerator->Read(reinterpret_cast<s16*>(&m_ifx_regs[DSP_COEF_A1_0]));
case DSP_ACDATA1: // Accelerator reads (Zelda type) - "UnkZelda"
return m_accelerator->ReadD3();
case DSP_ACDSAMP: // Processed sample accelerator read
return m_accelerator->ReadSample(reinterpret_cast<s16*>(&m_ifx_regs[DSP_COEF_A1_0]));
case DSP_ACDRAW: // Raw accelerator read
return m_accelerator->ReadRaw();
case DSP_ACIN:
return m_accelerator->GetInput();
default:
{

View File

@ -400,21 +400,21 @@ const std::array<pdlabel_t, 96> pdlabels =
{0xffcf, "DSMAL", "DSP DMA Mem Address L",},
{0xffd0, "0xffd0",nullptr,},
{0xffd1, "SampleFormat", "SampleFormat",},
{0xffd1, "FORMAT", "Accelerator sample format",},
{0xffd2, "0xffd2",nullptr,},
{0xffd3, "UnkZelda", "Unk Zelda reads/writes from/to it",},
{0xffd3, "ACDRAW", "Accelerator raw read/write from ARAM",},
{0xffd4, "ACSAH", "Accelerator start address H",},
{0xffd5, "ACSAL", "Accelerator start address L",},
{0xffd6, "ACEAH", "Accelerator end address H",},
{0xffd7, "ACEAL", "Accelerator end address L",},
{0xffd8, "ACCAH", "Accelerator current address H",},
{0xffd9, "ACCAL", "Accelerator current address L",},
{0xffda, "pred_scale", "pred_scale",},
{0xffdb, "yn1", "yn1",},
{0xffdc, "yn2", "yn2",},
{0xffdd, "ARAM", "Direct Read from ARAM (uses ADPCM)",},
{0xffda, "PRED_SCALE", "ADPCM predictor and scale",},
{0xffdb, "YN1", "ADPCM output history Y[N - 1]",},
{0xffdc, "YN2", "ADPCM output history Y[N - 2]",},
{0xffdd, "ACDSAMP", "Accelerator processed sample read from ARAM or ACIN",},
{0xffde, "GAIN", "Gain",},
{0xffdf, "0xffdf", nullptr,},
{0xffdf, "ACIN", "Accelerator MMIO PCM input value",},
{0xffe0, "0xffe0",nullptr,},
{0xffe1, "0xffe1",nullptr,},

View File

@ -246,7 +246,7 @@ AESndAccelerator::AESndAccelerator(DSPManager& dsp) : m_dsp(dsp)
AESndAccelerator::~AESndAccelerator() = default;
void AESndAccelerator::OnEndException()
void AESndAccelerator::OnSampleReadEndException()
{
// exception5 - this updates internal state
SetYn1(GetYn1());
@ -266,12 +266,11 @@ void AESndAccelerator::WriteMemory(u32 address, u8 value)
static constexpr std::array<s16, 16> ACCELERATOR_COEFS = {}; // all zeros
void AESndUCode::SetUpAccelerator(u16 format, [[maybe_unused]] u16 gain)
void AESndUCode::SetUpAccelerator(u16 format, u16 gain)
{
// setup_accl
m_accelerator.SetSampleFormat(format);
// not currently implemented, but it doesn't matter since the gain is configured to be a no-op
// m_accelerator.SetGain(gain);
m_accelerator.SetGain(gain);
m_accelerator.SetStartAddress(m_parameter_block.buf_start);
m_accelerator.SetEndAddress(m_parameter_block.buf_end);
m_accelerator.SetCurrentAddress(m_parameter_block.buf_curr);
@ -372,7 +371,7 @@ void AESndUCode::DoMixing()
while (counter_h >= 1)
{
counter_h--;
new_r = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_r = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
new_l = new_r;
}
break;
@ -383,8 +382,8 @@ void AESndUCode::DoMixing()
while (counter_h >= 1)
{
counter_h--;
new_r = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_l = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_r = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
new_l = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
}
break; // falls through to mix_samples normally
@ -394,7 +393,7 @@ void AESndUCode::DoMixing()
while (counter_h >= 1)
{
counter_h--;
new_r = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_r = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
new_l = new_r;
}
new_r ^= 0x8000;
@ -407,8 +406,8 @@ void AESndUCode::DoMixing()
while (counter_h >= 1)
{
counter_h--;
new_r = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_l = m_accelerator.Read(ACCELERATOR_COEFS.data());
new_r = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
new_l = m_accelerator.ReadSample(ACCELERATOR_COEFS.data());
}
new_r ^= 0x8000;
new_l ^= 0x8000;

View File

@ -30,7 +30,9 @@ public:
~AESndAccelerator();
protected:
void OnEndException() override;
void OnRawReadEndException() override {}
void OnRawWriteEndException() override {}
void OnSampleReadEndException() override;
u8 ReadMemory(u32 address) override;
void WriteMemory(u32 address, u8 value) override;

View File

@ -134,7 +134,9 @@ public:
PB_TYPE* acc_pb = nullptr;
protected:
void OnEndException() override
void OnRawReadEndException() override {}
void OnRawWriteEndException() override {}
void OnSampleReadEndException() override
{
if (acc_pb->audio_addr.looping)
{
@ -181,6 +183,7 @@ void AcceleratorSetup(HLEAccelerator* accelerator, PB_TYPE* pb)
accelerator->SetSampleFormat(pb->audio_addr.sample_format);
accelerator->SetYn1(pb->adpcm.yn1);
accelerator->SetYn2(pb->adpcm.yn2);
accelerator->SetGain(pb->adpcm.gain);
accelerator->SetPredScale(pb->adpcm.pred_scale);
}
@ -189,7 +192,7 @@ void AcceleratorSetup(HLEAccelerator* accelerator, PB_TYPE* pb)
// by the accelerator on real hardware).
u16 AcceleratorGetSample(HLEAccelerator* accelerator)
{
return accelerator->Read(accelerator->acc_pb->adpcm.coefs);
return accelerator->ReadSample(accelerator->acc_pb->adpcm.coefs);
}
// Reads samples from the input callback, resamples them to <count> samples at

View File

@ -9,15 +9,9 @@ lri $AC0.L, #0x0000 ; start
lri $AC1.M, #0x0000 ; end
lri $AC1.L, #0x0011 ; end
; Reset some registers
lri $AC0.H, #0xffff
sr @0xffda, $AC0.H ; pred scale
sr @0xffdb, $AC0.H ; yn1
sr @0xffdc, $AC0.H ; yn2
; Set the sample format
lri $AC0.H, #0x0
sr @0xffd1, $AC0.H
sr @FORMAT, $AC0.H
; Set the starting and current address
srs @ACSAH, $AC0.M
srs @ACCAH, $AC0.M
@ -27,25 +21,32 @@ srs @ACCAL, $AC0.L
srs @ACEAH, $AC1.M
srs @ACEAL, $AC1.L
; Reset some registers (these must be reset after setting FORMAT)
lri $AC0.H, #0xffff
sr @PRED_SCALE, $AC0.H
sr @YN1, $AC0.H
sr @YN2, $AC0.H
call load_hw_reg_to_regs
call send_back ; check the accelerator regs before a read
bloopi #40, end_of_loop
lr $IX3, @ARAM
lr $IX3, @ACDSAMP
call load_hw_reg_to_regs
call send_back ; after a read
nop ; Loops that end at a return of a call are buggy on hw
end_of_loop:
nop
jmp end_of_test
load_hw_reg_to_regs:
lr $AR0, @0xffd1 ; format
lr $AR0, @FORMAT
lr $AR1, @0xffd2 ; unknown
lr $AR2, @0xffda ; pred scale
lr $AR3, @0xffdb ; yn1
lr $IX0, @0xffdc ; yn2
lr $IX1, @0xffdf ; unknown accelerator register
lr $AR2, @PRED_SCALE
lr $AR3, @YN1
lr $IX0, @YN2
lr $IX1, @ACIN
lri $AC0.H, #0
lrs $AC0.M, @ACSAH

View File

@ -0,0 +1,79 @@
incdir "tests"
include "dsp_base.inc"
test_main:
; Test parameters
lri $AC0.M, #0x0000 ; start
lri $AC0.L, #0x0000 ; start
lri $AC1.M, #0x0000 ; end
lri $AC1.L, #0x0011 ; end
; Set the sample format
lri $AC0.H, #0x08 ; 4-bit PCM, gain scaling = x / 2048
sr @FORMAT, $AC0.H
; Set the starting and current address
srs @ACSAH, $AC0.M
srs @ACCAH, $AC0.M
srs @ACSAL, $AC0.L
srs @ACCAL, $AC0.L
; Set the ending address
srs @ACEAH, $AC1.M
srs @ACEAL, $AC1.L
; Set the gains
si @GAIN, #0x0800 ; 2048 / 2048 = 1.0
si @COEF_A1_0, #0x0400 ; 1024 / 2048 = 0.5
si @COEF_A2_0, #0x0200 ; 512 / 2048 = 0.25
; Reset some registers (these must be reset after setting FORMAT)
lri $AC0.H, #0x0000
sr @PRED_SCALE, $AC0.H ; use 0th coefficients
sr @YN1, $AC0.H
sr @YN2, $AC0.H
call load_hw_reg_to_regs
call send_back ; check the accelerator regs before a read
; Expected read sequence
; r[0] = (data[0] >> 4) + (0/2) + (0/4)
; r[1] = (data[0] & 0xf) + (r[0]/2) + (0/4)
; r[2] = (data[1] >> 4) + (r[1]/2) + (r[0]/4)
; r[3] = (data[1] & 0xf) + (r[2]/2) + (r[1]/4)
; ...
bloopi #40, end_of_loop
lr $IX3, @ACDSAMP
call load_hw_reg_to_regs
call send_back ; after a read
nop ; Loops that end at a return of a call are buggy on hw
end_of_loop:
nop
jmp end_of_test
load_hw_reg_to_regs:
lr $AR0, @FORMAT
lr $AR1, @0xffd2 ; unknown
lr $AR2, @PRED_SCALE
lr $AR3, @YN1
lr $IX0, @YN2
lr $IX1, @ACIN
lri $AC0.H, #0
lrs $AC0.M, @ACSAH
lrs $AC0.L, @ACSAL
lri $AC1.H, #0
lrs $AC1.M, @ACEAH
lrs $AC1.L, @ACEAL
lrs $AX0.H, @ACCAH
lrs $AX0.L, @ACCAL
lrs $AX1.H, @ACCAH
lrs $AX1.L, @ACCAL
lrs $AX1.H, @ACCAH
lrs $AX1.L, @ACCAL
ret

View File

@ -0,0 +1,112 @@
incdir "tests"
include "dsp_base.inc"
; To use: set up a buffer in main_spy,
; then modify the start, current, and ending addresses
; and verify things look correct
loop_read_test:
; Set the sample format
sr @FORMAT, $AC0.H
; Test parameters
lri $AC0.M, #0x0000 ; start
lri $AC0.L, #0x0000 ; start
lri $AC1.M, #0x0000 ; end
lri $AC1.L, #0x0011 ; end
; pred scale, coefs, etc do not matter for raw
; Set the starting and current address
srs @ACSAH, $AC0.M
srs @ACCAH, $AC0.M
srs @ACSAL, $AC0.L
srs @ACCAL, $AC0.L
; Set the ending address
srs @ACEAH, $AC1.M
srs @ACEAL, $AC1.L
call load_hw_reg_to_regs
call send_back ; check the accelerator regs before a read
bloopi #4, end_of_read_loop
lr $IX3, @ACDRAW ; Raw reads
call load_hw_reg_to_regs
call send_back ; after a read
nop
end_of_read_loop:
nop
ret
loop_write_test:
; Set the sample format
sr @FORMAT, $AC0.H
; Test parameters
lri $AC0.M, #0x0000 ; start
lri $AC0.L, #0x0000 ; start
lri $AC1.M, #0x0000 ; end
lri $AC1.L, #0x0011 ; end
; pred scale, coefs, etc do not matter for raw
; Set the starting and current address
srs @ACSAH, $AC0.M
srs @ACCAH, $AC0.M
srs @ACSAL, $AC0.L
srs @ACCAL, $AC0.L
; Set the ending address
srs @ACEAH, $AC1.M
srs @ACEAL, $AC1.L
call load_hw_reg_to_regs
call send_back ; check the accelerator regs before a write
bloopi #4, end_of_write_loop
sr @ACDRAW, $IX3 ; Raw writes
call load_hw_reg_to_regs
call send_back ; after a write
nop
end_of_write_loop:
nop
ret
test_main:
lri $AC0.H, #0x00 ; 4-bit
call loop_read_test
lri $AC0.H, #0x01 ; 8-bit
call loop_read_test
lri $AC0.H, #0x02 ; 16-bit
call loop_read_test
lri $AC0.H, #0x00 ; "4-bit", but all writes are 16-bits
call loop_write_test
lri $AC0.H, #0x01 ; "8-bit", but all writes are 16-bits
call loop_write_test
lri $AC0.H, #0x02 ; 16-bit
call loop_write_test
jmp end_of_test
load_hw_reg_to_regs:
lr $AR0, @FORMAT
lr $AR1, @0xffd2 ; unknown
lr $AR2, @PRED_SCALE
lr $AR3, @YN1
lr $IX0, @YN2
lr $IX1, @ACIN
lri $AC0.H, #0
lrs $AC0.M, @ACSAH
lrs $AC0.L, @ACSAL
lri $AC1.H, #0
lrs $AC1.M, @ACEAH
lrs $AC1.L, @ACEAL
lrs $AX0.H, @ACCAH
lrs $AX0.L, @ACCAL
lrs $AX1.H, @ACCAH
lrs $AX1.L, @ACCAL
lrs $AX1.H, @ACCAH
lrs $AX1.L, @ACCAL
ret

View File

@ -12,7 +12,7 @@ include "dsp_base.inc"
; AC1.M/L: end address
test_accelerator_addrs_ex:
; Set the sample format
sr @0xffd1, $AC0.H
sr @FORMAT, $AC0.H
; Set the accelerator start and current address.
srs @ACSAH, $AC0.M
@ -35,8 +35,8 @@ test_accelerator_addrs_ex:
lrs $AX0.L, @ACCAL
; Make the accelerator read memory
lrs $AX1.H, @ARAM
lrs $AX1.H, @ARAM
lrs $AX1.H, @ACDSAMP
lrs $AX1.H, @ACDSAMP
; AX1 -> new current position after read
lrs $AX1.H, @ACCAH
lrs $AX1.L, @ACCAL

View File

@ -142,10 +142,10 @@ irq5:
si @DMBL, #0x0000
si @DIRQ, #0x0001
lri $ac0.m, #0xbbbb
sr @0xffda, $ac0.m ; pred scale
sr @0xffdb, $ac0.m ; yn1
lr $ix2, @ARAM
sr @0xffdc, $ac0.m ; yn2
sr @PRED_SCALE, $ac0.m
sr @YN1, $ac0.m
lr $ix2, @ACDSAMP
sr @YN2, $ac0.m
rti
irq6:
lri $ac0.m, #0x0006

View File

@ -18,7 +18,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX1.H, #0x100
MULXMVZ $AX0.L, $AX1.H, $ACC0 ; UNSIGNED
@ -37,7 +37,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX1.H, #0x100
MULXMV $AX0.L, $AX1.H, $ACC0 ; UNSIGNED
@ -56,7 +56,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX1.H, #0x100
MULXAC $AX0.L, $AX1.H, $ACC0 ; UNSIGNED
@ -75,7 +75,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX1.H, #0x100
MULX $AX0.L, $AX1.H ; UNSIGNED
@ -95,7 +95,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX1.L, #0x100
MADDX $AX0.L, $AX1.L ; SIGNED (!)
@ -115,7 +115,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AC0.M, #0xFFFF
LRI $AX0.H, #0x100
MULC $AC0.M, $AX0.H ; SIGNED (!)
@ -135,7 +135,7 @@ call send_back
CLR $ACC0
CLRP
SET15
SET15
LRI $AC0.M, #0xFFFF
LRI $AX0.H, #0x100
MULCAC $AC0.M, $AX0.H, $ACC0 ; SIGNED (!)
@ -154,7 +154,7 @@ MOVP $ACC0
call send_back
CLR $ACC0
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX0.H, #0x100
MUL $AX0.L, $AX0.H ; SIGNED (!)
@ -173,7 +173,7 @@ MOVP $ACC0
call send_back
CLR $ACC0
SET15
SET15
LRI $AX0.L, #0xFFFF
LRI $AX0.H, #0x100
MULAC $AX0.L, $AX0.H, $ACC0 ; SIGNED (!)
@ -187,65 +187,3 @@ CLR15
; We're done, DO NOT DELETE THIS LINE
jmp end_of_test
; test accelerator
; TODO: DSPSpy puts a 16-bit ramp at 0x10000000
LRIS $AC1.M, #0x0a ; 16-bit PCM audio
;SRS @SampleFormat, $AC1.M
; Start accelerator position
LRI $AC1.M, #0x0100
SRS @ACCAH, $AC1.M
LRI $AC1.M, #0x1000
SRS @ACCAH, $AC1.M
; Current accelerator position
LRI $AC1.M, #0x0100
SRS @ACCAH, $AC1.M
LRI $AC1.M, #0x1000
SRS @ACCAH, $AC1.M
; End accelerator position
LRI $AC1.M, #0x0100
SRS @ACCAH, $AC1.M
LRI $AC1.M, #0x2000
SRS @ACCAH, $AC1.M
; Now to the interesting parameter - gain.
LRI $AC1.M, #0xFFFF
SRS @GAIN, $AC1.M
; Let's now load a sample through the accelerator.
LRS $AC1.M, @ARAM
call send_back
jmp end_of_test
; test addpaxz
call send_back
clrp
lri $AX0.L, #0x1111
lri $AX0.H, #0x2222
call send_back
clrp
addpaxz $ACC0, $AX0.H
call send_back
clrp
set40
addpaxz $ACC0, $AX0.H
set16
call send_back
clrp
set15
addpaxz $ACC0, $AX0.H
clr15
call send_back
jmp end_of_test

View File

@ -14,10 +14,10 @@ include "dsp_base_noirq.inc"
test_main:
; Use the accelerator to generate an IRQ by setting the start and end address to 0
; This will result in an interrupt on every read
SI @0xffda, #0 ; pred_scale
SI @0xffdb, #0 ; yn1
SI @0xffdc, #0 ; yn2
SI @0xffd1, #0 ; SampleFormat
SI @PRED_SCALE, #0
SI @YN1, #0
SI @YN2, #0
SI @FORMAT, #0
SI @ACSAH, #0
SI @ACCAH, #0
SI @ACSAL, #0
@ -27,24 +27,24 @@ test_main:
LRI $AX1.H, #0x0000
LRS $AX0.L, @ARAM ; Trigger interrupt
LRS $AX0.L, @ACDSAMP ; Trigger interrupt
CALL send_back
LRI $AX1.H, #0x0001
LRS $AX0.L, @ARAM ; Trigger interrupt
LRS $AX0.L, @ACDSAMP ; Trigger interrupt
CALL send_back
LRI $AX1.H, #0x0000
LRS $AX0.L, @ARAM ; Trigger interrupt
LRS $AX0.L, @ACDSAMP ; Trigger interrupt
CALL send_back
jmp end_of_test
accov_irq:
; Restore registers, otherwise no new interrupt will be generated
SI @0xffda, #0 ; pred_scale
SI @0xffdb, #0 ; yn1
SI @0xffdc, #0 ; yn2
SI @PRED_SCALE, #0
SI @YN1, #0
SI @YN2, #0
TSTAXH $AX1.H
LRI $AX1.L, #0x1111

View File

@ -17,13 +17,15 @@ public:
{
std::array<s16, 16> coefs{};
m_accov_raised = false;
return Read(coefs.data());
return ReadSample(coefs.data());
}
bool EndExceptionRaised() const { return m_accov_raised; }
protected:
void OnEndException() override
void OnRawReadEndException() override {}
void OnRawWriteEndException() override {}
void OnSampleReadEndException() override
{
EXPECT_TRUE(m_reads_stopped);
m_accov_raised = true;