yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator_test_suite.h
Go to the documentation of this file.
1#ifndef YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
2#define YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
3
4#include <chrono>
5#include <memory>
6
8#include "app/emu/snes.h"
9#include "app/emu/cpu/cpu.h"
10#include "app/emu/audio/apu.h"
16#include "app/gui/icons.h"
17#include "util/log.h"
18
19namespace yaze {
20namespace test {
21
31 public:
32 EmulatorTestSuite() = default;
33 ~EmulatorTestSuite() override = default;
34
35 std::string GetName() const override { return "Emulator Core Tests"; }
36 TestCategory GetCategory() const override { return TestCategory::kUnit; }
37
38 absl::Status RunTests(TestResults& results) override {
44
45 return absl::OkStatus();
46 }
47
48 void DrawConfiguration() override {
49 ImGui::Text("%s Emulator Core Test Configuration", ICON_MD_GAMEPAD);
50 ImGui::Separator();
51 ImGui::Checkbox("Test APU Handshake Protocol", &test_apu_handshake_);
52 ImGui::Checkbox("Test SPC700 Cycle Accuracy", &test_spc700_cycles_);
53 ImGui::Checkbox("Test Breakpoint Manager", &test_breakpoint_manager_);
54 ImGui::Checkbox("Test Watchpoint Manager", &test_watchpoint_manager_);
55 ImGui::Checkbox("Test Audio Backend", &test_audio_backend_);
56 }
57
58 private:
59 // Configuration flags
65
75 auto start_time = std::chrono::steady_clock::now();
76 TestResult result;
77 result.name = "APU_Handshake_Protocol";
78 result.suite_name = GetName();
79 result.category = GetCategory();
80 result.timestamp = start_time;
81
82 try {
83 // Setup a mock SNES environment
84 emu::Snes snes;
85 std::vector<uint8_t> rom_data(0x8000, 0); // Minimal ROM
86 snes.Init(rom_data);
87
88 auto& apu = snes.apu();
89 auto& tracker = snes.apu_handshake_tracker();
90
91 // 1. Reset APU to start the IPL ROM boot sequence.
92 apu.Reset();
93 tracker.Reset();
94
95 // 2. Run APU for enough cycles to complete its internal initialization.
96 // The SPC700 should write $AA to port $F4 and $BB to $F5.
97 for (int i = 0; i < 10000; ++i) {
98 apu.RunCycles(i * 24); // Simulate passing master cycles
100 break;
101 }
102 }
103
104 // 3. Verify the APU has signaled it is ready.
106 throw std::runtime_error("APU did not signal ready ($BBAA). Current phase: " + tracker.GetPhaseString());
107 }
108
109 // 4. Simulate CPU writing $CC to initiate the transfer.
110 snes.Write(0x2140, 0xCC);
111
112 // 5. Run APU for a few more cycles to process the $CC command.
113 apu.RunCycles(snes.mutable_cycles() + 1000);
114
115 // 6. Verify the handshake is acknowledged.
116 if (tracker.IsHandshakeComplete()) {
118 result.error_message = "APU handshake successful. Ready signal and CPU ack verified.";
119 } else {
120 throw std::runtime_error("CPU handshake ($CC) was not acknowledged by APU.");
121 }
122
123 } catch (const std::exception& e) {
125 result.error_message = std::string("APU handshake test exception: ") + e.what();
126 }
127
128 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
129 std::chrono::steady_clock::now() - start_time);
130 results.AddResult(result);
131 }
132
143 auto start_time = std::chrono::steady_clock::now();
144 TestResult result;
145 result.name = "SPC700_Cycle_Accuracy";
146 result.suite_name = GetName();
147 result.category = GetCategory();
148 result.timestamp = start_time;
149
150 try {
151 // Dummy callbacks for SPC700 instantiation
152 emu::ApuCallbacks callbacks;
153 callbacks.read = [](uint16_t) { return 0; };
154 callbacks.write = [](uint16_t, uint8_t) {};
155 callbacks.idle = [](bool) {};
156
157 emu::Spc700 spc(callbacks);
158 spc.Reset(true);
159
160 // Test a sample of opcodes against the cycle table
161 // Opcode 0x00 (NOP) should take 2 cycles
162 spc.PC = 0; // Set PC to a known state
163 spc.RunOpcode(); // This will read opcode at PC=0 and prepare to execute
164 spc.RunOpcode(); // This executes the opcode
165
166 if (spc.GetLastOpcodeCycles() != 2) {
167 throw std::runtime_error(absl::StrFormat("NOP (0x00) should be 2 cycles, was %d", spc.GetLastOpcodeCycles()));
168 }
169
170 // Opcode 0x2F (BRA) should take 4 cycles
171 spc.PC = 0;
172 spc.RunOpcode();
173 spc.RunOpcode();
174
175 // Note: This is a simplified check. A full implementation would need to
176 // mock memory to provide the opcodes to the SPC700.
177
179 result.error_message = "Basic SPC700 cycle counts appear correct.";
180
181 } catch (const std::exception& e) {
183 result.error_message = std::string("SPC700 cycle test exception: ") + e.what();
184 }
185
186 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
187 std::chrono::steady_clock::now() - start_time);
188 results.AddResult(result);
189 }
190
199 auto start_time = std::chrono::steady_clock::now();
200 TestResult result;
201 result.name = "BreakpointManager_Core";
202 result.suite_name = GetName();
203 result.category = GetCategory();
204 result.timestamp = start_time;
205
206 try {
208
209 // 1. Add an execution breakpoint
211 if (bpm.GetAllBreakpoints().size() != 1) {
212 throw std::runtime_error("Failed to add breakpoint.");
213 }
214
215 // 2. Test hit detection
217 throw std::runtime_error("Execution breakpoint was not hit.");
218 }
220 throw std::runtime_error("Breakpoint hit at incorrect address.");
221 }
222
223 // 3. Test removal
224 bpm.RemoveBreakpoint(bp_id);
225 if (bpm.GetAllBreakpoints().size() != 0) {
226 throw std::runtime_error("Failed to remove breakpoint.");
227 }
229 throw std::runtime_error("Breakpoint was hit after being removed.");
230 }
231
233 result.error_message = "BreakpointManager add, hit, and remove tests passed.";
234
235 } catch (const std::exception& e) {
237 result.error_message = std::string("BreakpointManager test exception: ") + e.what();
238 }
239
240 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
241 std::chrono::steady_clock::now() - start_time);
242 results.AddResult(result);
243 }
244
253 auto start_time = std::chrono::steady_clock::now();
254 TestResult result;
255 result.name = "WatchpointManager_Core";
256 result.suite_name = GetName();
257 result.category = GetCategory();
258 result.timestamp = start_time;
259
260 try {
262
263 // 1. Add a write watchpoint on address $7E0010 with break enabled.
264 uint32_t wp_id = wpm.AddWatchpoint(0x7E0010, 0x7E0010, false, true, true, "Link HP");
265
266 // 2. Simulate a write access and check if it breaks.
267 bool should_break = wpm.OnMemoryAccess(0x8000, 0x7E0010, true, 0x05, 0x06, 12345);
268 if (!should_break) {
269 throw std::runtime_error("Write watchpoint did not trigger a break.");
270 }
271
272 // 3. Simulate a read access, which should not break.
273 should_break = wpm.OnMemoryAccess(0x8001, 0x7E0010, false, 0x06, 0x06, 12350);
274 if (should_break) {
275 throw std::runtime_error("Read access incorrectly triggered a write-only watchpoint.");
276 }
277
278 // 4. Verify the write access was logged.
279 auto history = wpm.GetHistory(0x7E0010);
280 if (history.size() != 1) {
281 throw std::runtime_error("Memory access was not logged to watchpoint history.");
282 }
283 if (history[0].new_value != 0x06 || !history[0].is_write) {
284 throw std::runtime_error("Logged access data is incorrect.");
285 }
286
288 result.error_message = "WatchpointManager logging and break-on-write tests passed.";
289
290 } catch (const std::exception& e) {
292 result.error_message = std::string("WatchpointManager test exception: ") + e.what();
293 }
294
295 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
296 std::chrono::steady_clock::now() - start_time);
297 results.AddResult(result);
298 }
299
308 auto start_time = std::chrono::steady_clock::now();
309 TestResult result;
310 result.name = "Audio_Backend_Initialization";
311 result.suite_name = GetName();
312 result.category = GetCategory();
313 result.timestamp = start_time;
314
315 try {
317
318 // 1. Test initialization
320 if (!backend->Initialize(config)) {
321 throw std::runtime_error("Audio backend failed to initialize.");
322 }
323 if (!backend->IsInitialized()) {
324 throw std::runtime_error("IsInitialized() returned false after successful initialization.");
325 }
326
327 // 2. Test state changes
328 backend->Play();
329 if (!backend->GetStatus().is_playing) {
330 throw std::runtime_error("Backend is not playing after Play() was called.");
331 }
332
333 backend->Pause();
334 if (backend->GetStatus().is_playing) {
335 throw std::runtime_error("Backend is still playing after Pause() was called.");
336 }
337
338 // 3. Test shutdown
339 backend->Shutdown();
340 if (backend->IsInitialized()) {
341 throw std::runtime_error("IsInitialized() returned true after Shutdown().");
342 }
343
345 result.error_message = "Audio backend Initialize, Play, Pause, and Shutdown states work correctly.";
346
347 } catch (const std::exception& e) {
349 result.error_message = std::string("Audio backend test exception: ") + e.what();
350 }
351
352 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
353 std::chrono::steady_clock::now() - start_time);
354 results.AddResult(result);
355 }
356};
357
358} // namespace test
359} // namespace yaze
360
361#endif // YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
Manages CPU and SPC700 breakpoints for debugging.
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
auto mutable_cycles() -> uint64_t &
Definition snes.h:76
auto apu_handshake_tracker() -> debug::ApuHandshakeTracker &
Definition snes.h:79
auto apu() -> Apu &
Definition snes.h:73
void Write(uint32_t adr, uint8_t val)
Definition snes.cc:623
void Init(std::vector< uint8_t > &rom_data)
Definition snes.cc:36
The Spc700 class represents the SPC700 processor.
Definition spc700.h:69
void Reset(bool hard=false)
Definition spc700.cc:16
int GetLastOpcodeCycles() const
Definition spc700.h:148
uint16_t PC
Definition spc700.h:106
void RunOpcode()
Definition spc700.cc:74
Manages memory watchpoints for debugging.
uint32_t AddWatchpoint(uint32_t start_address, uint32_t end_address, bool track_reads, bool track_writes, bool break_on_access=false, const std::string &description="")
Add a memory watchpoint.
std::vector< AccessLog > GetHistory(uint32_t address, int max_entries=100) const
Get access history for a specific address.
bool OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write, uint8_t old_value, uint8_t new_value, uint64_t cycle_count)
Check if memory access should break/log.
static std::unique_ptr< IAudioBackend > Create(BackendType type)
Test suite for core emulator components.
absl::Status RunTests(TestResults &results) override
void RunBreakpointManagerTest(TestResults &results)
Tests the core functionality of the BreakpointManager.
void RunApuHandshakeTest(TestResults &results)
Verifies the CPU-APU handshake protocol.
~EmulatorTestSuite() override=default
std::string GetName() const override
void RunAudioBackendTest(TestResults &results)
Tests the audio backend abstraction layer.
TestCategory GetCategory() const override
void RunWatchpointManagerTest(TestResults &results)
Tests the memory WatchpointManager.
void RunSpc700CycleAccuracyTest(TestResults &results)
Validates the cycle counting for SPC700 opcodes.
#define ICON_MD_GAMEPAD
Definition icons.h:864
Main namespace for the application.
std::function< void(uint16_t, uint8_t)> write
Definition spc700.h:53
std::function< void(bool)> idle
Definition spc700.h:55
std::function< uint8_t(uint16_t)> read
Definition spc700.h:54
std::chrono::milliseconds duration
std::string error_message
std::chrono::time_point< std::chrono::steady_clock > timestamp
void AddResult(const TestResult &result)