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
3#include <SDL.h>
4
5#include <cstdint>
6#include <vector>
7
8#include "app/emu/audio/dsp.h"
12#include "util/log.h"
13
14namespace yaze {
15namespace emu {
16
17// Fixed-point cycle ratio for perfect precision (no floating-point drift)
18// APU runs at ~1.024 MHz, master clock at ~21.477 MHz (NTSC)
19// Ratio = (32040 * 32) / (1364 * 262 * 60) = 1,025,280 / 21,437,280
20static constexpr uint64_t kApuCyclesNumerator = 32040 * 32; // 1,025,280
21static constexpr uint64_t kApuCyclesDenominator = 1364 * 262 * 60; // 21,437,280
22
23// PAL timing: (32040 * 32) / (1364 * 312 * 50)
24static constexpr uint64_t kApuCyclesNumeratorPal = 32040 * 32; // 1,025,280
25static constexpr uint64_t kApuCyclesDenominatorPal = 1364 * 312 * 50; // 21,268,800
26
27// Legacy floating-point ratios (deprecated, kept for reference)
28// static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
29// static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
30
31// SNES IPL ROM - Anomie's official hardware dump (64 bytes)
32// With our critical fixes: CMP Z flag, multi-step bstep, address preservation
33static const uint8_t bootRom[0x40] = {
34 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
35 0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
36 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
37 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
38 0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
39 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
40
41// Helper to reset the cycle tracking on emulator reset
42static uint64_t g_last_master_cycles = 0;
43static void ResetCycleTracking() { g_last_master_cycles = 0; }
44
45void Apu::Init() {
46 ram.resize(0x10000);
47 for (int i = 0; i < 0x10000; i++) {
48 ram[i] = 0;
49 }
50}
51
52void Apu::Reset() {
53 LOG_DEBUG("APU", "Reset called");
54 spc700_.Reset(true);
55 dsp_.Reset();
56 for (int i = 0; i < 0x10000; i++) {
57 ram[i] = 0;
58 }
59 rom_readable_ = true;
60 dsp_adr_ = 0;
61 cycles_ = 0;
63 in_transfer_ = false;
64 ResetCycleTracking(); // Reset the master cycle delta tracking
65 std::fill(in_ports_.begin(), in_ports_.end(), 0);
66 std::fill(out_ports_.begin(), out_ports_.end(), 0);
67 for (int i = 0; i < 3; i++) {
68 timer_[i].cycles = 0;
69 timer_[i].divider = 0;
70 timer_[i].target = 0;
71 timer_[i].counter = 0;
72 timer_[i].enabled = false;
73 }
74
75 // Reset handshake tracker
78 }
79
80 LOG_DEBUG("APU", "Reset complete - IPL ROM readable, PC will be at $%04X",
81 spc700_.read_word(0xFFFE));
82}
83
84void Apu::RunCycles(uint64_t master_cycles) {
85 // Track master cycle delta (only advance by the difference since last call)
86 uint64_t master_delta = master_cycles - g_last_master_cycles;
87 g_last_master_cycles = master_cycles;
88
89 // Convert CPU master cycles to APU cycles using fixed-point ratio (no floating-point drift)
90 // target_apu_cycles = cycles_ + (master_delta * numerator) / denominator
91 uint64_t numerator = memory_.pal_timing() ? kApuCyclesNumeratorPal : kApuCyclesNumerator;
92 uint64_t denominator = memory_.pal_timing() ? kApuCyclesDenominatorPal : kApuCyclesDenominator;
93
94 const uint64_t target_apu_cycles = cycles_ + (master_delta * numerator) / denominator;
95
96 // Watchdog to detect infinite loops
97 static uint64_t last_log_cycle = 0;
98 static uint16_t last_pc = 0;
99 static int stuck_counter = 0;
100 static bool logged_transfer_state = false;
101
102 while (cycles_ < target_apu_cycles) {
103 // Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly.
104 uint16_t old_pc = spc700_.PC;
105 uint16_t current_pc = spc700_.PC;
106
107 // IPL ROM protocol analysis - let it run to see what happens
108 // Log IPL ROM transfer loop activity (every 1000 cycles when in critical range)
109 static uint64_t last_ipl_log = 0;
110 if (rom_readable_ && current_pc >= 0xFFD6 && current_pc <= 0xFFED) {
111 if (cycles_ - last_ipl_log > 10000) {
112 LOG_DEBUG("APU", "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
113 current_pc, spc700_.Y, in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]);
114 LOG_DEBUG("APU", " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: $00=$%02X $01=$%02X",
115 out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3], ram[0x00], ram[0x01]);
116 last_ipl_log = cycles_;
117 }
118 }
119
120 // Detect if SPC is stuck in tight loop
121 if (current_pc == last_pc) {
122 stuck_counter++;
123 if (stuck_counter > 10000 && cycles_ - last_log_cycle > 10000) {
124 LOG_DEBUG("APU", "SPC700 stuck at PC=$%04X for %d iterations",
125 current_pc, stuck_counter);
126 LOG_DEBUG("APU", "Port Status: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
127 in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]);
128 LOG_DEBUG("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X",
130 LOG_DEBUG("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO");
131 LOG_DEBUG("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X",
132 spc700_.Y, ram[0x00], ram[0x01]);
133 if (!logged_transfer_state && ram[0x00] == 0x19 && ram[0x01] == 0x00) {
134 LOG_DEBUG("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]);
135 logged_transfer_state = true;
136 }
137 last_log_cycle = cycles_;
138 stuck_counter = 0;
139 }
140 } else {
141 stuck_counter = 0;
142 }
143 last_pc = current_pc;
144
145 // Execute one complete SPC700 instruction and get exact cycle count
146 // Step() returns the precise number of cycles consumed by the instruction
147 int spc_cycles = spc700_.Step();
148
149 if (handshake_tracker_) {
151 }
152
153 // Advance APU cycles based on actual SPC700 instruction timing
154 // Each Cycle() call: ticks DSP every 32 cycles, updates timers, increments cycles_
155 for (int i = 0; i < spc_cycles; ++i) {
156 Cycle();
157 }
158 }
159}
160
162 if ((cycles_ & 0x1f) == 0) {
163 // every 32 cycles
164 dsp_.Cycle();
165 }
166
167 // handle timers
168 for (int i = 0; i < 3; i++) {
169 if (timer_[i].cycles == 0) {
170 timer_[i].cycles = i == 2 ? 16 : 128;
171 if (timer_[i].enabled) {
172 timer_[i].divider++;
173 if (timer_[i].divider == timer_[i].target) {
174 timer_[i].divider = 0;
175 timer_[i].counter++;
176 timer_[i].counter &= 0xf;
177 }
178 }
179 }
180 timer_[i].cycles--;
181 }
182
183 cycles_++;
184}
185
186uint8_t Apu::Read(uint16_t adr) {
187 static int port_read_count = 0;
188 switch (adr) {
189 case 0xf0:
190 case 0xf1:
191 case 0xfa:
192 case 0xfb:
193 case 0xfc: {
194 return 0;
195 }
196 case 0xf2: {
197 return dsp_adr_;
198 }
199 case 0xf3: {
200 return dsp_.Read(dsp_adr_ & 0x7f);
201 }
202 case 0xf4:
203 case 0xf5:
204 case 0xf6:
205 case 0xf7: {
206 uint8_t val = in_ports_[adr - 0xf4];
207 port_read_count++;
208 if (port_read_count < 10) { // Reduced to prevent logging overflow crash
209 LOG_DEBUG("APU", "SPC read port $%04X (F%d) = $%02X at PC=$%04X",
210 adr, adr - 0xf4 + 4, val, spc700_.PC);
211 }
212 return val;
213 }
214 case 0xf8:
215 case 0xf9: {
216 // Not I/O ports on real hardware; treat as general RAM region.
217 return ram[adr];
218 }
219 case 0xfd:
220 case 0xfe:
221 case 0xff: {
222 uint8_t ret = timer_[adr - 0xfd].counter;
223 timer_[adr - 0xfd].counter = 0;
224 return ret;
225 }
226 }
227 if (rom_readable_ && adr >= 0xffc0) {
228 return bootRom[adr - 0xffc0];
229 }
230 return ram[adr];
231}
232
233void Apu::Write(uint16_t adr, uint8_t val) {
234 static int port_write_count = 0;
235
236 switch (adr) {
237 case 0xf0: {
238 break; // test register
239 }
240 case 0xf1: {
241 bool old_rom_readable = rom_readable_;
242 for (int i = 0; i < 3; i++) {
243 if (!timer_[i].enabled && (val & (1 << i))) {
244 timer_[i].divider = 0;
245 timer_[i].counter = 0;
246 }
247 timer_[i].enabled = val & (1 << i);
248 }
249 if (val & 0x10) {
250 in_ports_[0] = 0;
251 in_ports_[1] = 0;
252 }
253 if (val & 0x20) {
254 in_ports_[2] = 0;
255 in_ports_[3] = 0;
256 }
257 // IPL ROM mapping: initially enabled; writing 1 to bit7 disables IPL ROM.
258 rom_readable_ = (val & 0x80) == 0;
259 if (old_rom_readable != rom_readable_) {
260 LOG_DEBUG("APU", "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X",
261 val, rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC);
262
263 // Track IPL ROM disable for handshake debugging
265 // IPL ROM disabled means audio driver uploaded successfully
267 }
268
269 // When IPL ROM is disabled, reset transfer tracking
270 if (!rom_readable_) {
271 in_transfer_ = false;
272 transfer_size_ = 0;
273 }
274 }
275 break;
276 }
277 case 0xf2: {
278 dsp_adr_ = val;
279 break;
280 }
281 case 0xf3: {
282 if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val);
283 break;
284 }
285 case 0xf4:
286 case 0xf5:
287 case 0xf6:
288 case 0xf7: {
289 out_ports_[adr - 0xf4] = val;
290
291 // Track SPC port writes for handshake debugging
292 if (handshake_tracker_) {
293 handshake_tracker_->OnSpcPortWrite(adr - 0xf4, val, spc700_.PC);
294 }
295
296 port_write_count++;
297 if (port_write_count < 10) { // Reduced to prevent logging overflow crash
298 LOG_DEBUG("APU", "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]",
299 adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_);
300 }
301
302 // Track when SPC enters transfer loop (echoes counter back)
303 // PC is at $FFE2 when the MOVSY write completes (CB F4 is 2 bytes at $FFE0)
304 if (adr == 0xf4 && spc700_.PC == 0xFFE2 && rom_readable_) {
305 // SPC is echoing counter back - we're in data transfer phase
306 if (!in_transfer_ && ram[0x00] != 0 && ram[0x01] == 0) {
307 // Small destination address (< $0100) suggests small transfer
308 // ALTTP uses $0019 for bootstrap
309 if (ram[0x00] < 0x80) {
310 transfer_size_ = 1; // Assume 1-byte bootstrap transfer
311 in_transfer_ = true;
312 LOG_DEBUG("APU", "Detected small transfer start: dest=$%02X%02X, size=%d",
313 ram[0x01], ram[0x00], transfer_size_);
314 }
315 }
316 }
317 break;
318 }
319 case 0xf8:
320 case 0xf9: {
321 // General RAM
322 break;
323 }
324 case 0xfa:
325 case 0xfb:
326 case 0xfc: {
327 timer_[adr - 0xfa].target = val;
328 break;
329 }
330 }
331 ram[adr] = val;
332}
333
334uint8_t Apu::SpcRead(uint16_t adr) {
335 Cycle();
336 return Read(adr);
337}
338
339void Apu::SpcWrite(uint16_t adr, uint8_t val) {
340 Cycle();
341 Write(adr, val);
342}
343
344void Apu::SpcIdle(bool waiting) { Cycle(); }
345
346} // namespace emu
347} // namespace yaze
bool in_transfer_
Definition apu.h:103
std::array< Timer, 3 > timer_
Definition apu.h:106
uint8_t SpcRead(uint16_t address)
Definition apu.cc:334
uint8_t Read(uint16_t address)
Definition apu.cc:186
uint32_t cycles_
Definition apu.h:99
MemoryImpl & memory_
Definition apu.h:105
debug::ApuHandshakeTracker * handshake_tracker_
Definition apu.h:109
void Write(uint16_t address, uint8_t data)
Definition apu.cc:233
void SpcIdle(bool waiting)
Definition apu.cc:344
uint8_t dsp_adr_
Definition apu.h:98
void Init()
Definition apu.cc:45
void Reset()
Definition apu.cc:52
uint8_t transfer_size_
Definition apu.h:102
void RunCycles(uint64_t cycles)
Definition apu.cc:84
Spc700 spc700_
Definition apu.h:117
void SpcWrite(uint16_t address, uint8_t data)
Definition apu.cc:339
std::vector< uint8_t > ram
Definition apu.h:93
std::array< uint8_t, 4 > out_ports_
Definition apu.h:92
bool rom_readable_
Definition apu.h:96
std::array< uint8_t, 6 > in_ports_
Definition apu.h:91
void Cycle()
Definition apu.cc:161
uint8_t Read(uint8_t adr)
Definition dsp.cc:431
void Cycle()
Definition dsp.cc:134
void Reset()
Definition dsp.cc:68
void Write(uint8_t adr, uint8_t val)
Definition dsp.cc:433
auto pal_timing() const -> bool override
Definition memory.h:278
void Reset(bool hard=false)
Definition spc700.cc:16
uint16_t read_word(uint16_t address)
Definition spc700.h:156
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:104
Main namespace for the application.