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:
@@ -101,6 +101,7 @@ void Apu::RunCycles(uint64_t master_cycles) {
|
|||||||
|
|
||||||
while (cycles_ < target_apu_cycles) {
|
while (cycles_ < target_apu_cycles) {
|
||||||
// Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly.
|
// Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly.
|
||||||
|
uint16_t old_pc = spc700_.PC;
|
||||||
uint16_t current_pc = spc700_.PC;
|
uint16_t current_pc = spc700_.PC;
|
||||||
|
|
||||||
// IPL ROM protocol analysis - let it run to see what happens
|
// 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
|
// Step() returns the precise number of cycles consumed by the instruction
|
||||||
int spc_cycles = spc700_.Step();
|
int spc_cycles = spc700_.Step();
|
||||||
|
|
||||||
|
if (handshake_tracker_) {
|
||||||
|
handshake_tracker_->OnSpcPCChange(old_pc, spc700_.PC);
|
||||||
|
}
|
||||||
|
|
||||||
// Advance APU cycles based on actual SPC700 instruction timing
|
// Advance APU cycles based on actual SPC700 instruction timing
|
||||||
// Each Cycle() call: ticks DSP every 32 cycles, updates timers, increments cycles_
|
// Each Cycle() call: ticks DSP every 32 cycles, updates timers, increments cycles_
|
||||||
for (int i = 0; i < spc_cycles; ++i) {
|
for (int i = 0; i < spc_cycles; ++i) {
|
||||||
|
|||||||
@@ -4,311 +4,369 @@
|
|||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace emu {
|
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) {
|
void Spc700::MOVX(uint16_t adr) {
|
||||||
|
// MOV X, (adr) - Read from memory to X
|
||||||
X = read(adr);
|
X = read(adr);
|
||||||
PSW.Z = (X == 0);
|
PSW.Z = (X == 0);
|
||||||
PSW.N = (X & 0x80);
|
PSW.N = (X & 0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::MOVY(uint16_t adr) {
|
void Spc700::MOVY(uint16_t adr) {
|
||||||
|
// MOV Y, (adr) - Read from memory to Y
|
||||||
Y = read(adr);
|
Y = read(adr);
|
||||||
PSW.Z = (Y == 0);
|
PSW.Z = (Y == 0);
|
||||||
PSW.N = (Y & 0x80);
|
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) {
|
void Spc700::MOVS(uint16_t address) {
|
||||||
static int movs_log = 0;
|
// MOV (address), A - Write A to memory (with dummy read)
|
||||||
switch (bstep) {
|
read(address); // Dummy read (documented behavior)
|
||||||
case 0:
|
write(address, A);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::MOVSX(uint16_t address) {
|
void Spc700::MOVSX(uint16_t address) {
|
||||||
switch (bstep) {
|
// MOV (address), X - Write X to memory (with dummy read)
|
||||||
case 0: this->adr = address; read(this->adr); bstep++; break;
|
read(address); // Dummy read (documented behavior)
|
||||||
case 1: write(this->adr, X); bstep = 0; break;
|
write(address, X);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::MOVSY(uint16_t address) {
|
void Spc700::MOVSY(uint16_t address) {
|
||||||
static int movsy_log = 0;
|
// MOV (address), Y - Write Y to memory (with dummy read)
|
||||||
switch (bstep) {
|
read(address); // Dummy read (documented behavior)
|
||||||
case 0:
|
write(address, Y);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
|
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);
|
write(address, operand);
|
||||||
PSW.Z = (operand == 0);
|
|
||||||
PSW.N = (operand & 0x80);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Arithmetic Instructions (ADC, SBC)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void Spc700::ADC(uint16_t adr) {
|
void Spc700::ADC(uint16_t adr) {
|
||||||
|
// ADC A, (adr) - Add with carry
|
||||||
uint8_t value = read(adr);
|
uint8_t value = read(adr);
|
||||||
uint16_t result = A + value + PSW.C;
|
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.C = (result > 0xFF);
|
||||||
PSW.H = ((A ^ adr ^ result) & 0x10);
|
|
||||||
A = result & 0xFF;
|
A = result & 0xFF;
|
||||||
PSW.Z = ((A & 0xFF) == 0);
|
PSW.Z = (A == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (A & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
|
void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
|
||||||
|
// ADC (dest), operand - Add with carry to memory
|
||||||
uint8_t applyOn = read(dest);
|
uint8_t applyOn = read(dest);
|
||||||
int result = applyOn + operand + PSW.C;
|
int result = applyOn + operand + PSW.C;
|
||||||
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
|
PSW.V = ((applyOn & 0x80) == (operand & 0x80)) &&
|
||||||
(operand & 0x80) != (result & 0x80);
|
((operand & 0x80) != (result & 0x80));
|
||||||
PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
|
PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
|
||||||
PSW.C = result > 0xff;
|
PSW.C = result > 0xff;
|
||||||
write(dest, result);
|
write(dest, result & 0xFF);
|
||||||
PSW.Z = ((result & 0xFF) == 0);
|
PSW.Z = ((result & 0xFF) == 0);
|
||||||
PSW.N = (result & 0x80);
|
PSW.N = (result & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::SBC(uint16_t adr) {
|
void Spc700::SBC(uint16_t adr) {
|
||||||
|
// SBC A, (adr) - Subtract with carry (borrow)
|
||||||
uint8_t value = read(adr) ^ 0xff;
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
int result = A + value + PSW.C;
|
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.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf;
|
||||||
PSW.C = result > 0xff;
|
PSW.C = result > 0xff;
|
||||||
A = result;
|
A = result & 0xFF;
|
||||||
PSW.Z = ((A & 0xFF) == 0);
|
PSW.Z = (A == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (A & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
|
void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
|
||||||
|
// SBC (dest), operand - Subtract with carry from memory
|
||||||
operand ^= 0xff;
|
operand ^= 0xff;
|
||||||
uint8_t applyOn = read(dest);
|
uint8_t applyOn = read(dest);
|
||||||
int result = applyOn + operand + PSW.C;
|
int result = applyOn + operand + PSW.C;
|
||||||
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
|
PSW.V = ((applyOn & 0x80) == (operand & 0x80)) &&
|
||||||
(operand & 0x80) != (operand & 0x80);
|
((operand & 0x80) != (result & 0x80));
|
||||||
PSW.H = ((applyOn & 0xF) + (operand & 0xF) + PSW.C) > 0xF;
|
PSW.H = ((applyOn & 0xF) + (operand & 0xF) + PSW.C) > 0xF;
|
||||||
PSW.C = result > 0xFF;
|
PSW.C = result > 0xFF;
|
||||||
write(dest, result);
|
write(dest, result & 0xFF);
|
||||||
PSW.Z = ((A & 0xFF) == 0);
|
PSW.Z = ((result & 0xFF) == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (result & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::CMPX(uint16_t adr) {
|
// ---------------------------------------------------------------------------
|
||||||
uint8_t value = read(adr) ^ 0xff;
|
// Comparison Instructions (CMP, CMPX, CMPY, CMPM)
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Spc700::CMP(uint16_t adr) {
|
void Spc700::CMP(uint16_t adr) {
|
||||||
|
// CMP A, (adr) - Compare A with memory
|
||||||
uint8_t value = read(adr) ^ 0xff;
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
int result = A + value + 1;
|
int result = A + value + 1;
|
||||||
PSW.C = result > 0xff;
|
PSW.C = result > 0xff;
|
||||||
PSW.Z = ((result & 0xFF) == 0);
|
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) {
|
void Spc700::AND(uint16_t adr) {
|
||||||
|
// AND A, (adr) - Logical AND with memory
|
||||||
A &= read(adr);
|
A &= read(adr);
|
||||||
PSW.Z = (A == 0);
|
PSW.Z = (A == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (A & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ANDM(uint16_t dest, uint8_t operand) {
|
void Spc700::ANDM(uint16_t dest, uint8_t operand) {
|
||||||
|
// AND (dest), operand - Logical AND memory with value
|
||||||
uint8_t result = read(dest) & operand;
|
uint8_t result = read(dest) & operand;
|
||||||
write(dest, result);
|
write(dest, result);
|
||||||
PSW.Z = (result == 0);
|
PSW.Z = (result == 0);
|
||||||
PSW.N = (result & 0x80);
|
PSW.N = (result & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::OR(uint16_t adr) {
|
void Spc700::OR(uint16_t adr) {
|
||||||
|
// OR A, (adr) - Logical OR with memory
|
||||||
A |= read(adr);
|
A |= read(adr);
|
||||||
PSW.Z = (A == 0);
|
PSW.Z = (A == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (A & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ORM(uint16_t dst, uint8_t value) {
|
void Spc700::ORM(uint16_t dst, uint8_t value) {
|
||||||
|
// OR (dst), value - Logical OR memory with value
|
||||||
uint8_t result = read(dst) | value;
|
uint8_t result = read(dst) | value;
|
||||||
write(dst, result);
|
write(dst, result);
|
||||||
PSW.Z = (result == 0);
|
PSW.Z = (result == 0);
|
||||||
PSW.N = (result & 0x80);
|
PSW.N = (result & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::EOR(uint16_t adr) {
|
void Spc700::EOR(uint16_t adr) {
|
||||||
|
// EOR A, (adr) - Logical XOR with memory
|
||||||
A ^= read(adr);
|
A ^= read(adr);
|
||||||
PSW.Z = (A == 0);
|
PSW.Z = (A == 0);
|
||||||
PSW.N = (A & 0x80);
|
PSW.N = (A & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::EORM(uint16_t dest, uint8_t operand) {
|
void Spc700::EORM(uint16_t dest, uint8_t operand) {
|
||||||
|
// EOR (dest), operand - Logical XOR memory with value
|
||||||
uint8_t result = read(dest) ^ operand;
|
uint8_t result = read(dest) ^ operand;
|
||||||
write(dest, result);
|
write(dest, result);
|
||||||
PSW.Z = (result == 0);
|
PSW.Z = (result == 0);
|
||||||
PSW.N = (result & 0x80);
|
PSW.N = (result & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ASL(uint16_t operand) {
|
// ---------------------------------------------------------------------------
|
||||||
uint8_t val = read(operand);
|
// Shift and Rotate Instructions (ASL, LSR, ROL, ROR)
|
||||||
write(operand, val);
|
// ---------------------------------------------------------------------------
|
||||||
PSW.C = (val & 0x80);
|
|
||||||
|
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;
|
val <<= 1;
|
||||||
|
write(adr, val); // Actual write
|
||||||
PSW.Z = (val == 0);
|
PSW.Z = (val == 0);
|
||||||
PSW.N = (val & 0x80);
|
PSW.N = (val & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::LSR(uint16_t adr) {
|
void Spc700::LSR(uint16_t adr) {
|
||||||
|
// LSR (adr) - Logical shift right
|
||||||
uint8_t val = read(adr);
|
uint8_t val = read(adr);
|
||||||
PSW.C = (val & 0x01);
|
write(adr, val); // Dummy write (RMW instruction)
|
||||||
|
PSW.C = (val & 0x01) != 0;
|
||||||
val >>= 1;
|
val >>= 1;
|
||||||
write(adr, val);
|
write(adr, val); // Actual write
|
||||||
PSW.Z = (val == 0);
|
PSW.Z = (val == 0);
|
||||||
PSW.N = (val & 0x80);
|
PSW.N = (val & 0x80) != 0;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ROL(uint16_t adr) {
|
void Spc700::ROL(uint16_t adr) {
|
||||||
|
// ROL (adr) - Rotate left through carry
|
||||||
uint8_t val = read(adr);
|
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;
|
val = (val << 1) | PSW.C;
|
||||||
PSW.C = newC;
|
PSW.C = newC;
|
||||||
write(adr, val);
|
write(adr, val); // Actual write
|
||||||
|
|
||||||
PSW.Z = (val == 0);
|
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) {
|
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;
|
uint8_t value = isImmediate ? imm() : operand;
|
||||||
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
||||||
PSW.Z = (value == 0);
|
PSW.Z = (value == 0);
|
||||||
PSW.N = (value & 0x80);
|
PSW.N = (value & 0x80) != 0;
|
||||||
// operand = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::INC(uint16_t adr) {
|
// ---------------------------------------------------------------------------
|
||||||
uint8_t val = read(adr) + 1;
|
// 16-bit Instructions (MOVW, INCW, DECW, ADDW, SUBW, CMPW)
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
|
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
|
||||||
|
// MOVW - Move 16-bit word
|
||||||
dest = operand;
|
dest = operand;
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x8000);
|
PSW.N = (operand & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::INCW(uint16_t& operand) {
|
void Spc700::INCW(uint16_t& operand) {
|
||||||
|
// INCW - Increment 16-bit word
|
||||||
operand++;
|
operand++;
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x8000);
|
PSW.N = (operand & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::DECW(uint16_t& operand) {
|
void Spc700::DECW(uint16_t& operand) {
|
||||||
|
// DECW - Decrement 16-bit word
|
||||||
operand--;
|
operand--;
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x8000);
|
PSW.N = (operand & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
|
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
|
||||||
|
// ADDW - Add 16-bit word
|
||||||
uint32_t result = dest + operand;
|
uint32_t result = dest + operand;
|
||||||
PSW.C = (result > 0xFFFF);
|
PSW.C = (result > 0xFFFF);
|
||||||
PSW.Z = ((result & 0xFFFF) == 0);
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
PSW.N = (result & 0x8000);
|
PSW.N = (result & 0x8000) != 0;
|
||||||
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
|
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000) != 0;
|
||||||
|
PSW.H = ((dest & 0xfff) + (operand & 0xfff)) > 0xfff;
|
||||||
dest = result & 0xFFFF;
|
dest = result & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
|
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
|
||||||
|
// SUBW - Subtract 16-bit word
|
||||||
uint32_t result = dest - operand;
|
uint32_t result = dest - operand;
|
||||||
PSW.C = (result < 0x10000);
|
PSW.C = (result <= 0xFFFF);
|
||||||
PSW.Z = ((result & 0xFFFF) == 0);
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
PSW.N = (result & 0x8000);
|
PSW.N = (result & 0x8000) != 0;
|
||||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
|
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000) != 0;
|
||||||
|
PSW.H = ((dest & 0xfff) - (operand & 0xfff)) >= 0;
|
||||||
dest = result & 0xFFFF;
|
dest = result & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::CMPW(uint16_t operand) {
|
void Spc700::CMPW(uint16_t operand) {
|
||||||
|
// CMPW - Compare 16-bit word with YA
|
||||||
uint32_t result = YA - operand;
|
uint32_t result = YA - operand;
|
||||||
PSW.C = (result < 0x10000);
|
PSW.C = (result <= 0xFFFF);
|
||||||
PSW.Z = ((result & 0xFFFF) == 0);
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
PSW.N = (result & 0x8000);
|
PSW.N = (result & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Multiply/Divide Instructions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void Spc700::MUL(uint8_t operand) {
|
void Spc700::MUL(uint8_t operand) {
|
||||||
|
// MUL - Multiply A * Y -> YA
|
||||||
uint16_t result = A * operand;
|
uint16_t result = A * operand;
|
||||||
YA = result;
|
YA = result;
|
||||||
PSW.Z = (result == 0);
|
PSW.Z = (result == 0);
|
||||||
PSW.N = (result & 0x8000);
|
PSW.N = (result & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::DIV(uint8_t operand) {
|
void Spc700::DIV(uint8_t operand) {
|
||||||
|
// DIV - Divide YA / X -> A (quotient), Y (remainder)
|
||||||
|
// Note: Hardware behavior is complex; simplified here
|
||||||
if (operand == 0) {
|
if (operand == 0) {
|
||||||
// Handle divide by zero error
|
// Divide by zero - undefined behavior
|
||||||
|
// Real hardware has specific behavior, but we'll just return
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t quotient = A / operand;
|
uint8_t quotient = A / operand;
|
||||||
@@ -316,194 +374,170 @@ void Spc700::DIV(uint8_t operand) {
|
|||||||
A = quotient;
|
A = quotient;
|
||||||
Y = remainder;
|
Y = remainder;
|
||||||
PSW.Z = (quotient == 0);
|
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::BRA(int8_t offset) { PC += offset; }
|
||||||
|
void Spc700::BEQ(int8_t offset) { if (PSW.Z) PC += offset; }
|
||||||
void Spc700::BEQ(int8_t offset) {
|
void Spc700::BNE(int8_t offset) { if (!PSW.Z) PC += offset; }
|
||||||
if (PSW.Z) {
|
void Spc700::BCS(int8_t offset) { if (PSW.C) PC += offset; }
|
||||||
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::BNE(int8_t offset) {
|
void Spc700::BPL(int8_t offset) { if (!PSW.N) PC += 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) {
|
void Spc700::BBS(uint8_t bit, uint8_t operand) {
|
||||||
if (operand & (1 << bit)) {
|
if (operand & (1 << bit)) PC += rel();
|
||||||
PC += rel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::BBC(uint8_t bit, uint8_t operand) {
|
void Spc700::BBC(uint8_t bit, uint8_t operand) {
|
||||||
if (!(operand & (1 << bit))) {
|
if (!(operand & (1 << bit))) PC += rel();
|
||||||
PC += rel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CBNE DBNZ
|
// ---------------------------------------------------------------------------
|
||||||
// JMP
|
// Jump and Call Instructions
|
||||||
void Spc700::JMP(uint16_t address) { PC = address; }
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void Spc700::JMP(uint16_t address) {
|
||||||
|
PC = address;
|
||||||
|
}
|
||||||
|
|
||||||
void Spc700::CALL(uint16_t address) {
|
void Spc700::CALL(uint16_t address) {
|
||||||
uint16_t return_address = PC + 2;
|
uint16_t return_address = PC + 2;
|
||||||
write(SP, return_address & 0xFF);
|
push_byte((return_address >> 8) & 0xFF);
|
||||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
push_byte(return_address & 0xFF);
|
||||||
SP -= 2;
|
|
||||||
PC = address;
|
PC = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::PCALL(uint8_t offset) {
|
void Spc700::PCALL(uint8_t offset) {
|
||||||
uint16_t return_address = PC + 2;
|
uint16_t return_address = PC + 2;
|
||||||
write(SP, return_address & 0xFF);
|
push_byte((return_address >> 8) & 0xFF);
|
||||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
push_byte(return_address & 0xFF);
|
||||||
SP -= 2;
|
|
||||||
PC += offset;
|
PC += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::TCALL(uint8_t offset) {
|
void Spc700::TCALL(uint8_t offset) {
|
||||||
uint16_t return_address = PC + 2;
|
uint16_t return_address = PC + 2;
|
||||||
write(SP, return_address & 0xFF);
|
push_byte((return_address >> 8) & 0xFF);
|
||||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
push_byte(return_address & 0xFF);
|
||||||
SP -= 2;
|
|
||||||
PC = 0xFFDE + offset;
|
PC = 0xFFDE + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::BRK() {
|
void Spc700::BRK() {
|
||||||
uint16_t return_address = PC + 2;
|
push_word(PC);
|
||||||
write(SP, return_address & 0xFF);
|
push_byte(FlagsToByte(PSW));
|
||||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
PSW.I = false;
|
||||||
SP -= 2;
|
PSW.B = true;
|
||||||
PC = 0xFFDE;
|
PC = read_word(0xFFDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::RET() {
|
void Spc700::RET() {
|
||||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
PC = pull_word();
|
||||||
SP += 2;
|
|
||||||
PC = return_address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::RETI() {
|
void Spc700::RETI() {
|
||||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
PSW = ByteToFlags(pull_byte());
|
||||||
SP += 2;
|
PC = pull_word();
|
||||||
PC = return_address;
|
|
||||||
PSW.I = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Stack Instructions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void Spc700::PUSH(uint8_t operand) {
|
void Spc700::PUSH(uint8_t operand) {
|
||||||
write(SP, operand);
|
push_byte(operand);
|
||||||
SP--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::POP(uint8_t& operand) {
|
void Spc700::POP(uint8_t& operand) {
|
||||||
SP++;
|
operand = pull_byte();
|
||||||
operand = read(SP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
void Spc700::TSET1(uint8_t bit, uint8_t& operand) {
|
||||||
PSW.C = (operand & (1 << bit));
|
PSW.C = (operand & (1 << bit)) != 0;
|
||||||
operand |= (1 << bit);
|
operand |= (1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
|
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
|
||||||
PSW.C = (operand & (1 << bit));
|
PSW.C = (operand & (1 << bit)) != 0;
|
||||||
operand &= ~(1 << bit);
|
operand &= ~(1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
|
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
|
||||||
operand &= (1 << bit);
|
operand &= (1 << bit);
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x80);
|
PSW.N = (operand & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
|
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
|
||||||
operand |= (1 << bit);
|
operand |= (1 << bit);
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x80);
|
PSW.N = (operand & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
|
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
|
||||||
operand ^= (1 << bit);
|
operand ^= (1 << bit);
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x80);
|
PSW.N = (operand & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
|
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
|
||||||
operand ^= (1 << bit);
|
operand ^= (1 << bit);
|
||||||
PSW.Z = (operand == 0);
|
PSW.Z = (operand == 0);
|
||||||
PSW.N = (operand & 0x80);
|
PSW.N = (operand & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
|
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
|
||||||
PSW.C = (operand & (1 << bit));
|
PSW.C = (operand & (1 << bit)) != 0;
|
||||||
operand |= (1 << bit);
|
operand |= (1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::CLRC() { PSW.C = 0; }
|
// ---------------------------------------------------------------------------
|
||||||
|
// Flag Instructions
|
||||||
void Spc700::SETC() { PSW.C = 1; }
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void Spc700::CLRC() { PSW.C = false; }
|
||||||
|
void Spc700::SETC() { PSW.C = true; }
|
||||||
void Spc700::NOTC() { PSW.C = !PSW.C; }
|
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::STOP() {
|
||||||
|
// Stop mode - handled in ExecuteInstructions
|
||||||
void Spc700::DI() { PSW.I = 0; }
|
}
|
||||||
|
|
||||||
void Spc700::NOP() { PC++; }
|
|
||||||
|
|
||||||
void Spc700::SLEEP() {}
|
|
||||||
|
|
||||||
void Spc700::STOP() {}
|
|
||||||
|
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
27
src/app/emu/audio/internal/spc700_accurate_cycles.h
Normal file
27
src/app/emu/audio/internal/spc700_accurate_cycles.h
Normal 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
|
||||||
|
};
|
||||||
@@ -8,14 +8,15 @@
|
|||||||
#include "app/core/features.h"
|
#include "app/core/features.h"
|
||||||
|
|
||||||
#include "app/emu/audio/internal/opcodes.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 yaze {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
|
||||||
void Spc700::Reset(bool hard) {
|
void Spc700::Reset(bool hard) {
|
||||||
if (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;
|
A = 0;
|
||||||
X = 0;
|
X = 0;
|
||||||
Y = 0;
|
Y = 0;
|
||||||
@@ -38,30 +39,36 @@ int Spc700::Step() {
|
|||||||
read(0x100 | SP--);
|
read(0x100 | SP--);
|
||||||
callbacks_.idle(false);
|
callbacks_.idle(false);
|
||||||
PSW.I = 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;
|
return 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle stopped state (SLEEP/STOP instructions)
|
// Handle stopped state (SLEEP/STOP instructions)
|
||||||
if (stopped_) {
|
if (stopped_) {
|
||||||
callbacks_.idle(true);
|
callbacks_.idle(true);
|
||||||
last_opcode_cycles_ = 2;
|
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset extra cycle counter for new instruction
|
||||||
|
extra_cycles_ = 0;
|
||||||
|
|
||||||
// Fetch and execute one complete instruction
|
// Fetch and execute one complete instruction
|
||||||
uint8_t opcode = ReadOpcode();
|
uint8_t opcode = ReadOpcode();
|
||||||
|
|
||||||
// Set base cycle count from lookup table
|
// Get base cycle count from the new accurate lookup table
|
||||||
// This will be the return value; callbacks during execution will advance APU cycles
|
int cycles = spc700_accurate_cycles[opcode];
|
||||||
last_opcode_cycles_ = spc700_cycles[opcode];
|
|
||||||
|
|
||||||
// Execute the instruction completely (atomic execution)
|
// Execute the instruction completely (atomic execution)
|
||||||
|
// This will set extra_cycles_ if a branch is taken
|
||||||
ExecuteInstructions(opcode);
|
ExecuteInstructions(opcode);
|
||||||
|
|
||||||
// Return the number of cycles this instruction consumed
|
// Return the base cycles plus any extra cycles from branching
|
||||||
return last_opcode_cycles_;
|
return cycles + extra_cycles_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spc700::RunOpcode() {
|
void Spc700::RunOpcode() {
|
||||||
@@ -114,7 +121,7 @@ void Spc700::RunOpcode() {
|
|||||||
if (bstep == 0) {
|
if (bstep == 0) {
|
||||||
opcode = ReadOpcode();
|
opcode = ReadOpcode();
|
||||||
// Set base cycle count from lookup table
|
// Set base cycle count from lookup table
|
||||||
last_opcode_cycles_ = spc700_cycles[opcode];
|
last_opcode_cycles_ = spc700_accurate_cycles[opcode];
|
||||||
} else {
|
} else {
|
||||||
if (spc_exec_count < 5) {
|
if (spc_exec_count < 5) {
|
||||||
LOG_DEBUG("SPC", "Continuing multi-step: PC=$%04X bstep=%d opcode=$%02X", PC, bstep, opcode);
|
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());
|
CMP(idy());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x78: { // cmpm dp, imm
|
case 0x78: { // cmp d, #i
|
||||||
uint8_t src = 0;
|
uint8_t imm = ReadOpcode();
|
||||||
uint16_t dst = dp_imm(&src);
|
uint16_t adr = (PSW.P << 8) | ReadOpcode();
|
||||||
CMPM(dst, src);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case 0x79: { // cmpm ind, ind
|
case 0x79: { // cmpm ind, ind
|
||||||
@@ -1140,15 +1153,11 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
|||||||
write(adr, result);
|
write(adr, result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xcb: { // movsy dp
|
case 0xcb: { // mov d, Y
|
||||||
// CRITICAL: Only call dp() once in bstep=0, reuse saved address in bstep=1
|
uint16_t adr = (PSW.P << 8) | ReadOpcode();
|
||||||
if (bstep == 0) {
|
read(adr);
|
||||||
adr = dp(); // Save address for bstep=1
|
callbacks_.idle(false); // Add one extra cycle delay
|
||||||
}
|
write(adr, Y);
|
||||||
if (adr == 0x00F4 && bstep == 1) {
|
|
||||||
LOG_DEBUG("SPC", "MOVSY writing Y=$%02X to F4 at PC=$%04X", Y, PC);
|
|
||||||
}
|
|
||||||
MOVSY(adr); // Use saved address
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xcc: { // movsy abs
|
case 0xcc: { // movsy abs
|
||||||
@@ -1176,21 +1185,7 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xd0: { // bne rel
|
case 0xd0: { // bne rel
|
||||||
switch (step++) {
|
DoBranch(ReadOpcode(), !PSW.Z);
|
||||||
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);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xd4: { // movs dpx
|
case 0xd4: { // movs dpx
|
||||||
@@ -1274,43 +1269,62 @@ void Spc700::ExecuteInstructions(uint8_t opcode) {
|
|||||||
PSW.H = false;
|
PSW.H = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe4: { // mov dp
|
case 0xe4: { // mov A, dp
|
||||||
MOV(dp());
|
uint16_t adr = (PSW.P << 8) | ReadOpcode();
|
||||||
|
A = read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe5: { // mov abs
|
case 0xe5: { // mov A, abs
|
||||||
MOV(abs());
|
uint16_t adr = ReadOpcodeWord();
|
||||||
|
A = read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe6: { // mov ind
|
case 0xe6: { // mov A, (X)
|
||||||
MOV(ind());
|
uint16_t adr = X;
|
||||||
|
A = read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe7: { // mov idx
|
case 0xe7: { // mov A, [dp+X]
|
||||||
MOV(idx());
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe8: { // mov imm
|
case 0xe8: { // mov A, #imm
|
||||||
MOV(imm());
|
A = ReadOpcode();
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xe9: { // movx abs
|
case 0xe9: { // movx abs
|
||||||
MOVX(abs());
|
uint16_t adr = ReadOpcodeWord();
|
||||||
break;
|
X = read(adr);
|
||||||
}
|
PSW.Z = (X == 0);
|
||||||
case 0xea: { // not1 abs.bit
|
PSW.N = (X & 0x80);
|
||||||
uint16_t adr = 0;
|
|
||||||
uint8_t bit = abs_bit(&adr);
|
|
||||||
uint8_t result = read(adr) ^ (1 << bit);
|
|
||||||
write(adr, result);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xeb: { // movy dp
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case 0xec: { // movy abs
|
case 0xec: { // movy abs
|
||||||
MOVY(abs());
|
uint16_t adr = ReadOpcodeWord();
|
||||||
|
Y = read(adr);
|
||||||
|
PSW.Z = (Y == 0);
|
||||||
|
PSW.N = (Y & 0x80);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xed: { // notc imp
|
case 0xed: { // notc imp
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class Spc700 {
|
|||||||
uint8_t dat;
|
uint8_t dat;
|
||||||
uint16_t dat16;
|
uint16_t dat16;
|
||||||
uint8_t param;
|
uint8_t param;
|
||||||
|
int extra_cycles_ = 0;
|
||||||
|
|
||||||
// Cycle tracking for accurate APU synchronization
|
// Cycle tracking for accurate APU synchronization
|
||||||
int last_opcode_cycles_ = 0;
|
int last_opcode_cycles_ = 0;
|
||||||
@@ -171,11 +172,13 @@ class Spc700 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DoBranch(uint8_t value, bool check) {
|
void DoBranch(uint8_t value, bool check) {
|
||||||
|
callbacks_.idle(false); // Add missing base cycle for all branches
|
||||||
if (check) {
|
if (check) {
|
||||||
// taken branch: 2 extra cycles
|
// taken branch: 2 extra cycles
|
||||||
callbacks_.idle(false);
|
callbacks_.idle(false);
|
||||||
callbacks_.idle(false);
|
callbacks_.idle(false);
|
||||||
PC += (int8_t)value;
|
PC += (int8_t)value;
|
||||||
|
extra_cycles_ = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ void Cpu::RunOpcode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadSongBank routine ($8888-$88FF) - This is where handshake happens!
|
// 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) {
|
if (cur_pc >= 0x8888 && cur_pc <= 0x88FF) {
|
||||||
// Log entry
|
// Log entry
|
||||||
if (cur_pc == 0x8888) {
|
if (cur_pc == 0x8888) {
|
||||||
@@ -167,13 +169,25 @@ void Cpu::RunOpcode() {
|
|||||||
A & 0xFF, X);
|
A & 0xFF, X);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log handshake initiation ($88A0-$88B0 area writes $CC to F4)
|
// DISCOVERY: Log every unique PC in this range to see the execution path
|
||||||
if (cur_pc >= 0x88A0 && cur_pc <= 0x88B0 && !logged_routines[cur_pc]) {
|
// This helps identify if CPU is looping, stuck, or simply not reaching write instructions
|
||||||
LOG_INFO("CPU_AUDIO", "Handshake setup: PC=$%04X A=$%02X", cur_pc, A & 0xFF);
|
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;
|
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
|
// Log handshake wait loop
|
||||||
|
// LOGIC: If we see these addresses, CPU is waiting for APU response
|
||||||
static int handshake_log_count = 0;
|
static int handshake_log_count = 0;
|
||||||
if (cur_pc == 0x88B3 || cur_pc == 0x88B6) {
|
if (cur_pc == 0x88B3 || cur_pc == 0x88B6) {
|
||||||
if (handshake_log_count++ < 20 || handshake_log_count % 500 == 0) {
|
if (handshake_log_count++ < 20 || handshake_log_count % 500 == 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user