mirror of
https://github.com/TomHarte/CLK.git
synced 2025-03-25 06:30:38 +00:00
Merge pull request #1053 from TomHarte/65816Tests
Add 65816 test generator; correct disagreements with other emulations.
This commit is contained in:
commit
9888f079fa
OSBindings/Mac
Processors
6502Esque
65816
@ -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 */,
|
||||
|
302
OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm
Normal file
302
OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm
Normal file
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user