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
4#include <SDL.h>
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, 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", current_pc,
110 stuck_counter_ * 60);
111 fprintf(stderr, "ERROR: This likely indicates a hang or infinite loop\n");
112 return absl::InternalError("APU stuck in infinite loop");
113 }
114 } else {
115 stuck_counter_ = 0;
116 }
117 last_pc_ = current_pc;
118 }
119 }
120
121 printf("Emulation complete: %d frames\n", frame);
122 return absl::OkStatus();
123 }
124
125 private:
126 void LogApuState(int frame) {
127 auto& apu = snes_->apu();
128 auto& spc = apu.spc700();
129 auto& tracker = snes_->apu_handshake_tracker();
130
131 printf("=== Frame %d APU State ===\n", frame);
132 printf(" SPC700 PC: $%04X\n", spc.PC);
133 printf(" SPC700 A: $%02X\n", spc.A);
134 printf(" SPC700 X: $%02X\n", spc.X);
135 printf(" SPC700 Y: $%02X\n", spc.Y);
136 printf(" SPC700 SP: $%02X\n", spc.SP);
137 printf(" SPC700 PSW: N=%d V=%d P=%d B=%d H=%d I=%d Z=%d C=%d\n", spc.PSW.N,
138 spc.PSW.V, spc.PSW.P, spc.PSW.B, spc.PSW.H, spc.PSW.I, spc.PSW.Z,
139 spc.PSW.C);
140 printf(" APU Cycles: %llu\n", apu.GetCycles());
141
142 // Port status
143 printf(" Input Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X\n",
144 apu.in_ports_[0], apu.in_ports_[1], apu.in_ports_[2],
145 apu.in_ports_[3]);
146 printf(" Output Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X\n",
147 apu.out_ports_[0], apu.out_ports_[1], apu.out_ports_[2],
148 apu.out_ports_[3]);
149
150 // Handshake phase
151 const char* handshake_phase = "UNKNOWN";
152 switch (tracker.GetPhase()) {
154 handshake_phase = "RESET";
155 break;
157 handshake_phase = "IPL_BOOT";
158 break;
160 handshake_phase = "WAITING_BBAA";
161 break;
163 handshake_phase = "HANDSHAKE_CC";
164 break;
166 handshake_phase = "TRANSFER_ACTIVE";
167 break;
169 handshake_phase = "TRANSFER_DONE";
170 break;
172 handshake_phase = "RUNNING";
173 break;
174 }
175 printf(" Handshake: %s\n", handshake_phase);
176
177 // Zero page (used by IPL ROM)
178 auto& ram = apu.ram;
179 printf(" Zero Page: $00=$%02X $01=$%02X $02=$%02X $03=$%02X\n", ram[0x00],
180 ram[0x01], ram[0x02], ram[0x03]);
181
182 // Check reset vector
183 uint16_t reset_vector =
184 static_cast<uint16_t>(ram[0xFFFE] | (ram[0xFFFF] << 8));
185 printf(" Reset Vector ($FFFE-$FFFF): $%04X\n", reset_vector);
186 }
187
188 void Cleanup() {
189 if (sdl_initialized_) {
190 SDL_Quit();
191 sdl_initialized_ = false;
192 }
193 }
194
195 std::unique_ptr<Snes> snes_;
196 std::vector<uint8_t> rom_data_;
197 bool sdl_initialized_ = false;
198 uint16_t last_pc_ = 0;
200};
201
202} // namespace test
203} // namespace emu
204} // namespace yaze
205
206int main(int argc, char** argv) {
207 absl::ParseCommandLine(argc, argv);
208
209 // Configure logging
210 std::string rom_path = absl::GetFlag(FLAGS_rom);
211 int max_frames = absl::GetFlag(FLAGS_max_frames);
212 int log_interval = absl::GetFlag(FLAGS_log_interval);
213 bool verbose = absl::GetFlag(FLAGS_verbose);
214 bool trace_apu = absl::GetFlag(FLAGS_trace_apu);
215
216 if (rom_path.empty()) {
217 std::cerr << "Error: --rom flag is required\n";
218 std::cerr << "Usage: yaze_emu_test --rom=zelda3.sfc [options]\n";
219 std::cerr << "\nOptions:\n";
220 std::cerr << " --rom=PATH Path to ROM file (required)\n";
221 std::cerr << " --max_frames=N Run for N frames (0=infinite, "
222 "default=60)\n";
223 std::cerr << " --log_interval=N Log APU state every N frames "
224 "(default=10)\n";
225 std::cerr << " --verbose Enable verbose logging\n";
226 std::cerr << " --trace_apu Enable detailed APU instruction "
227 "tracing\n";
228 return 1;
229 }
230
231 // Set log level
232 if (verbose) {
233 // Enable all logging
234 std::cout << "Verbose logging enabled\n";
235 }
236
237 // Create and run headless emulator
239
240 auto status = emulator.Init(rom_path);
241 if (!status.ok()) {
242 std::cerr << "Initialization failed: " << status.message() << "\n";
243 return 1;
244 }
245
246 status = emulator.RunFrames(max_frames, log_interval);
247 if (!status.ok()) {
248 std::cerr << "Emulation failed: " << status.message() << "\n";
249 return 1;
250 }
251
252 std::cout << "Test completed successfully\n";
253 return 0;
254}
std::vector< uint8_t > rom_data_
Definition emu_test.cc:196
std::unique_ptr< Snes > snes_
Definition emu_test.cc:195
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, rom, "", "Path to ROM file to test")
Main namespace for the application.