@@ -16,6 +16,8 @@

namespace DSP
{
class Accelerator;

namespace JIT
{
namespace x86
@@ -299,6 +301,8 @@ struct SDSP
// Accelerator / DMA / other hardware registers. Not GPRs.
std::array<u16, 256> ifx_regs;

std::unique_ptr<Accelerator> accelerator;

// When state saving, all of the above can just be memcpy'd into the save state.
// The below needs special handling.
u16* iram;
@@ -98,7 +98,7 @@ u16 gdsp_mbox_read_l(Mailbox mbx)
return (u16)value;
}

void gdsp_ifx_write(u32 addr, u32 val)
void gdsp_ifx_write(u32 addr, u16 val)
{
g_dsp_cap->LogIFXWrite(addr, val);

@@ -136,10 +136,6 @@ void gdsp_ifx_write(u32 addr, u32 val)
g_dsp.ifx_regs[DSP_DSBL] = 0;
break;

case DSP_ACDATA1: // Accelerator write (Zelda type) - "UnkZelda"
dsp_write_aram_d3(val);
break;

case DSP_GAIN:
if (val)
{
@@ -151,21 +147,45 @@ void gdsp_ifx_write(u32 addr, u32 val)
case DSP_DSCR:
g_dsp.ifx_regs[addr & 0xFF] = val;
break;
/*
case DSP_ACCAL:
dsp_step_accelerator();
break;
*/

// Masking occurs for the start and end addresses as soon as the registers are written to.
case DSP_ACSAH:
g_dsp.accelerator->SetStartAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetStartAddress()));
break;
case DSP_ACSAL:
g_dsp.accelerator->SetStartAddress(
static_cast<u16>(g_dsp.accelerator->GetStartAddress() >> 16) << 16 | val);
break;
case DSP_ACEAH:
g_dsp.ifx_regs[addr & 0xff] = val & 0x3fff;
g_dsp.accelerator->SetEndAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetEndAddress()));
break;
case DSP_ACEAL:
g_dsp.accelerator->SetEndAddress(
static_cast<u16>(g_dsp.accelerator->GetEndAddress() >> 16) << 16 | val);
break;

// This also happens for the current address, but with a different mask.
case DSP_ACCAH:
g_dsp.ifx_regs[addr & 0xff] = val & 0xbfff;
g_dsp.accelerator->SetCurrentAddress(val << 16 |
static_cast<u16>(g_dsp.accelerator->GetCurrentAddress()));
break;
case DSP_ACCAL:
g_dsp.accelerator->SetCurrentAddress(
static_cast<u16>(g_dsp.accelerator->GetCurrentAddress() >> 16) << 16 | val);
break;
case DSP_FORMAT:
g_dsp.accelerator->SetSampleFormat(val);
break;
case DSP_YN1:
g_dsp.accelerator->SetYn1(val);
break;
case DSP_YN2:
g_dsp.accelerator->SetYn2(val);
break;
case DSP_PRED_SCALE:
g_dsp.accelerator->SetPredScale(val);
break;
case DSP_ACDATA1: // Accelerator write (Zelda type) - "UnkZelda"
g_dsp.accelerator->WriteD3(val);
break;

default:
@@ -208,11 +228,30 @@ static u16 _gdsp_ifx_read(u16 addr)
case DSP_DSCR:
return g_dsp.ifx_regs[addr & 0xFF];

case DSP_ACSAH:
return static_cast<u16>(g_dsp.accelerator->GetStartAddress() >> 16);
case DSP_ACSAL:
return static_cast<u16>(g_dsp.accelerator->GetStartAddress());
case DSP_ACEAH:
return static_cast<u16>(g_dsp.accelerator->GetEndAddress() >> 16);
case DSP_ACEAL:
return static_cast<u16>(g_dsp.accelerator->GetEndAddress());
case DSP_ACCAH:
return static_cast<u16>(g_dsp.accelerator->GetCurrentAddress() >> 16);
case DSP_ACCAL:
return static_cast<u16>(g_dsp.accelerator->GetCurrentAddress());
case DSP_FORMAT:
return g_dsp.accelerator->GetSampleFormat();
case DSP_YN1:
return g_dsp.accelerator->GetYn1();
case DSP_YN2:
return g_dsp.accelerator->GetYn2();
case DSP_PRED_SCALE:
return g_dsp.accelerator->GetPredScale();
case DSP_ACCELERATOR: // ADPCM Accelerator reads
return dsp_read_accelerator();

return g_dsp.accelerator->Read(reinterpret_cast<s16*>(&g_dsp.ifx_regs[DSP_COEF_A1_0]));
case DSP_ACDATA1: // Accelerator reads (Zelda type) - "UnkZelda"
return dsp_read_aram_d3();
return g_dsp.accelerator->ReadD3();

default:
if ((addr & 0xff) >= 0xa0)
@@ -22,6 +22,6 @@ u16 gdsp_mbox_read_h(Mailbox mbx);
u16 gdsp_mbox_read_l(Mailbox mbx);

void gdsp_ifx_init();
void gdsp_ifx_write(u32 addr, u32 val);
void gdsp_ifx_write(u32 addr, u16 val);
u16 gdsp_ifx_read(u16 addr);
} // namespace DSP
@@ -530,9 +530,9 @@ void DSPEmitter::dmem_write(X64Reg value)
FixupBranch end = J(true);
// else if (saddr == 0xf)
SetJumpTarget(ifx);
// Does it mean gdsp_ifx_write needs u32 rather than u16?
DSPJitRegCache c(m_gpr);
X64Reg abisafereg = m_gpr.MakeABICallSafe(value);
MOVZX(32, 16, abisafereg, R(abisafereg));
m_gpr.PushRegs();
ABI_CallFunctionRR(gdsp_ifx_write, EAX, abisafereg);
m_gpr.PopRegs();
@@ -13,6 +13,7 @@
#endif

#include <functional>
#include <memory>

#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
@@ -163,57 +164,33 @@ void DumpPB(const PB_TYPE& pb)
#endif

// Simulated accelerator state.
static u32 acc_loop_addr, acc_end_addr;
static u32* acc_cur_addr;
static PB_TYPE* acc_pb;
static bool acc_end_reached;

// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb, u32* cur_addr)
{
acc_pb = pb;
// Masking occurs for the start and end addresses as soon as the registers are written to.
acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr) & 0x3fffffff;
acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr) & 0x3fffffff;
acc_cur_addr = cur_addr;
// It also happens for the current address, but with a different mask.
*acc_cur_addr &= 0xbfffffff;
acc_end_reached = false;
}

// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
class HLEAccelerator final : public Accelerator
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;

auto end_address_reached = [] {
// loop back to loop_addr.
*acc_cur_addr = acc_loop_addr;

protected:
void OnEndException() override
{
if (acc_pb->audio_addr.looping)
{
// Set the ADPCM info to continue processing at loop_addr.
//
// For some reason, yn1 and yn2 aren't set if the voice is not of
// stream type. This is what the AX UCode does and I don't really
// know why.
acc_pb->adpcm.pred_scale = acc_pb->adpcm_loop_info.pred_scale;
SetPredScale(acc_pb->adpcm_loop_info.pred_scale);
if (!acc_pb->is_stream)
{
acc_pb->adpcm.yn1 = acc_pb->adpcm_loop_info.yn1;
acc_pb->adpcm.yn2 = acc_pb->adpcm_loop_info.yn2;
SetYn1(acc_pb->adpcm_loop_info.yn1);
SetYn2(acc_pb->adpcm_loop_info.yn2);
}
#ifdef AX_GC
else
{
// Refresh YN1 and YN2. This indirectly causes the accelerator to resume reads.
SetYn1(GetYn1());
SetYn2(GetYn2());
#ifdef AX_GC
// If we're streaming, increment the loop counter.
acc_pb->loop_counter++;
}
#endif
}
}
else
{
@@ -222,18 +199,45 @@ u16 AcceleratorGetSample()

#ifdef AX_WII
// One of the few meaningful differences between AXGC and AXWii:
// while AXGC handles non looping voices ending by having 0000
// samples at the loop address, AXWii has the 0000 samples
// internally in DRAM and use an internal pointer to it (loop addr
// does not contain 0000 samples on AXWii!).
// while AXGC handles non looping voices ending by relying on the
// accelerator to stop reads once the loop address is reached,
// AXWii has the 0000 samples internally in DRAM and use an internal
// pointer to it (loop addr does not contain 0000 samples on AXWii!).
acc_end_reached = true;
#endif
}
};
}

u8 ReadMemory(u32 address) override { return ReadARAM(address); }
void WriteMemory(u32 address, u8 value) override { WriteARAM(value, address); }
};

static std::unique_ptr<Accelerator> s_accelerator = std::make_unique<HLEAccelerator>();

// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb)
{
acc_pb = pb;
s_accelerator->SetStartAddress(HILO_TO_32(pb->audio_addr.loop_addr));
s_accelerator->SetEndAddress(HILO_TO_32(pb->audio_addr.end_addr));
s_accelerator->SetCurrentAddress(HILO_TO_32(pb->audio_addr.cur_addr));
s_accelerator->SetSampleFormat(pb->audio_addr.sample_format);
s_accelerator->SetYn1(pb->adpcm.yn1);
s_accelerator->SetYn2(pb->adpcm.yn2);
s_accelerator->SetPredScale(pb->adpcm.pred_scale);
acc_end_reached = false;
}

// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;

return ReadAccelerator(acc_loop_addr, acc_end_addr, acc_cur_addr,
acc_pb->audio_addr.sample_format, &acc_pb->adpcm.yn1, &acc_pb->adpcm.yn2,
&acc_pb->adpcm.pred_scale, acc_pb->adpcm.coefs, end_address_reached);
return s_accelerator->Read(acc_pb->adpcm.coefs);
}

// Reads samples from the input callback, resamples them to <count> samples at
@@ -375,8 +379,7 @@ u32 ResampleAudio(std::function<s16(u32)> input_callback, s16* output, u32 count
// if required.
void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
{
u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
AcceleratorSetup(&pb, &cur_addr);
AcceleratorSetup(&pb);

if (coeffs)
coeffs += pb.coef_select * 0x200;
@@ -385,9 +388,12 @@ void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
pb.src.cur_addr_frac, HILO_TO_32(pb.src.ratio), pb.src_type, coeffs);
pb.src.cur_addr_frac = (curr_pos & 0xFFFF);

// Update current position in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(cur_addr >> 16) & 0xbfff;
pb.audio_addr.cur_addr_lo = static_cast<u16>(cur_addr);
// Update current position, YN1, YN2 and pred scale in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(s_accelerator->GetCurrentAddress() >> 16);
pb.audio_addr.cur_addr_lo = static_cast<u16>(s_accelerator->GetCurrentAddress());
pb.adpcm.yn1 = s_accelerator->GetYn1();
pb.adpcm.yn2 = s_accelerator->GetYn2();
pb.adpcm.pred_scale = s_accelerator->GetPredScale();
}

// Add samples to an output buffer, with optional volume ramping.
@@ -17,6 +17,7 @@
#include "Common/Thread.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/DSP/DSPAccelerator.h"
#include "Core/DSP/DSPCaptureLogger.h"
#include "Core/DSP/DSPCore.h"
#include "Core/DSP/DSPHWInterface.h"
@@ -71,6 +72,7 @@ void DSPLLE::DoState(PointerWrap& p)

p.Do(g_dsp.step_counter);
p.DoArray(g_dsp.ifx_regs);
g_dsp.accelerator->DoState(p);
p.Do(g_dsp.mbox[0]);
p.Do(g_dsp.mbox[1]);
Common::UnWriteProtectMemory(g_dsp.iram, DSP_IRAM_BYTE_SIZE, false);
@@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;

// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 89; // Last changed in PR 5890
static const u32 STATE_VERSION = 90; // Last changed in PR 6077

// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,
@@ -390,6 +390,10 @@ void handle_dsp_mail(void)
while (real_dsp.CheckMailTo())
;
}
else if (mail == 0x80050000)
{
CON_PrintRow(4, 25, "ACCOV at step %i", dsp_steps);
}

// ROM dumping mails
else if (mail == 0x8888c0de)
@@ -0,0 +1,64 @@
incdir "tests"
include "dsp_base.inc"

; Test parameters
lri $AC0.M, #0x0000 ; start
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
; 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 #40, end_of_loop
lr $IX3, @ARAM
call load_hw_reg_to_regs
call send_back ; after a read
end_of_loop:
nop

jmp end_of_test

load_hw_reg_to_regs:
lr $AR0, @0xffd1 ; format
lr $AR1, @0xffd2 ; unknown
lr $AR2, @0xffda ; pred scale
lr $AR3, @0xffdb ; yn1
lr $IX0, @0xffdc ; yn2
lr $IX1, @0xffdf ; unknown accelerator register

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
@@ -106,7 +106,7 @@ MEM_LO: equ 0x0f7F
jmp start_of_test

; This is where we jump when we're done testing, see above.
; We just fall into a loop, playing dead until someone resets the DSP.
; We just fall into a loop, playing dead until someone resets the DSP.
end_of_test:
nop
jmp end_of_test
@@ -138,8 +138,18 @@ irq4:
lri $ac0.m, #0x0004
jmp irq
irq5:
lri $ac0.m, #0x0005
jmp irq
lrs $ac0.m, @DMBH
andcf $ac0.m, #0x8000
jlz irq5
si @DMBH, #0x8005
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
rti
irq6:
lri $ac0.m, #0x0006
jmp irq
@@ -156,7 +166,7 @@ irq:
si @DIRQ, #0x0001
halt ; Through some magic this allows us to properly ack the exception in dspspy
;rti ; allow dumping of ucodes which cause exceptions...probably not safe at all

; DMA:s the current state of the registers back to the PowerPC. To do this,
; it must write the contents of all regs to DRAM.
; Unfortunately, this loop uses ar0 so it's best to use AR1 and friends for testing
@@ -216,13 +226,13 @@ send_back:
dma_copy:
mrr $ax0.l, $ac1.m

; Wait for the CPU to send us a mail.
; Wait for the CPU to send us a mail.
call 0x807e
si @DMBH, #0x8888
si @DMBL, #0xfeeb
si @DIRQ, #0x0001
; wait for the CPU to recieve our response before we execute the next op

; wait for the CPU to recieve our response before we execute the next op
call 0x8078
andi $ac0.m, #0x7fff
lrs $ac1.m, @CMBL
@@ -261,14 +271,14 @@ dma_copy:
lrri $ac0.m, @$ar0
lrri $ac1.m, @$ar0
lr $ar0, @REGS_BASE

ret ; from send_back

; If you are in set40 mode, use this instead of send_back if you want to stay
; in set40 mode.
send_back_40:
set16
call send_back
call send_back
set40
ret

@@ -2,11 +2,12 @@ add_dolphin_test(MMIOTest MMIOTest.cpp)
add_dolphin_test(PageFaultTest PageFaultTest.cpp)
add_dolphin_test(CoreTimingTest CoreTimingTest.cpp)

add_dolphin_test(DSPAcceleratorTest DSP/DSPAcceleratorTest.cpp)
add_dolphin_test(DSPAssemblyTest
DSP/DSPAssemblyTest.cpp
DSP/DSPTestBinary.cpp
DSP/DSPTestText.cpp
DSP/HermesBinary.cpp
)
)

add_dolphin_test(ESFormatsTest IOS/ES/FormatsTest.cpp IOS/ES/TestBinaryData.cpp)
@@ -0,0 +1,187 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <array>

#include <gtest/gtest.h>

#include "Common/CommonTypes.h"
#include "Core/DSP/DSPAccelerator.h"

// Simulated DSP accelerator.
class TestAccelerator : public DSP::Accelerator
{
public:
// For convenience.
u16 TestRead()
{
std::array<s16, 16> coefs{};
m_accov_raised = false;
return Read(coefs.data());
}

bool EndExceptionRaised() const { return m_accov_raised; }
protected:
void OnEndException() override
{
EXPECT_TRUE(m_reads_stopped);
m_accov_raised = true;
}
u8 ReadMemory(u32 address) override { return 0; }
void WriteMemory(u32 address, u8 value) override {}
bool m_accov_raised = false;
};

TEST(DSPAccelerator, Initialization)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x00001000);
EXPECT_EQ(accelerator.GetStartAddress(), 0x00000000u);
EXPECT_EQ(accelerator.GetCurrentAddress(), 0x00000000u);
EXPECT_EQ(accelerator.GetEndAddress(), 0x00001000u);
}

TEST(DSPAccelerator, SimpleReads)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x00001000);

for (size_t i = 1; i <= 0xf; ++i)
{
accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + i);
}
}

TEST(DSPAccelerator, AddressMasking)
{
TestAccelerator accelerator;

accelerator.SetCurrentAddress(0x48000000);
accelerator.SetStartAddress(0x48000000);
accelerator.SetEndAddress(0x48001000);
EXPECT_EQ(accelerator.GetStartAddress(), 0x08000000u);
EXPECT_EQ(accelerator.GetCurrentAddress(), 0x08000000u);
EXPECT_EQ(accelerator.GetEndAddress(), 0x08001000u);

accelerator.SetCurrentAddress(0xffffffff);
accelerator.SetStartAddress(0xffffffff);
accelerator.SetEndAddress(0xffffffff);
EXPECT_EQ(accelerator.GetStartAddress(), 0x3fffffffu);
EXPECT_EQ(accelerator.GetCurrentAddress(), 0xbfffffffu);
EXPECT_EQ(accelerator.GetEndAddress(), 0x3fffffffu);
}

TEST(DSPAccelerator, PredScaleRegisterMasking)
{
TestAccelerator accelerator;

accelerator.SetPredScale(0xbbbb);
EXPECT_EQ(accelerator.GetPredScale(), 0x3bu);
accelerator.SetPredScale(0xcccc);
EXPECT_EQ(accelerator.GetPredScale(), 0x4cu);
accelerator.SetPredScale(0xffff);
EXPECT_EQ(accelerator.GetPredScale(), 0x7fu);
}

TEST(DSPAccelerator, OverflowBehaviour)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x0000000f);

for (size_t i = 1; i <= 0xf; ++i)
{
accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + i);
}

accelerator.TestRead();
EXPECT_TRUE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress());

// Since an ACCOV has fired, reads are stopped (until the YN2 register is reset),
// so the current address shouldn't be updated for this read.
accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress());

// Simulate a write to YN2, which internally resets the "reads stopped" flag.
// After resetting it, reads should work once again.
accelerator.SetYn2(0);
for (size_t i = 1; i <= 0xf; ++i)
{
accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + i);
}
}

TEST(DSPAccelerator, OverflowFor16ByteAlignedAddresses)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x00000010);

for (size_t i = 1; i <= 0xf; ++i)
{
accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + i);
}

accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + 1);

accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + 2);
}

TEST(DSPAccelerator, OverflowForXXXXXXX1Addresses)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x00000011);

for (size_t i = 1; i <= 0xf; ++i)
{
accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + i);
}

accelerator.TestRead();
EXPECT_FALSE(accelerator.EndExceptionRaised());
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress());

accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), accelerator.GetStartAddress() + 1);
}

TEST(DSPAccelerator, CurrentAddressSkips)
{
TestAccelerator accelerator;
accelerator.SetCurrentAddress(0x00000000);
accelerator.SetStartAddress(0x00000000);
accelerator.SetEndAddress(0x00001000);

for (size_t j = 1; j <= 0xf; ++j)
accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), 0x0000000fu);

accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), 0x00000012u);

accelerator.TestRead();
EXPECT_EQ(accelerator.GetCurrentAddress(), 0x00000013u);
}