Refactor Snes class and enhance Apu functionality

- Updated Snes constructor to initialize CPU callbacks directly, improving readability and maintainability.
- Removed unnecessary CpuCallbacks parameter from Cpu constructor, streamlining the class design.
- Added new methods in Apu for retrieving cycles, status, control, and handling DMA transfers, enhancing audio processing capabilities.
- Introduced unit tests for Apu to validate initialization, sample generation, and handshake timing, ensuring robust audio functionality.
This commit is contained in:
scawful
2025-09-24 12:51:29 -04:00
parent f1b1c91986
commit 3734884ba3
7 changed files with 289 additions and 18 deletions

134
test/emu/audio/apu_test.cc Normal file
View File

@@ -0,0 +1,134 @@
#include "app/emu/audio/apu.h"
#include "app/emu/memory/memory.h"
#include <gmock/gmock-nice-strict.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace yaze {
namespace test {
using testing::_;
using testing::Return;
using yaze::emu::Apu;
using yaze::emu::MemoryImpl;
class ApuTest : public ::testing::Test {
protected:
void SetUp() override {
memory_ = std::make_unique<MemoryImpl>();
apu_ = std::make_unique<Apu>(*memory_);
apu_->Init();
}
std::unique_ptr<MemoryImpl> memory_;
std::unique_ptr<Apu> apu_;
};
// Test the IPL ROM handshake sequence timing
TEST_F(ApuTest, IplRomHandshakeTiming) {
// 1. Initial state check
EXPECT_EQ(apu_->Read(0x00) & 0x80, 0); // Ready bit should be clear
// 2. Start handshake
apu_->Write(0x00, 0x80); // Set control register bit 7
// 3. Wait for APU ready signal with cycle counting
int cycles = 0;
const int max_cycles = 1000; // Maximum expected cycles for handshake
while (!(apu_->Read(0x00) & 0x80) && cycles < max_cycles) {
apu_->RunCycles(1);
cycles++;
}
// 4. Verify timing constraints
EXPECT_LT(cycles, max_cycles); // Should complete within max cycles
EXPECT_GT(cycles, 0); // Should take some cycles
EXPECT_TRUE(apu_->Read(0x00) & 0x80); // Ready bit should be set
// 5. Verify handshake completion
EXPECT_EQ(apu_->GetStatus() & 0x80, 0x80); // Ready bit in status register
}
// Test APU initialization sequence
TEST_F(ApuTest, ApuInitialization) {
// 1. Check initial state
EXPECT_EQ(apu_->GetStatus(), 0x00);
EXPECT_EQ(apu_->GetControl(), 0x00);
// 2. Initialize APU
apu_->Init();
// 3. Verify initialization
EXPECT_EQ(apu_->GetStatus(), 0x00);
EXPECT_EQ(apu_->GetControl(), 0x00);
// 4. Check DSP registers are initialized
for (int i = 0; i < 128; i++) {
EXPECT_EQ(apu_->Read(0x00 + i), 0x00);
}
}
// Test sample generation and timing
TEST_F(ApuTest, SampleGenerationTiming) {
// 1. Generate samples
const int sample_count = 1024;
std::vector<int16_t> samples(sample_count);
// 2. Measure timing
uint64_t start_cycles = apu_->GetCycles();
apu_->GetSamples(samples.data(), sample_count, false);
uint64_t end_cycles = apu_->GetCycles();
// 3. Verify timing
EXPECT_GT(end_cycles - start_cycles, 0);
// 4. Verify samples
bool has_non_zero = false;
for (int i = 0; i < sample_count; ++i) {
if (samples[i] != 0) {
has_non_zero = true;
break;
}
}
EXPECT_TRUE(has_non_zero);
}
// Test DSP register access timing
TEST_F(ApuTest, DspRegisterAccessTiming) {
// 1. Write to DSP registers
const uint8_t test_value = 0x42;
uint64_t start_cycles = apu_->GetCycles();
apu_->Write(0x00, 0x80); // Set control register
apu_->Write(0x01, test_value); // Write to DSP address
uint64_t end_cycles = apu_->GetCycles();
// 2. Verify timing
EXPECT_GT(end_cycles - start_cycles, 0);
// 3. Verify register access
EXPECT_EQ(apu_->Read(0x01), test_value);
}
// Test DMA transfer timing
TEST_F(ApuTest, DmaTransferTiming) {
// 1. Prepare DMA data
const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
// 2. Measure DMA timing
uint64_t start_cycles = apu_->GetCycles();
apu_->WriteDma(0x00, data, sizeof(data));
uint64_t end_cycles = apu_->GetCycles();
// 3. Verify timing
EXPECT_GT(end_cycles - start_cycles, 0);
// 4. Verify DMA transfer
EXPECT_EQ(apu_->Read(0x00), 0x01);
EXPECT_EQ(apu_->Read(0x01), 0x02);
}
} // namespace test
} // namespace yaze

View File

@@ -0,0 +1,122 @@
#include "app/emu/audio/apu.h"
#include "app/emu/memory/memory.h"
#include <gmock/gmock-nice-strict.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace yaze {
namespace test {
using testing::_;
using testing::Return;
using yaze::emu::Apu;
using yaze::emu::MemoryImpl;
class IplHandshakeTest : public ::testing::Test {
protected:
void SetUp() override {
memory_ = std::make_unique<MemoryImpl>();
apu_ = std::make_unique<Apu>(*memory_);
apu_->Init();
}
std::unique_ptr<MemoryImpl> memory_;
std::unique_ptr<Apu> apu_;
};
// Test IPL ROM handshake timing with exact cycle counts
TEST_F(IplHandshakeTest, ExactCycleTiming) {
// 1. Initial state
EXPECT_EQ(apu_->Read(0x00) & 0x80, 0); // Ready bit should be clear
// 2. Start handshake
apu_->Write(0x00, 0x80); // Set control register bit 7
// 3. Run exact number of cycles for handshake
const int expected_cycles = 64; // Expected cycle count for handshake
apu_->RunCycles(expected_cycles);
// 4. Verify handshake completed
EXPECT_TRUE(apu_->Read(0x00) & 0x80); // Ready bit should be set
EXPECT_EQ(apu_->GetStatus() & 0x80, 0x80); // Ready bit in status register
}
// Test IPL ROM handshake timing with cycle range
TEST_F(IplHandshakeTest, CycleRange) {
// 1. Initial state
EXPECT_EQ(apu_->Read(0x00) & 0x80, 0); // Ready bit should be clear
// 2. Start handshake
apu_->Write(0x00, 0x80); // Set control register bit 7
// 3. Wait for handshake with cycle counting
int cycles = 0;
const int min_cycles = 32; // Minimum expected cycles
const int max_cycles = 96; // Maximum expected cycles
while (!(apu_->Read(0x00) & 0x80) && cycles < max_cycles) {
apu_->RunCycles(1);
cycles++;
}
// 4. Verify timing constraints
EXPECT_GE(cycles, min_cycles); // Should take at least min_cycles
EXPECT_LE(cycles, max_cycles); // Should complete within max_cycles
EXPECT_TRUE(apu_->Read(0x00) & 0x80); // Ready bit should be set
}
// Test IPL ROM handshake with multiple attempts
TEST_F(IplHandshakeTest, MultipleAttempts) {
const int num_attempts = 10;
std::vector<int> cycle_counts;
for (int i = 0; i < num_attempts; i++) {
// Reset APU
apu_->Init();
// Start handshake
apu_->Write(0x00, 0x80);
// Count cycles until ready
int cycles = 0;
while (!(apu_->Read(0x00) & 0x80) && cycles < 1000) {
apu_->RunCycles(1);
cycles++;
}
// Record cycle count
cycle_counts.push_back(cycles);
// Verify handshake completed
EXPECT_TRUE(apu_->Read(0x00) & 0x80);
}
// Verify cycle count consistency
int min_cycles = *std::min_element(cycle_counts.begin(), cycle_counts.end());
int max_cycles = *std::max_element(cycle_counts.begin(), cycle_counts.end());
EXPECT_LE(max_cycles - min_cycles, 2); // Cycle count should be consistent
}
// Test IPL ROM handshake with interrupts
TEST_F(IplHandshakeTest, WithInterrupts) {
// 1. Initial state
EXPECT_EQ(apu_->Read(0x00) & 0x80, 0);
// 2. Enable interrupts
apu_->Write(0x00, 0x80 | 0x40); // Set control register bits 7 and 6
// 3. Run cycles with interrupts
int cycles = 0;
while (!(apu_->Read(0x00) & 0x80) && cycles < 1000) {
apu_->RunCycles(1);
cycles++;
}
// 4. Verify handshake completed
EXPECT_TRUE(apu_->Read(0x00) & 0x80);
EXPECT_EQ(apu_->GetStatus() & 0x80, 0x80);
}
} // namespace test
} // namespace yaze

View File

@@ -13,7 +13,6 @@ namespace test {
using yaze::emu::AsmParser;
using yaze::emu::Cpu;
using yaze::emu::CpuCallbacks;
using yaze::emu::MockClock;
using yaze::emu::MockMemory;
/**
@@ -29,9 +28,8 @@ class CpuTest : public ::testing::Test {
AsmParser asm_parser;
MockMemory mock_memory;
MockClock mock_clock;
CpuCallbacks cpu_callbacks;
Cpu cpu{mock_memory, mock_clock, cpu_callbacks};
Cpu cpu{mock_memory};
};
using ::testing::_;
@@ -42,7 +40,6 @@ using ::testing::Return;
// ============================================================================
TEST_F(CpuTest, AsmParserTokenizerOk) {
AsmParser asm_parser;
std::string instruction = R"(
ADC.b #$01
LDA.b #$FF
@@ -58,7 +55,6 @@ TEST_F(CpuTest, AsmParserTokenizerOk) {
}
TEST_F(CpuTest, AsmParserSingleInstructionOk) {
AsmParser asm_parser;
std::string instruction = "ADC.b #$01";
std::vector<std::string> tokens = asm_parser.Tokenize(instruction);