1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-03-25 06:30:38 +00:00

Merge pull request from TomHarte/65816Tests

Add 65816 test generator; correct disagreements with other emulations.
This commit is contained in:
Thomas Harte 2022-06-24 21:28:24 -04:00 committed by GitHub
commit 9888f079fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 460 additions and 109 deletions

@ -12,6 +12,7 @@
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */; };
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
@ -1110,6 +1111,7 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 65816ComparativeTests.mm; sourceTree = "<group>"; };
4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ROMCatalogue.cpp; sourceTree = "<group>"; };
4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMCatalogue.hpp; sourceTree = "<group>"; };
4B051C94266EF50200CA44E8 /* AppleIIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIController.swift; sourceTree = "<group>"; };
@ -4230,6 +4232,7 @@
children = (
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
@ -6053,6 +6056,7 @@
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */,
4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */,
4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */,
4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */,
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */,
4B778EFA23A5EB790000D260 /* DMK.cpp in Sources */,
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */,

@ -0,0 +1,302 @@
//
// 65816ComparativeTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 18/06/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#include "65816.hpp"
#import <XCTest/XCTest.h>
#include <array>
#include <optional>
#include <vector>
#include <unordered_map>
namespace {
struct StopException {};
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
// Use a map to store RAM contents, in order to preserve initialised state.
std::unordered_map<uint32_t, uint8_t> ram;
std::unordered_map<uint32_t, uint8_t> inventions;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) {
// Record the basics of the operation.
auto &cycle = cycles.emplace_back();
cycle.operation = operation;
cycle.extended_bus = processor.get_extended_bus_output();
// Perform the operation, and fill in the cycle's value.
using BusOperation = CPU::MOS6502Esque::BusOperation;
auto ram_value = ram.find(address);
switch(operation) {
case BusOperation::ReadOpcode:
if(initial_pc && (*initial_pc != address || !allow_pc_repetition)) {
cycles.pop_back();
pc_overshoot = -1;
throw StopException();
}
initial_pc = address;
[[fallthrough]];
case BusOperation::Read:
case BusOperation::ReadProgram:
case BusOperation::ReadVector:
cycle.address = address;
if(ram_value != ram.end()) {
cycle.value = *value = ram_value->second;
} else {
cycle.value = *value = uint8_t(rand() >> 8);
inventions[address] = ram[address] = *cycle.value;
}
break;
case BusOperation::Write:
cycle.address = address;
cycle.value = ram[address] = *value;
break;
case BusOperation::Ready:
case BusOperation::None:
throw StopException();
break;
case BusOperation::InternalOperationWrite:
cycle.value = *value = ram_value->second;
[[fallthrough]];
case BusOperation::InternalOperationRead:
cycle.address = address;
break;
default: assert(false);
}
// Don't occupy any bonus time.
return Cycles(1);
}
void setup(uint8_t opcode) {
ram.clear();
inventions.clear();
cycles.clear();
pc_overshoot = 0;
initial_pc = std::nullopt;
// For MVP or MVN, keep tracking fetches via the same location.
// For other instructions, don't. That's to avoid endless loops
// for flow control that happens to jump back to where it began.
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
using Register = CPU::MOS6502Esque::Register;
const uint32_t pc =
processor.get_value_of_register(Register::ProgramCounter) |
(processor.get_value_of_register(Register::ProgramBank) << 16);
inventions[pc] = ram[pc] = opcode;
}
int pc_overshoot = 0;
std::optional<uint32_t> initial_pc;
bool allow_pc_repetition = false;
struct Cycle {
CPU::MOS6502Esque::BusOperation operation;
std::optional<uint32_t> address;
std::optional<uint8_t> value;
int extended_bus;
};
std::vector<Cycle> cycles;
CPU::WDC65816::Processor<BusHandler, false> processor;
BusHandler() : processor(*this) {
// Never run the official reset procedure.
processor.set_power_on(false);
}
};
template <typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
using Register = CPU::MOS6502Esque::Register;
fprintf(file, "\"pc\": %d, ", (processor.get_value_of_register(Register::ProgramCounter) + pc_offset) & 65535);
fprintf(file, "\"s\": %d, ", processor.get_value_of_register(Register::StackPointer));
fprintf(file, "\"p\": %d, ", processor.get_value_of_register(Register::Flags));
fprintf(file, "\"a\": %d, ", processor.get_value_of_register(Register::A));
fprintf(file, "\"x\": %d, ", processor.get_value_of_register(Register::X));
fprintf(file, "\"y\": %d, ", processor.get_value_of_register(Register::Y));
fprintf(file, "\"dbr\": %d, ", processor.get_value_of_register(Register::DataBank));
fprintf(file, "\"d\": %d, ", processor.get_value_of_register(Register::Direct));
fprintf(file, "\"pbr\": %d, ", processor.get_value_of_register(Register::ProgramBank));
fprintf(file, "\"e\": %d, ", processor.get_value_of_register(Register::EmulationFlag));
}
void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
fprintf(file, "\"ram\": [");
bool is_first = true;
for(const auto &pair: data) {
if(!is_first) fprintf(file, ", ");
is_first = false;
fprintf(file, "[%d, %d]", pair.first, pair.second);
}
fprintf(file, "]");
}
}
// MARK: - New test generator.
@interface TestGenerator : NSObject
@end
@implementation TestGenerator
- (void)generate {
BusHandler handler;
// Make tests repeatable, at least for any given instance of
// the runtime.
srand(65816);
NSString *const tempDir = NSTemporaryDirectory();
NSLog(@"Outputting to %@", tempDir);
for(int operation = 0; operation < 512; operation++) {
const bool is_emulated = operation & 256;
const uint8_t opcode = operation & 255;
NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'];
FILE *const target = fopen(targetName.UTF8String, "wt");
bool is_first_test = true;
fprintf(target, "[");
for(int test = 0; test < 10'000; test++) {
if(!is_first_test) fprintf(target, ",\n");
is_first_test = false;
// Ensure processor's next action is an opcode fetch.
handler.processor.restart_operation_fetch();
// Randomise most of the processor state...
using Register = CPU::MOS6502Esque::Register;
handler.processor.set_value_of_register(Register::A, rand() >> 8);
handler.processor.set_value_of_register(Register::Flags, rand() >> 8);
handler.processor.set_value_of_register(Register::X, rand() >> 8);
handler.processor.set_value_of_register(Register::Y, rand() >> 8);
handler.processor.set_value_of_register(Register::ProgramCounter, rand() >> 8);
handler.processor.set_value_of_register(Register::StackPointer, rand() >> 8);
handler.processor.set_value_of_register(Register::DataBank, rand() >> 8);
handler.processor.set_value_of_register(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of_register(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given.
// And is set last to ensure proper internal state is applied.
handler.processor.set_value_of_register(Register::EmulationFlag, is_emulated);
// Establish the opcode.
handler.setup(opcode);
// Dump initial state.
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
fprintf(target, "\"initial\": {");
print_registers(target, handler.processor, 0);
// Run to the second opcode fetch.
try {
handler.processor.run_for(Cycles(100));
} catch (const StopException &) {}
// Dump all inventions as initial memory state.
print_ram(target, handler.inventions);
// Dump final state.
fprintf(target, "}, \"final\": {");
print_registers(target, handler.processor, handler.pc_overshoot);
print_ram(target, handler.ram);
fprintf(target, "}, ");
// Append cycles.
fprintf(target, "\"cycles\": [");
bool is_first_cycle = true;
for(const auto &cycle: handler.cycles) {
if(!is_first_cycle) fprintf(target, ",");
is_first_cycle = false;
bool vda = false;
bool vpa = false;
bool vpb = false;
bool read = false;
bool wait = false;
using BusOperation = CPU::MOS6502Esque::BusOperation;
switch(cycle.operation) {
case BusOperation::Read: read = vda = true; break;
case BusOperation::ReadOpcode: read = vda = vpa = true; break;
case BusOperation::ReadProgram: read = vpa = true; break;
case BusOperation::ReadVector: read = vpb = vda = true; break;
case BusOperation::InternalOperationRead: read = true; break;
case BusOperation::Write: vda = true; break;
case BusOperation::InternalOperationWrite: break;
case BusOperation::None:
case BusOperation::Ready: wait = true; break;
default:
assert(false);
}
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
fprintf(target, "[");
if(cycle.address) {
fprintf(target, "%d, ", *cycle.address);
} else {
fprintf(target, "null, ");
}
if(cycle.value) {
fprintf(target, "%d, ", *cycle.value);
} else {
fprintf(target, "null, ");
}
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
vda ? 'd' : '-',
vpa ? 'p' : '-',
vpb ? 'v' : '-',
wait ? '-' : (read ? 'r' : 'w'),
wait ? '-' : (emulation ? 'e' : '-'),
wait ? '-' : (memory_size ? 'm' : '-'),
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-')
);
}
// Terminate object.
fprintf(target, "]}");
}
fprintf(target, "]");
fclose(target);
}
}
@end
// MARK: - Existing test evaluator.
@interface WDC65816ComparativeTests : XCTestCase
@end
@implementation WDC65816ComparativeTests
// A generator for tests; not intended to be a permanent fixture.
//- (void)testGenerate {
// [[[TestGenerator alloc] init] generate];
//}
@end

@ -84,11 +84,7 @@ class Krom65816Tests: XCTestCase {
cpuState += String(format: "A:%04x ", machine.value(for: .A))
cpuState += String(format: "X:%04x ", machine.value(for: .X))
cpuState += String(format: "Y:%04x ", machine.value(for: .Y))
if emulationFlag {
cpuState += String(format: "S:01%02x ", machine.value(for: .stackPointer))
} else {
cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer))
}
cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer))
cpuState += String(format: "D:%04x ", machine.value(for: .direct))
cpuState += String(format: "DB:%02x ", machine.value(for: .dataBank))

@ -77,7 +77,7 @@ enum BusOperation {
/// 65816: indicates that a read was signalled with VPA.
ReadProgram,
/// 6502: never signalled.
/// 65816: indicates that a read was signalled with VPB.
/// 65816: indicates that a read was signalled with VPB and VDA.
ReadVector,
/// 6502: never signalled.
/// 65816: indicates that a read was signalled, but neither VDA nor VPA were active.

@ -64,7 +64,7 @@ struct LazyFlags {
}
uint8_t get() const {
return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | decimal;
return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | Flag::Break | decimal;
}
LazyFlags() {

@ -45,7 +45,8 @@ class ProcessorBase: protected ProcessorStorage {
inline bool get_is_resetting() const;
/*!
Returns the current state of all lines not ordinarily pushed to the BusHandler.
Returns the current state of all lines not ordinarily pushed to the BusHandler,
as listed in the ExtendedBusOutput enum.
*/
inline int get_extended_bus_output();
@ -54,6 +55,12 @@ class ProcessorBase: protected ProcessorStorage {
*/
inline bool is_jammed() const;
/*!
FOR TESTING PURPOSES ONLY: forces the processor into a state where
the next thing it intends to do is fetch a new opcode.
*/
inline void restart_operation_fetch();
void set_value_of_register(Register r, uint16_t value);
uint16_t get_value_of_register(Register r) const;
};

@ -14,7 +14,10 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const {
switch (r) {
case Register::ProgramCounter: return registers_.pc;
case Register::LastOperationAddress: return last_operation_pc_;
case Register::StackPointer: return registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff);
case Register::StackPointer:
return
(registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff)) |
(registers_.emulation_flag ? 0x100 : 0x000);
case Register::Flags: return get_flags();
case Register::A: return registers_.a.full;
case Register::X: return registers_.x.full;

@ -9,7 +9,7 @@
template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler, uses_ready_line>::run_for(const Cycles cycles) {
#define perform_bus(address, value, operation) \
bus_address_ = address; \
bus_address_ = address & 0xff'ffff; \
bus_value_ = value; \
bus_operation_ = operation
@ -99,6 +99,14 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
perform_bus(registers_.pc | registers_.program_bank, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
case CycleFetchPreviousPCThrowaway:
perform_bus(((registers_.pc - 1) & 0xffff) | registers_.program_bank, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
case CycleFetchPreviousThrowaway:
perform_bus(bus_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
//
// Data fetches and stores.
//
@ -111,6 +119,13 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
read(data_address_, data_buffer_.next_input());
break;
case CycleStoreOrFetchDataThrowaway:
if(registers_.emulation_flag) {
perform_bus(data_address_, data_buffer_.preview_output(), MOS6502Esque::InternalOperationWrite);
break;
}
[[fallthrough]];
case CycleFetchDataThrowaway:
perform_bus(data_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead);
break;
@ -137,10 +152,6 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
write(data_address_, data_buffer_.next_output());
break;
case CycleStoreDataThrowaway:
perform_bus(data_address_, data_buffer_.preview_output(), MOS6502Esque::InternalOperationWrite);
break;
case CycleStoreIncrementData:
write(data_address_, data_buffer_.next_output());
increment_data_address();
@ -293,8 +304,12 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_address_ = instruction_buffer_.value + registers_.x.full + registers_.data_bank;
incorrect_data_address_ = ((data_address_ & 0x00ff) | (instruction_buffer_.value & 0xff00)) + registers_.data_bank;
// If the incorrect address isn't actually incorrect, skip its usage.
if(operation == OperationConstructAbsoluteXRead && data_address_ == incorrect_data_address_) {
// "Add 1 cycle for indexing across page boundaries, or write, or X=0"
// (i.e. don't add 1 cycle if x = 1 and this is a read, and a page boundary wasn't crossed)
if(
operation == OperationConstructAbsoluteXRead &&
data_address_ == incorrect_data_address_ &&
registers_.mx_flags[1]) {
++next_op_;
}
data_address_increment_mask_ = 0xff'ff'ff;
@ -305,8 +320,12 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_address_ = instruction_buffer_.value + registers_.y.full + registers_.data_bank;
incorrect_data_address_ = (data_address_ & 0xff) + (instruction_buffer_.value & 0xff00) + registers_.data_bank;
// If the incorrect address isn't actually incorrect, skip its usage.
if(operation == OperationConstructAbsoluteYRead && data_address_ == incorrect_data_address_) {
// "Add 1 cycle for indexing across page boundaries, or write, or X=0"
// (i.e. don't add 1 cycle if x = 1 and this is a read, and a page boundary wasn't crossed)
if(
operation == OperationConstructAbsoluteYRead &&
data_address_ == incorrect_data_address_ &&
registers_.mx_flags[1]) {
++next_op_;
}
data_address_increment_mask_ = 0xff'ff'ff;
@ -338,10 +357,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
continue;
case OperationConstructDirectIndexedIndirect:
data_address_ = registers_.data_bank + ((
data_address_ = (
((registers_.direct + registers_.x.full + instruction_buffer_.value) & registers_.e_masks[1]) +
(registers_.direct & registers_.e_masks[0])
) & 0xffff);
) & 0xffff;
data_address_increment_mask_ = 0x00'ff'ff;
if(!(registers_.direct&0xff)) {
@ -408,7 +427,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case OperationPrepareException:
data_buffer_.value = uint32_t((registers_.pc << 8) | get_flags());
if(registers_.emulation_flag) {
if(!exception_is_interrupt_) data_buffer_.value |= Flag::Break;
if(exception_is_interrupt_) data_buffer_.value &= ~uint32_t(Flag::Break);
data_buffer_.size = 3;
registers_.data_bank = 0;
++next_op_;
@ -556,11 +575,6 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case PHP:
data_buffer_.value = get_flags();
data_buffer_.size = 1;
if(registers_.emulation_flag) {
// On the 6502, the break flag is set during a PHP.
data_buffer_.value |= Flag::Break;
}
break;
case NOP: break;
@ -796,7 +810,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
assert(data_buffer_.size == 2 - m_flag());
registers_.flags.set_n(uint16_t(data_buffer_.value), registers_.m_shift);
registers_.flags.set_z(uint16_t(data_buffer_.value & registers_.a.full), registers_.m_shift);
registers_.flags.overflow = data_buffer_.value & Flag::Overflow;
registers_.flags.overflow = (data_buffer_.value >> registers_.m_shift) & Flag::Overflow;
break;
case BITimm:
@ -828,7 +842,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
data_buffer_.value = uint32_t(registers_.pc + int8_t(instruction_buffer_.value)); \
data_buffer_.size = 2; \
\
if((registers_.pc & 0xff00) == (instruction_buffer_.value & 0xff00)) { \
if( \
!registers_.emulation_flag || \
(registers_.pc & 0xff00) == (instruction_buffer_.value & 0xff00) \
) { \
++next_op_; \
} \
}
@ -902,18 +919,25 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
case SBC:
if(registers_.flags.decimal) {
// I've yet to manage to find a rational way to map this to an ADC,
// hence the yucky repetition of code here.
const uint16_t a = registers_.a.full & registers_.m_masks[1];
unsigned int result = 0;
unsigned int borrow = registers_.flags.carry ^ 1;
const uint16_t decimal_result = uint16_t(a - data_buffer_.value - borrow);
data_buffer_.value = ~data_buffer_.value & registers_.m_masks[1];
#define nibble(mask, adjustment, carry) \
result += (a & mask) - (data_buffer_.value & mask) - borrow; \
if(result > mask) result -= adjustment; \
borrow = (result > mask) ? carry : 0; \
result &= (carry - 1);
int result = registers_.flags.carry;
uint16_t partials = 0;
#define nibble(mask, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
result -= ((result - carry) >> 16) & adjustment; \
result &= (carry & ~(result >> 1)) | (carry - 1);
// i.e. add the next nibble to that in the accumulator, with carry, and
// store it to result. Keep a copy for the partials.
//
// If result is less than carry, subtract adjustment.
//
// Allow onward carry if the bit immediately above this nibble is 1, and
// the current partial result is positive.
nibble(0x000f, 0x0006, 0x00010);
nibble(0x00f0, 0x0060, 0x00100);
@ -922,9 +946,9 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
#undef nibble
registers_.flags.overflow = (( (decimal_result ^ a) & (~decimal_result ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.set_nz(uint16_t(result), registers_.m_shift);
registers_.flags.carry = ((borrow >> 16)&1)^1;
registers_.flags.carry = (result >> (8 + registers_.m_shift))&1;
LDA(result);
break;
@ -941,10 +965,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
uint16_t partials = 0;
result = registers_.flags.carry;
#define nibble(mask, limit, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry;
#define nibble(mask, limit, adjustment, carry) \
result += (a & mask) + (data_buffer_.value & mask); \
partials += result & mask; \
if(result >= limit) result = ((result + adjustment) & (carry - 1)) + carry;
nibble(0x000f, 0x000a, 0x0006, 0x00010);
nibble(0x00f0, 0x00a0, 0x0060, 0x00100);
@ -953,8 +977,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
#undef nibble
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
} else {
result = int(a + data_buffer_.value + registers_.flags.carry);
registers_.flags.overflow = (( (uint16_t(result) ^ registers_.a.full) & (uint16_t(result) ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40;
@ -1065,3 +1088,10 @@ int ProcessorBase::get_extended_bus_output() {
(registers_.mx_flags[1] ? ExtendedBusOutput::IndexSize : 0) |
(registers_.emulation_flag ? ExtendedBusOutput::Emulation : 0);
}
void ProcessorBase::restart_operation_fetch() {
// Find a OperationMoveToNextProgram, so that the main loop can make
// relevant decisions.
next_op_ = micro_ops_.data();
while(*next_op_ != OperationMoveToNextProgram) ++next_op_;
}

@ -195,8 +195,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
if(!is8bit) target(CycleFetchIncrementData); // Data low.
target(CycleFetchData); // Data [high].
if(!is8bit) target(CycleFetchDataThrowaway); // 16-bit: reread final byte of data.
else target(CycleStoreDataThrowaway); // 8-bit rewrite final byte of data.
target(CycleStoreOrFetchDataThrowaway); // Native mode: reread final byte of data.
// Emulated mode: rewrite final byte of data.
target(OperationPerform); // Perform operation within the data buffer.
@ -320,7 +320,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(OperationCopyPBRToData); // Copy PBR to the data register.
target(CyclePush); // PBR.
target(CycleAccessStack); // IO.
target(CycleFetchPreviousThrowaway); // IO.
target(CycleFetchPC); // New PBR.
@ -416,10 +416,10 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 10a. Direct; d.
// (That's zero page in 6502 terms)
static void direct(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // DO.
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -430,7 +430,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_modify_write(is8bit, target);
}
@ -440,9 +440,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectIndexedIndirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
@ -458,7 +458,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
@ -473,13 +473,17 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(OperationCopyDataToInstruction);
target(OperationConstructAbsoluteYRead);
if(type == AccessType::Read) {
target(OperationConstructAbsoluteYRead); // Calculate data address, potentially skipping the next fetch.
} else {
target(OperationConstructAbsoluteY); // Calculate data address.
}
target(CycleFetchIncorrectDataAddress); // IO.
read_write(type, is8bit, target);
@ -490,7 +494,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchIncrementData); // AAH.
@ -506,7 +510,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectLong);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchIncrementData); // AAH.
@ -522,9 +526,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectX);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -534,9 +538,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectX);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_modify_write(is8bit, target);
}
@ -546,9 +550,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirectY);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
read_write(type, is8bit, target);
}
@ -563,7 +567,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
static void immediate_rep_sep(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // IDL.
target(CycleFetchPCThrowaway); // "Add 1 cycle for REP and SEP"
target(CycleFetchPreviousPCThrowaway); // "Add 1 cycle for REP and SEP"
target(OperationPerform);
}
@ -594,34 +598,34 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 20. Relative; r.
static void relative(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset.
target(CycleFetchIncrementPC); // Offset.
target(OperationPerform); // The branch instructions will all skip one or three
// of the next cycles, depending on the effect of
// the jump. It'll also calculate the correct target
// address, placing it into the data buffer.
target(OperationPerform); // The branch instructions will all skip one or three
// of the next cycles, depending on the effect of
// the jump. It'll also calculate the correct target
// address, placing it into the data buffer.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationCopyDataToPC); // Install the address that was calculated above.
target(OperationCopyDataToPC); // Install the address that was calculated above.
}
// 21. Relative long; rl.
static void relative_long(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationPerform); // [BRL]
target(OperationPerform); // [BRL]
}
// 22a. Stack; s, abort/irq/nmi/res.
//
// Combined here with reset, which is the same sequence but with a different stack access.
static void stack_exception_impl(AccessType, bool, const std::function<void(MicroOp)> &target, MicroOp stack_op) {
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationPrepareException); // Populates the data buffer; if the exception is a
// reset then switches to the reset tail program.
@ -686,27 +690,27 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 22e. Stack; s, PEI.
static void stack_pei(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // DO.
target(CycleFetchIncrementPC); // DO.
target(OperationConstructDirect);
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
}
// 22f. Stack; s, PER.
static void stack_per(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // Offset low.
target(CycleFetchIncrementPC); // Offset high.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructPER);
target(CyclePush); // AAH.
target(CyclePush); // AAL.
target(CyclePush); // AAH.
target(CyclePush); // AAL.
}
// 22g. Stack; s, RTI.
@ -724,20 +728,20 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 22h. Stack; s, RTS.
static void stack_rts(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CyclePull); // PCL.
target(CyclePull); // PCH.
target(CycleAccessStack); // IO.
target(CyclePull); // PCL.
target(CyclePull); // PCH.
target(CycleFetchPreviousThrowaway); // IO.
target(OperationPerform); // [RTS]
target(OperationPerform); // [RTS]
}
// 22i. Stack; s, RTL.
static void stack_rtl(AccessType, bool, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // IO.
target(CycleFetchIncrementPC); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchPCThrowaway); // IO.
target(CyclePull); // New PCL.
target(CyclePull); // New PCH.
@ -771,8 +775,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 23. Stack Relative; d, s.
static void stack_relative(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructStackRelative);
read_write(type, is8bit, target);
@ -780,13 +784,13 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
// 24. Stack Relative Indirect Indexed (d, s), y.
static void stack_relative_indexed_indirect(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPCThrowaway); // IO.
target(CycleFetchIncrementPC); // SO.
target(CycleFetchPreviousPCThrowaway); // IO.
target(OperationConstructStackRelative);
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CycleFetchDataThrowaway); // IO.
target(CycleFetchIncrementData); // AAL.
target(CycleFetchData); // AAH.
target(CycleFetchDataThrowaway); // IO.
target(OperationConstructStackRelativeIndexedIndirect);
read_write(type, is8bit, target);

@ -13,6 +13,10 @@ enum MicroOp: uint8_t {
CycleFetchPC,
/// Fetches a byte from the program counter without incrementing it, and throws it away.
CycleFetchPCThrowaway,
/// Fetches a byte from (PC - 1), and throws it away; useful for IO cycles that immediately follow incremented PCs.
CycleFetchPreviousPCThrowaway,
/// Fetches from whichever address was used in the last bus cycle, and throws away the result.
CycleFetchPreviousThrowaway,
/// The same as CycleFetchIncrementPC but indicates valid program address rather than valid data address.
CycleFetchOpcode,
@ -37,8 +41,9 @@ enum MicroOp: uint8_t {
/// Stores a byte from the data buffer.
CycleStoreData,
/// Stores the most recent byte placed into the data buffer without removing it.
CycleStoreDataThrowaway,
/// Emulated mode: stores the most recent byte placed into the data buffer without removing it;
/// Native mode: performs CycleFetchDataThrowaway.
CycleStoreOrFetchDataThrowaway,
/// Stores a byte to the data address from the data buffer and increments the data address.
CycleStoreIncrementData,
/// Stores a byte to the data address from the data buffer and decrements the data address.
@ -271,7 +276,7 @@ struct ProcessorStorage {
// Registers.
RegisterPair16 a;
RegisterPair16 x, y;
RegisterPair16 s;
RegisterPair16 s;
uint16_t pc;
// Flags aplenty.