yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
apu.cc
Go to the documentation of this file.
1#include "app/emu/audio/apu.h"
2
4
5#include <algorithm>
6#include <cstdint>
7#include <vector>
8
9#include "app/emu/audio/dsp.h"
13#include "util/log.h"
14
15namespace yaze {
16namespace emu {
17
18// Fixed-point cycle ratio for perfect precision (no floating-point drift)
19// APU runs at ~1.024 MHz, master clock at ~21.477 MHz (NTSC)
20// Ratio = (32040 * 32) / (1364 * 262 * 60) = 1,025,280 / 21,437,280
21static constexpr uint64_t kApuCyclesNumerator = 32040 * 32; // 1,025,280
22static constexpr uint64_t kApuCyclesDenominator =
23 1364 * 262 * 60; // 21,437,280
24
25// PAL timing: (32040 * 32) / (1364 * 312 * 50)
26static constexpr uint64_t kApuCyclesNumeratorPal = 32040 * 32; // 1,025,280
27static constexpr uint64_t kApuCyclesDenominatorPal =
28 1364 * 312 * 50; // 21,268,800
29
30// Legacy floating-point ratios (deprecated, kept for reference)
31// static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
32// static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312
33// * 50.0);
34
35// SNES IPL ROM - Anomie's official hardware dump (64 bytes)
36// With our critical fixes: CMP Z flag, multi-step bstep, address preservation
37static const uint8_t bootRom[0x40] = {
38 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
39 0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
40 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
41 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
42 0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
43 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
44
45
46
47void Apu::Init() {
48 ram.resize(0x10000);
49 for (int i = 0; i < 0x10000; i++) {
50 ram[i] = 0;
51 }
52}
53
54void Apu::Reset() {
55 LOG_DEBUG("APU", "Reset called");
56 spc700_.Reset(true);
57 dsp_.Reset();
58 for (int i = 0; i < 0x10000; i++) {
59 ram[i] = 0;
60 }
61 rom_readable_ = true;
62 dsp_adr_ = 0;
63 LOG_INFO("APU", "Init: Num=%llu, Den=%llu, Ratio=%.4f", kApuCyclesNumerator, kApuCyclesDenominator, (double)kApuCyclesNumerator / kApuCyclesDenominator);
64 cycles_ = 0;
66 in_transfer_ = false;
67 last_master_cycles_ = 0; // Reset the master cycle delta tracking
68
69 std::fill(in_ports_.begin(), in_ports_.end(), 0);
70 std::fill(out_ports_.begin(), out_ports_.end(), 0);
71 for (int i = 0; i < 3; i++) {
72 timer_[i].cycles = 0;
73 timer_[i].divider = 0;
74 timer_[i].target = 0;
75 timer_[i].counter = 0;
76 timer_[i].enabled = false;
77 }
78
79 // Reset handshake tracker
82 }
83
84 LOG_DEBUG("APU", "Reset complete - IPL ROM readable, PC will be at $%04X",
85 spc700_.read_word(0xFFFE));
86}
87
88void Apu::RunCycles(uint64_t master_cycles) {
89 // Track master cycle delta (only advance by the difference since last call)
90 uint64_t master_delta = master_cycles - last_master_cycles_;
91 last_master_cycles_ = master_cycles;
92
93 // Convert CPU master cycles to APU cycles using fixed-point ratio (no
94 // floating-point drift)
95 // Target APU cycle count is derived from master clock ratio:
96 // APU Clock (~1.024MHz) / Master Clock (~21.477MHz)
97 // target_apu_cycles = cycles_ + (master_delta * numerator) / denominator
98 // This ensures the APU stays perfectly synchronized with the CPU over long periods.
99 uint64_t numerator =
100 memory_.pal_timing() ? kApuCyclesNumeratorPal : kApuCyclesNumerator;
101 uint64_t denominator =
102 memory_.pal_timing() ? kApuCyclesDenominatorPal : kApuCyclesDenominator;
103
104 const uint64_t target_apu_cycles =
105 cycles_ + (master_delta * numerator) / denominator;
106
107 // Debug: Log cycle ratio periodically
108 static uint64_t last_debug_log = 0;
109 static uint64_t total_master_delta = 0;
110 static uint64_t total_apu_cycles_run = 0;
111 static int call_count = 0;
112 uint64_t apu_before = cycles_;
113 uint64_t expected_this_call = (master_delta * numerator) / denominator;
114 total_master_delta += master_delta;
115 call_count++;
116
117 // Log first few calls and periodically after
118 static int verbose_log_count = 0;
119 if (verbose_log_count < 10 || (call_count % 1000 == 0)) {
120 LOG_INFO("APU", "RunCycles ENTRY: master_delta=%llu, expected=%llu, cycles_=%llu, target=%llu",
121 master_delta, expected_this_call, cycles_, target_apu_cycles);
122 verbose_log_count++;
123 }
124
125 // Watchdog to detect infinite loops
126 static uint64_t last_log_cycle = 0;
127 static uint16_t last_pc = 0;
128 static int stuck_counter = 0;
129 // Log Timer 0 fires per frame (Diagnostic)
130 // static int timer0_fires = 0; // Unused
131 // static int timer0_log = 0;
132 static bool logged_transfer_state = false;
133
134 while (cycles_ < target_apu_cycles) {
135 // Execute one SPC700 opcode (variable cycles) then advance APU cycles
136 // accordingly.
137 uint16_t old_pc = spc700_.PC;
138 uint16_t current_pc = spc700_.PC;
139
140 // IPL ROM protocol analysis - let it run to see what happens
141 // Log IPL ROM transfer loop activity (every 1000 cycles when in critical
142 // range)
143 static uint64_t last_ipl_log = 0;
144 if (rom_readable_ && current_pc >= 0xFFD6 && current_pc <= 0xFFED) {
145 if (cycles_ - last_ipl_log > 10000) {
146 LOG_DEBUG("APU",
147 "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X "
148 "F6=$%02X F7=$%02X",
149 current_pc, spc700_.Y, in_ports_[0], in_ports_[1],
150 in_ports_[2], in_ports_[3]);
151 LOG_DEBUG("APU",
152 " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: "
153 "$00=$%02X $01=$%02X",
155 ram[0x00], ram[0x01]);
156 last_ipl_log = cycles_;
157 }
158 }
159
160 // Detect if SPC is stuck in tight loop
161 if (current_pc == last_pc) {
162 stuck_counter++;
163 if (stuck_counter > 10000 && cycles_ - last_log_cycle > 10000) {
164 LOG_DEBUG("APU", "SPC700 stuck at PC=$%04X for %d iterations",
165 current_pc, stuck_counter);
166 LOG_DEBUG("APU", "Port Status: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
167 in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]);
168 LOG_DEBUG("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
170 LOG_DEBUG("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO");
171 LOG_DEBUG("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", spc700_.Y,
172 ram[0x00], ram[0x01]);
173 if (!logged_transfer_state && ram[0x00] == 0x19 && ram[0x01] == 0x00) {
174 LOG_DEBUG("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]);
175 logged_transfer_state = true;
176 }
177 last_log_cycle = cycles_;
178 stuck_counter = 0;
179 }
180 } else {
181 stuck_counter = 0;
182 }
183 last_pc = current_pc;
184
185 // Execute one complete SPC700 instruction and get exact cycle count
186 // Step() returns the precise number of cycles consumed by the instruction
187 int spc_cycles = spc700_.Step();
188
189 if (handshake_tracker_) {
191 }
192
193 // Advance APU cycles based on actual SPC700 instruction timing
194 // Each Cycle() call: ticks DSP every 32 cycles, updates timers, increments
195 // cycles_
196 for (int i = 0; i < spc_cycles; ++i) {
197 Cycle();
198 }
199 }
200
201 // Debug: track APU cycles actually run vs expected
202 uint64_t apu_actually_run = cycles_ - apu_before;
203 total_apu_cycles_run += apu_actually_run;
204
205 // Log exit for first few calls
206 if (verbose_log_count <= 10) {
207 LOG_INFO("APU", "RunCycles EXIT: ran=%llu, expected=%llu, overshoot=%lld, cycles_=%llu",
208 apu_actually_run, expected_this_call,
209 (int64_t)apu_actually_run - (int64_t)expected_this_call,
210 cycles_);
211 }
212
213 // Log every ~1M APU cycles
214 if (cycles_ - last_debug_log > 1000000) {
215 uint64_t expected_apu = (total_master_delta * numerator) / denominator;
216 double ratio = (double)total_apu_cycles_run / (double)expected_apu;
217 LOG_INFO("APU", "TIMING: calls=%d, master_delta=%llu, expected_apu=%llu, actual_apu=%llu, ratio=%.2fx",
218 call_count, total_master_delta, expected_apu, total_apu_cycles_run, ratio);
219 last_debug_log = cycles_;
220 total_master_delta = 0;
221 total_apu_cycles_run = 0;
222 call_count = 0;
223 }
224}
225
227 if ((cycles_ & 0x1f) == 0) {
228 // every 32 cycles
229 dsp_.Cycle();
230 }
231
232 // handle timers
233 for (int i = 0; i < 3; i++) {
234 if (timer_[i].cycles == 0) {
235 timer_[i].cycles = i == 2 ? 16 : 128;
236 if (timer_[i].enabled) {
237 timer_[i].divider++;
238 if (timer_[i].divider == timer_[i].target) {
239 timer_[i].divider = 0;
240 timer_[i].counter++;
241 timer_[i].counter &= 0xf;
242 }
243 }
244 }
245 timer_[i].cycles--;
246 }
247
248 cycles_++;
249
250
251}
252
253uint8_t Apu::Read(uint16_t adr) {
254 static int port_read_count = 0;
255 switch (adr) {
256 case 0xf0:
257 case 0xf1:
258 case 0xfa:
259 case 0xfb:
260 case 0xfc: {
261 return 0;
262 }
263 case 0xf2: {
264 return dsp_adr_;
265 }
266 case 0xf3: {
267 return dsp_.Read(dsp_adr_ & 0x7f);
268 }
269 case 0xf4:
270 case 0xf5:
271 case 0xf6:
272 case 0xf7: {
273 uint8_t val = in_ports_[adr - 0xf4];
274 // port_read_count++;
275 // if (port_read_count < 10) { // Reduced to prevent logging overflow crash
276 // LOG_DEBUG("APU", "SPC read port $%04X (F%d) = $%02X at PC=$%04X", adr,
277 // adr - 0xf4 + 4, val, spc700_.PC);
278 // }
279 return val;
280 }
281 case 0xf8:
282 case 0xf9: {
283 // Not I/O ports on real hardware; treat as general RAM region.
284 return ram[adr];
285 }
286 case 0xfd:
287 case 0xfe:
288 case 0xff: {
289 uint8_t ret = timer_[adr - 0xfd].counter;
290 timer_[adr - 0xfd].counter = 0;
291 return ret;
292 }
293 }
294 if (rom_readable_ && adr >= 0xffc0) {
295 return bootRom[adr - 0xffc0];
296 }
297 return ram[adr];
298}
299
300void Apu::Write(uint16_t adr, uint8_t val) {
301 static int port_write_count = 0;
302
303 switch (adr) {
304 case 0xf0: {
305 break; // test register
306 }
307 case 0xf1: {
308 bool old_rom_readable = rom_readable_;
309 for (int i = 0; i < 3; i++) {
310 if (!timer_[i].enabled && (val & (1 << i))) {
311 timer_[i].divider = 0;
312 timer_[i].counter = 0;
313 }
314 timer_[i].enabled = val & (1 << i);
315 }
316 if (val & 0x10) {
317 in_ports_[0] = 0;
318 in_ports_[1] = 0;
319 }
320 if (val & 0x20) {
321 in_ports_[2] = 0;
322 in_ports_[3] = 0;
323 }
324 // IPL ROM mapping: initially enabled; writing 1 to bit7 disables IPL ROM.
325 rom_readable_ = (val & 0x80) == 0;
326 if (old_rom_readable != rom_readable_) {
327 LOG_DEBUG("APU",
328 "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X", val,
329 rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC);
330
331 // Track IPL ROM disable for handshake debugging
333 // IPL ROM disabled means audio driver uploaded successfully
335 }
336
337 // When IPL ROM is disabled, reset transfer tracking
338 if (!rom_readable_) {
339 in_transfer_ = false;
340 transfer_size_ = 0;
341 }
342 }
343 break;
344 }
345 case 0xf2: {
346 dsp_adr_ = val;
347 break;
348 }
349 case 0xf3: {
350 if (dsp_adr_ < 0x80)
351 dsp_.Write(dsp_adr_, val);
352 break;
353 }
354 case 0xf4:
355 case 0xf5:
356 case 0xf6:
357 case 0xf7: {
358 out_ports_[adr - 0xf4] = val;
359
360 // Track SPC port writes for handshake debugging
361 if (handshake_tracker_) {
362 handshake_tracker_->OnSpcPortWrite(adr - 0xf4, val, spc700_.PC);
363 }
364
365 port_write_count++;
366 if (port_write_count < 10) { // Reduced to prevent logging overflow crash
367 LOG_DEBUG(
368 "APU",
369 "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]",
370 adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_);
371 }
372
373 // Track when SPC enters transfer loop (echoes counter back)
374 // PC is at $FFE2 when the MOVSY write completes (CB F4 is 2 bytes at
375 // $FFE0)
376 if (adr == 0xf4 && spc700_.PC == 0xFFE2 && rom_readable_) {
377 // SPC is echoing counter back - we're in data transfer phase
378 if (!in_transfer_ && ram[0x00] != 0 && ram[0x01] == 0) {
379 // Small destination address (< $0100) suggests small transfer
380 // ALTTP uses $0019 for bootstrap
381 if (ram[0x00] < 0x80) {
382 transfer_size_ = 1; // Assume 1-byte bootstrap transfer
383 in_transfer_ = true;
384 LOG_DEBUG("APU",
385 "Detected small transfer start: dest=$%02X%02X, size=%d",
386 ram[0x01], ram[0x00], transfer_size_);
387 }
388 }
389 }
390 break;
391 }
392 case 0xf8:
393 case 0xf9: {
394 // General RAM
395 ram[adr] = val;
396 break;
397 }
398 case 0xfa:
399 case 0xfb:
400 case 0xfc: {
401 int i = adr - 0xfa;
402 timer_[i].target = val;
403 if (i == 0) {
404 LOG_INFO("APU", "Timer 0 Target set to %d ($%02X)", val, val);
405 }
406 break;
407 }
408 }
409 ram[adr] = val;
410}
411
412uint8_t Apu::SpcRead(uint16_t adr) {
413 Cycle();
414 return Read(adr);
415}
416
417void Apu::SpcWrite(uint16_t adr, uint8_t val) {
418 Cycle();
419 Write(adr, val);
420}
421
422void Apu::SpcIdle(bool waiting) {
423 Cycle();
424}
425
426void Apu::SaveState(std::ostream& stream) {
427 stream.write(reinterpret_cast<const char*>(&rom_readable_), sizeof(rom_readable_));
428 stream.write(reinterpret_cast<const char*>(&dsp_adr_), sizeof(dsp_adr_));
429 stream.write(reinterpret_cast<const char*>(&cycles_), sizeof(cycles_));
430 stream.write(reinterpret_cast<const char*>(&transfer_size_), sizeof(transfer_size_));
431 stream.write(reinterpret_cast<const char*>(&in_transfer_), sizeof(in_transfer_));
432
433 stream.write(reinterpret_cast<const char*>(timer_.data()), sizeof(timer_));
434
435 stream.write(reinterpret_cast<const char*>(in_ports_.data()), sizeof(in_ports_));
436 stream.write(reinterpret_cast<const char*>(out_ports_.data()), sizeof(out_ports_));
437
438 constexpr uint32_t kMaxRamSize = 0x10000;
439 uint32_t ram_size = static_cast<uint32_t>(std::min<uint32_t>(ram.size(), kMaxRamSize));
440 stream.write(reinterpret_cast<const char*>(&ram_size), sizeof(ram_size));
441 if (ram_size > 0) {
442 stream.write(reinterpret_cast<const char*>(ram.data()), ram_size * sizeof(uint8_t));
443 }
444
445 dsp_.SaveState(stream);
446 spc700_.SaveState(stream);
447}
448
449void Apu::LoadState(std::istream& stream) {
450 stream.read(reinterpret_cast<char*>(&rom_readable_), sizeof(rom_readable_));
451 stream.read(reinterpret_cast<char*>(&dsp_adr_), sizeof(dsp_adr_));
452 stream.read(reinterpret_cast<char*>(&cycles_), sizeof(cycles_));
453 stream.read(reinterpret_cast<char*>(&transfer_size_), sizeof(transfer_size_));
454 stream.read(reinterpret_cast<char*>(&in_transfer_), sizeof(in_transfer_));
455
456 stream.read(reinterpret_cast<char*>(timer_.data()), sizeof(timer_));
457
458 stream.read(reinterpret_cast<char*>(in_ports_.data()), sizeof(in_ports_));
459 stream.read(reinterpret_cast<char*>(out_ports_.data()), sizeof(out_ports_));
460
461 uint32_t ram_size;
462 stream.read(reinterpret_cast<char*>(&ram_size), sizeof(ram_size));
463 constexpr uint32_t kMaxRamSize = 0x10000;
464 uint32_t safe_size = std::min<uint32_t>(ram_size, kMaxRamSize);
465 ram.resize(safe_size);
466 if (safe_size > 0) {
467 stream.read(reinterpret_cast<char*>(ram.data()), safe_size * sizeof(uint8_t));
468 }
469 if (ram_size > safe_size) {
470 std::vector<char> discard((ram_size - safe_size) * sizeof(uint8_t));
471 stream.read(discard.data(), discard.size());
472 }
473
474 dsp_.LoadState(stream);
475 spc700_.LoadState(stream);
476}
477
478void Apu::BootstrapDirect(uint16_t entry_point) {
479 LOG_INFO("APU", "BootstrapDirect: Setting PC to $%04X", entry_point);
480
481 // 1. Disable IPL ROM by setting the control bit
482 // Writing 0x80 to $F1 disables IPL ROM mapping at $FFC0-$FFFF
483 ram[0xF1] = 0x80;
484 rom_readable_ = false;
485
486 // 2. Set SPC700 PC to driver entry point
487 spc700_.PC = entry_point;
488
489 // 3. Initialize SPC state for driver execution
490 spc700_.SP = 0xEF; // Stack pointer at typical location
491 spc700_.A = 0;
492 spc700_.X = 0;
493 spc700_.Y = 0;
494
495 // 4. Clear flags
496 spc700_.PSW.N = false;
497 spc700_.PSW.V = false;
498 spc700_.PSW.P = false;
499 spc700_.PSW.B = false;
500 spc700_.PSW.H = false;
501 spc700_.PSW.I = false;
502 spc700_.PSW.Z = false;
503 spc700_.PSW.C = false;
504
505 // 5. Clear ports for fresh communication
506 for (int i = 0; i < 4; i++) {
507 in_ports_[i] = 0;
508 out_ports_[i] = 0;
509 }
510
511 // 6. Reset transfer tracking state
512 in_transfer_ = false;
513 transfer_size_ = 0;
514
515 LOG_INFO("APU", "BootstrapDirect complete: IPL ROM disabled, driver ready at $%04X",
516 entry_point);
517}
518
519} // namespace emu
520} // namespace yaze
bool in_transfer_
Definition apu.h:150
std::array< Timer, 3 > timer_
Definition apu.h:153
uint8_t SpcRead(uint16_t address)
Definition apu.cc:412
uint8_t Read(uint16_t address)
Definition apu.cc:253
MemoryImpl & memory_
Definition apu.h:152
void LoadState(std::istream &stream)
Definition apu.cc:449
debug::ApuHandshakeTracker * handshake_tracker_
Definition apu.h:156
void Write(uint16_t address, uint8_t data)
Definition apu.cc:300
void SpcIdle(bool waiting)
Definition apu.cc:422
uint8_t dsp_adr_
Definition apu.h:144
uint64_t last_master_cycles_
Definition apu.h:146
void Init()
Definition apu.cc:47
void Reset()
Definition apu.cc:54
uint8_t transfer_size_
Definition apu.h:149
void RunCycles(uint64_t cycles)
Definition apu.cc:88
Spc700 spc700_
Definition apu.h:164
void SpcWrite(uint16_t address, uint8_t data)
Definition apu.cc:417
uint64_t cycles_
Definition apu.h:145
std::vector< uint8_t > ram
Definition apu.h:139
std::array< uint8_t, 4 > out_ports_
Definition apu.h:138
void BootstrapDirect(uint16_t entry_point)
Bootstrap SPC directly to driver code (bypasses IPL ROM handshake)
Definition apu.cc:478
bool rom_readable_
Definition apu.h:142
std::array< uint8_t, 6 > in_ports_
Definition apu.h:137
void Cycle()
Definition apu.cc:226
void SaveState(std::ostream &stream)
Definition apu.cc:426
uint8_t Read(uint8_t adr)
Definition dsp.cc:483
void Cycle()
Definition dsp.cc:142
void Reset()
Definition dsp.cc:68
void Write(uint8_t adr, uint8_t val)
Definition dsp.cc:487
void LoadState(std::istream &stream)
Definition dsp.cc:957
void SaveState(std::ostream &stream)
Definition dsp.cc:868
auto pal_timing() const -> bool override
Definition memory.h:278
void Reset(bool hard=false)
Definition spc700.cc:16
void LoadState(std::istream &stream)
Definition spc700.cc:1498
void SaveState(std::ostream &stream)
Definition spc700.cc:1474
uint16_t read_word(uint16_t address)
Definition spc700.h:159
uint16_t PC
Definition spc700.h:106
void OnSpcPCChange(uint16_t old_pc, uint16_t new_pc)
void OnSpcPortWrite(uint8_t port, uint8_t value, uint16_t pc)
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_INFO(category, format,...)
Definition log.h:105
SDL2/SDL3 compatibility layer.