mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-27 17:39:34 -06:00
Merge pull request #10766 from xperia64/dsp_format_fun
DSP accelerator improvements
This commit is contained in:
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
{
|
||||
|
@ -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,},
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
79
Source/DSPSpy/tests/accelerator_pcm_test.ds
Normal file
79
Source/DSPSpy/tests/accelerator_pcm_test.ds
Normal 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
|
112
Source/DSPSpy/tests/accelerator_raw_test.ds
Normal file
112
Source/DSPSpy/tests/accelerator_raw_test.ds
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -46,7 +46,7 @@
|
||||
% Document front page material
|
||||
\title{\textbf{\Huge GameCube DSP User's Manual}}
|
||||
\author{Reverse-engineered and documented by Duddie \\ \href{mailto:duddie@walla.com}{duddie@walla.com}}
|
||||
\date{\today\\v0.1.5}
|
||||
\date{\today\\v0.1.7}
|
||||
|
||||
% Title formatting commands
|
||||
\newcommand{\OpcodeTitle}[1]{\subsection{#1}\label{instruction:#1}}
|
||||
@ -263,6 +263,8 @@ The purpose of this documentation is purely academic and it aims at understandin
|
||||
0.1.3 & 2022.05.27 & Pokechu22 & Renamed \texttt{CMPAR} instruction to \texttt{CMPAXH} \\ \hline
|
||||
0.1.4 & 2022.06.02 & Pokechu22 & Fixed typos; added sections on 16-bit and 40-bit modes and on main and extended opcode writing to the same register. \\ \hline
|
||||
0.1.5 & 2022.09.29 & vpelletier & Fixed \texttt{BLOOP} and \texttt{BLOOPI} suboperation order \\ \hline
|
||||
0.1.6 & 2022.06.20 & xperia64 & Accelerator documentation updates, fix register typo in ANDC and ORC descriptions \\ \hline
|
||||
0.1.7 & 2025.04.21 & Tilka & Fixed typos and complained about GFDL \\ \hline
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
@ -387,7 +389,7 @@ You may not copy, modify, sublicense, or distribute the Document except as expre
|
||||
|
||||
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/.
|
||||
|
||||
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
|
||||
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
@ -655,15 +657,15 @@ Exception vectors are located at address \Address{0x0000} in Instruction RAM.
|
||||
\centering
|
||||
\begin{tabular}{|l|l|l|l|}
|
||||
\hline
|
||||
\textbf{Level} & \textbf{Address} & \textbf{Name} & \textbf{Description} \\ \hline
|
||||
0 & \Address{0x0000} & \texttt{RESET} & \\ \hline
|
||||
1 & \Address{0x0002} & \texttt{STOVF} & Stack under/overflow \\ \hline
|
||||
2 & \Address{0x0004} & & \\ \hline
|
||||
3 & \Address{0x0006} & & \\ \hline
|
||||
4 & \Address{0x0008} & & \\ \hline
|
||||
5 & \Address{0x000A} & \texttt{ACCOV} & Accelerator address overflow \\ \hline
|
||||
6 & \Address{0x000C} & & \\ \hline
|
||||
7 & \Address{0x000E} & \texttt{INT} & External interrupt (from CPU) \\ \hline
|
||||
\textbf{Level} & \textbf{Address} & \textbf{Name} & \textbf{Description} \\ \hline
|
||||
0 & \Address{0x0000} & \texttt{RESET} & \\ \hline
|
||||
1 & \Address{0x0002} & \texttt{STOVF} & Stack under/overflow \\ \hline
|
||||
2 & \Address{0x0004} & & \\ \hline
|
||||
3 & \Address{0x0006} & \texttt{ACRROV} & Accelerator raw read address overflow \\ \hline
|
||||
4 & \Address{0x0008} & \texttt{ACRWOV} & Accelerator raw write address overflow \\ \hline
|
||||
5 & \Address{0x000A} & \texttt{ACSOV} & Accelerator sample read address overflow \\ \hline
|
||||
6 & \Address{0x000C} & & \\ \hline
|
||||
7 & \Address{0x000E} & \texttt{INT} & External interrupt (from CPU) \\ \hline
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
@ -681,11 +683,11 @@ Hardware registers (IFX) occupy the address space at \Address{0xFFxx} in the Dat
|
||||
\hline
|
||||
\textbf{Address} & \textbf{Name} & \textbf{Description} \\ \hline
|
||||
\multicolumn{3}{|l|}{\textit{ADPCM Coefficients}} \\ \hline
|
||||
\Address{0xFFA0} & \Register{COEF\_A1\_0} & A1 Coefficient \# 0 \\ \hline
|
||||
\Address{0xFFA1} & \Register{COEF\_A2\_0} & A2 Coefficient \# 0 \\ \hline
|
||||
\multicolumn{3}{|c|}{$\vdots$} \\ \hline
|
||||
\Address{0xFFAE} & \Register{COEF\_A1\_7} & A1 Coefficient \# 7 \\ \hline
|
||||
\Address{0xFFAF} & \Register{COEF\_A2\_7} & A2 Coefficient \# 7 \\ \hline
|
||||
\Address{0xFFA0} & \Register{COEF\_A1\_0} & A1 Coefficient \# 0 \\ \hline
|
||||
\Address{0xFFA1} & \Register{COEF\_A2\_0} & A2 Coefficient \# 0 \\ \hline
|
||||
\multicolumn{3}{|c|}{$\vdots$} \\ \hline
|
||||
\Address{0xFFAE} & \Register{COEF\_A1\_7} & A1 Coefficient \# 7 \\ \hline
|
||||
\Address{0xFFAF} & \Register{COEF\_A2\_7} & A2 Coefficient \# 7 \\ \hline
|
||||
\multicolumn{3}{|l|}{\textit{DMA Interface}} \\ \hline
|
||||
\Address{0xFFC9} & \Register{DSCR} & DMA control \\ \hline
|
||||
\Address{0xFFCB} & \Register{DSBL} & Block length \\ \hline
|
||||
@ -695,19 +697,19 @@ Hardware registers (IFX) occupy the address space at \Address{0xFFxx} in the Dat
|
||||
\multicolumn{3}{|l|}{\textit{Accelerator}} \\ \hline
|
||||
\Address{0xFFD1} & \Register{FORMAT} & Accelerator sample format \\ \hline
|
||||
\Address{0xFFD2} & \Register{ACUNK1} & Unknown, usually 3 \\ \hline
|
||||
\Address{0xFFD3} & \Register{ACDATA1} & Alternative ARAM interface \\ \hline
|
||||
\Address{0xFFD3} & \Register{ACDRAW} & Accelerator raw data \\ \hline
|
||||
\Address{0xFFD4} & \Register{ACSAH} & Accelerator start address H \\ \hline
|
||||
\Address{0xFFD5} & \Register{ACSAL} & Accelerator start address L \\ \hline
|
||||
\Address{0xFFD6} & \Register{ACEAH} & Accelerator end address H \\ \hline
|
||||
\Address{0xFFD7} & \Register{ACEAL} & Accelerator end address L \\ \hline
|
||||
\Address{0xFFD8} & \Register{ACCAH} & Accelerator current address H \\ \hline
|
||||
\Address{0xFFD9} & \Register{ACCAL} & Accelerator current address L \\ \hline
|
||||
\Address{0xFFDA} & \Register{SCALE} & ADPCM predictor and scale \\ \hline
|
||||
\Address{0xFFDB} & \Register{YN1} & ADPCM YN1 \\ \hline
|
||||
\Address{0xFFDC} & \Register{YN2} & ADPCM YN2 \\ \hline
|
||||
\Address{0xFFDD} & \Register{ACDAT} & Accelerator data \\ \hline
|
||||
\Address{0xFFDA} & \Register{PRED\_SCALE} & ADPCM predictor and scale \\ \hline
|
||||
\Address{0xFFDB} & \Register{YN1} & ADPCM output history Y[N - 1] \\ \hline
|
||||
\Address{0xFFDC} & \Register{YN2} & ADPCM output history Y[N - 2] \\ \hline
|
||||
\Address{0xFFDD} & \Register{ACDSAMP} & Accelerator processed sample \\ \hline
|
||||
\Address{0xFFDE} & \Register{GAIN} & Gain \\ \hline
|
||||
\Address{0xFFDF} & \Register{ACUNK2} & Unknown, usually \Value{0x0C} \\ \hline
|
||||
\Address{0xFFDF} & \Register{ACIN} & Accelerator input \\ \hline
|
||||
\Address{0xFFED} & \Register{AMDM} & ARAM DMA Request Mask \\ \hline
|
||||
\multicolumn{3}{|l|}{\textit{Interrupts}} \\ \hline
|
||||
\Address{0xFFFB} & \Register{DIRQ} & IRQ request \\ \hline
|
||||
@ -762,20 +764,29 @@ The GameCube DSP is connected to the memory bus through a DMA channel. DMA can b
|
||||
\section{Accelerator}
|
||||
|
||||
The accelerator is used to transfer data from accelerator memory (ARAM) to DSP memory. The accelerator area can be marked with \Register{ACSA} (start) and \Register{ACEA} (end) addresses.
|
||||
Current address for the accelerator can be set or read from the \Register{ACCA} register. Reading from accelerator memory is done by reading from the \Register{ACDAT} register.
|
||||
This register contains data from ARAM pointed to by the \Register{ACCA} register.
|
||||
Current address for the accelerator can be set or read from the \Register{ACCA} register. Accessing accelerator memory is done by reading or writing the \Register{ACDRAW} register for raw data, or reading the \Register{ACDSAMP} register for processed sample data.
|
||||
These registers contain raw or processed sample data from ARAM pointed to by the \Register{ACCA} register.
|
||||
After reading the data, \Register{ACCA} is incremented by one.
|
||||
After \Register{ACCA} grows bigger than the area pointed to by \Register{ACEA}, it gets reset to a value from \Register{ACSA} and the \Exception{ACCOV} interrupt is generated.
|
||||
After \Register{ACCA} grows bigger than the area pointed to by \Register{ACEA}, it gets reset to a value from \Register{ACSA} and an exception is generated. Raw reads generate exception \Exception{ACRROV}, raw writes generate exception \Exception{ACRWOV}, and sample reads generate exception \Exception{ACSOV}.
|
||||
|
||||
\RegisterBitOverview{0xFFD1}{FORMAT}{Accelerator sample format}{dddd dddd dddd dddd}
|
||||
\RegisterBitOverview{0xFFD1}{FORMAT}{Accelerator sample format}{---- ---- --gg ddss}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{\begin{tabular}[c]{@{}l@{}}
|
||||
\Value{0x00} - ADPCM audio \\
|
||||
\Value{0x05} - u8 reads (D3) \\
|
||||
\Value{0x06} - u16 reads (D3) \\
|
||||
\Value{0x0A} - 16-bit PCM audio, u16 writes (D3) \\
|
||||
\Value{0x19} - 8-bit PCM audio
|
||||
\RegisterBitDescription{5--4}{g}{R/W}{\begin{tabular}[c]{@{}l@{}}
|
||||
\Value{0} - PCM gain/coef scaling = 1/2048 \\
|
||||
\Value{1} - PCM gain/coef scaling = 1/1 \\
|
||||
\Value{2} - PCM gain/coef scaling = 1/65536 \\
|
||||
\end{tabular}}
|
||||
\RegisterBitDescription{3--2}{d}{R/W}{\begin{tabular}[c]{@{}l@{}}
|
||||
\Value{0} - ADPCM decoding from ARAM \\
|
||||
\Value{1} - PCM decoding from \Register{ACIN}, \Register{ACCA} doesn't increment \\
|
||||
\Value{2} - PCM decoding from ARAM \\
|
||||
\Value{3} - PCM decoding from \Register{ACIN}, \Register{ACCA} increments \\
|
||||
\end{tabular}}
|
||||
\RegisterBitDescription{1--0}{s}{R/W}{\begin{tabular}[c]{@{}l@{}}
|
||||
\Value{0} - 4-bit \\
|
||||
\Value{1} - 8-bit \\
|
||||
\Value{2} - 16-bit \\
|
||||
\end{tabular}}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
@ -785,10 +796,10 @@ After \Register{ACCA} grows bigger than the area pointed to by \Register{ACEA},
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Usually 3}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFD3}{ACDATA1}{Alternative ARAM interface}{dddd dddd dddd dddd}
|
||||
\RegisterBitOverview{0xFFD3}{ACDRAW}{Raw ARAM Access}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Reads from or writes to data pointed to by current accelerator address, and then increments the current address. It is unclear whether this respects the start and end addresses.}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Reads from or writes to raw data pointed to by current accelerator address, and then increments the current address. Reads respect the FORMAT size. Writes are always 16-bit and treat the addresses as such. Writes require that the uppermost bit of the current address is set. Reads that overflow the end address throw exception \Exception{ACRROV}. Writes that overflow throw exception \Exception{ACRWOV}.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFD4}{ACSAH}{Accelerator Start Address H}{dddd dddd dddd dddd}
|
||||
@ -829,7 +840,7 @@ After \Register{ACCA} grows bigger than the area pointed to by \Register{ACEA},
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Bits 15--0 of the accelerator current address}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFDA}{SCALE}{ADPCM predictor and scale}{---- ---- -ppp ssss}
|
||||
\RegisterBitOverview{0xFFDA}{PRED\_SCALE}{ADPCM predictor and scale}{---- ---- -ppp ssss}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{6--4}{d}{R/W}{Used to decide which pair of coefficients to use (\Register{COEF\_A1\_p} and \Register{COEF\_A2\_p}, at $\Address{0xFFA0} + 2p$ and $\Address{0xFFA0} + 2p + 1$)}
|
||||
@ -841,31 +852,31 @@ After \Register{ACCA} grows bigger than the area pointed to by \Register{ACEA},
|
||||
\RegisterBitOverview{0xFFDB}{YN1}{ADPCM YN1}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Last value read by the accelerator, updated to the new value of \Register{ACDAT} when \Register{ACDAT} is read. Used when calculating ADPCM, but updated for all sample formats.}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Last value read by the accelerator, updated to the new value of \Register{ACDSAMP} when \Register{ACDSAMP} is read. Used and updated for all sample formats. Multiplied by the A1 coefficient selected by PRED\_SCALE and scaled per FORMAT.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFDC}{YN1}{ADPCM YN2}{dddd dddd dddd dddd}
|
||||
\RegisterBitOverview{0xFFDC}{YN2}{ADPCM YN2}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Second-last value read by the accelerator, updated to the previous value of \Register{YN1} when \Register{ACDAT} is read. Used when calculating ADPCM, but updated for all sample formats. Writing this value starts the accelerator.}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Second-last value read by the accelerator, updated to the previous value of \Register{YN1} when \Register{ACDSAMP} is read. Used and updated for all sample formats. Multiplied by the A2 coefficient selected by PRED\_SCALE and scaled per FORMAT. Writing this value starts the accelerator.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFDD}{ACDAT}{Accelerator data}{dddd dddd dddd dddd}
|
||||
\RegisterBitOverview{0xFFDD}{ACDSAMP}{Accelerator data}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R}{Reads new data from the accelerator. When there is no data left, returns 0.}
|
||||
\RegisterBitDescription{15--0}{d}{R}{Reads new proccessed sample data from the accelerator. Data is processed per FORMAT. When there is no data left, returns 0.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFDE}{GAIN}{Gain}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Exact behavior unknown}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Applied in PCM FORMATs. Raw sample is multiplied by GAIN, then scaled per the gain scale bits of FORMAT.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFDF}{ACUNK2}{Unknown 2}{dddd dddd dddd dddd}
|
||||
\RegisterBitOverview{0xFFDF}{ACIN}{Accelerator Input}{dddd dddd dddd dddd}
|
||||
|
||||
\begin{RegisterBitDescriptions}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Usually \Value{0x0C}}
|
||||
\RegisterBitDescription{15--0}{d}{R/W}{Used as the sample input in place of ARAM reads when FORMAT specifies it.}
|
||||
\end{RegisterBitDescriptions}
|
||||
|
||||
\RegisterBitOverview{0xFFEF}{AMDM}{ARAM DMA Request Mask}{---- ---- ---- ---m}
|
||||
@ -1006,7 +1017,7 @@ Functions used for describing opcode operation.
|
||||
\begin{description}
|
||||
\item \Function{PUSH\_STACK(\$stR)}
|
||||
\begin{description}
|
||||
\item \textbf{Description:} \\
|
||||
\item \textbf{Description:} \\
|
||||
Pushes value onto given stack referenced by stack register \Register{\$stR}. Operation moves values down in internal stack.
|
||||
|
||||
\item \textbf{Operation:} \\
|
||||
@ -1017,7 +1028,7 @@ Functions used for describing opcode operation.
|
||||
\begin{description}
|
||||
\item \Function{POP\_STACK(\$stR)}
|
||||
\begin{description}
|
||||
\item \textbf{Description:} \\
|
||||
\item \textbf{Description:} \\
|
||||
Pops value from stack referenced by stack register \Register{\$stR}. Operation moves values up in internal stack.
|
||||
|
||||
\item \textbf{Operation:} \\
|
||||
@ -1358,7 +1369,7 @@ A ``-'' indicates that the flag retains its previous value, a ``0'' indicates th
|
||||
\end{DSPOpcodeFormat}
|
||||
|
||||
\begin{DSPOpcodeDescription}
|
||||
\item Logic AND middle part of accumulator \Register{\$acD.m} with middle part of accumulator \Register{\$ax(1-D)}.m.
|
||||
\item Logic AND middle part of accumulator \Register{\$acD.m} with middle part of accumulator \Register{\$ac(1-D)}.m.
|
||||
\end{DSPOpcodeDescription}
|
||||
|
||||
\begin{DSPOpcodeOperation}
|
||||
@ -3640,7 +3651,7 @@ A ``-'' indicates that the flag retains its previous value, a ``0'' indicates th
|
||||
\end{DSPOpcodeFormat}
|
||||
|
||||
\begin{DSPOpcodeDescription}
|
||||
\item Logic OR middle part of accumulator \Register{\$acD.m} with middle part of accumulator \Register{\$ax(1-D).m}.
|
||||
\item Logic OR middle part of accumulator \Register{\$acD.m} with middle part of accumulator \Register{\$ac(1-D).m}.
|
||||
\end{DSPOpcodeDescription}
|
||||
|
||||
\begin{DSPOpcodeOperation}
|
||||
|
Reference in New Issue
Block a user