diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h index b971a0e2..974d031d 100644 --- a/src/app/emu/audio/apu.h +++ b/src/app/emu/audio/apu.h @@ -65,6 +65,18 @@ class Apu { auto dsp() -> Dsp & { return dsp_; } auto spc700() -> Spc700 & { return spc700_; } + uint64_t GetCycles() const { return cycles_; } + uint8_t GetStatus() const { return ram[0x00]; } + uint8_t GetControl() const { return ram[0x01]; } + void GetSamples(int16_t *buffer, int count, bool loop = false) { + dsp_.GetSamples(buffer, count, loop); + } + void WriteDma(uint16_t address, const uint8_t *data, int count) { + for (int i = 0; i < count; i++) { + ram[address + i] = data[i]; + } + } + // Port buffers (equivalent to $2140 to $2143 for the main CPU) std::array in_ports_; // includes 2 bytes of ram std::array out_ports_; diff --git a/src/app/emu/cpu/cpu.h b/src/app/emu/cpu/cpu.h index 1206c506..78f7f301 100644 --- a/src/app/emu/cpu/cpu.h +++ b/src/app/emu/cpu/cpu.h @@ -31,10 +31,12 @@ class InstructionEntry { class Cpu { public: - explicit Cpu(Memory& mem, CpuCallbacks& callbacks) - : memory(mem), callbacks_(callbacks) {} + explicit Cpu(Memory& mem) : memory(mem) {} void Reset(bool hard = false); + auto& callbacks() { return callbacks_; } + const auto& callbacks() const { return callbacks_; } + void RunOpcode(); void ExecuteInstruction(uint8_t opcode); @@ -781,8 +783,8 @@ class Cpu { bool int_wanted_ = false; bool int_delay_ = false; - CpuCallbacks callbacks_; Memory& memory; + CpuCallbacks callbacks_; }; } // namespace emu diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index e6981cd5..d385081a 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -2,6 +2,8 @@ #define YAZE_APP_EMU_SNES_H #include +#include +#include #include "app/emu/audio/apu.h" #include "app/emu/cpu/cpu.h" @@ -21,7 +23,11 @@ struct Input { class Snes { public: - Snes() = default; + Snes() { + cpu_.callbacks().read_byte = [this](uint32_t adr) { return CpuRead(adr); }; + cpu_.callbacks().write_byte = [this](uint32_t adr, uint8_t val) { CpuWrite(adr, val); }; + cpu_.callbacks().idle = [this](bool waiting) { CpuIdle(waiting); }; + } ~Snes() = default; void Init(std::vector& rom_data); @@ -61,14 +67,11 @@ class Snes { auto get_ram() -> uint8_t* { return ram; } auto mutable_cycles() -> uint64_t& { return cycles_; } + bool fast_mem_ = false; + private: MemoryImpl memory_; - CpuCallbacks cpu_callbacks_ = { - [&](uint32_t adr) { return CpuRead(adr); }, - [&](uint32_t adr, uint8_t val) { CpuWrite(adr, val); }, - [&](bool waiting) { CpuIdle(waiting); }, - }; - Cpu cpu_{memory_, cpu_callbacks_}; + Cpu cpu_{memory_}; Ppu ppu_{memory_}; Apu apu_{memory_}; @@ -111,12 +114,9 @@ class Snes { bool auto_joy_read_ = false; uint16_t auto_joy_timer_ = 0; bool ppu_latch_; - - bool fast_mem_ = false; }; } // namespace emu - } // namespace yaze #endif // YAZE_APP_EMU_SNES_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 08571e68..10a7f75e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,11 @@ add_executable( zelda3/message_test.cc zelda3/overworld_test.cc zelda3/sprite_builder_test.cc + emu/cpu_test.cc + emu/ppu_test.cc + emu/spc700_test.cc + emu/audio/apu_test.cc + emu/audio/ipl_handshake_test.cc integration/dungeon_editor_test.cc zelda3/object_parser_test.cc zelda3/object_parser_structs_test.cc diff --git a/test/emu/audio/apu_test.cc b/test/emu/audio/apu_test.cc new file mode 100644 index 00000000..dff1e4dc --- /dev/null +++ b/test/emu/audio/apu_test.cc @@ -0,0 +1,134 @@ +#include "app/emu/audio/apu.h" +#include "app/emu/memory/memory.h" + +#include +#include +#include + +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(); + apu_ = std::make_unique(*memory_); + apu_->Init(); + } + + std::unique_ptr memory_; + std::unique_ptr 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 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 \ No newline at end of file diff --git a/test/emu/audio/ipl_handshake_test.cc b/test/emu/audio/ipl_handshake_test.cc new file mode 100644 index 00000000..4338a337 --- /dev/null +++ b/test/emu/audio/ipl_handshake_test.cc @@ -0,0 +1,122 @@ +#include "app/emu/audio/apu.h" +#include "app/emu/memory/memory.h" + +#include +#include +#include + +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(); + apu_ = std::make_unique(*memory_); + apu_->Init(); + } + + std::unique_ptr memory_; + std::unique_ptr 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 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 \ No newline at end of file diff --git a/test/emu/cpu_test.cc b/test/emu/cpu_test.cc index 93179e68..8814c612 100644 --- a/test/emu/cpu_test.cc +++ b/test/emu/cpu_test.cc @@ -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 tokens = asm_parser.Tokenize(instruction);