yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emu_test.cc
Go to the documentation of this file.
1// Headless Emulator Test Harness
2// Minimal SDL initialization for testing APU without GUI overhead
3
5
6#include <cstdint>
7#include <cstdio>
8#include <iostream>
9#include <memory>
10#include <string>
11#include <vector>
12
13#include "absl/flags/flag.h"
14#include "absl/flags/parse.h"
15#include "absl/status/status.h"
16#include "absl/status/statusor.h"
17#include "app/emu/snes.h"
18#include "util/log.h"
19
20ABSL_FLAG(std::string, emu_test_rom, "", "Path to ROM file to test");
21ABSL_FLAG(int, max_frames, 60, "Maximum frames to run (0 = infinite)");
22ABSL_FLAG(int, log_interval, 10, "Log APU state every N frames");
23ABSL_FLAG(bool, dump_audio, false, "Dump audio output to WAV file");
24ABSL_FLAG(std::string, audio_file, "apu_test.wav", "Audio dump filename");
25ABSL_FLAG(bool, verbose, false, "Enable verbose logging");
26ABSL_FLAG(bool, trace_apu, false, "Enable detailed APU instruction tracing");
27
28namespace yaze {
29namespace emu {
30namespace test {
31
33 public:
34 HeadlessEmulator() = default;
36
37 absl::Status Init(const std::string& rom_path) {
38 // Initialize minimal SDL (audio + events only)
39 if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) {
40 return absl::InternalError(
41 absl::StrCat("SDL_Init failed: ", SDL_GetError()));
42 }
43 sdl_initialized_ = true;
44
45 // Load ROM file
46 FILE* file = fopen(rom_path.c_str(), "rb");
47 if (!file) {
48 return absl::NotFoundError(
49 absl::StrCat("Failed to open ROM: ", rom_path));
50 }
51
52 fseek(file, 0, SEEK_END);
53 size_t size = ftell(file);
54 fseek(file, 0, SEEK_SET);
55
56 rom_data_.resize(size);
57 if (fread(rom_data_.data(), 1, size, file) != size) {
58 fclose(file);
59 return absl::InternalError("Failed to read ROM data");
60 }
61 fclose(file);
62
63 printf("Loaded ROM: %zu bytes\n", size);
64
65 // Initialize SNES emulator
66 snes_ = std::make_unique<Snes>();
67 snes_->Init(rom_data_);
68 snes_->Reset(true);
69
70 printf("SNES initialized and reset\n");
71 printf("APU PC after reset: $%04X\n", snes_->apu().spc700().PC);
72 printf("APU cycles: %llu\n", snes_->apu().GetCycles());
73
74 return absl::OkStatus();
75 }
76
77 absl::Status RunFrames(int max_frames, int log_interval) {
78 int frame = 0;
79 bool infinite = (max_frames == 0);
80
81 printf("Starting emulation (max_frames=%d, log_interval=%d)\n", max_frames,
82 log_interval);
83
84 while (infinite || frame < max_frames) {
85 // Run one frame
86 snes_->RunFrame();
87 frame++;
88
89 // Periodic APU state logging
90 if (log_interval > 0 && frame % log_interval == 0) {
91 LogApuState(frame);
92 }
93
94 // Check for exit events
95 SDL_Event event;
96 while (SDL_PollEvent(&event)) {
97 if (event.type == SDL_QUIT) {
98 printf("SDL_QUIT received, stopping emulation\n");
99 return absl::OkStatus();
100 }
101 }
102
103 // Check for stuck APU (PC not advancing)
104 if (frame % 60 == 0) {
105 uint16_t current_pc = snes_->apu().spc700().PC;
106 if (current_pc == last_pc_ && frame > 60) {
108 if (stuck_counter_ > 5) {
109 fprintf(stderr, "ERROR: APU stuck at PC=$%04X for %d frames\n",
110 current_pc, stuck_counter_ * 60);
111 fprintf(stderr,
112 "ERROR: This likely indicates a hang or infinite loop\n");
113 return absl::InternalError("APU stuck in infinite loop");
114 }
115 } else {
116 stuck_counter_ = 0;
117 }
118 last_pc_ = current_pc;
119
120 // Debug: Log ALTTP game module ($7E0010) - check every frame
121 uint8_t game_module = snes_->get_ram()[0x10];
122 uint8_t submodule = snes_->get_ram()[0x11];
123 static uint8_t last_module = 0xFF;
124 static uint8_t last_submodule = 0xFF;
125 if (game_module != last_module || submodule != last_submodule) {
126 printf("[GAME] Frame %d: Module 0x%02X -> 0x%02X, Sub 0x%02X -> 0x%02X\n",
127 frame, last_module, game_module, last_submodule, submodule);
128 last_module = game_module;
129 last_submodule = submodule;
130 }
131 }
132 }
133
134 printf("Emulation complete: %d frames\n", frame);
135 return absl::OkStatus();
136 }
137
138 private:
139 void LogApuState(int frame) {
140 auto& apu = snes_->apu();
141 auto& spc = apu.spc700();
142 auto& tracker = snes_->apu_handshake_tracker();
143
144 printf("=== Frame %d APU State ===\n", frame);
145 printf(" SPC700 PC: $%04X\n", spc.PC);
146 printf(" SPC700 A: $%02X\n", spc.A);
147 printf(" SPC700 X: $%02X\n", spc.X);
148 printf(" SPC700 Y: $%02X\n", spc.Y);
149 printf(" SPC700 SP: $%02X\n", spc.SP);
150 printf(" SPC700 PSW: N=%d V=%d P=%d B=%d H=%d I=%d Z=%d C=%d\n", spc.PSW.N,
151 spc.PSW.V, spc.PSW.P, spc.PSW.B, spc.PSW.H, spc.PSW.I, spc.PSW.Z,
152 spc.PSW.C);
153 printf(" APU Cycles: %llu\n", apu.GetCycles());
154
155 // Port status
156 printf(" Input Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X\n",
157 apu.in_ports_[0], apu.in_ports_[1], apu.in_ports_[2],
158 apu.in_ports_[3]);
159 printf(" Output Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X\n",
160 apu.out_ports_[0], apu.out_ports_[1], apu.out_ports_[2],
161 apu.out_ports_[3]);
162
163 // Handshake phase
164 const char* handshake_phase = "UNKNOWN";
165 switch (tracker.GetPhase()) {
167 handshake_phase = "RESET";
168 break;
170 handshake_phase = "IPL_BOOT";
171 break;
173 handshake_phase = "WAITING_BBAA";
174 break;
176 handshake_phase = "HANDSHAKE_CC";
177 break;
179 handshake_phase = "TRANSFER_ACTIVE";
180 break;
182 handshake_phase = "TRANSFER_DONE";
183 break;
185 handshake_phase = "RUNNING";
186 break;
187 }
188 printf(" Handshake: %s\n", handshake_phase);
189
190 // Zero page (used by IPL ROM)
191 auto& ram = apu.ram;
192 printf(" Zero Page: $00=$%02X $01=$%02X $02=$%02X $03=$%02X\n", ram[0x00],
193 ram[0x01], ram[0x02], ram[0x03]);
194
195 // Check reset vector
196 uint16_t reset_vector =
197 static_cast<uint16_t>(ram[0xFFFE] | (ram[0xFFFF] << 8));
198 printf(" Reset Vector ($FFFE-$FFFF): $%04X\n", reset_vector);
199 }
200
201 void Cleanup() {
202 if (sdl_initialized_) {
203 SDL_Quit();
204 sdl_initialized_ = false;
205 }
206 }
207
208 std::unique_ptr<Snes> snes_;
209 std::vector<uint8_t> rom_data_;
210 bool sdl_initialized_ = false;
211 uint16_t last_pc_ = 0;
213};
214
215} // namespace test
216} // namespace emu
217} // namespace yaze
218
219int main(int argc, char** argv) {
220 absl::ParseCommandLine(argc, argv);
221
222 // Configure logging
223 std::string rom_path = absl::GetFlag(FLAGS_emu_test_rom);
224 int max_frames = absl::GetFlag(FLAGS_max_frames);
225 int log_interval = absl::GetFlag(FLAGS_log_interval);
226 bool verbose = absl::GetFlag(FLAGS_verbose);
227 bool trace_apu = absl::GetFlag(FLAGS_trace_apu);
228
229 if (rom_path.empty()) {
230 std::cerr << "Error: --emu_test_rom flag is required\n";
231 std::cerr << "Usage: yaze_emu_test --emu_test_rom=zelda3.sfc [options]\n";
232 std::cerr << "\nOptions:\n";
233 std::cerr << " --emu_test_rom=PATH Path to ROM file (required)\n";
234 std::cerr << " --max_frames=N Run for N frames (0=infinite, "
235 "default=60)\n";
236 std::cerr << " --log_interval=N Log APU state every N frames "
237 "(default=10)\n";
238 std::cerr << " --verbose Enable verbose logging\n";
239 std::cerr << " --trace_apu Enable detailed APU instruction "
240 "tracing\n";
241 return 1;
242 }
243
244 // Set log level
245 if (verbose) {
246 // Enable all logging
247 std::cout << "Verbose logging enabled\n";
248 }
249
250 // Create and run headless emulator
252
253 auto status = emulator.Init(rom_path);
254 if (!status.ok()) {
255 std::cerr << "Initialization failed: " << status.message() << "\n";
256 return 1;
257 }
258
259 status = emulator.RunFrames(max_frames, log_interval);
260 if (!status.ok()) {
261 std::cerr << "Emulation failed: " << status.message() << "\n";
262 return 1;
263 }
264
265 std::cout << "Test completed successfully\n";
266 return 0;
267}
std::vector< uint8_t > rom_data_
Definition emu_test.cc:209
std::unique_ptr< Snes > snes_
Definition emu_test.cc:208
absl::Status Init(const std::string &rom_path)
Definition emu_test.cc:37
absl::Status RunFrames(int max_frames, int log_interval)
Definition emu_test.cc:77
ABSL_FLAG(std::string, emu_test_rom, "", "Path to ROM file to test")
int main(int argc, char **argv)
Definition emu_test.cc:219
SDL2/SDL3 compatibility layer.