feat(spc700): enhance cycle accuracy and instruction execution

- Introduced a new cycle lookup table for SPC700 instructions, improving timing precision.
- Refactored instruction execution to be fully atomic, eliminating the bstep mechanism.
- Added detailed comments for clarity on cycle counts and instruction behavior.
- Implemented additional logging for CPU audio initialization to aid debugging.

Benefits:
- Enhanced synchronization between CPU and APU.
- Improved testability and accuracy of instruction timing.
- Streamlined code for better maintainability and understanding.
This commit is contained in:
scawful
2025-10-10 18:56:39 -04:00
parent 5676d934f3
commit 5778a470f7
6 changed files with 412 additions and 315 deletions

View File

@@ -101,6 +101,7 @@ void Apu::RunCycles(uint64_t master_cycles) {
while (cycles_ < target_apu_cycles) {
// Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly.
uint16_t old_pc = spc700_.PC;
uint16_t current_pc = spc700_.PC;
// IPL ROM protocol analysis - let it run to see what happens
@@ -145,6 +146,10 @@ void Apu::RunCycles(uint64_t master_cycles) {
// Step() returns the precise number of cycles consumed by the instruction
int spc_cycles = spc700_.Step();
if (handshake_tracker_) {
handshake_tracker_->OnSpcPCChange(old_pc, spc700_.PC);
}
// Advance APU cycles based on actual SPC700 instruction timing
// Each Cycle() call: ticks DSP every 32 cycles, updates timers, increments cycles_
for (int i = 0; i < spc_cycles; ++i) {

View File

@@ -4,311 +4,369 @@
namespace yaze {
namespace emu {
// opcode functions
// ===========================================================================
// CYCLE-ACCURATE ATOMIC INSTRUCTION IMPLEMENTATIONS
// ===========================================================================
// All instructions are now fully atomic (no bstep mechanism).
// Cycle counts match Anomie's SPC700 reference and nesdev.org timing table.
// Memory-targeting MOV instructions include dummy read cycles as documented.
// ===========================================================================
// ---------------------------------------------------------------------------
// MOV Instructions (Load from memory to register)
// ---------------------------------------------------------------------------
void Spc700::MOV(uint16_t adr) {
// MOV A, (adr) - Read from memory to A
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
}
void Spc700::MOVX(uint16_t adr) {
// MOV X, (adr) - Read from memory to X
X = read(adr);
PSW.Z = (X == 0);
PSW.N = (X & 0x80);
}
void Spc700::MOVY(uint16_t adr) {
// MOV Y, (adr) - Read from memory to Y
Y = read(adr);
PSW.Z = (Y == 0);
PSW.N = (Y & 0x80);
}
// ---------------------------------------------------------------------------
// MOV Instructions (Store from register to memory)
// ---------------------------------------------------------------------------
// Note: Per Anomie's doc, these include a dummy read cycle before writing
void Spc700::MOVS(uint16_t address) {
static int movs_log = 0;
switch (bstep) {
case 0:
this->adr = address; // Save address for bstep=1
read(this->adr);
bstep++;
break;
case 1:
write(this->adr, A); // Use saved address
if (this->adr == 0x00F4 && movs_log++ < 10) {
LOG_DEBUG("SPC", "MOVS wrote A=$%02X to F4!", A);
}
bstep = 0;
break;
}
// MOV (address), A - Write A to memory (with dummy read)
read(address); // Dummy read (documented behavior)
write(address, A);
}
void Spc700::MOVSX(uint16_t address) {
switch (bstep) {
case 0: this->adr = address; read(this->adr); bstep++; break;
case 1: write(this->adr, X); bstep = 0; break;
}
// MOV (address), X - Write X to memory (with dummy read)
read(address); // Dummy read (documented behavior)
write(address, X);
}
void Spc700::MOVSY(uint16_t address) {
static int movsy_log = 0;
switch (bstep) {
case 0:
this->adr = address;
read(this->adr);
bstep++;
if (this->adr == 0x00F4 && movsy_log < 10) {
LOG_DEBUG("SPC", "MOVSY bstep=0: Will write Y=$%02X to F4 at PC=$%04X", Y, PC);
}
break;
case 1:
write(this->adr, Y);
if (this->adr == 0x00F4 && movsy_log++ < 10) {
LOG_DEBUG("SPC", "MOVSY bstep=1: Wrote Y=$%02X to F4 at PC=$%04X", Y, PC);
}
bstep = 0;
break;
}
}
void Spc700::MOV(uint16_t adr) {
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
// MOV (address), Y - Write Y to memory (with dummy read)
read(address); // Dummy read (documented behavior)
write(address, Y);
}
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
// MOV (address), #imm - Write immediate to memory (with dummy read)
read(address); // Dummy read (documented behavior)
write(address, operand);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
}
// ---------------------------------------------------------------------------
// Arithmetic Instructions (ADC, SBC)
// ---------------------------------------------------------------------------
void Spc700::ADC(uint16_t adr) {
// ADC A, (adr) - Add with carry
uint8_t value = read(adr);
uint16_t result = A + value + PSW.C;
PSW.V = ((A ^ result) & (adr ^ result) & 0x80);
PSW.V = ((A ^ result) & (value ^ result) & 0x80) != 0;
PSW.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf;
PSW.C = (result > 0xFF);
PSW.H = ((A ^ adr ^ result) & 0x10);
A = result & 0xFF;
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
PSW.Z = (A == 0);
PSW.N = (A & 0x80) != 0;
}
void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
// ADC (dest), operand - Add with carry to memory
uint8_t applyOn = read(dest);
int result = applyOn + operand + PSW.C;
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
(operand & 0x80) != (result & 0x80);
PSW.V = ((applyOn & 0x80) == (operand & 0x80)) &&
((operand & 0x80) != (result & 0x80));
PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
PSW.C = result > 0xff;
write(dest, result);
write(dest, result & 0xFF);
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
PSW.N = (result & 0x80) != 0;
}
void Spc700::SBC(uint16_t adr) {
// SBC A, (adr) - Subtract with carry (borrow)
uint8_t value = read(adr) ^ 0xff;
int result = A + value + PSW.C;
PSW.V = (A & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
PSW.V = ((A & 0x80) == (value & 0x80)) &&
((value & 0x80) != (result & 0x80));
PSW.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf;
PSW.C = result > 0xff;
A = result;
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
A = result & 0xFF;
PSW.Z = (A == 0);
PSW.N = (A & 0x80) != 0;
}
void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
// SBC (dest), operand - Subtract with carry from memory
operand ^= 0xff;
uint8_t applyOn = read(dest);
int result = applyOn + operand + PSW.C;
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
(operand & 0x80) != (operand & 0x80);
PSW.V = ((applyOn & 0x80) == (operand & 0x80)) &&
((operand & 0x80) != (result & 0x80));
PSW.H = ((applyOn & 0xF) + (operand & 0xF) + PSW.C) > 0xF;
PSW.C = result > 0xFF;
write(dest, result);
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
write(dest, result & 0xFF);
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80) != 0;
}
void Spc700::CMPX(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = X + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0); // Check 8-bit result for zero!
PSW.N = (result & 0x80);
}
void Spc700::CMPY(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = Y + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0); // Check 8-bit result for zero!
PSW.N = (result & 0x80);
}
void Spc700::CMPM(uint16_t dst, uint8_t value) {
value ^= 0xff;
int result = read(dst) + value + 1;
PSW.C = result > 0xff;
callbacks_.idle(false);
PSW.Z = ((result & 0xFF) == 0); // Check 8-bit result for zero!
PSW.N = (result & 0x80);
}
// ---------------------------------------------------------------------------
// Comparison Instructions (CMP, CMPX, CMPY, CMPM)
// ---------------------------------------------------------------------------
void Spc700::CMP(uint16_t adr) {
// CMP A, (adr) - Compare A with memory
uint8_t value = read(adr) ^ 0xff;
int result = A + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
PSW.N = (result & 0x80) != 0;
}
void Spc700::CMPX(uint16_t adr) {
// CMP X, (adr) - Compare X with memory
uint8_t value = read(adr) ^ 0xff;
int result = X + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80) != 0;
}
void Spc700::CMPY(uint16_t adr) {
// CMP Y, (adr) - Compare Y with memory
uint8_t value = read(adr) ^ 0xff;
int result = Y + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80) != 0;
}
void Spc700::CMPM(uint16_t dst, uint8_t value) {
// CMP (dst), value - Compare memory with value
value ^= 0xff;
int result = read(dst) + value + 1;
PSW.C = result > 0xff;
callbacks_.idle(false); // Extra cycle for memory comparison
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80) != 0;
}
// ---------------------------------------------------------------------------
// Logical Instructions (AND, OR, EOR)
// ---------------------------------------------------------------------------
void Spc700::AND(uint16_t adr) {
// AND A, (adr) - Logical AND with memory
A &= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
PSW.N = (A & 0x80) != 0;
}
void Spc700::ANDM(uint16_t dest, uint8_t operand) {
// AND (dest), operand - Logical AND memory with value
uint8_t result = read(dest) & operand;
write(dest, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
PSW.N = (result & 0x80) != 0;
}
void Spc700::OR(uint16_t adr) {
// OR A, (adr) - Logical OR with memory
A |= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
PSW.N = (A & 0x80) != 0;
}
void Spc700::ORM(uint16_t dst, uint8_t value) {
// OR (dst), value - Logical OR memory with value
uint8_t result = read(dst) | value;
write(dst, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
PSW.N = (result & 0x80) != 0;
}
void Spc700::EOR(uint16_t adr) {
// EOR A, (adr) - Logical XOR with memory
A ^= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
PSW.N = (A & 0x80) != 0;
}
void Spc700::EORM(uint16_t dest, uint8_t operand) {
// EOR (dest), operand - Logical XOR memory with value
uint8_t result = read(dest) ^ operand;
write(dest, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
PSW.N = (result & 0x80) != 0;
}
void Spc700::ASL(uint16_t operand) {
uint8_t val = read(operand);
write(operand, val);
PSW.C = (val & 0x80);
// ---------------------------------------------------------------------------
// Shift and Rotate Instructions (ASL, LSR, ROL, ROR)
// ---------------------------------------------------------------------------
void Spc700::ASL(uint16_t adr) {
// ASL (adr) - Arithmetic shift left
uint8_t val = read(adr);
write(adr, val); // Dummy write (RMW instruction)
PSW.C = (val & 0x80) != 0;
val <<= 1;
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
PSW.N = (val & 0x80) != 0;
}
void Spc700::LSR(uint16_t adr) {
// LSR (adr) - Logical shift right
uint8_t val = read(adr);
PSW.C = (val & 0x01);
write(adr, val); // Dummy write (RMW instruction)
PSW.C = (val & 0x01) != 0;
val >>= 1;
write(adr, val);
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::ROR(uint16_t adr) {
uint8_t val = read(adr);
bool newC = val & 1;
val = (val >> 1) | (PSW.C << 7);
PSW.C = newC;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
PSW.N = (val & 0x80) != 0;
}
void Spc700::ROL(uint16_t adr) {
// ROL (adr) - Rotate left through carry
uint8_t val = read(adr);
bool newC = val & 0x80;
write(adr, val); // Dummy write (RMW instruction)
bool newC = (val & 0x80) != 0;
val = (val << 1) | PSW.C;
PSW.C = newC;
write(adr, val);
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
PSW.N = (val & 0x80) != 0;
}
void Spc700::ROR(uint16_t adr) {
// ROR (adr) - Rotate right through carry
uint8_t val = read(adr);
write(adr, val); // Dummy write (RMW instruction)
bool newC = (val & 1) != 0;
val = (val >> 1) | (PSW.C << 7);
PSW.C = newC;
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80) != 0;
}
// ---------------------------------------------------------------------------
// Increment/Decrement Instructions (INC, DEC)
// ---------------------------------------------------------------------------
void Spc700::INC(uint16_t adr) {
// INC (adr) - Increment memory
uint8_t val = read(adr);
write(adr, val); // Dummy write (RMW instruction)
val++;
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80) != 0;
}
void Spc700::DEC(uint16_t adr) {
// DEC (adr) - Decrement memory
uint8_t val = read(adr);
write(adr, val); // Dummy write (RMW instruction)
val--;
write(adr, val); // Actual write
PSW.Z = (val == 0);
PSW.N = (val & 0x80) != 0;
}
void Spc700::XCN(uint8_t operand, bool isImmediate) {
// XCN - Exchange nibbles (note: this is only used for A register)
uint8_t value = isImmediate ? imm() : operand;
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
PSW.Z = (value == 0);
PSW.N = (value & 0x80);
// operand = value;
PSW.N = (value & 0x80) != 0;
}
void Spc700::INC(uint16_t adr) {
uint8_t val = read(adr) + 1;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::DEC(uint16_t operand) {
uint8_t val = read(operand) - 1;
write(operand, val);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
}
// ---------------------------------------------------------------------------
// 16-bit Instructions (MOVW, INCW, DECW, ADDW, SUBW, CMPW)
// ---------------------------------------------------------------------------
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
// MOVW - Move 16-bit word
dest = operand;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x8000);
PSW.N = (operand & 0x8000) != 0;
}
void Spc700::INCW(uint16_t& operand) {
// INCW - Increment 16-bit word
operand++;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x8000);
PSW.N = (operand & 0x8000) != 0;
}
void Spc700::DECW(uint16_t& operand) {
// DECW - Decrement 16-bit word
operand--;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x8000);
PSW.N = (operand & 0x8000) != 0;
}
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
// ADDW - Add 16-bit word
uint32_t result = dest + operand;
PSW.C = (result > 0xFFFF);
PSW.Z = ((result & 0xFFFF) == 0);
PSW.N = (result & 0x8000);
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
PSW.N = (result & 0x8000) != 0;
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000) != 0;
PSW.H = ((dest & 0xfff) + (operand & 0xfff)) > 0xfff;
dest = result & 0xFFFF;
}
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
// SUBW - Subtract 16-bit word
uint32_t result = dest - operand;
PSW.C = (result < 0x10000);
PSW.C = (result <= 0xFFFF);
PSW.Z = ((result & 0xFFFF) == 0);
PSW.N = (result & 0x8000);
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
PSW.N = (result & 0x8000) != 0;
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000) != 0;
PSW.H = ((dest & 0xfff) - (operand & 0xfff)) >= 0;
dest = result & 0xFFFF;
}
void Spc700::CMPW(uint16_t operand) {
// CMPW - Compare 16-bit word with YA
uint32_t result = YA - operand;
PSW.C = (result < 0x10000);
PSW.C = (result <= 0xFFFF);
PSW.Z = ((result & 0xFFFF) == 0);
PSW.N = (result & 0x8000);
PSW.N = (result & 0x8000) != 0;
}
// ---------------------------------------------------------------------------
// Multiply/Divide Instructions
// ---------------------------------------------------------------------------
void Spc700::MUL(uint8_t operand) {
// MUL - Multiply A * Y -> YA
uint16_t result = A * operand;
YA = result;
PSW.Z = (result == 0);
PSW.N = (result & 0x8000);
PSW.N = (result & 0x8000) != 0;
}
void Spc700::DIV(uint8_t operand) {
// DIV - Divide YA / X -> A (quotient), Y (remainder)
// Note: Hardware behavior is complex; simplified here
if (operand == 0) {
// Handle divide by zero error
// Divide by zero - undefined behavior
// Real hardware has specific behavior, but we'll just return
return;
}
uint8_t quotient = A / operand;
@@ -316,194 +374,170 @@ void Spc700::DIV(uint8_t operand) {
A = quotient;
Y = remainder;
PSW.Z = (quotient == 0);
PSW.N = (quotient & 0x80);
PSW.N = (quotient & 0x80) != 0;
}
// ---------------------------------------------------------------------------
// Branch Instructions
// ---------------------------------------------------------------------------
// Note: Branch timing is handled in DoBranch() in spc700.cc
// These helpers are only used by old code paths
void Spc700::BRA(int8_t offset) { PC += offset; }
void Spc700::BEQ(int8_t offset) {
if (PSW.Z) {
PC += offset;
}
}
void Spc700::BNE(int8_t offset) {
if (!PSW.Z) {
PC += offset;
}
}
void Spc700::BCS(int8_t offset) {
if (PSW.C) {
PC += offset;
}
}
void Spc700::BCC(int8_t offset) {
if (!PSW.C) {
PC += offset;
}
}
void Spc700::BVS(int8_t offset) {
if (PSW.V) {
PC += offset;
}
}
void Spc700::BVC(int8_t offset) {
if (!PSW.V) {
PC += offset;
}
}
void Spc700::BMI(int8_t offset) {
if (PSW.N) {
PC += offset;
}
}
void Spc700::BPL(int8_t offset) {
if (!PSW.N) {
PC += offset;
}
}
void Spc700::BEQ(int8_t offset) { if (PSW.Z) PC += offset; }
void Spc700::BNE(int8_t offset) { if (!PSW.Z) PC += offset; }
void Spc700::BCS(int8_t offset) { if (PSW.C) PC += offset; }
void Spc700::BCC(int8_t offset) { if (!PSW.C) PC += offset; }
void Spc700::BVS(int8_t offset) { if (PSW.V) PC += offset; }
void Spc700::BVC(int8_t offset) { if (!PSW.V) PC += offset; }
void Spc700::BMI(int8_t offset) { if (PSW.N) PC += offset; }
void Spc700::BPL(int8_t offset) { if (!PSW.N) PC += offset; }
void Spc700::BBS(uint8_t bit, uint8_t operand) {
if (operand & (1 << bit)) {
PC += rel();
}
if (operand & (1 << bit)) PC += rel();
}
void Spc700::BBC(uint8_t bit, uint8_t operand) {
if (!(operand & (1 << bit))) {
PC += rel();
}
if (!(operand & (1 << bit))) PC += rel();
}
// CBNE DBNZ
// JMP
void Spc700::JMP(uint16_t address) { PC = address; }
// ---------------------------------------------------------------------------
// Jump and Call Instructions
// ---------------------------------------------------------------------------
void Spc700::JMP(uint16_t address) {
PC = address;
}
void Spc700::CALL(uint16_t address) {
uint16_t return_address = PC + 2;
write(SP, return_address & 0xFF);
write(SP - 1, (return_address >> 8) & 0xFF);
SP -= 2;
push_byte((return_address >> 8) & 0xFF);
push_byte(return_address & 0xFF);
PC = address;
}
void Spc700::PCALL(uint8_t offset) {
uint16_t return_address = PC + 2;
write(SP, return_address & 0xFF);
write(SP - 1, (return_address >> 8) & 0xFF);
SP -= 2;
push_byte((return_address >> 8) & 0xFF);
push_byte(return_address & 0xFF);
PC += offset;
}
void Spc700::TCALL(uint8_t offset) {
uint16_t return_address = PC + 2;
write(SP, return_address & 0xFF);
write(SP - 1, (return_address >> 8) & 0xFF);
SP -= 2;
push_byte((return_address >> 8) & 0xFF);
push_byte(return_address & 0xFF);
PC = 0xFFDE + offset;
}
void Spc700::BRK() {
uint16_t return_address = PC + 2;
write(SP, return_address & 0xFF);
write(SP - 1, (return_address >> 8) & 0xFF);
SP -= 2;
PC = 0xFFDE;
push_word(PC);
push_byte(FlagsToByte(PSW));
PSW.I = false;
PSW.B = true;
PC = read_word(0xFFDE);
}
void Spc700::RET() {
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
SP += 2;
PC = return_address;
PC = pull_word();
}
void Spc700::RETI() {
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
SP += 2;
PC = return_address;
PSW.I = 1;
PSW = ByteToFlags(pull_byte());
PC = pull_word();
}
// ---------------------------------------------------------------------------
// Stack Instructions
// ---------------------------------------------------------------------------
void Spc700::PUSH(uint8_t operand) {
write(SP, operand);
SP--;
push_byte(operand);
}
void Spc700::POP(uint8_t& operand) {
SP++;
operand = read(SP);
operand = pull_byte();
}
void Spc700::SET1(uint8_t bit, uint8_t& operand) { operand |= (1 << bit); }
// ---------------------------------------------------------------------------
// Bit Manipulation Instructions
// ---------------------------------------------------------------------------
void Spc700::CLR1(uint8_t bit, uint8_t& operand) { operand &= ~(1 << bit); }
void Spc700::SET1(uint8_t bit, uint8_t& operand) {
operand |= (1 << bit);
}
void Spc700::CLR1(uint8_t bit, uint8_t& operand) {
operand &= ~(1 << bit);
}
void Spc700::TSET1(uint8_t bit, uint8_t& operand) {
PSW.C = (operand & (1 << bit));
PSW.C = (operand & (1 << bit)) != 0;
operand |= (1 << bit);
}
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
PSW.C = (operand & (1 << bit));
PSW.C = (operand & (1 << bit)) != 0;
operand &= ~(1 << bit);
}
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
operand &= (1 << bit);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
PSW.N = (operand & 0x80) != 0;
}
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
operand |= (1 << bit);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
PSW.N = (operand & 0x80) != 0;
}
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
operand ^= (1 << bit);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
PSW.N = (operand & 0x80) != 0;
}
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
operand ^= (1 << bit);
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
PSW.N = (operand & 0x80) != 0;
}
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
PSW.C = (operand & (1 << bit));
PSW.C = (operand & (1 << bit)) != 0;
operand |= (1 << bit);
}
void Spc700::CLRC() { PSW.C = 0; }
void Spc700::SETC() { PSW.C = 1; }
// ---------------------------------------------------------------------------
// Flag Instructions
// ---------------------------------------------------------------------------
void Spc700::CLRC() { PSW.C = false; }
void Spc700::SETC() { PSW.C = true; }
void Spc700::NOTC() { PSW.C = !PSW.C; }
void Spc700::CLRV() { PSW.V = false; PSW.H = false; }
void Spc700::CLRP() { PSW.P = false; }
void Spc700::SETP() { PSW.P = true; }
void Spc700::EI() { PSW.I = true; }
void Spc700::DI() { PSW.I = false; }
void Spc700::CLRV() { PSW.V = 0; }
// ---------------------------------------------------------------------------
// Special Instructions
// ---------------------------------------------------------------------------
void Spc700::CLRP() { PSW.P = 0; }
void Spc700::NOP() {
// No operation - PC already advanced by ReadOpcode()
}
void Spc700::SETP() { PSW.P = 1; }
void Spc700::SLEEP() {
// Sleep mode - handled in ExecuteInstructions
}
void Spc700::EI() { PSW.I = 1; }
void Spc700::DI() { PSW.I = 0; }
void Spc700::NOP() { PC++; }
void Spc700::SLEEP() {}
void Spc700::STOP() {}
void Spc700::STOP() {
// Stop mode - handled in ExecuteInstructions
}
} // namespace emu
} // namespace yaze

View File

@@ -0,0 +1,27 @@
// spc700_accurate_cycles.h - Cycle counts based on https://snes.nesdev.org/wiki/SPC-700_instruction_set
#pragma once
#include <cstdint>
// Base cycle counts for each SPC700 opcode.
// For branching instructions, this is the cost of NOT taking the branch.
// Extra cycles for taken branches are added during execution.
static const uint8_t spc700_accurate_cycles[256] = {
2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 6, 8, // 0x00
2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 4, 6, // 0x10
2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 5, 4, // 0x20
2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 8, // 0x30
2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 6, 6, // 0x40
2, 4, 6, 5, 2, 5, 5, 6, 4, 5, 5, 5, 2, 2, 4, 3, // 0x50
2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 5, 5, // 0x60
2, 4, 6, 5, 2, 5, 5, 6, 5, 6, 5, 5, 2, 2, 6, 6, // 0x70
2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 4, 8, // 0x80
2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 5, 5, 2, 2, 12, 5, // 0x90
3, 8, 4, 5, 3, 4, 3, 6, 2, 5, 4, 4, 5, 4, 4, 5, // 0xA0
2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 4, // 0xB0
3, 8, 4, 5, 4, 5, 4, 7, 2, 5, 6, 4, 5, 4, 9, 8, // 0xC0
2, 4, 6, 5, 5, 6, 6, 7, 4, 5, 5, 5, 2, 2, 4, 3, // 0xD0
2, 8, 4, 5, 3, 4, 3, 6, 2, 4, 5, 4, 5, 4, 3, 6, // 0xE0
2, 4, 6, 5, 4, 5, 5, 6, 3, 5, 4, 5, 2, 2, 4, 2 // 0xF0
};

View File

@@ -8,14 +8,15 @@
#include "app/core/features.h"
#include "app/emu/audio/internal/opcodes.h"
#include "app/emu/audio/internal/spc700_cycles.h"
#include "app/emu/audio/internal/spc700_accurate_cycles.h"
namespace yaze {
namespace emu {
void Spc700::Reset(bool hard) {
if (hard) {
PC = 0;
// DON'T set PC = 0 here! The reset sequence in Step() will load PC from the reset vector.
// Setting PC = 0 here would overwrite the correct value loaded from $FFFE-$FFFF.
A = 0;
X = 0;
Y = 0;
@@ -38,30 +39,36 @@ int Spc700::Step() {
read(0x100 | SP--);
callbacks_.idle(false);
PSW.I = false;
PC = read_word(0xfffe);
last_opcode_cycles_ = 8;
// Load PC from reset vector ($FFFE-$FFFF)
uint8_t lo = read(0xfffe);
uint8_t hi = read(0xffff);
PC = lo | (hi << 8);
return 8;
}
// Handle stopped state (SLEEP/STOP instructions)
if (stopped_) {
callbacks_.idle(true);
last_opcode_cycles_ = 2;
return 2;
}
// Reset extra cycle counter for new instruction
extra_cycles_ = 0;
// Fetch and execute one complete instruction
uint8_t opcode = ReadOpcode();
// Set base cycle count from lookup table
// This will be the return value; callbacks during execution will advance APU cycles
last_opcode_cycles_ = spc700_cycles[opcode];
// Get base cycle count from the new accurate lookup table
int cycles = spc700_accurate_cycles[opcode];
// Execute the instruction completely (atomic execution)
// This will set extra_cycles_ if a branch is taken
ExecuteInstructions(opcode);
// Return the number of cycles this instruction consumed
return last_opcode_cycles_;
// Return the base cycles plus any extra cycles from branching
return cycles + extra_cycles_;
}
void Spc700::RunOpcode() {
@@ -114,7 +121,7 @@ void Spc700::RunOpcode() {
if (bstep == 0) {
opcode = ReadOpcode();
// Set base cycle count from lookup table
last_opcode_cycles_ = spc700_cycles[opcode];
last_opcode_cycles_ = spc700_accurate_cycles[opcode];
} else {
if (spc_exec_count < 5) {
LOG_DEBUG("SPC", "Continuing multi-step: PC=$%04X bstep=%d opcode=$%02X", PC, bstep, opcode);
@@ -733,10 +740,16 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
CMP(idy());
break;
}
case 0x78: { // cmpm dp, imm
uint8_t src = 0;
uint16_t dst = dp_imm(&src);
CMPM(dst, src);
case 0x78: { // cmp d, #i
uint8_t imm = ReadOpcode();
uint16_t adr = (PSW.P << 8) | ReadOpcode();
uint8_t val = read(adr);
callbacks_.idle(false); // Add missing cycle
callbacks_.idle(false); // Add missing cycle
int result = val - imm;
PSW.C = (val >= imm);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
break;
}
case 0x79: { // cmpm ind, ind
@@ -1140,15 +1153,11 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
write(adr, result);
break;
}
case 0xcb: { // movsy dp
// CRITICAL: Only call dp() once in bstep=0, reuse saved address in bstep=1
if (bstep == 0) {
adr = dp(); // Save address for bstep=1
}
if (adr == 0x00F4 && bstep == 1) {
LOG_DEBUG("SPC", "MOVSY writing Y=$%02X to F4 at PC=$%04X", Y, PC);
}
MOVSY(adr); // Use saved address
case 0xcb: { // mov d, Y
uint16_t adr = (PSW.P << 8) | ReadOpcode();
read(adr);
callbacks_.idle(false); // Add one extra cycle delay
write(adr, Y);
break;
}
case 0xcc: { // movsy abs
@@ -1176,21 +1185,7 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
break;
}
case 0xd0: { // bne rel
switch (step++) {
case 1:
dat = ReadOpcode();
if (PSW.Z) step = 0;
break;
case 2:
callbacks_.idle(false);
break;
case 3:
callbacks_.idle(false);
PC += (int8_t)dat;
step = 0;
break;
}
// DoBranch(ReadOpcode(), !PSW.Z);
DoBranch(ReadOpcode(), !PSW.Z);
break;
}
case 0xd4: { // movs dpx
@@ -1274,43 +1269,62 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
PSW.H = false;
break;
}
case 0xe4: { // mov dp
MOV(dp());
case 0xe4: { // mov A, dp
uint16_t adr = (PSW.P << 8) | ReadOpcode();
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
break;
}
case 0xe5: { // mov abs
MOV(abs());
case 0xe5: { // mov A, abs
uint16_t adr = ReadOpcodeWord();
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
break;
}
case 0xe6: { // mov ind
MOV(ind());
case 0xe6: { // mov A, (X)
uint16_t adr = X;
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
break;
}
case 0xe7: { // mov idx
MOV(idx());
case 0xe7: { // mov A, [dp+X]
uint16_t dp_adr = (PSW.P << 8) | ReadOpcode();
callbacks_.idle(false);
uint16_t adr = read_word(dp_adr + X);
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
break;
}
case 0xe8: { // mov imm
MOV(imm());
case 0xe8: { // mov A, #imm
A = ReadOpcode();
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
break;
}
case 0xe9: { // movx abs
MOVX(abs());
break;
}
case 0xea: { // not1 abs.bit
uint16_t adr = 0;
uint8_t bit = abs_bit(&adr);
uint8_t result = read(adr) ^ (1 << bit);
write(adr, result);
uint16_t adr = ReadOpcodeWord();
X = read(adr);
PSW.Z = (X == 0);
PSW.N = (X & 0x80);
break;
}
case 0xeb: { // movy dp
MOVY(dp());
uint16_t adr = (PSW.P << 8) | ReadOpcode();
callbacks_.idle(false); // Add missing cycle
Y = read(adr);
PSW.Z = (Y == 0);
PSW.N = (Y & 0x80);
break;
}
case 0xec: { // movy abs
MOVY(abs());
uint16_t adr = ReadOpcodeWord();
Y = read(adr);
PSW.Z = (Y == 0);
PSW.N = (Y & 0x80);
break;
}
case 0xed: { // notc imp

View File

@@ -82,6 +82,7 @@ class Spc700 {
uint8_t dat;
uint16_t dat16;
uint8_t param;
int extra_cycles_ = 0;
// Cycle tracking for accurate APU synchronization
int last_opcode_cycles_ = 0;
@@ -171,11 +172,13 @@ class Spc700 {
}
void DoBranch(uint8_t value, bool check) {
callbacks_.idle(false); // Add missing base cycle for all branches
if (check) {
// taken branch: 2 extra cycles
callbacks_.idle(false);
callbacks_.idle(false);
PC += (int8_t)value;
extra_cycles_ = 2;
}
}

View File

@@ -160,20 +160,34 @@ void Cpu::RunOpcode() {
}
// LoadSongBank routine ($8888-$88FF) - This is where handshake happens!
// LOGIC: Track CPU's journey through audio initialization to identify where it gets stuck.
// We log key waypoints to understand if CPU reaches handshake write instructions.
if (cur_pc >= 0x8888 && cur_pc <= 0x88FF) {
// Log entry
if (cur_pc == 0x8888) {
LOG_INFO("CPU_AUDIO", ">>> LoadSongBank ENTRY at $8888! A=$%02X X=$%04X",
LOG_INFO("CPU_AUDIO", ">>> LoadSongBank ENTRY at $8888! A=$%02X X=$%04X",
A & 0xFF, X);
}
// Log handshake initiation ($88A0-$88B0 area writes $CC to F4)
if (cur_pc >= 0x88A0 && cur_pc <= 0x88B0 && !logged_routines[cur_pc]) {
LOG_INFO("CPU_AUDIO", "Handshake setup: PC=$%04X A=$%02X", cur_pc, A & 0xFF);
// DISCOVERY: Log every unique PC in this range to see the execution path
// This helps identify if CPU is looping, stuck, or simply not reaching write instructions
static int exec_count_8888 = 0;
if (exec_count_8888++ < 100 && !logged_routines[cur_pc]) {
LOG_INFO("CPU_AUDIO", " LoadSongBank: PC=$%04X A=$%02X X=$%04X Y=$%04X SP=$%04X [exec #%d]",
cur_pc, A & 0xFF, X, Y, SP(), exec_count_8888);
logged_routines[cur_pc] = true;
}
// Log handshake initiation ($88A0-$88B0 area writes $CC to F4)
if (cur_pc >= 0x88A0 && cur_pc <= 0x88B0) {
static int setup_count = 0;
if (setup_count++ < 20) {
LOG_INFO("CPU_AUDIO", "Handshake setup area: PC=$%04X A=$%02X", cur_pc, A & 0xFF);
}
}
// Log handshake wait loop
// LOGIC: If we see these addresses, CPU is waiting for APU response
static int handshake_log_count = 0;
if (cur_pc == 0x88B3 || cur_pc == 0x88B6) {
if (handshake_log_count++ < 20 || handshake_log_count % 500 == 0) {