mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
82 Commits
2025-10-19
...
Turbo6502
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b04608d68 | ||
|
|
2bac276870 | ||
|
|
213f9850e7 | ||
|
|
378bffbf84 | ||
|
|
c291d5313d | ||
|
|
1f6f665639 | ||
|
|
e81233c586 | ||
|
|
b946029394 | ||
|
|
6095936354 | ||
|
|
fe79a1231d | ||
|
|
76a5872d17 | ||
|
|
0d72c75e15 | ||
|
|
2e0e89c494 | ||
|
|
7d6b7a5874 | ||
|
|
48f8ddf53a | ||
|
|
9aae07b737 | ||
|
|
d7abdc8017 | ||
|
|
cb81156835 | ||
|
|
1fd8d94e2e | ||
|
|
e4fe127444 | ||
|
|
aeabd5f113 | ||
|
|
58f7d4065c | ||
|
|
60f25a3ba4 | ||
|
|
d267571dc6 | ||
|
|
3c34aa6696 | ||
|
|
5dc00a2092 | ||
|
|
b20d489bf0 | ||
|
|
df39870587 | ||
|
|
f742eab4be | ||
|
|
e9c8c61dcf | ||
|
|
e5f09002e9 | ||
|
|
d42f005e17 | ||
|
|
24e060abee | ||
|
|
8b6d763442 | ||
|
|
e239745f63 | ||
|
|
cfef2b4e19 | ||
|
|
cf93c39881 | ||
|
|
5d223bce4c | ||
|
|
b454ebc1c9 | ||
|
|
7cf9910cae | ||
|
|
79ab1d8cb1 | ||
|
|
7cd20f5d12 | ||
|
|
5396d751e1 | ||
|
|
d23e715650 | ||
|
|
0791bce338 | ||
|
|
2bcb74072a | ||
|
|
c5f2f17f33 | ||
|
|
62a8bf4261 | ||
|
|
ebda18b44e | ||
|
|
a8f41b9017 | ||
|
|
410c19a7da | ||
|
|
a346e2e04b | ||
|
|
2d114b6677 | ||
|
|
02e74ca1f4 | ||
|
|
69122cdec4 | ||
|
|
d730168631 | ||
|
|
2f210ebe3b | ||
|
|
693b53baa2 | ||
|
|
77554879a5 | ||
|
|
45363922b5 | ||
|
|
0463c1ceda | ||
|
|
b35a55a658 | ||
|
|
4da68c9fa8 | ||
|
|
72f133f31b | ||
|
|
af4a8f6d9c | ||
|
|
b5899a2e42 | ||
|
|
4ee8f8564e | ||
|
|
ff08c03bc5 | ||
|
|
95dd430b0d | ||
|
|
20eb8b1442 | ||
|
|
2d6a0b3ed0 | ||
|
|
80f0ce78e0 | ||
|
|
fde0e2434e | ||
|
|
fe2da7fd95 | ||
|
|
25e783ff2f | ||
|
|
2eb94f1b66 | ||
|
|
2cdf6ac8f9 | ||
|
|
309c58a93d | ||
|
|
700bd0ddd4 | ||
|
|
24fcbea6f2 | ||
|
|
fddc9c8c48 | ||
|
|
294893b7da |
@@ -94,7 +94,13 @@ public:
|
||||
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||
|
||||
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
|
||||
// Registers below 12 are write-only; no registers are defined above position 17
|
||||
// (other than the UM6845R-specific test register as per above).
|
||||
//
|
||||
// Per the BBC Wiki, attempting to read such a register results in 0.
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0x00;
|
||||
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
|
||||
@@ -114,6 +120,7 @@ public:
|
||||
case 6: layout_.vertical.displayed = value; break;
|
||||
case 7: layout_.vertical.start_sync = value; break;
|
||||
case 8:
|
||||
printf("Interlace mode: %d", value & 3);
|
||||
switch(value & 3) {
|
||||
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
|
||||
case 0b01: layout_.interlace_mode_ = InterlaceMode::Sync; break;
|
||||
@@ -156,7 +163,7 @@ public:
|
||||
|
||||
0x7f, // Start horizontal retrace.
|
||||
0x1f, 0x7f, 0x7f,
|
||||
0xff, 0x1f, 0x7f, 0x1f,
|
||||
0xfc, 0x1f, 0x7f, 0x1f,
|
||||
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
|
||||
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Machines/Utility/Typer.hpp"
|
||||
|
||||
#include "Processors/6502/6502.hpp"
|
||||
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||
|
||||
#include "Components/6522/6522.hpp"
|
||||
#include "Components/6845/CRTC6845.hpp"
|
||||
@@ -1149,10 +1150,44 @@ private:
|
||||
|
||||
using namespace BBCMicro;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Handler {
|
||||
uint8_t memory_[65536];
|
||||
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> data) {
|
||||
if constexpr (!is_dataless(operation)) {
|
||||
if constexpr (is_read(operation)) {
|
||||
data = memory_[address];
|
||||
} else {
|
||||
memory_[address] = data;
|
||||
}
|
||||
}
|
||||
return Cycles(1);
|
||||
}
|
||||
};
|
||||
|
||||
struct Traits {
|
||||
static constexpr auto uses_ready_line = false;
|
||||
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
|
||||
using BusHandlerT = Handler;
|
||||
};
|
||||
|
||||
void test_it() {
|
||||
Handler handler;
|
||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, Traits> processor(handler);
|
||||
processor.run_for(Cycles(1000));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<Machine> Machine::BBCMicro(
|
||||
const Analyser::Static::Target *target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) {
|
||||
test_it();
|
||||
|
||||
using Target = Analyser::Static::Acorn::BBCMicroTarget;
|
||||
const Target *const acorn_target = dynamic_cast<const Target *>(target);
|
||||
if(acorn_target->has_1770dfs || acorn_target->has_adfs) {
|
||||
|
||||
@@ -641,6 +641,7 @@
|
||||
4B882F5B2C2F9C7700D84031 /* Shaker in Resources */ = {isa = PBXBuildFile; fileRef = 4B882F5A2C2F9C7700D84031 /* Shaker */; };
|
||||
4B882F5C2C32199400D84031 /* MachineForTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */; };
|
||||
4B882F5D2C3219A400D84031 /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
|
||||
4B884C7E2EA8640700073840 /* 6502Mk2Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B884C7D2EA8640700073840 /* 6502Mk2Tests.mm */; };
|
||||
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
4B8855A62E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
4B8855A72E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
@@ -1801,6 +1802,13 @@
|
||||
4B882F582C2F9C6900D84031 /* CPCShakerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CPCShakerTests.mm; sourceTree = "<group>"; };
|
||||
4B882F5A2C2F9C7700D84031 /* Shaker */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaker; sourceTree = "<group>"; };
|
||||
4B884C6F2EA28E0F00073840 /* CompileTimeCounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompileTimeCounter.hpp; sourceTree = "<group>"; };
|
||||
4B884C722EA47FCC00073840 /* 6502Mk2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Mk2.hpp; sourceTree = "<group>"; };
|
||||
4B884C762EA5D6C300073840 /* 6502.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502.hpp; sourceTree = "<group>"; };
|
||||
4B884C792EA6DBAA00073840 /* Decoder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
|
||||
4B884C7A2EA72EF300073840 /* Model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Model.hpp; sourceTree = "<group>"; };
|
||||
4B884C7B2EA7F4AE00073840 /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
4B884C7C2EA7F4F300073840 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
|
||||
4B884C7D2EA8640700073840 /* 6502Mk2Tests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 6502Mk2Tests.mm; sourceTree = "<group>"; };
|
||||
4B88559C2E8185BF00E251DD /* SizedInt.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SizedInt.hpp; sourceTree = "<group>"; };
|
||||
4B8855A22E84D51B00E251DD /* SAA5050.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SAA5050.hpp; sourceTree = "<group>"; };
|
||||
4B8855A32E84D51B00E251DD /* SAA5050.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SAA5050.cpp; sourceTree = "<group>"; };
|
||||
@@ -3859,6 +3867,27 @@
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B884C732EA47FCC00073840 /* 6502Mk2 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B884C722EA47FCC00073840 /* 6502Mk2.hpp */,
|
||||
4B884C792EA6DBAA00073840 /* Decoder.hpp */,
|
||||
4B884C7A2EA72EF300073840 /* Model.hpp */,
|
||||
4B884C7C2EA7F4F300073840 /* Perform.hpp */,
|
||||
4B884C7B2EA7F4AE00073840 /* Registers.hpp */,
|
||||
4B884C772EA5D6C300073840 /* Implementation */,
|
||||
);
|
||||
path = 6502Mk2;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B884C772EA5D6C300073840 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B884C762EA5D6C300073840 /* 6502.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8855A42E84D51B00E251DD /* SAA5050 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -4779,6 +4808,7 @@
|
||||
4B85322922778E4200F26553 /* Comparative68000.hpp */,
|
||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
|
||||
4BC62FF128A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m */,
|
||||
4B884C7D2EA8640700073840 /* 6502Mk2Tests.mm */,
|
||||
4BDA7F8229C4EA28007A10A5 /* 6809OperationMapperTests.mm */,
|
||||
423BDC492AB24699008E37B6 /* 8088Tests.mm */,
|
||||
4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */,
|
||||
@@ -4900,6 +4930,7 @@
|
||||
4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */,
|
||||
4B1414561B58879D00E04248 /* 6502 */,
|
||||
4B4DEC15252BFA9C004583AC /* 6502Esque */,
|
||||
4B884C732EA47FCC00073840 /* 6502Mk2 */,
|
||||
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
|
||||
42AD552D2A0C4D5000ACE410 /* 68000 */,
|
||||
4B77069E1EC9045B0053B588 /* Z80 */,
|
||||
@@ -6809,6 +6840,7 @@
|
||||
4B4DEC07252BFA56004583AC /* 65816Base.cpp in Sources */,
|
||||
4B7752A628217DF80073E2C5 /* Dave.cpp in Sources */,
|
||||
4B778F2323A5EDE40000D260 /* Tape.cpp in Sources */,
|
||||
4B884C7E2EA8640700073840 /* 6502Mk2Tests.mm in Sources */,
|
||||
4B7752A728217E060073E2C5 /* Blitter.cpp in Sources */,
|
||||
4B06AAD22C645F190034D014 /* I2C.cpp in Sources */,
|
||||
4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
|
||||
212
OSBindings/Mac/Clock SignalTests/6502Mk2Tests.mm
Normal file
212
OSBindings/Mac/Clock SignalTests/6502Mk2Tests.mm
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// 6502Mk2Tests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 21/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "6502Mk2.hpp"
|
||||
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
// MARK: - Test paths
|
||||
|
||||
// The tests themselves are not duplicated in this repository; provide their real paths here.
|
||||
constexpr char TestSuiteHome[] = "/Users/thomasharte/Downloads/65x02-main/";
|
||||
|
||||
// MARK: - BusHandler
|
||||
|
||||
namespace {
|
||||
|
||||
struct TestComplete {};
|
||||
|
||||
struct BusHandler {
|
||||
uint8_t memory[65536];
|
||||
int opcode_reads;
|
||||
|
||||
struct Access {
|
||||
bool read;
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
};
|
||||
std::vector<Access> accesses;
|
||||
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> data) {
|
||||
// Check for end of tests.
|
||||
opcode_reads += operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode;
|
||||
if(opcode_reads == 2) {
|
||||
throw TestComplete{};
|
||||
}
|
||||
|
||||
// Perform and record access.
|
||||
if constexpr (is_read(operation)) {
|
||||
data = memory[address];
|
||||
accesses.emplace_back(true, address, data);
|
||||
} else {
|
||||
memory[address] = data;
|
||||
accesses.emplace_back(false, address, data);
|
||||
}
|
||||
return Cycles(1);
|
||||
}
|
||||
};
|
||||
|
||||
struct Traits {
|
||||
static constexpr auto uses_ready_line = false;
|
||||
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
|
||||
using BusHandlerT = BusHandler;
|
||||
};
|
||||
|
||||
CPU::MOS6502Mk2::Registers registersFrom(NSDictionary *dictionary) {
|
||||
CPU::MOS6502Mk2::Registers result;
|
||||
result.a = [dictionary[@"a"] intValue];
|
||||
result.x = [dictionary[@"x"] intValue];
|
||||
result.y = [dictionary[@"y"] intValue];
|
||||
result.s = [dictionary[@"s"] intValue];
|
||||
result.pc.full = [dictionary[@"pc"] intValue];
|
||||
result.flags = CPU::MOS6502Mk2::Flags([dictionary[@"p"] intValue]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <CPU::MOS6502Mk2::Model model>
|
||||
void testExecution(NSDictionary *test, BusHandler &handler) {
|
||||
CPU::MOS6502Mk2::Processor<model, Traits> processor(handler);
|
||||
|
||||
NSDictionary *initial = test[@"initial"];
|
||||
const auto initial_registers = registersFrom(initial);
|
||||
processor.set_registers(initial_registers);
|
||||
for(NSArray *value in initial[@"ram"]) {
|
||||
handler.memory[[value[0] intValue]] = [value[1] intValue];
|
||||
}
|
||||
|
||||
processor.template set<CPU::MOS6502Mk2::Line::PowerOn>(false);
|
||||
handler.opcode_reads = 0;
|
||||
handler.accesses.clear();
|
||||
|
||||
const uint8_t opcode = handler.memory[initial_registers.pc.full];
|
||||
|
||||
try {
|
||||
processor.run_for(Cycles(11)); // To catch the entirety of a JAM as in the JSON.
|
||||
} catch (TestComplete) {}
|
||||
|
||||
NSDictionary *final = test[@"final"];
|
||||
const auto final_registers = registersFrom(final);
|
||||
XCTAssertEqual(final_registers.a, processor.registers().a);
|
||||
XCTAssertEqual(final_registers.x, processor.registers().x);
|
||||
XCTAssertEqual(final_registers.y, processor.registers().y);
|
||||
XCTAssertEqual(final_registers.s, processor.registers().s);
|
||||
XCTAssert(final_registers.pc == processor.registers().pc);
|
||||
XCTAssert(final_registers.flags <=> processor.registers().flags == std::strong_ordering::equal);
|
||||
|
||||
// 65c02 exceptions:
|
||||
//
|
||||
// * I suspect the NOPs are mistimed;
|
||||
// * I am confident that the extra accessed address following an immediate decimal arithmetic is incorrect; and
|
||||
// * I am certain that the extra address in JMP (abs,X) is wrong, being a regression.
|
||||
std::bitset<16> ignore_addresses;
|
||||
if(is_65c02(model)) {
|
||||
const auto instruction = CPU::MOS6502Mk2::Decoder<model>::decode(opcode);
|
||||
|
||||
if(
|
||||
instruction.operation == CPU::MOS6502Mk2::Operation::NOP ||
|
||||
instruction.operation == CPU::MOS6502Mk2::Operation::FastNOP
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
ignore_addresses[2] =
|
||||
(
|
||||
instruction.operation == CPU::MOS6502Mk2::Operation::ADC ||
|
||||
instruction.operation == CPU::MOS6502Mk2::Operation::SBC
|
||||
) &&
|
||||
instruction.mode == CPU::MOS6502Mk2::AddressingMode::Immediate &&
|
||||
initial_registers.flags.decimal;
|
||||
ignore_addresses[3] = instruction.mode == CPU::MOS6502Mk2::AddressingMode::JMPAbsoluteIndexedIndirect;
|
||||
}
|
||||
|
||||
auto found_cycle = handler.accesses.begin();
|
||||
for(NSArray *cycle in test[@"cycles"]) {
|
||||
XCTAssertNotEqual(found_cycle, handler.accesses.end());
|
||||
|
||||
if(!ignore_addresses[std::distance(handler.accesses.begin(), found_cycle)]) {
|
||||
XCTAssertEqual(found_cycle->address, [cycle[0] intValue]);
|
||||
XCTAssertEqual(found_cycle->value, [cycle[1] intValue]);
|
||||
}
|
||||
|
||||
NSString *type = cycle[2];
|
||||
XCTAssert([type isEqualToString:@"read"] || [type isEqualToString:@"write"]);
|
||||
XCTAssertEqual([type isEqualToString:@"read"], found_cycle->read);
|
||||
|
||||
++found_cycle;
|
||||
}
|
||||
XCTAssertEqual(found_cycle, handler.accesses.end());
|
||||
}
|
||||
|
||||
void testExecution(CPU::MOS6502Mk2::Model model, NSDictionary *test, BusHandler &handler) {
|
||||
switch(model) {
|
||||
default: __builtin_unreachable();
|
||||
case CPU::MOS6502Mk2::Model::NES6502: testExecution<CPU::MOS6502Mk2::Model::NES6502>(test, handler); break;
|
||||
case CPU::MOS6502Mk2::Model::M6502: testExecution<CPU::MOS6502Mk2::Model::M6502>(test, handler); break;
|
||||
case CPU::MOS6502Mk2::Model::Synertek65C02: testExecution<CPU::MOS6502Mk2::Model::Synertek65C02>(test, handler); break;
|
||||
case CPU::MOS6502Mk2::Model::Rockwell65C02: testExecution<CPU::MOS6502Mk2::Model::Rockwell65C02>(test, handler); break;
|
||||
// case CPU::MOS6502Mk2::Model::WDC65C02: testExecution<CPU::MOS6502Mk2::Model::WDC65C02>(test, handler); break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - XCTestCase
|
||||
|
||||
@interface m6502Mk2Tests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation m6502Mk2Tests {
|
||||
BusHandler handler;
|
||||
}
|
||||
|
||||
- (void)testFile:(NSString *)file model:(CPU::MOS6502Mk2::Model)model {
|
||||
NSLog(@"Starting %@", file);
|
||||
NSArray *tests =
|
||||
[NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:file] options:0 error:nil];
|
||||
for(NSDictionary *test in tests) {
|
||||
testExecution(model, test, handler);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testPath:(NSString *)path model:(CPU::MOS6502Mk2::Model)model {
|
||||
NSLog(@"Into %@", path);
|
||||
NSArray<NSString *> *const files =
|
||||
[[[NSFileManager defaultManager]
|
||||
contentsOfDirectoryAtPath:path
|
||||
error:nil
|
||||
] sortedArrayUsingSelector:@selector(compare:)];
|
||||
for(NSString *file in files) {
|
||||
[self testFile:[path stringByAppendingPathComponent:file] model:model];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testAll {
|
||||
NSString *const path = [NSString stringWithUTF8String:TestSuiteHome];
|
||||
|
||||
[self
|
||||
testPath:[path stringByAppendingPathComponent:@"nes6502/v1"]
|
||||
model:CPU::MOS6502Mk2::Model::NES6502];
|
||||
[self
|
||||
testPath:[path stringByAppendingPathComponent:@"6502/v1"]
|
||||
model:CPU::MOS6502Mk2::Model::M6502];
|
||||
[self
|
||||
testPath:[path stringByAppendingPathComponent:@"synertek65c02/v1"]
|
||||
model:CPU::MOS6502Mk2::Model::Synertek65C02];
|
||||
[self
|
||||
testPath:[path stringByAppendingPathComponent:@"rockwell65c02/v1"]
|
||||
model:CPU::MOS6502Mk2::Model::Rockwell65C02];
|
||||
[self
|
||||
testPath:[path stringByAppendingPathComponent:@"wdc65c02/v1"]
|
||||
model:CPU::MOS6502Mk2::Model::WDC65C02];
|
||||
}
|
||||
|
||||
@end
|
||||
237
Processors/6502Mk2/6502Mk2.hpp
Normal file
237
Processors/6502Mk2/6502Mk2.hpp
Normal file
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// 6502Mk2.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Decoder.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Perform.hpp"
|
||||
#include "Registers.hpp"
|
||||
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
// MARK: - Control bus.
|
||||
|
||||
enum class BusOperation {
|
||||
/// 6502: a read was signalled.
|
||||
/// 65816: a read was signalled with VDA.
|
||||
Read,
|
||||
/// 6502: a read was signalled with SYNC.
|
||||
/// 65816: a read was signalled with VDA and VPA.
|
||||
ReadOpcode,
|
||||
/// 6502: never signalled.
|
||||
/// 65816: a read was signalled with VPA.
|
||||
ReadProgram,
|
||||
/// 6502: never signalled.
|
||||
/// 65816: a read was signalled with VPB and VDA.
|
||||
ReadVector,
|
||||
/// 6502: never signalled.
|
||||
/// 65816: a read was signalled, but neither VDA nor VPA were active.
|
||||
InternalOperationRead,
|
||||
|
||||
/// All processors: indicates that the processor is paused due to the RDY input.
|
||||
/// 65C02 and 65816: indicates a WAI is ongoing.
|
||||
Ready,
|
||||
// TODO: should ReadyRead be distinguished from ReadyWrite?
|
||||
|
||||
/// 65C02 and 65816: indicates a STP condition.
|
||||
None,
|
||||
|
||||
/// 6502: a write was signalled.
|
||||
/// 65816: a write was signalled with VDA.
|
||||
Write,
|
||||
/// 6502: never signalled.
|
||||
/// 65816: a write was signalled, but neither VDA nor VPA were active.
|
||||
InternalOperationWrite,
|
||||
};
|
||||
|
||||
constexpr bool is_read(const BusOperation op) { return op <= BusOperation::InternalOperationRead; }
|
||||
constexpr bool is_write(const BusOperation op) { return op >= BusOperation::Write; }
|
||||
constexpr bool is_access(const BusOperation op) { return op <= BusOperation::ReadVector || op == BusOperation::Write; }
|
||||
constexpr bool is_dataless(const BusOperation op) { return !is_read(op) && !is_write(op); }
|
||||
|
||||
enum class Line {
|
||||
Reset,
|
||||
IRQ,
|
||||
PowerOn,
|
||||
Overflow,
|
||||
NMI,
|
||||
};
|
||||
|
||||
// MARK: - Address bus.
|
||||
|
||||
namespace Address {
|
||||
|
||||
struct Literal {
|
||||
constexpr Literal(const uint16_t address) noexcept : address_(address) {}
|
||||
operator uint16_t() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t address_;
|
||||
};
|
||||
|
||||
template <uint8_t Page>
|
||||
struct FixedPage {
|
||||
FixedPage(const uint8_t address) noexcept : address_(address) {}
|
||||
operator uint16_t() const {
|
||||
return (Page << 8) | address_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t address_;
|
||||
};
|
||||
|
||||
using ZeroPage = FixedPage<0x00>;
|
||||
using Stack = FixedPage<0x01>;
|
||||
using Vector = FixedPage<0xff>;
|
||||
|
||||
} // namespace Address
|
||||
|
||||
// MARK: - Data bus.
|
||||
|
||||
namespace Data {
|
||||
|
||||
/// A value that can be read from or written to, without effect.
|
||||
struct NoValue {
|
||||
operator uint8_t() const { return 0xff; }
|
||||
NoValue() = default;
|
||||
constexpr NoValue(uint8_t) noexcept {}
|
||||
};
|
||||
|
||||
template <BusOperation, typename Enable = void> struct Value;
|
||||
template <BusOperation operation> struct Value<operation, std::enable_if_t<is_read(operation)>> {
|
||||
using type = uint8_t &;
|
||||
};
|
||||
template <BusOperation operation> struct Value<operation, std::enable_if_t<is_write(operation)>> {
|
||||
using type = const uint8_t;
|
||||
};
|
||||
template <BusOperation operation> struct Value<operation, std::enable_if_t<is_dataless(operation)>> {
|
||||
using type = const NoValue;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
template <BusOperation operation> using data_t = typename Data::Value<operation>::type;
|
||||
|
||||
// MARK: - Storage.
|
||||
|
||||
/*!
|
||||
An opcode that is guaranteed to cause a 6502 to jam.
|
||||
*/
|
||||
constexpr uint8_t JamOpcode = 0xf2;
|
||||
|
||||
template <Model model, typename Traits, typename Enable = void> class Storage;
|
||||
template <Model model, typename Traits> class Storage<model, Traits, std::enable_if_t<is_8bit(model)>> {
|
||||
public:
|
||||
Storage(Traits::BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
|
||||
const Registers ®isters() const { return registers_; }
|
||||
void set_registers(const Registers ®isters) {
|
||||
registers_ = registers;
|
||||
}
|
||||
|
||||
template <Line line> bool get() const;
|
||||
template <Line line> inline void set(const bool value) {
|
||||
const auto set_interrupt_request = [&](const Inputs::InterruptRequest request) {
|
||||
inputs_.interrupt_requests =
|
||||
(inputs_.interrupt_requests & ~request) |
|
||||
(value ? request : 0);
|
||||
};
|
||||
|
||||
switch(line) {
|
||||
case Line::Reset: set_interrupt_request(Inputs::InterruptRequest::Reset); break;
|
||||
case Line::PowerOn: set_interrupt_request(Inputs::InterruptRequest::PowerOn); break;
|
||||
case Line::IRQ: set_interrupt_request(Inputs::InterruptRequest::IRQ); break;
|
||||
|
||||
// These are both edge triggered, I think?
|
||||
// case Line::Overflow: set_interrupt_request(Inputs::InterruptRequest::Reset); break;
|
||||
// case Line::NMI: set_interrupt_request(Inputs::InterruptRequest::NMI); break;
|
||||
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get whether the 6502 would reset at the next opportunity.
|
||||
bool is_resetting() const;
|
||||
|
||||
/*!
|
||||
Queries whether the 6502 is now 'jammed'; i.e. has entered an invalid state
|
||||
such that it will not of itself perform any more meaningful processing.
|
||||
|
||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||
*/
|
||||
bool is_jammed() const {
|
||||
return resume_point_ == ResumePoint::Jam;
|
||||
}
|
||||
|
||||
protected:
|
||||
Traits::BusHandlerT &bus_handler_;
|
||||
uint8_t opcode_, operand_;
|
||||
Instruction decoded_;
|
||||
|
||||
Registers registers_;
|
||||
uint16_t operation_pc_;
|
||||
RegisterPair16 address_;
|
||||
bool did_adjust_top_;
|
||||
|
||||
Cycles cycles_;
|
||||
|
||||
enum ResumePoint {
|
||||
FetchDecode,
|
||||
Jam,
|
||||
Max,
|
||||
};
|
||||
int resume_point_ = ResumePoint::FetchDecode;
|
||||
|
||||
struct Inputs {
|
||||
bool ready = false;
|
||||
|
||||
enum InterruptRequest: uint8_t {
|
||||
Reset = 0x80,
|
||||
IRQ = Flag::Interrupt,
|
||||
NMI = 0x20,
|
||||
|
||||
PowerOn = 0x10,
|
||||
};
|
||||
uint8_t interrupt_requests = InterruptRequest::PowerOn;
|
||||
} inputs_;
|
||||
};
|
||||
|
||||
// MARK: - Base.
|
||||
|
||||
enum class PausePrecision {
|
||||
BetweenInstructions,
|
||||
AnyCycle,
|
||||
};
|
||||
|
||||
// TODO: concept to explain and verify Traits.
|
||||
template <Model model, typename Traits>
|
||||
struct Processor: public Storage<model, Traits> {
|
||||
inline void run_for(Cycles);
|
||||
|
||||
/*!
|
||||
**FOR TEST CASES 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();
|
||||
|
||||
private:
|
||||
using Storage = Storage<model, Traits>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Implementations.
|
||||
#include "Implementation/6502.hpp"
|
||||
626
Processors/6502Mk2/Decoder.hpp
Normal file
626
Processors/6502Mk2/Decoder.hpp
Normal file
@@ -0,0 +1,626 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
enum class Operation {
|
||||
BRK,
|
||||
NOP, FastNOP,
|
||||
|
||||
ORA, AND, EOR,
|
||||
INS, ADC, SBC,
|
||||
CMP, CPX, CPY,
|
||||
BIT, BITNoNV,
|
||||
LDA, LDX, LDY, LAX,
|
||||
STA, STX, STY, STZ, SAX, SHA, SHX, SHY, SHS,
|
||||
ASL, ASO, ROL, RLA, LSR, LSE, ASR, ROR, RRA,
|
||||
CLC, CLI, CLV, CLD, SEC, SEI, SED,
|
||||
RMB, SMB, TRB, TSB,
|
||||
INC, DEC, INX, DEX, INY, DEY, INA, DEA, DCP,
|
||||
BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ, BRA,
|
||||
BBRBBS,
|
||||
TXA, TYA, TXS, TAY, TAX, TSX,
|
||||
ARR, SBX, LXA, ANE, ANC, LAS,
|
||||
JSR, RTI, RTS,
|
||||
|
||||
PHP, PLP, JMP,
|
||||
JAM, STP, WAI,
|
||||
};
|
||||
|
||||
enum class AddressingMode {
|
||||
Implied,
|
||||
Immediate,
|
||||
Accumulator,
|
||||
Relative,
|
||||
|
||||
Push,
|
||||
Pull,
|
||||
|
||||
Absolute,
|
||||
AbsoluteIndexed,
|
||||
FastAbsoluteIndexedModify,
|
||||
Zero,
|
||||
ZeroIndexed,
|
||||
ZeroIndirect,
|
||||
IndexedIndirect,
|
||||
IndirectIndexed,
|
||||
|
||||
// Irregular flow control.
|
||||
BRK, JSR, RTI, RTS,
|
||||
BBRBBS,
|
||||
JMPAbsolute,
|
||||
JMPAbsoluteIndirect,
|
||||
JMPAbsoluteIndexedIndirect,
|
||||
|
||||
// Irregular unintended, undocumented and unreliable.
|
||||
SHxIndirectIndexed,
|
||||
SHxAbsoluteXY,
|
||||
|
||||
// Terminal or semi-terminal.
|
||||
STP,
|
||||
WAI,
|
||||
JAM,
|
||||
|
||||
Max,
|
||||
};
|
||||
|
||||
enum class Index {
|
||||
X, Y
|
||||
};
|
||||
|
||||
constexpr Index index_of(const Operation operation) {
|
||||
switch(operation) {
|
||||
default: return Index::X;
|
||||
|
||||
case Operation::STX: case Operation::LDX:
|
||||
case Operation::SAX: case Operation::LAX:
|
||||
return Index::Y;
|
||||
}
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
Read, Modify, Write
|
||||
};
|
||||
|
||||
constexpr Type type_of(const Operation operation) {
|
||||
switch(operation) {
|
||||
// All of these don't really fit the type orthodoxy.
|
||||
case Operation::BRK: case Operation::JAM:
|
||||
case Operation::SHA: case Operation::SHX: case Operation::SHY:
|
||||
case Operation::SHS: case Operation::CLC: case Operation::CLI:
|
||||
case Operation::CLV: case Operation::CLD: case Operation::SEC:
|
||||
case Operation::SEI: case Operation::SED:
|
||||
case Operation::INX: case Operation::DEX: case Operation::INY:
|
||||
case Operation::DEY: case Operation::INA: case Operation::DEA:
|
||||
case Operation::BPL: case Operation::BMI: case Operation::BVC:
|
||||
case Operation::BVS: case Operation::BCC: case Operation::BCS:
|
||||
case Operation::BNE: case Operation::BEQ: case Operation::BRA:
|
||||
case Operation::TXA: case Operation::TYA: case Operation::TXS:
|
||||
case Operation::TAY: case Operation::TAX: case Operation::TSX:
|
||||
case Operation::JSR: case Operation::RTI: case Operation::RTS:
|
||||
case Operation::PHP: case Operation::PLP: case Operation::JMP:
|
||||
case Operation::BBRBBS:
|
||||
case Operation::STP: case Operation::WAI:
|
||||
case Operation::FastNOP:
|
||||
return Type::Modify;
|
||||
|
||||
case Operation::ORA: case Operation::AND: case Operation::EOR:
|
||||
case Operation::ADC: case Operation::SBC:
|
||||
case Operation::CMP: case Operation::CPX: case Operation::CPY:
|
||||
case Operation::BIT: case Operation::BITNoNV:
|
||||
case Operation::LDA: case Operation::LDX:
|
||||
case Operation::LDY: case Operation::LAX:
|
||||
case Operation::ARR: case Operation::SBX: case Operation::LXA:
|
||||
case Operation::ANE: case Operation::ANC: case Operation::LAS:
|
||||
case Operation::NOP:
|
||||
return Type::Read;
|
||||
|
||||
case Operation::STA: case Operation::STX: case Operation::STY:
|
||||
case Operation::STZ: case Operation::SAX:
|
||||
return Type::Write;
|
||||
|
||||
case Operation::INS:
|
||||
case Operation::ASL: case Operation::ASO: case Operation::ROL:
|
||||
case Operation::RLA: case Operation::LSR: case Operation::LSE:
|
||||
case Operation::ASR: case Operation::ROR: case Operation::RRA:
|
||||
case Operation::RMB: case Operation::SMB:
|
||||
case Operation::TRB: case Operation::TSB:
|
||||
case Operation::INC: case Operation::DEC: case Operation::DCP:
|
||||
return Type::Modify;
|
||||
}
|
||||
return Type::Read;
|
||||
}
|
||||
|
||||
struct Instruction {
|
||||
consteval Instruction(const AddressingMode mode, const Operation operation) noexcept :
|
||||
operation(operation), mode(mode), index(index_of(operation)), type(type_of(operation)) {}
|
||||
consteval Instruction(const AddressingMode mode, const Index index, const Operation operation) noexcept :
|
||||
operation(operation), mode(mode), index(index), type(type_of(operation)) {}
|
||||
Instruction() = default;
|
||||
|
||||
Operation operation;
|
||||
AddressingMode mode;
|
||||
Index index;
|
||||
Type type;
|
||||
};
|
||||
|
||||
template <Model model, typename Enable = void> struct Decoder;
|
||||
|
||||
template <Model model>
|
||||
struct Decoder<model, std::enable_if_t<is_6502(model)>> {
|
||||
static constexpr Instruction decode(const uint8_t opcode) {
|
||||
using enum AddressingMode;
|
||||
switch(opcode) {
|
||||
case 0x00: return {BRK, Operation::BRK};
|
||||
case 0x20: return {JSR, Operation::JSR};
|
||||
case 0x40: return {RTI, Operation::RTI};
|
||||
case 0x60: return {RTS, Operation::RTS};
|
||||
case 0x80: return {Immediate, Operation::NOP};
|
||||
case 0xa0: return {Immediate, Operation::LDY};
|
||||
case 0xc0: return {Immediate, Operation::CPY};
|
||||
case 0xe0: return {Immediate, Operation::CPX};
|
||||
|
||||
case 0x01: return {IndexedIndirect, Operation::ORA};
|
||||
case 0x21: return {IndexedIndirect, Operation::AND};
|
||||
case 0x41: return {IndexedIndirect, Operation::EOR};
|
||||
case 0x61: return {IndexedIndirect, Operation::ADC};
|
||||
case 0x81: return {IndexedIndirect, Operation::STA};
|
||||
case 0xa1: return {IndexedIndirect, Operation::LDA};
|
||||
case 0xc1: return {IndexedIndirect, Operation::CMP};
|
||||
case 0xe1: return {IndexedIndirect, Operation::SBC};
|
||||
|
||||
case 0x02: return {JAM, Operation::JAM};
|
||||
case 0x22: return {JAM, Operation::JAM};
|
||||
case 0x42: return {JAM, Operation::JAM};
|
||||
case 0x62: return {JAM, Operation::JAM};
|
||||
case 0x82: return {Immediate, Operation::NOP};
|
||||
case 0xa2: return {Immediate, Operation::LDX};
|
||||
case 0xc2: return {Immediate, Operation::NOP};
|
||||
case 0xe2: return {Immediate, Operation::NOP};
|
||||
|
||||
case 0x03: return {IndexedIndirect, Operation::ASO};
|
||||
case 0x23: return {IndexedIndirect, Operation::RLA};
|
||||
case 0x43: return {IndexedIndirect, Operation::LSE};
|
||||
case 0x63: return {IndexedIndirect, Operation::RRA};
|
||||
case 0x83: return {IndexedIndirect, Operation::SAX};
|
||||
case 0xa3: return {IndexedIndirect, Operation::LAX};
|
||||
case 0xc3: return {IndexedIndirect, Operation::DCP};
|
||||
case 0xe3: return {IndexedIndirect, Operation::INS};
|
||||
|
||||
case 0x04: return {Zero, Operation::NOP};
|
||||
case 0x24: return {Zero, Operation::BIT};
|
||||
case 0x44: return {Zero, Operation::NOP};
|
||||
case 0x64: return {Zero, Operation::NOP};
|
||||
case 0x84: return {Zero, Operation::STY};
|
||||
case 0xa4: return {Zero, Operation::LDY};
|
||||
case 0xc4: return {Zero, Operation::CPY};
|
||||
case 0xe4: return {Zero, Operation::CPX};
|
||||
|
||||
case 0x05: return {Zero, Operation::ORA};
|
||||
case 0x25: return {Zero, Operation::AND};
|
||||
case 0x45: return {Zero, Operation::EOR};
|
||||
case 0x65: return {Zero, Operation::ADC};
|
||||
case 0x85: return {Zero, Operation::STA};
|
||||
case 0xa5: return {Zero, Operation::LDA};
|
||||
case 0xc5: return {Zero, Operation::CMP};
|
||||
case 0xe5: return {Zero, Operation::SBC};
|
||||
|
||||
case 0x06: return {Zero, Operation::ASL};
|
||||
case 0x26: return {Zero, Operation::ROL};
|
||||
case 0x46: return {Zero, Operation::LSR};
|
||||
case 0x66: return {Zero, Operation::ROR};
|
||||
case 0x86: return {Zero, Operation::STX};
|
||||
case 0xa6: return {Zero, Operation::LDX};
|
||||
case 0xc6: return {Zero, Operation::DEC};
|
||||
case 0xe6: return {Zero, Operation::INC};
|
||||
|
||||
case 0x07: return {Zero, Operation::ASO};
|
||||
case 0x27: return {Zero, Operation::RLA};
|
||||
case 0x47: return {Zero, Operation::LSE};
|
||||
case 0x67: return {Zero, Operation::RRA};
|
||||
case 0x87: return {Zero, Operation::SAX};
|
||||
case 0xa7: return {Zero, Operation::LAX};
|
||||
case 0xc7: return {Zero, Operation::DCP};
|
||||
case 0xe7: return {Zero, Operation::INS};
|
||||
|
||||
case 0x08: return {Push, Operation::PHP};
|
||||
case 0x28: return {Pull, Operation::PLP};
|
||||
case 0x48: return {Push, Operation::STA};
|
||||
case 0x68: return {Pull, Operation::LDA};
|
||||
case 0x88: return {Implied, Operation::DEY};
|
||||
case 0xa8: return {Implied, Operation::TAY};
|
||||
case 0xc8: return {Implied, Operation::INY};
|
||||
case 0xe8: return {Implied, Operation::INX};
|
||||
|
||||
case 0x09: return {Immediate, Operation::ORA};
|
||||
case 0x29: return {Immediate, Operation::AND};
|
||||
case 0x49: return {Immediate, Operation::EOR};
|
||||
case 0x69: return {Immediate, Operation::ADC};
|
||||
case 0x89: return {Immediate, Operation::NOP};
|
||||
case 0xa9: return {Immediate, Operation::LDA};
|
||||
case 0xc9: return {Immediate, Operation::CMP};
|
||||
case 0xe9: return {Immediate, Operation::SBC};
|
||||
|
||||
case 0x0a: return {Accumulator, Operation::ASL};
|
||||
case 0x2a: return {Accumulator, Operation::ROL};
|
||||
case 0x4a: return {Accumulator, Operation::LSR};
|
||||
case 0x6a: return {Accumulator, Operation::ROR};
|
||||
case 0x8a: return {Implied, Operation::TXA};
|
||||
case 0xaa: return {Implied, Operation::TAX};
|
||||
case 0xca: return {Implied, Operation::DEX};
|
||||
case 0xea: return {Implied, Operation::NOP};
|
||||
|
||||
case 0x0b: return {Immediate, Operation::ANC};
|
||||
case 0x2b: return {Immediate, Operation::ANC};
|
||||
case 0x4b: return {Immediate, Operation::ASR};
|
||||
case 0x6b: return {Immediate, Operation::ARR};
|
||||
case 0x8b: return {Immediate, Operation::ANE};
|
||||
case 0xab: return {Immediate, Operation::LXA};
|
||||
case 0xcb: return {Immediate, Operation::SBX};
|
||||
case 0xeb: return {Immediate, Operation::SBC};
|
||||
|
||||
case 0x0c: return {Absolute, Operation::NOP};
|
||||
case 0x2c: return {Absolute, Operation::BIT};
|
||||
case 0x4c: return {JMPAbsolute, Operation::JMP};
|
||||
case 0x6c: return {JMPAbsoluteIndirect, Operation::JMP};
|
||||
case 0x8c: return {Absolute, Operation::STY};
|
||||
case 0xac: return {Absolute, Operation::LDY};
|
||||
case 0xcc: return {Absolute, Operation::CPY};
|
||||
case 0xec: return {Absolute, Operation::CPX};
|
||||
|
||||
case 0x0d: return {Absolute, Operation::ORA};
|
||||
case 0x2d: return {Absolute, Operation::AND};
|
||||
case 0x4d: return {Absolute, Operation::EOR};
|
||||
case 0x6d: return {Absolute, Operation::ADC};
|
||||
case 0x8d: return {Absolute, Operation::STA};
|
||||
case 0xad: return {Absolute, Operation::LDA};
|
||||
case 0xcd: return {Absolute, Operation::CMP};
|
||||
case 0xed: return {Absolute, Operation::SBC};
|
||||
|
||||
case 0x0e: return {Absolute, Operation::ASL};
|
||||
case 0x2e: return {Absolute, Operation::ROL};
|
||||
case 0x4e: return {Absolute, Operation::LSR};
|
||||
case 0x6e: return {Absolute, Operation::ROR};
|
||||
case 0x8e: return {Absolute, Operation::STX};
|
||||
case 0xae: return {Absolute, Operation::LDX};
|
||||
case 0xce: return {Absolute, Operation::DEC};
|
||||
case 0xee: return {Absolute, Operation::INC};
|
||||
|
||||
case 0x0f: return {Absolute, Operation::ASO};
|
||||
case 0x2f: return {Absolute, Operation::RLA};
|
||||
case 0x4f: return {Absolute, Operation::LSE};
|
||||
case 0x6f: return {Absolute, Operation::RRA};
|
||||
case 0x8f: return {Absolute, Operation::SAX};
|
||||
case 0xaf: return {Absolute, Operation::LAX};
|
||||
case 0xcf: return {Absolute, Operation::DCP};
|
||||
case 0xef: return {Absolute, Operation::INS};
|
||||
|
||||
case 0x10: return {Relative, Operation::BPL};
|
||||
case 0x30: return {Relative, Operation::BMI};
|
||||
case 0x50: return {Relative, Operation::BVC};
|
||||
case 0x70: return {Relative, Operation::BVS};
|
||||
case 0x90: return {Relative, Operation::BCC};
|
||||
case 0xb0: return {Relative, Operation::BCS};
|
||||
case 0xd0: return {Relative, Operation::BNE};
|
||||
case 0xf0: return {Relative, Operation::BEQ};
|
||||
|
||||
case 0x11: return {IndirectIndexed, Operation::ORA};
|
||||
case 0x31: return {IndirectIndexed, Operation::AND};
|
||||
case 0x51: return {IndirectIndexed, Operation::EOR};
|
||||
case 0x71: return {IndirectIndexed, Operation::ADC};
|
||||
case 0x91: return {IndirectIndexed, Operation::STA};
|
||||
case 0xb1: return {IndirectIndexed, Operation::LDA};
|
||||
case 0xd1: return {IndirectIndexed, Operation::CMP};
|
||||
case 0xf1: return {IndirectIndexed, Operation::SBC};
|
||||
|
||||
case 0x12: return {JAM, Operation::JAM};
|
||||
case 0x32: return {JAM, Operation::JAM};
|
||||
case 0x52: return {JAM, Operation::JAM};
|
||||
case 0x72: return {JAM, Operation::JAM};
|
||||
case 0x92: return {JAM, Operation::JAM};
|
||||
case 0xb2: return {JAM, Operation::JAM};
|
||||
case 0xd2: return {JAM, Operation::JAM};
|
||||
case 0xf2: return {JAM, Operation::JAM};
|
||||
|
||||
case 0x13: return {IndirectIndexed, Operation::ASO};
|
||||
case 0x33: return {IndirectIndexed, Operation::RLA};
|
||||
case 0x53: return {IndirectIndexed, Operation::LSE};
|
||||
case 0x73: return {IndirectIndexed, Operation::RRA};
|
||||
case 0x93: return {SHxIndirectIndexed, Operation::SHA};
|
||||
case 0xb3: return {IndirectIndexed, Operation::LAX};
|
||||
case 0xd3: return {IndirectIndexed, Operation::DCP};
|
||||
case 0xf3: return {IndirectIndexed, Operation::INS};
|
||||
|
||||
case 0x14: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x34: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x54: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x74: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x94: return {ZeroIndexed, Operation::STY};
|
||||
case 0xb4: return {ZeroIndexed, Operation::LDY};
|
||||
case 0xd4: return {ZeroIndexed, Operation::NOP};
|
||||
case 0xf4: return {ZeroIndexed, Operation::NOP};
|
||||
|
||||
case 0x15: return {ZeroIndexed, Operation::ORA};
|
||||
case 0x35: return {ZeroIndexed, Operation::AND};
|
||||
case 0x55: return {ZeroIndexed, Operation::EOR};
|
||||
case 0x75: return {ZeroIndexed, Operation::ADC};
|
||||
case 0x95: return {ZeroIndexed, Operation::STA};
|
||||
case 0xb5: return {ZeroIndexed, Operation::LDA};
|
||||
case 0xd5: return {ZeroIndexed, Operation::CMP};
|
||||
case 0xf5: return {ZeroIndexed, Operation::SBC};
|
||||
|
||||
case 0x16: return {ZeroIndexed, Operation::ASL};
|
||||
case 0x36: return {ZeroIndexed, Operation::ROL};
|
||||
case 0x56: return {ZeroIndexed, Operation::LSR};
|
||||
case 0x76: return {ZeroIndexed, Operation::ROR};
|
||||
case 0x96: return {ZeroIndexed, Operation::STX};
|
||||
case 0xb6: return {ZeroIndexed, Operation::LDX};
|
||||
case 0xd6: return {ZeroIndexed, Operation::DEC};
|
||||
case 0xf6: return {ZeroIndexed, Operation::INC};
|
||||
|
||||
case 0x17: return {ZeroIndexed, Operation::ASO};
|
||||
case 0x37: return {ZeroIndexed, Operation::RLA};
|
||||
case 0x57: return {ZeroIndexed, Operation::LSE};
|
||||
case 0x77: return {ZeroIndexed, Operation::RRA};
|
||||
case 0x97: return {ZeroIndexed, Operation::SAX};
|
||||
case 0xb7: return {ZeroIndexed, Operation::LAX};
|
||||
case 0xd7: return {ZeroIndexed, Operation::DCP};
|
||||
case 0xf7: return {ZeroIndexed, Operation::INS};
|
||||
|
||||
case 0x18: return {Implied, Operation::CLC};
|
||||
case 0x38: return {Implied, Operation::SEC};
|
||||
case 0x58: return {Implied, Operation::CLI};
|
||||
case 0x78: return {Implied, Operation::SEI};
|
||||
case 0x98: return {Implied, Operation::TYA};
|
||||
case 0xb8: return {Implied, Operation::CLV};
|
||||
case 0xd8: return {Implied, Operation::CLD};
|
||||
case 0xf8: return {Implied, Operation::SED};
|
||||
|
||||
case 0x19: return {AbsoluteIndexed, Index::Y, Operation::ORA};
|
||||
case 0x39: return {AbsoluteIndexed, Index::Y, Operation::AND};
|
||||
case 0x59: return {AbsoluteIndexed, Index::Y, Operation::EOR};
|
||||
case 0x79: return {AbsoluteIndexed, Index::Y, Operation::ADC};
|
||||
case 0x99: return {AbsoluteIndexed, Index::Y, Operation::STA};
|
||||
case 0xb9: return {AbsoluteIndexed, Index::Y, Operation::LDA};
|
||||
case 0xd9: return {AbsoluteIndexed, Index::Y, Operation::CMP};
|
||||
case 0xf9: return {AbsoluteIndexed, Index::Y, Operation::SBC};
|
||||
|
||||
case 0x1a: return {Implied, Operation::NOP};
|
||||
case 0x3a: return {Implied, Operation::NOP};
|
||||
case 0x5a: return {Implied, Operation::NOP};
|
||||
case 0x7a: return {Implied, Operation::NOP};
|
||||
case 0x9a: return {Implied, Operation::TXS};
|
||||
case 0xba: return {Implied, Operation::TSX};
|
||||
case 0xda: return {Implied, Operation::NOP};
|
||||
case 0xfa: return {Implied, Operation::NOP};
|
||||
|
||||
case 0x1b: return {AbsoluteIndexed, Index::Y, Operation::ASO};
|
||||
case 0x3b: return {AbsoluteIndexed, Index::Y, Operation::RLA};
|
||||
case 0x5b: return {AbsoluteIndexed, Index::Y, Operation::LSE};
|
||||
case 0x7b: return {AbsoluteIndexed, Index::Y, Operation::RRA};
|
||||
case 0x9b: return {SHxAbsoluteXY, Operation::SHS};
|
||||
case 0xbb: return {AbsoluteIndexed, Index::Y, Operation::LAS};
|
||||
case 0xdb: return {AbsoluteIndexed, Index::Y, Operation::DCP};
|
||||
case 0xfb: return {AbsoluteIndexed, Index::Y, Operation::INS};
|
||||
|
||||
case 0x1c: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
case 0x3c: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
case 0x5c: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
case 0x7c: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
case 0x9c: return {SHxAbsoluteXY, Operation::SHY};
|
||||
case 0xbc: return {AbsoluteIndexed, Index::X, Operation::LDY};
|
||||
case 0xdc: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
case 0xfc: return {AbsoluteIndexed, Index::X, Operation::NOP};
|
||||
|
||||
case 0x1d: return {AbsoluteIndexed, Index::X, Operation::ORA};
|
||||
case 0x3d: return {AbsoluteIndexed, Index::X, Operation::AND};
|
||||
case 0x5d: return {AbsoluteIndexed, Index::X, Operation::EOR};
|
||||
case 0x7d: return {AbsoluteIndexed, Index::X, Operation::ADC};
|
||||
case 0x9d: return {AbsoluteIndexed, Index::X, Operation::STA};
|
||||
case 0xbd: return {AbsoluteIndexed, Index::X, Operation::LDA};
|
||||
case 0xdd: return {AbsoluteIndexed, Index::X, Operation::CMP};
|
||||
case 0xfd: return {AbsoluteIndexed, Index::X, Operation::SBC};
|
||||
|
||||
case 0x1e: return {AbsoluteIndexed, Operation::ASL};
|
||||
case 0x3e: return {AbsoluteIndexed, Operation::ROL};
|
||||
case 0x5e: return {AbsoluteIndexed, Operation::LSR};
|
||||
case 0x7e: return {AbsoluteIndexed, Operation::ROR};
|
||||
case 0x9e: return {SHxAbsoluteXY, Operation::SHX};
|
||||
case 0xbe: return {AbsoluteIndexed, Operation::LDX};
|
||||
case 0xde: return {AbsoluteIndexed, Operation::DEC};
|
||||
case 0xfe: return {AbsoluteIndexed, Operation::INC};
|
||||
|
||||
case 0x1f: return {AbsoluteIndexed, Operation::ASO};
|
||||
case 0x3f: return {AbsoluteIndexed, Operation::RLA};
|
||||
case 0x5f: return {AbsoluteIndexed, Operation::LSE};
|
||||
case 0x7f: return {AbsoluteIndexed, Operation::RRA};
|
||||
case 0x9f: return {SHxAbsoluteXY, Operation::SHA};
|
||||
case 0xbf: return {AbsoluteIndexed, Operation::LAX};
|
||||
case 0xdf: return {AbsoluteIndexed, Operation::DCP};
|
||||
case 0xff: return {AbsoluteIndexed, Operation::INS};
|
||||
}
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
||||
};
|
||||
|
||||
template <Model model>
|
||||
struct Decoder<model, std::enable_if_t<model == Model::Synertek65C02>> {
|
||||
static constexpr Instruction decode(const uint8_t opcode) {
|
||||
using enum AddressingMode;
|
||||
switch(opcode) {
|
||||
default: return Decoder<Model::M6502>::decode(opcode);
|
||||
|
||||
case 0x80: return {Relative, Operation::BRA};
|
||||
|
||||
case 0x02: return {Immediate, Operation::NOP};
|
||||
case 0x22: return {Immediate, Operation::NOP};
|
||||
case 0x42: return {Immediate, Operation::NOP};
|
||||
case 0x62: return {Immediate, Operation::NOP};
|
||||
|
||||
case 0x03: return {Implied, Operation::FastNOP};
|
||||
case 0x23: return {Implied, Operation::FastNOP};
|
||||
case 0x43: return {Implied, Operation::FastNOP};
|
||||
case 0x63: return {Implied, Operation::FastNOP};
|
||||
case 0x83: return {Implied, Operation::FastNOP};
|
||||
case 0xa3: return {Implied, Operation::FastNOP};
|
||||
case 0xc3: return {Implied, Operation::FastNOP};
|
||||
case 0xe3: return {Implied, Operation::FastNOP};
|
||||
|
||||
case 0x04: return {Zero, Operation::TSB};
|
||||
case 0x64: return {Zero, Operation::STZ};
|
||||
case 0x9e: return {AbsoluteIndexed, Operation::STZ};
|
||||
|
||||
case 0x07: return {Zero, Operation::NOP};
|
||||
case 0x27: return {Zero, Operation::NOP};
|
||||
case 0x47: return {Zero, Operation::NOP};
|
||||
case 0x67: return {Zero, Operation::NOP};
|
||||
case 0x87: return {Zero, Operation::NOP};
|
||||
case 0xa7: return {Zero, Operation::NOP};
|
||||
case 0xc7: return {Zero, Operation::NOP};
|
||||
case 0xe7: return {Zero, Operation::NOP};
|
||||
|
||||
case 0x89: return {Immediate, Operation::BITNoNV};
|
||||
|
||||
case 0x0b: return {Implied, Operation::FastNOP};
|
||||
case 0x2b: return {Implied, Operation::FastNOP};
|
||||
case 0x4b: return {Implied, Operation::FastNOP};
|
||||
case 0x6b: return {Implied, Operation::FastNOP};
|
||||
case 0x8b: return {Implied, Operation::FastNOP};
|
||||
case 0xab: return {Implied, Operation::FastNOP};
|
||||
case 0xcb: return {Implied, Operation::FastNOP};
|
||||
case 0xeb: return {Implied, Operation::FastNOP};
|
||||
|
||||
case 0x0c: return {Absolute, Operation::TSB};
|
||||
|
||||
case 0x0f: return {Absolute, Operation::FastNOP};
|
||||
case 0x2f: return {Absolute, Operation::FastNOP};
|
||||
case 0x4f: return {Absolute, Operation::FastNOP};
|
||||
case 0x6f: return {Absolute, Operation::FastNOP};
|
||||
case 0x8f: return {Absolute, Operation::FastNOP};
|
||||
case 0xaf: return {Absolute, Operation::FastNOP};
|
||||
case 0xcf: return {Absolute, Operation::FastNOP};
|
||||
case 0xef: return {Absolute, Operation::FastNOP};
|
||||
|
||||
case 0x12: return {ZeroIndirect, Operation::ORA};
|
||||
case 0x32: return {ZeroIndirect, Operation::AND};
|
||||
case 0x52: return {ZeroIndirect, Operation::EOR};
|
||||
case 0x72: return {ZeroIndirect, Operation::ADC};
|
||||
case 0x92: return {ZeroIndirect, Operation::STA};
|
||||
case 0xb2: return {ZeroIndirect, Operation::LDA};
|
||||
case 0xd2: return {ZeroIndirect, Operation::CMP};
|
||||
case 0xf2: return {ZeroIndirect, Operation::SBC};
|
||||
|
||||
case 0x13: return {Implied, Operation::FastNOP};
|
||||
case 0x33: return {Implied, Operation::FastNOP};
|
||||
case 0x53: return {Implied, Operation::FastNOP};
|
||||
case 0x73: return {Implied, Operation::FastNOP};
|
||||
case 0x93: return {Implied, Operation::FastNOP};
|
||||
case 0xb3: return {Implied, Operation::FastNOP};
|
||||
case 0xd3: return {Implied, Operation::FastNOP};
|
||||
case 0xf3: return {Implied, Operation::FastNOP};
|
||||
|
||||
case 0x14: return {Zero, Operation::TRB};
|
||||
case 0x34: return {ZeroIndexed, Operation::BIT};
|
||||
case 0x74: return {ZeroIndexed, Operation::STZ};
|
||||
|
||||
case 0x17: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x37: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x57: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x77: return {ZeroIndexed, Operation::NOP};
|
||||
case 0x97: return {ZeroIndexed, Operation::NOP};
|
||||
case 0xb7: return {ZeroIndexed, Operation::NOP};
|
||||
case 0xd7: return {ZeroIndexed, Operation::NOP};
|
||||
case 0xf7: return {ZeroIndexed, Operation::NOP};
|
||||
|
||||
case 0x1a: return {Implied, Operation::INA};
|
||||
case 0x3a: return {Implied, Operation::DEA};
|
||||
case 0x5a: return {Push, Operation::STY};
|
||||
case 0x7a: return {Pull, Operation::LDY};
|
||||
case 0xda: return {Push, Operation::STX};
|
||||
case 0xfa: return {Pull, Operation::LDX};
|
||||
|
||||
case 0x1b: return {Implied, Operation::FastNOP};
|
||||
case 0x3b: return {Implied, Operation::FastNOP};
|
||||
case 0x5b: return {Implied, Operation::FastNOP};
|
||||
case 0x7b: return {Implied, Operation::FastNOP};
|
||||
case 0x9b: return {Implied, Operation::FastNOP};
|
||||
case 0xbb: return {Implied, Operation::FastNOP};
|
||||
case 0xdb: return {ZeroIndexed, Operation::NOP};
|
||||
case 0xfb: return {Implied, Operation::FastNOP};
|
||||
|
||||
case 0x1c: return {Absolute, Operation::TRB};
|
||||
case 0x3c: return {AbsoluteIndexed, Operation::BIT};
|
||||
case 0x5c: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0x7c: return {JMPAbsoluteIndexedIndirect, Operation::JMP};
|
||||
case 0x9c: return {Absolute, Operation::STZ};
|
||||
|
||||
case 0x1e: return {FastAbsoluteIndexedModify, Operation::ASL};
|
||||
case 0x3e: return {FastAbsoluteIndexedModify, Operation::ROL};
|
||||
case 0x5e: return {FastAbsoluteIndexedModify, Operation::LSR};
|
||||
case 0x7e: return {FastAbsoluteIndexedModify, Operation::ROR};
|
||||
|
||||
case 0x1f: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0x3f: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0x5f: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0x7f: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0x9f: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0xbf: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0xdf: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
case 0xff: return {AbsoluteIndexed, Operation::FastNOP};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <Model model>
|
||||
struct Decoder<model, std::enable_if_t<model == Model::Rockwell65C02>> {
|
||||
static constexpr Instruction decode(const uint8_t opcode) {
|
||||
using enum AddressingMode;
|
||||
switch(opcode) {
|
||||
default: return Decoder<Model::Synertek65C02>::decode(opcode);
|
||||
|
||||
case 0x0f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x2f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x4f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x6f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x8f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xaf: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xcf: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xef: return {BBRBBS, Operation::BBRBBS};
|
||||
|
||||
case 0x1f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x3f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x5f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x7f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0x9f: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xbf: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xdf: return {BBRBBS, Operation::BBRBBS};
|
||||
case 0xff: return {BBRBBS, Operation::BBRBBS};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <Model model>
|
||||
struct Decoder<model, std::enable_if_t<model == Model::WDC65C02>> {
|
||||
static constexpr Instruction decode(const uint8_t opcode) {
|
||||
using enum AddressingMode;
|
||||
switch(opcode) {
|
||||
default: return Decoder<Model::Rockwell65C02>::decode(opcode);
|
||||
case 0xdb: return {STP, Operation::STP};
|
||||
case 0xcb: return {WAI, Operation::WAI};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
724
Processors/6502Mk2/Implementation/6502.hpp
Normal file
724
Processors/6502Mk2/Implementation/6502.hpp
Normal file
@@ -0,0 +1,724 @@
|
||||
//
|
||||
// 6502.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Processors/6502Mk2/Decoder.hpp"
|
||||
#include "Processors/6502Mk2/Perform.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// On the 65c02: http://www.6502.org/tutorials/65c02opcodes.html
|
||||
// Some bus captures to substantiate 65c02 timing:
|
||||
// https://github.com/CompuSAR/sar6502/blob/master/sar6502.srcs/sim_1/new/test_plan.mem
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
template <Model model, typename Traits>
|
||||
void Processor<model, Traits>::restart_operation_fetch() {
|
||||
Storage::resume_point_ = Storage::ResumePoint::FetchDecode;
|
||||
}
|
||||
|
||||
template <Model model, typename Traits>
|
||||
void Processor<model, Traits>::run_for(const Cycles cycles) {
|
||||
Storage::cycles_ += cycles;
|
||||
if(Storage::cycles_ <= Cycles(0)) return;
|
||||
|
||||
#define restore_point() (__COUNTER__ + int(ResumePoint::Max) + int(AddressingMode::Max))
|
||||
|
||||
#define join(a, b) a##b
|
||||
#define attach(a, b) join(a, b)
|
||||
#define access_label() attach(repeat, __LINE__)
|
||||
|
||||
// TODO: find a way not to generate a restore point if pause precision and uses_ready_line/model allows it.
|
||||
#define access(type, addr, value) { \
|
||||
static constexpr int location = restore_point(); \
|
||||
[[fallthrough]]; case location: \
|
||||
[[maybe_unused]] access_label(): \
|
||||
\
|
||||
if constexpr (Traits::pause_precision >= PausePrecision::AnyCycle) { \
|
||||
if(Storage::cycles_ <= Cycles(0)) { \
|
||||
Storage::resume_point_ = location; \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if(Traits::uses_ready_line && (is_read(type) || is_65c02(model)) && Storage::inputs_.ready) { \
|
||||
Storage::cycles_ -= Storage::bus_handler_.template perform<BusOperation::Ready>( \
|
||||
addr, \
|
||||
Data::NoValue{} \
|
||||
); \
|
||||
goto access_label(); \
|
||||
} \
|
||||
\
|
||||
Storage::cycles_ -= Storage::bus_handler_.template perform<type>(addr, value); \
|
||||
}
|
||||
|
||||
#define access_program(name) int(ResumePoint::Max) + int(AddressingMode::name)
|
||||
|
||||
using ResumePoint = Storage::ResumePoint;
|
||||
using InterruptRequest = Storage::Inputs::InterruptRequest;
|
||||
auto ®isters = Storage::registers_;
|
||||
uint8_t throwaway = 0;
|
||||
|
||||
const auto index = [&] {
|
||||
return Storage::decoded_.index == Index::X ? registers.x : registers.y;
|
||||
};
|
||||
const auto check_interrupt = [] {
|
||||
// TODO.
|
||||
};
|
||||
const auto perform_operation = [&] {
|
||||
CPU::MOS6502Mk2::perform<model>(
|
||||
Storage::decoded_.operation,
|
||||
registers,
|
||||
Storage::operand_,
|
||||
Storage::opcode_
|
||||
);
|
||||
};
|
||||
const auto needs_65c02_extra_arithmetic_cycle = [&] {
|
||||
return
|
||||
is_65c02(model) &&
|
||||
(
|
||||
Storage::decoded_.operation == Operation::ADC ||
|
||||
Storage::decoded_.operation == Operation::SBC
|
||||
) && registers.flags.decimal;
|
||||
};
|
||||
|
||||
using Literal = Address::Literal;
|
||||
using ZeroPage = Address::ZeroPage;
|
||||
using Stack = Address::Stack;
|
||||
using Vector = Address::Vector;
|
||||
|
||||
while(true) switch(Storage::resume_point_) {
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
|
||||
// MARK: - Read, write or modify a zero-page address.
|
||||
|
||||
access_zero:
|
||||
++registers.pc.full;
|
||||
if constexpr (is_65c02(model)) {
|
||||
if(Storage::decoded_.operation == Operation::FastNOP) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
}
|
||||
|
||||
if(Storage::decoded_.type == Type::Write) {
|
||||
goto access_zero_write;
|
||||
}
|
||||
|
||||
// ADC and SBC decimal take an extra cycle on the 65c02.
|
||||
if(needs_65c02_extra_arithmetic_cycle()) {
|
||||
goto access_zero_65c02_decimal;
|
||||
}
|
||||
|
||||
// Read.
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
if(Storage::decoded_.type == Type::Read) {
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
// Modify stall.
|
||||
access(
|
||||
is_65c02(model) ? BusOperation::Read : BusOperation::Write,
|
||||
ZeroPage(Storage::address_.halves.low),
|
||||
Storage::operand_
|
||||
);
|
||||
|
||||
// Write.
|
||||
access_zero_write:
|
||||
check_interrupt();
|
||||
perform_operation();
|
||||
access(BusOperation::Write, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
access_zero_65c02_decimal:
|
||||
access(BusOperation::Read, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - Read, write or modify an arbitrary address.
|
||||
|
||||
access_absolute:
|
||||
++registers.pc.full;
|
||||
if constexpr (is_65c02(model)) {
|
||||
if(Storage::decoded_.operation == Operation::FastNOP) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
}
|
||||
if(Storage::decoded_.type == Type::Write) {
|
||||
goto access_absolute_write;
|
||||
}
|
||||
|
||||
// ADC and SBC decimal take an extra cycle on the 65c02.
|
||||
if(needs_65c02_extra_arithmetic_cycle()) {
|
||||
goto access_absolute_65c02_decimal;
|
||||
}
|
||||
|
||||
// Read.
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), Storage::operand_);
|
||||
if(Storage::decoded_.type == Type::Read) {
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
// Modify stall.
|
||||
access(
|
||||
is_65c02(model) ? BusOperation::Read : BusOperation::Write,
|
||||
Literal(Storage::address_.full),
|
||||
Storage::operand_
|
||||
);
|
||||
|
||||
// Write.
|
||||
access_absolute_write:
|
||||
check_interrupt();
|
||||
perform_operation();
|
||||
access(BusOperation::Write, Literal(Storage::address_.full), Storage::operand_);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
access_absolute_65c02_decimal:
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), Storage::operand_);
|
||||
check_interrupt();
|
||||
perform_operation();
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), Storage::operand_);
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - Fetch/decode.
|
||||
|
||||
fetch_decode:
|
||||
case ResumePoint::FetchDecode:
|
||||
|
||||
// Pause precision will always be at least operation by operation.
|
||||
if(Storage::cycles_ <= Cycles(0)) {
|
||||
Storage::resume_point_ = ResumePoint::FetchDecode;
|
||||
return;
|
||||
}
|
||||
|
||||
if(Storage::inputs_.interrupt_requests) {
|
||||
goto interrupt;
|
||||
}
|
||||
|
||||
access(BusOperation::ReadOpcode, Literal(registers.pc.full), Storage::opcode_);
|
||||
++registers.pc.full;
|
||||
Storage::decoded_ = Decoder<model>::decode(Storage::opcode_);
|
||||
|
||||
// 65c02 special case: support single-cycle NOPs.
|
||||
if constexpr (is_65c02(model)) {
|
||||
if(
|
||||
Storage::decoded_.mode == AddressingMode::Implied &&
|
||||
Storage::decoded_.operation == Operation::FastNOP
|
||||
) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
}
|
||||
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::operand_);
|
||||
|
||||
Storage::resume_point_ = ResumePoint::Max + int(Storage::decoded_.mode);
|
||||
break;
|
||||
|
||||
// MARK: - Immediate, Implied, Accumulator.
|
||||
|
||||
case access_program(Immediate):
|
||||
if(needs_65c02_extra_arithmetic_cycle()) {
|
||||
goto immediate_65c02_decimal;
|
||||
}
|
||||
++registers.pc.full;
|
||||
[[fallthrough]];
|
||||
|
||||
case access_program(Implied):
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
|
||||
immediate_65c02_decimal:
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::operand_);
|
||||
|
||||
++registers.pc.full;
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(Accumulator):
|
||||
CPU::MOS6502Mk2::perform<model>(
|
||||
Storage::decoded_.operation,
|
||||
registers,
|
||||
registers.a,
|
||||
Storage::opcode_
|
||||
);
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - Stack.
|
||||
|
||||
case access_program(Pull):
|
||||
access(BusOperation::Read, Stack(registers.s), Storage::operand_);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), Storage::operand_);
|
||||
perform_operation();
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(Push):
|
||||
check_interrupt();
|
||||
perform_operation();
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), Storage::operand_);
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - Relative, and BBR/BBS (for the 65c02).
|
||||
|
||||
case access_program(Relative):
|
||||
++registers.pc.full;
|
||||
|
||||
if(!test(Storage::decoded_.operation, registers)) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
Storage::address_ = registers.pc;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
|
||||
registers.pc.full += int8_t(Storage::operand_);
|
||||
if(registers.pc.halves.high == Storage::address_.halves.high) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
Storage::address_.halves.low = registers.pc.halves.low;
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(BBRBBS):
|
||||
++registers.pc.full;
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
access(BusOperation::Read, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
access(BusOperation::Read, ZeroPage(Storage::address_.halves.low), Storage::operand_);
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.low);
|
||||
|
||||
++registers.pc.full;
|
||||
if(!test_bbr_bbs(Storage::opcode_, Storage::operand_)) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
Storage::operand_ = Storage::address_.halves.low;
|
||||
Storage::address_ = registers.pc;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
|
||||
registers.pc.full += int8_t(Storage::operand_);
|
||||
if(registers.pc.halves.high == Storage::address_.halves.high) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - Zero.
|
||||
|
||||
case access_program(Zero):
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
goto access_zero;
|
||||
|
||||
// MARK: - Zero indexed.
|
||||
|
||||
case access_program(ZeroIndexed):
|
||||
if constexpr (is_65c02(model)) {
|
||||
check_interrupt();
|
||||
}
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), throwaway);
|
||||
Storage::address_.halves.low += index();
|
||||
|
||||
goto access_zero;
|
||||
|
||||
// MARK: - Zero indirect (which is exclusive to the 65c02).
|
||||
|
||||
case access_program(ZeroIndirect):
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_++), Storage::address_.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.high);
|
||||
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Absolute.
|
||||
|
||||
case access_program(Absolute):
|
||||
++registers.pc.full;
|
||||
|
||||
if constexpr (is_65c02(model)) {
|
||||
check_interrupt();
|
||||
}
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Absolute indexed.
|
||||
|
||||
case access_program(AbsoluteIndexed):
|
||||
++registers.pc.full;
|
||||
|
||||
// Read top half of address.
|
||||
if constexpr (is_65c02(model)) {
|
||||
check_interrupt();
|
||||
}
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
|
||||
// If this is a read and the top byte doesn't need adjusting, skip that cycle.
|
||||
Storage::operand_ = Storage::address_.halves.high;
|
||||
Storage::address_.full += index();
|
||||
if(Storage::decoded_.type == Type::Read && Storage::operand_ == Storage::address_.halves.high) {
|
||||
goto access_absolute;
|
||||
}
|
||||
|
||||
if constexpr (is_65c02(model)) {
|
||||
goto absolute_indexed_65c02_tail;
|
||||
}
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
goto access_absolute;
|
||||
|
||||
absolute_indexed_65c02_tail:
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Fast absolute indexed modify, which is a 65c02 improvement but not applied universally.
|
||||
|
||||
case access_program(FastAbsoluteIndexedModify):
|
||||
++registers.pc.full;
|
||||
|
||||
// Read top half of address.
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
|
||||
// If this is a read and the top byte doesn't need adjusting, skip that cycle.
|
||||
Storage::operand_ = Storage::address_.halves.high;
|
||||
Storage::address_.full += index();
|
||||
if(Storage::address_.halves.high == Storage::operand_) {
|
||||
goto access_absolute;
|
||||
}
|
||||
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Indexed indirect.
|
||||
|
||||
case access_program(IndexedIndirect):
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), throwaway);
|
||||
Storage::operand_ += registers.x;
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.low);
|
||||
++Storage::operand_;
|
||||
if constexpr (is_65c02(model)) {
|
||||
check_interrupt();
|
||||
}
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.high);
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Indirect indexed.
|
||||
|
||||
case access_program(IndirectIndexed):
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.low);
|
||||
++Storage::operand_;
|
||||
|
||||
if constexpr (is_65c02(model)) {
|
||||
check_interrupt();
|
||||
}
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.high);
|
||||
|
||||
Storage::operand_ = Storage::address_.halves.high;
|
||||
Storage::address_.full += registers.y;
|
||||
if(Storage::decoded_.type == Type::Read && Storage::address_.halves.high == Storage::operand_) {
|
||||
goto access_absolute;
|
||||
}
|
||||
|
||||
if constexpr (is_65c02(model)) {
|
||||
goto indirect_indexed_65c02_tail;
|
||||
}
|
||||
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
|
||||
goto access_absolute;
|
||||
|
||||
indirect_indexed_65c02_tail:
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
goto access_absolute;
|
||||
|
||||
// MARK: - Potentially-faulty addressing of SHA/SHX/SHY/SHS.
|
||||
|
||||
case access_program(SHxAbsoluteXY):
|
||||
++registers.pc.full;
|
||||
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
++registers.pc.full;
|
||||
|
||||
Storage::operand_ = Storage::address_.halves.high;
|
||||
Storage::address_.full += (Storage::decoded_.operation == Operation::SHY) ? registers.x : registers.y;
|
||||
Storage::did_adjust_top_ = Storage::address_.halves.high != Storage::operand_;
|
||||
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
|
||||
switch(Storage::decoded_.operation) {
|
||||
default: __builtin_unreachable();
|
||||
case Operation::SHA:
|
||||
Operations::sha(registers, Storage::address_, Storage::operand_, Storage::did_adjust_top_);
|
||||
break;
|
||||
case Operation::SHX:
|
||||
Operations::shx(registers, Storage::address_, Storage::operand_, Storage::did_adjust_top_);
|
||||
break;
|
||||
case Operation::SHY:
|
||||
Operations::shy(registers, Storage::address_, Storage::operand_, Storage::did_adjust_top_);
|
||||
break;
|
||||
case Operation::SHS:
|
||||
Operations::shs(registers, Storage::address_, Storage::operand_, Storage::did_adjust_top_);
|
||||
break;
|
||||
}
|
||||
|
||||
check_interrupt();
|
||||
access(BusOperation::Write, Literal(Storage::address_.full), Storage::operand_);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(SHxIndirectIndexed):
|
||||
++registers.pc.full;
|
||||
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.low);
|
||||
++Storage::operand_;
|
||||
|
||||
access(BusOperation::Read, ZeroPage(Storage::operand_), Storage::address_.halves.high);
|
||||
|
||||
Storage::operand_ = Storage::address_.halves.high;
|
||||
Storage::address_.full += registers.y;
|
||||
Storage::did_adjust_top_ = Storage::address_.halves.high != Storage::operand_;
|
||||
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), throwaway);
|
||||
std::swap(Storage::address_.halves.high, Storage::operand_);
|
||||
|
||||
check_interrupt();
|
||||
assert(Storage::decoded_.operation == Operation::SHA);
|
||||
Operations::sha(registers, Storage::address_, Storage::operand_, Storage::did_adjust_top_);
|
||||
access(BusOperation::Write, Literal(Storage::address_.full), Storage::operand_);
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - JAM
|
||||
|
||||
case access_program(JAM):
|
||||
access(BusOperation::Read, Vector(0xff), throwaway);
|
||||
access(BusOperation::Read, Vector(0xfe), throwaway);
|
||||
access(BusOperation::Read, Vector(0xfe), throwaway);
|
||||
|
||||
Storage::resume_point_ = ResumePoint::Jam;
|
||||
[[fallthrough]];
|
||||
case ResumePoint::Jam:
|
||||
jammed:
|
||||
if(Storage::cycles_ <= Cycles(0)) {
|
||||
return;
|
||||
}
|
||||
Storage::cycles_ -= Storage::bus_handler_.template perform<BusOperation::Read>(
|
||||
Vector(0xff),
|
||||
throwaway
|
||||
);
|
||||
goto jammed;
|
||||
|
||||
// MARK: - Flow control (other than BRK).
|
||||
|
||||
case access_program(JSR):
|
||||
++registers.pc.full;
|
||||
access(BusOperation::Read, Stack(registers.s), throwaway);
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.high);
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.low);
|
||||
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), registers.pc.halves.high);
|
||||
registers.pc.halves.low = Storage::operand_;
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(RTI):
|
||||
access(BusOperation::Read, Stack(registers.s), Storage::operand_);
|
||||
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), Storage::operand_);
|
||||
registers.flags = Flags(Storage::operand_);
|
||||
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), registers.pc.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), registers.pc.halves.high);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(RTS):
|
||||
access(BusOperation::Read, Stack(registers.s), Storage::operand_);
|
||||
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), registers.pc.halves.low);
|
||||
access(BusOperation::Read, Stack(registers.inc_s()), registers.pc.halves.high);
|
||||
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
++registers.pc.full;
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(JMPAbsolute):
|
||||
++registers.pc.full;
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(registers.pc.full), registers.pc.halves.high);
|
||||
registers.pc.halves.low = Storage::operand_;
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(JMPAbsoluteIndirect):
|
||||
++registers.pc.full;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), registers.pc.halves.low);
|
||||
++Storage::address_.halves.low;
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), registers.pc.halves.high);
|
||||
|
||||
if constexpr (!is_65c02(model)) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
|
||||
Storage::address_.halves.high += !Storage::address_.halves.low;
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), registers.pc.halves.high);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
case access_program(JMPAbsoluteIndexedIndirect):
|
||||
++registers.pc.full;
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::address_.halves.high);
|
||||
Storage::address_.halves.low = Storage::operand_;
|
||||
|
||||
access(BusOperation::Read, Literal(registers.pc.full), throwaway);
|
||||
|
||||
Storage::address_.full += registers.x;
|
||||
access(BusOperation::Read, Literal(Storage::address_.full++), registers.pc.halves.low);
|
||||
access(BusOperation::Read, Literal(Storage::address_.full), registers.pc.halves.high);
|
||||
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - NMI/IRQ/Reset, and BRK.
|
||||
|
||||
case access_program(BRK):
|
||||
++registers.pc.full;
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.high);
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.low);
|
||||
access(
|
||||
BusOperation::Write,
|
||||
Stack(registers.dec_s()),
|
||||
static_cast<uint8_t>(registers.flags) | Flag::Break
|
||||
);
|
||||
|
||||
registers.flags.inverse_interrupt = 0;
|
||||
if constexpr (is_65c02(model)) {
|
||||
registers.flags.decimal = 0;
|
||||
}
|
||||
|
||||
access(BusOperation::Read, Vector(0xfe), registers.pc.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Vector(0xff), registers.pc.halves.high);
|
||||
goto fetch_decode;
|
||||
|
||||
interrupt:
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::operand_);
|
||||
access(BusOperation::Read, Literal(registers.pc.full), Storage::operand_);
|
||||
|
||||
if(Storage::inputs_.interrupt_requests & (InterruptRequest::Reset | InterruptRequest::PowerOn)) {
|
||||
Storage::inputs_.interrupt_requests &= ~InterruptRequest::PowerOn;
|
||||
goto reset;
|
||||
}
|
||||
assert(Storage::inputs_.interrupt_requests & (InterruptRequest::IRQ | InterruptRequest::NMI));
|
||||
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.high);
|
||||
access(BusOperation::Write, Stack(registers.dec_s()), registers.pc.halves.low);
|
||||
access(
|
||||
BusOperation::Write,
|
||||
Stack(registers.dec_s()),
|
||||
static_cast<uint8_t>(registers.flags) & ~Flag::Break
|
||||
);
|
||||
|
||||
registers.flags.inverse_interrupt = 0;
|
||||
if constexpr (is_65c02(model)) registers.flags.decimal = 0;
|
||||
|
||||
if(Storage::inputs_.interrupt_requests & InterruptRequest::NMI) {
|
||||
goto nmi;
|
||||
}
|
||||
|
||||
access(BusOperation::Read, Vector(0xfe), registers.pc.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Vector(0xff), registers.pc.halves.high);
|
||||
goto fetch_decode;
|
||||
|
||||
nmi:
|
||||
access(BusOperation::Read, Vector(0xfa), registers.pc.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Vector(0xfb), registers.pc.halves.high);
|
||||
goto fetch_decode;
|
||||
|
||||
reset:
|
||||
access(BusOperation::Read, Stack(registers.dec_s()), Storage::operand_);
|
||||
access(BusOperation::Read, Stack(registers.dec_s()), Storage::operand_);
|
||||
access(BusOperation::Read, Stack(registers.dec_s()), Storage::operand_);
|
||||
|
||||
registers.flags.inverse_interrupt = 0;
|
||||
if constexpr (is_65c02(model)) registers.flags.decimal = 0;
|
||||
|
||||
access(BusOperation::Read, Vector(0xfc), registers.pc.halves.low);
|
||||
check_interrupt();
|
||||
access(BusOperation::Read, Vector(0xfd), registers.pc.halves.high);
|
||||
goto fetch_decode;
|
||||
|
||||
// MARK: - STP and WAI.
|
||||
case access_program(STP):
|
||||
stopped:
|
||||
if(Storage::cycles_ <= Cycles(0)) {
|
||||
Storage::resume_point_ = access_program(STP);
|
||||
return;
|
||||
}
|
||||
access(BusOperation::None, Vector(0xff), Data::NoValue{});
|
||||
goto stopped;
|
||||
|
||||
case access_program(WAI):
|
||||
waiting:
|
||||
if(Storage::cycles_ <= Cycles(0)) {
|
||||
Storage::resume_point_ = access_program(WAI);
|
||||
return;
|
||||
}
|
||||
access(BusOperation::Ready, Vector(0xff), Data::NoValue{});
|
||||
if(Storage::inputs_.interrupt_requests) {
|
||||
goto fetch_decode;
|
||||
}
|
||||
goto waiting;
|
||||
}
|
||||
|
||||
#undef access_program
|
||||
#undef access
|
||||
#undef access_label
|
||||
#undef attach
|
||||
#undef join
|
||||
#undef restore_point
|
||||
}
|
||||
|
||||
}
|
||||
27
Processors/6502Mk2/Model.hpp
Normal file
27
Processors/6502Mk2/Model.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Model.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
enum Model {
|
||||
NES6502, // The NES's 6502; like a 6502 but lacking decimal mode (though it retains the decimal flag).
|
||||
M6502, // NMOS 6502.
|
||||
Synertek65C02, // A 6502 extended with BRA, P[H/L][X/Y], STZ, TRB, TSB and the (zp) addressing mode, and more.
|
||||
Rockwell65C02, // The Synertek extended with BBR, BBS, RMB and SMB.
|
||||
WDC65C02, // The Rockwell extended with STP and WAI.
|
||||
M65816, // The "16-bit" successor to the 6502.
|
||||
};
|
||||
constexpr bool has_decimal_mode(const Model model) { return model != Model::NES6502; }
|
||||
constexpr bool is_8bit(const Model model) { return model <= Model::WDC65C02; }
|
||||
constexpr bool is_16bit(const Model model) { return model == Model::M65816; }
|
||||
constexpr bool is_65c02(const Model model) { return model >= Model::Synertek65C02; }
|
||||
constexpr bool is_6502(const Model model) { return model <= Model::M6502; }
|
||||
|
||||
}
|
||||
447
Processors/6502Mk2/Perform.hpp
Normal file
447
Processors/6502Mk2/Perform.hpp
Normal file
@@ -0,0 +1,447 @@
|
||||
//
|
||||
// Perform.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Registers.hpp"
|
||||
|
||||
#include "Numeric/Carry.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
namespace Operations {
|
||||
|
||||
template <typename RegistersT>
|
||||
void ane(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.a = (registers.a | 0xee) & operand & registers.x;
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void anc(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.a &= operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
registers.flags.carry = registers.a >> 7;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT>
|
||||
void adc(RegistersT ®isters, const uint8_t operand) {
|
||||
uint8_t result = registers.a + operand + registers.flags.carry;
|
||||
registers.flags.carry = result < registers.a + registers.flags.carry;
|
||||
|
||||
if(!has_decimal_mode(model) || !registers.flags.decimal) {
|
||||
registers.flags.set_v(result, registers.a, operand);
|
||||
registers.flags.set_nz(registers.a = result);
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (!is_65c02(model)) {
|
||||
registers.flags.zero_result = result;
|
||||
}
|
||||
|
||||
// General ADC logic:
|
||||
//
|
||||
// Detecting decimal carry means finding occasions when two digits added together totalled
|
||||
// more than 9. Within each four-bit window that means testing the digit itself and also
|
||||
// testing for carry — e.g. 5 + 5 = 0xA, which is detectable only by the value of the final
|
||||
// digit, but 9 + 9 = 0x18, which is detectable only by spotting the carry.
|
||||
|
||||
// Only a single bit of carry can flow from the bottom nibble to the top.
|
||||
//
|
||||
// So if that carry already happened, fix up the bottom without permitting another;
|
||||
// otherwise permit the carry to happen (and check whether carry then rippled out of bit 7).
|
||||
if(Numeric::carried_in<4>(registers.a, operand, result)) {
|
||||
result = (result & 0xf0) | ((result + 0x06) & 0x0f);
|
||||
} else if((result & 0xf) > 0x9) {
|
||||
registers.flags.carry |= result >= 0x100 - 0x6;
|
||||
result += 0x06;
|
||||
}
|
||||
|
||||
// 6502 quirk: N and V are set before the full result is computed but
|
||||
// after the low nibble has been corrected.
|
||||
if constexpr (!is_65c02(model)) {
|
||||
registers.flags.negative_result = result;
|
||||
}
|
||||
registers.flags.set_v(result, registers.a, operand);
|
||||
|
||||
// i.e. fix high nibble if there was carry out of bit 7 already, or if the
|
||||
// top nibble is too large (in which case there will be carry after the fix-up).
|
||||
registers.flags.carry |= result >= 0xa0;
|
||||
if(registers.flags.carry) {
|
||||
result += 0x60;
|
||||
}
|
||||
|
||||
registers.a = result;
|
||||
if constexpr (is_65c02(model)) {
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT>
|
||||
void sbc(RegistersT ®isters, const uint8_t operand) {
|
||||
if(!has_decimal_mode(model) || !registers.flags.decimal) {
|
||||
adc<Model::NES6502>(registers, ~operand); // Lie about the model to carry forward the fact of not-decimal.
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t operand_complement = ~operand;
|
||||
uint8_t result = registers.a + operand_complement + registers.flags.carry;
|
||||
|
||||
// All flags are set based only on the decimal result.
|
||||
registers.flags.carry = result < registers.a + registers.flags.carry;
|
||||
if constexpr (!is_65c02(model)) {
|
||||
registers.flags.set_nz(result);
|
||||
}
|
||||
registers.flags.set_v(result, registers.a, operand_complement);
|
||||
|
||||
// General SBC logic:
|
||||
//
|
||||
// Because the range of valid numbers starts at 0, any subtraction that should have
|
||||
// caused decimal carry and which requires a digit fix up will definitely have caused
|
||||
// binary carry: the subtraction will have crossed zero and gone into negative numbers.
|
||||
//
|
||||
// So just test for carry (well, actually borrow, which is !carry).
|
||||
|
||||
// The bottom nibble is adjusted if there was borrow into the top nibble;
|
||||
// on a 6502 additional borrow isn't propagated but on a 65C02 it is.
|
||||
// This difference affects invalid BCD numbers only — valid numbers will
|
||||
// never be less than -9 so adding 10 will always generate carry.
|
||||
if(!Numeric::carried_in<4>(registers.a, operand_complement, result)) {
|
||||
if constexpr (is_65c02(model)) {
|
||||
result += 0xfa;
|
||||
} else {
|
||||
result = (result & 0xf0) | ((result + 0xfa) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
// The top nibble is adjusted only if there was borrow out of the whole byte.
|
||||
if(!registers.flags.carry) {
|
||||
result += 0xa0;
|
||||
}
|
||||
|
||||
registers.a = result;
|
||||
if constexpr (is_65c02(model)) {
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT>
|
||||
void arr(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.a &= operand;
|
||||
const uint8_t unshifted_a = registers.a;
|
||||
registers.a = uint8_t((registers.a >> 1) | (registers.flags.carry << 7));
|
||||
registers.flags.set_nz(registers.a);
|
||||
registers.flags.overflow = (registers.a^(registers.a << 1))&Flag::Overflow;
|
||||
|
||||
if(registers.flags.decimal && has_decimal_mode(model)) {
|
||||
if((unshifted_a&0xf) + (unshifted_a&0x1) > 5) registers.a = ((registers.a + 6)&0xf) | (registers.a & 0xf0);
|
||||
registers.flags.carry = ((unshifted_a&0xf0) + (unshifted_a&0x10) > 0x50) ? 1 : 0;
|
||||
if(registers.flags.carry) registers.a += 0x60;
|
||||
} else {
|
||||
registers.flags.carry = (registers.a >> 6)&1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void sbx(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.x &= registers.a;
|
||||
registers.flags.carry = operand <= registers.x;
|
||||
registers.x -= operand;
|
||||
registers.flags.set_nz(registers.x);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void asl(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.carry = operand >> 7;
|
||||
operand <<= 1;
|
||||
registers.flags.set_nz(operand);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void aso(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.carry = operand >> 7;
|
||||
operand <<= 1;
|
||||
registers.a |= operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void rol(RegistersT ®isters, uint8_t &operand) {
|
||||
const uint8_t temp8 = uint8_t((operand << 1) | registers.flags.carry);
|
||||
registers.flags.carry = operand >> 7;
|
||||
registers.flags.set_nz(operand = temp8);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void rla(RegistersT ®isters, uint8_t &operand) {
|
||||
const uint8_t temp8 = uint8_t((operand << 1) | registers.flags.carry);
|
||||
registers.flags.carry = operand >> 7;
|
||||
operand = temp8;
|
||||
registers.a &= operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void lsr(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.carry = operand & 1;
|
||||
operand >>= 1;
|
||||
registers.flags.set_nz(operand);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void lse(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.carry = operand & 1;
|
||||
operand >>= 1;
|
||||
registers.a ^= operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void asr(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.a &= operand;
|
||||
registers.flags.carry = registers.a & 1;
|
||||
registers.a >>= 1;
|
||||
registers.flags.set_nz(registers.a);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void ror(RegistersT ®isters, uint8_t &operand) {
|
||||
const uint8_t temp8 = uint8_t((operand >> 1) | (registers.flags.carry << 7));
|
||||
registers.flags.carry = operand & 1;
|
||||
registers.flags.set_nz(operand = temp8);
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT>
|
||||
void rra(RegistersT ®isters, uint8_t &operand) {
|
||||
const uint8_t temp8 = uint8_t((operand >> 1) | (registers.flags.carry << 7));
|
||||
registers.flags.carry = operand & 1;
|
||||
Operations::adc<model>(registers, temp8);
|
||||
operand = temp8;
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void compare(RegistersT ®isters, const uint8_t lhs, const uint8_t rhs) {
|
||||
registers.flags.carry = rhs <= lhs;
|
||||
registers.flags.set_nz(lhs - rhs);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void bit(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.flags.zero_result = operand & registers.a;
|
||||
registers.flags.negative_result = operand;
|
||||
registers.flags.overflow = operand & Flag::Overflow;
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void bit_no_nv(RegistersT ®isters, const uint8_t operand) {
|
||||
registers.flags.zero_result = operand & registers.a;
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void trb(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.zero_result = operand & registers.a;
|
||||
operand &= ~registers.a;
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void tsb(RegistersT ®isters, uint8_t &operand) {
|
||||
registers.flags.zero_result = operand & registers.a;
|
||||
operand |= registers.a;
|
||||
}
|
||||
|
||||
inline void rmb(uint8_t &operand, const uint8_t opcode) {
|
||||
operand &= ~(1 << (opcode >> 4));
|
||||
}
|
||||
|
||||
inline void smb(uint8_t &operand, const uint8_t opcode) {
|
||||
operand |= 1 << ((opcode >> 4)&7);
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void sha(RegistersT ®isters, RegisterPair16 &address, uint8_t &operand, const bool did_adjust_top) {
|
||||
if(did_adjust_top) {
|
||||
address.halves.high = operand = registers.a & registers.x & address.halves.high;
|
||||
} else {
|
||||
operand = registers.a & registers.x & (address.halves.high + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void shx(RegistersT ®isters, RegisterPair16 &address, uint8_t &operand, const bool did_adjust_top) {
|
||||
if(did_adjust_top) {
|
||||
address.halves.high = operand = registers.x & address.halves.high;
|
||||
} else {
|
||||
operand = registers.x & (address.halves.high + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void shy(RegistersT ®isters, RegisterPair16 &address, uint8_t &operand, const bool did_adjust_top) {
|
||||
if(did_adjust_top) {
|
||||
address.halves.high = operand = registers.y & address.halves.high;
|
||||
} else {
|
||||
operand = registers.y & (address.halves.high + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
void shs(RegistersT ®isters, RegisterPair16 &address, uint8_t &operand, const bool did_adjust_top) {
|
||||
registers.s = registers.a & registers.x;
|
||||
if(did_adjust_top) {
|
||||
address.halves.high = operand = registers.s & address.halves.high;
|
||||
} else {
|
||||
operand = registers.s & (address.halves.high + 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename RegistersT>
|
||||
bool test(const Operation operation, RegistersT ®isters) {
|
||||
switch(operation) {
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
|
||||
case Operation::BPL: return !(registers.flags.negative_result & 0x80);
|
||||
case Operation::BMI: return registers.flags.negative_result & 0x80;
|
||||
case Operation::BVC: return !registers.flags.overflow;
|
||||
case Operation::BVS: return registers.flags.overflow;
|
||||
case Operation::BCC: return !registers.flags.carry;
|
||||
case Operation::BCS: return registers.flags.carry;
|
||||
case Operation::BNE: return registers.flags.zero_result;
|
||||
case Operation::BEQ: return !registers.flags.zero_result;
|
||||
case Operation::BRA: return true;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool test_bbr_bbs(const uint8_t opcode, const uint8_t test_byte) {
|
||||
const auto mask = uint8_t(1 << ((opcode >> 4)&7)); // Get bit.
|
||||
const auto required = (opcode & 0x80) ? mask : 0; // Check for BBR or BBS.
|
||||
return (test_byte & mask) == required; // Compare.
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT>
|
||||
void perform(
|
||||
const Operation operation,
|
||||
RegistersT ®isters,
|
||||
uint8_t &operand,
|
||||
const uint8_t opcode
|
||||
) {
|
||||
switch(operation) {
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
|
||||
case Operation::NOP: break;
|
||||
|
||||
// MARK: - Bitwise logic.
|
||||
|
||||
case Operation::ORA: registers.flags.set_nz(registers.a |= operand); break;
|
||||
case Operation::AND: registers.flags.set_nz(registers.a &= operand); break;
|
||||
case Operation::EOR: registers.flags.set_nz(registers.a ^= operand); break;
|
||||
|
||||
// MARK: - Loads and stores.
|
||||
|
||||
case Operation::LDA: registers.flags.set_nz(registers.a = operand); break;
|
||||
case Operation::LDX: registers.flags.set_nz(registers.x = operand); break;
|
||||
case Operation::LDY: registers.flags.set_nz(registers.y = operand); break;
|
||||
case Operation::LAX: registers.flags.set_nz(registers.a = registers.x = operand); break;
|
||||
case Operation::LXA:
|
||||
registers.a = registers.x = (registers.a | 0xee) & operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
break;
|
||||
case Operation::PLP: registers.flags = Flags(operand); break;
|
||||
|
||||
case Operation::STA: operand = registers.a; break;
|
||||
case Operation::STX: operand = registers.x; break;
|
||||
case Operation::STY: operand = registers.y; break;
|
||||
case Operation::STZ: operand = 0; break;
|
||||
case Operation::SAX: operand = registers.a & registers.x; break;
|
||||
case Operation::PHP: operand = static_cast<uint8_t>(registers.flags) | Flag::Break; break;
|
||||
|
||||
case Operation::CLC: registers.flags.carry = 0; break;
|
||||
case Operation::CLI: registers.flags.inverse_interrupt = Flag::Interrupt; break;
|
||||
case Operation::CLV: registers.flags.overflow = 0; break;
|
||||
case Operation::CLD: registers.flags.decimal = 0; break;
|
||||
case Operation::SEC: registers.flags.carry = Flag::Carry; break;
|
||||
case Operation::SEI: registers.flags.inverse_interrupt = 0; break;
|
||||
case Operation::SED: registers.flags.decimal = Flag::Decimal; break;
|
||||
|
||||
case Operation::ANE: Operations::ane(registers, operand); break;
|
||||
case Operation::ANC: Operations::anc(registers, operand); break;
|
||||
case Operation::LAS:
|
||||
registers.a = registers.x = registers.s = registers.s & operand;
|
||||
registers.flags.set_nz(registers.a);
|
||||
break;
|
||||
|
||||
// MARK: - Transfers.
|
||||
|
||||
case Operation::TXA: registers.flags.set_nz(registers.a = registers.x); break;
|
||||
case Operation::TYA: registers.flags.set_nz(registers.a = registers.y); break;
|
||||
case Operation::TXS: registers.s = registers.x; break;
|
||||
case Operation::TAY: registers.flags.set_nz(registers.y = registers.a); break;
|
||||
case Operation::TAX: registers.flags.set_nz(registers.x = registers.a); break;
|
||||
case Operation::TSX: registers.flags.set_nz(registers.x = registers.s); break;
|
||||
|
||||
// MARK: - Increments and decrements.
|
||||
|
||||
case Operation::INC: registers.flags.set_nz(++operand); break;
|
||||
case Operation::DEC: registers.flags.set_nz(--operand); break;
|
||||
case Operation::INA: registers.flags.set_nz(++registers.a); break;
|
||||
case Operation::DEA: registers.flags.set_nz(--registers.a); break;
|
||||
case Operation::INX: registers.flags.set_nz(++registers.x); break;
|
||||
case Operation::DEX: registers.flags.set_nz(--registers.x); break;
|
||||
case Operation::INY: registers.flags.set_nz(++registers.y); break;
|
||||
case Operation::DEY: registers.flags.set_nz(--registers.y); break;
|
||||
|
||||
// MARK: - Shifts and rolls.
|
||||
|
||||
case Operation::ASL: Operations::asl(registers, operand); break;
|
||||
case Operation::ASO: Operations::aso(registers, operand); break;
|
||||
case Operation::ROL: Operations::rol(registers, operand); break;
|
||||
case Operation::RLA: Operations::rla(registers, operand); break;
|
||||
case Operation::LSR: Operations::lsr(registers, operand); break;
|
||||
case Operation::LSE: Operations::lse(registers, operand); break;
|
||||
case Operation::ASR: Operations::asr(registers, operand); break;
|
||||
case Operation::ROR: Operations::ror(registers, operand); break;
|
||||
case Operation::RRA: Operations::rra<model>(registers, operand); break;
|
||||
|
||||
// MARK: - Bit logic.
|
||||
|
||||
case Operation::BIT: Operations::bit(registers, operand); break;
|
||||
case Operation::BITNoNV: Operations::bit_no_nv(registers, operand); break;
|
||||
case Operation::TRB: Operations::trb(registers, operand); break;
|
||||
case Operation::TSB: Operations::tsb(registers, operand); break;
|
||||
case Operation::RMB: Operations::rmb(operand, opcode); break;
|
||||
case Operation::SMB: Operations::smb(operand, opcode); break;
|
||||
|
||||
// MARK: - Compare
|
||||
|
||||
case Operation::DCP:
|
||||
--operand;
|
||||
Operations::compare(registers, registers.a, operand);
|
||||
break;
|
||||
case Operation::CMP: Operations::compare(registers, registers.a, operand); break;
|
||||
case Operation::CPX: Operations::compare(registers, registers.x, operand); break;
|
||||
case Operation::CPY: Operations::compare(registers, registers.y, operand); break;
|
||||
|
||||
// MARK: - Arithmetic.
|
||||
|
||||
case Operation::INS:
|
||||
++operand;
|
||||
Operations::sbc<model>(registers, operand);
|
||||
break;
|
||||
case Operation::SBC: Operations::sbc<model>(registers, operand); break;
|
||||
case Operation::ADC: Operations::adc<model>(registers, operand); break;
|
||||
case Operation::ARR: Operations::arr<model>(registers, operand); break;
|
||||
case Operation::SBX: Operations::sbx(registers, operand); break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
129
Processors/6502Mk2/Registers.hpp
Normal file
129
Processors/6502Mk2/Registers.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Registers.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/10/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Numeric/RegisterSizes.hpp"
|
||||
|
||||
#include <compare>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CPU::MOS6502Mk2 {
|
||||
|
||||
enum class Register {
|
||||
LastOperationAddress,
|
||||
ProgramCounter,
|
||||
StackPointer,
|
||||
Flags,
|
||||
A,
|
||||
X,
|
||||
Y,
|
||||
|
||||
//
|
||||
// 65816 only.
|
||||
//
|
||||
EmulationFlag,
|
||||
DataBank,
|
||||
ProgramBank,
|
||||
Direct
|
||||
};
|
||||
|
||||
/*
|
||||
Flags as defined on the 6502; can be used to decode the result of @c value_of(Flags) or to form a value for
|
||||
the corresponding set.
|
||||
*/
|
||||
enum Flag: uint8_t {
|
||||
Sign = 0b1000'0000,
|
||||
Overflow = 0b0100'0000,
|
||||
Always = 0b0010'0000,
|
||||
Break = 0b0001'0000,
|
||||
Decimal = 0b0000'1000,
|
||||
Interrupt = 0b0000'0100,
|
||||
Zero = 0b0000'0010,
|
||||
Carry = 0b0000'0001,
|
||||
|
||||
//
|
||||
// 65816 only.
|
||||
//
|
||||
MemorySize = Always,
|
||||
IndexSize = Break,
|
||||
};
|
||||
|
||||
struct Flags {
|
||||
/// Sets N and Z flags per the 8-bit value @c value.
|
||||
void set_nz(const uint8_t value) {
|
||||
zero_result = negative_result = value;
|
||||
}
|
||||
|
||||
/// Sets N and Z flags per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
|
||||
void set_nz(const uint16_t value, const int shift) {
|
||||
negative_result = uint8_t(value >> shift);
|
||||
zero_result = uint8_t(value | (value >> shift));
|
||||
}
|
||||
|
||||
/// Sets the Z flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
|
||||
void set_z(const uint16_t value, const int shift) {
|
||||
zero_result = uint8_t(value | (value >> shift));
|
||||
}
|
||||
|
||||
/// Sets the N flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
|
||||
void set_n(const uint16_t value, const int shift) {
|
||||
negative_result = uint8_t(value >> shift);
|
||||
}
|
||||
|
||||
void set_v(const uint8_t result, const uint8_t lhs, const uint8_t rhs) {
|
||||
// TODO: can this be done lazily?
|
||||
overflow = (( (result^lhs) & (result^rhs) ) & 0x80) >> 1;
|
||||
}
|
||||
|
||||
explicit operator uint8_t() const {
|
||||
return
|
||||
carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) |
|
||||
(zero_result ? 0 : Flag::Zero) | Flag::Always | Flag::Break | decimal;
|
||||
}
|
||||
|
||||
Flags() {
|
||||
// Only the interrupt flag is defined upon reset but get_flags isn't going to
|
||||
// mask the other flags so we need to do that, at least.
|
||||
carry &= Flag::Carry;
|
||||
decimal &= Flag::Decimal;
|
||||
overflow &= Flag::Overflow;
|
||||
}
|
||||
|
||||
Flags(const uint8_t flags) {
|
||||
carry = flags & Flag::Carry;
|
||||
negative_result = flags & Flag::Sign;
|
||||
zero_result = (~flags) & Flag::Zero;
|
||||
overflow = flags & Flag::Overflow;
|
||||
inverse_interrupt = (~flags) & Flag::Interrupt;
|
||||
decimal = flags & Flag::Decimal;
|
||||
}
|
||||
|
||||
auto operator <=> (const Flags &rhs) const {
|
||||
return static_cast<uint8_t>(*this) <=> static_cast<uint8_t>(rhs);
|
||||
}
|
||||
|
||||
uint8_t negative_result = 0; /// Bit 7 = the negative flag.
|
||||
uint8_t zero_result = 0; /// Non-zero if the zero flag is clear, zero if it is set.
|
||||
uint8_t carry = 0; /// Contains Flag::Carry.
|
||||
uint8_t decimal = 0; /// Contains Flag::Decimal.
|
||||
uint8_t overflow = 0; /// Contains Flag::Overflow.
|
||||
uint8_t inverse_interrupt = 0; /// Contains Flag::Interrupt, complemented.
|
||||
};
|
||||
|
||||
struct Registers {
|
||||
uint8_t a, x, y, s;
|
||||
RegisterPair16 pc;
|
||||
Flags flags;
|
||||
|
||||
auto operator <=> (const Registers &) const = default;
|
||||
|
||||
uint8_t dec_s() { return s--; }
|
||||
uint8_t inc_s() { return ++s; }
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user