1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

82 Commits

Author SHA1 Message Date
Thomas Harte
8b04608d68 Implement STP and WAI. 2025-10-24 23:47:30 -04:00
Thomas Harte
2bac276870 Populate type_of. 2025-10-24 23:42:40 -04:00
Thomas Harte
213f9850e7 Add WDC65C02 decoder. 2025-10-24 23:41:55 -04:00
Thomas Harte
378bffbf84 Implement BBR/BBS. 2025-10-24 23:37:18 -04:00
Thomas Harte
c291d5313d Fix PLX. 2025-10-24 22:22:05 -04:00
Thomas Harte
1f6f665639 Exclude all 65c02 NOPs from timing checks. 2025-10-24 22:19:15 -04:00
Thomas Harte
e81233c586 Implement JMP (abs,x). 2025-10-24 22:16:36 -04:00
Thomas Harte
b946029394 Correct 65c02 JMPAbsoluteIndirect. 2025-10-24 21:57:53 -04:00
Thomas Harte
6095936354 Exclude tests I believe faulty. 2025-10-24 21:54:23 -04:00
Thomas Harte
fe79a1231d Add extra cycle to immediate decimal arithmetic. 2025-10-24 21:39:14 -04:00
Thomas Harte
76a5872d17 Install extra cycle for 65c02 decimal arithmetic. 2025-10-24 21:24:05 -04:00
Thomas Harte
0d72c75e15 Give modify stalls to fast NOPs. 2025-10-24 16:51:07 -04:00
Thomas Harte
2e0e89c494 Implement fast modify path; fix more NOPs. 2025-10-24 16:43:57 -04:00
Thomas Harte
7d6b7a5874 Adjust 0x?b NOPs. 2025-10-24 15:57:54 -04:00
Thomas Harte
48f8ddf53a 65c02: make AbsoluteIndexed modify cycle harmless. 2025-10-24 15:55:45 -04:00
Thomas Harte
9aae07b737 Implement zero indirect addressing mode. 2025-10-24 15:53:55 -04:00
Thomas Harte
d7abdc8017 Transfer ownership of final PC increment, to accomodate 65c02 misreads. 2025-10-24 15:49:17 -04:00
Thomas Harte
cb81156835 65c02: distinguish 'fast' NOPs from regular. 2025-10-24 13:52:32 -04:00
Thomas Harte
1fd8d94e2e Import further NOPs. 2025-10-24 13:33:10 -04:00
Thomas Harte
e4fe127444 Fix 65c02 modify cycles: read/read/write, not read/write/write. 2025-10-24 13:30:10 -04:00
Thomas Harte
aeabd5f113 Patch in TSB and TRB. 2025-10-24 12:33:13 -04:00
Thomas Harte
58f7d4065c 65c02: support single-cycle NOP. 2025-10-24 12:29:53 -04:00
Thomas Harte
60f25a3ba4 Add some of the easier overrides. 2025-10-24 12:21:53 -04:00
Thomas Harte
d267571dc6 Add spot to fill in Synertek mappings. 2025-10-24 12:13:10 -04:00
Thomas Harte
3c34aa6696 Setup to test 65c02s. 2025-10-24 12:07:40 -04:00
Thomas Harte
5dc00a2092 Update #undef list. 2025-10-24 12:00:22 -04:00
Thomas Harte
b20d489bf0 Remove SHA/SHX/etc. 2025-10-24 11:58:55 -04:00
Thomas Harte
df39870587 Factor out index decision. 2025-10-24 11:53:55 -04:00
Thomas Harte
f742eab4be Reduce to a generic case. 2025-10-23 21:54:50 -04:00
Thomas Harte
e9c8c61dcf Reformulate to be slightly more conditional, but substantially deduplicate code. 2025-10-23 21:52:31 -04:00
Thomas Harte
e5f09002e9 Extract bit operators. 2025-10-23 20:47:55 -04:00
Thomas Harte
d42f005e17 Improve consistency. 2025-10-23 20:43:15 -04:00
Thomas Harte
24e060abee Elide ADC logic. 2025-10-23 19:54:07 -04:00
Thomas Harte
8b6d763442 Reduce duplication within ARR. 2025-10-23 19:42:36 -04:00
Thomas Harte
e239745f63 Fix typo. 2025-10-23 19:35:40 -04:00
Thomas Harte
cfef2b4e19 Eliminate 16-bit arithmetic from SBX. 2025-10-23 19:32:50 -04:00
Thomas Harte
cf93c39881 Pull out overflow logic, remove 16-bit arithmetic from ADC. 2025-10-23 18:23:09 -04:00
Thomas Harte
5d223bce4c Pull out and simplify compare. 2025-10-23 17:47:15 -04:00
Thomas Harte
b454ebc1c9 Extricate further operations. 2025-10-23 17:41:13 -04:00
Thomas Harte
7cf9910cae Pull ADC, SBC and some others out.
This resolves the wacky control flow somewhat.
2025-10-23 17:15:21 -04:00
Thomas Harte
79ab1d8cb1 Implement final SHA. 2025-10-23 13:42:23 -04:00
Thomas Harte
7cd20f5d12 Add all absolute-indexed oddities. 2025-10-23 13:39:03 -04:00
Thomas Harte
5396d751e1 Support SHX and a SHA. 2025-10-23 13:27:55 -04:00
Thomas Harte
d23e715650 Decision: these five have weird addressing, so that counts as weird addressing modes. 2025-10-23 13:13:01 -04:00
Thomas Harte
0791bce338 Fix everything other than the oddball SHA/SHX/SHY/SHS. 2025-10-22 22:12:32 -04:00
Thomas Harte
2bcb74072a Add trqnsfers, correct a STA. 2025-10-22 21:20:11 -04:00
Thomas Harte
c5f2f17f33 Further populate perform.
First failing test is now 0x8a.
2025-10-22 21:13:57 -04:00
Thomas Harte
62a8bf4261 Add missing RTS cycle. 2025-10-22 17:56:11 -04:00
Thomas Harte
ebda18b44e Implement the two JMPs. 2025-10-22 17:52:55 -04:00
Thomas Harte
a8f41b9017 Implement RTI and RTS. 2025-10-22 17:48:19 -04:00
Thomas Harte
410c19a7da Fix pull. 2025-10-22 17:43:08 -04:00
Thomas Harte
a346e2e04b Transcribe bit logic. 2025-10-22 17:40:03 -04:00
Thomas Harte
2d114b6677 Implement JSR. 2025-10-22 17:37:01 -04:00
Thomas Harte
02e74ca1f4 Add absolute-indexed addressing. 2025-10-22 17:18:54 -04:00
Thomas Harte
69122cdec4 Swing at zero-indexed addressing. 2025-10-22 17:12:23 -04:00
Thomas Harte
d730168631 Remove dead label. 2025-10-22 13:30:24 -04:00
Thomas Harte
2f210ebe3b Fix IndexedIndirect/IndirectIndexed confusion, proceed to test 0x14. 2025-10-22 13:29:45 -04:00
Thomas Harte
693b53baa2 Proceed through absolute addressing to test 0x10. 2025-10-22 13:05:46 -04:00
Thomas Harte
77554879a5 Add missing 0x?e group. 2025-10-22 13:00:36 -04:00
Thomas Harte
45363922b5 Adds rolls and shifts, and zero-page addressing. 2025-10-22 12:56:07 -04:00
Thomas Harte
0463c1ceda Reduce repetition. 2025-10-21 23:21:11 -04:00
Thomas Harte
b35a55a658 Implement jamming. 2025-10-21 23:16:59 -04:00
Thomas Harte
4da68c9fa8 Implement IndirectIndexedRead. 2025-10-21 23:01:41 -04:00
Thomas Harte
72f133f31b Do enough work to verify BRK. 2025-10-21 22:07:35 -04:00
Thomas Harte
af4a8f6d9c Add enough to attempt to run processor tests. 2025-10-21 21:30:42 -04:00
Thomas Harte
b5899a2e42 Implement simplest operations. 2025-10-21 17:33:36 -04:00
Thomas Harte
4ee8f8564e Catch unimplemented. 2025-10-21 13:40:23 -04:00
Thomas Harte
ff08c03bc5 Coral into building. 2025-10-21 13:31:48 -04:00
Thomas Harte
95dd430b0d Shoehorn in an invocation. 2025-10-21 13:12:58 -04:00
Thomas Harte
20eb8b1442 Move RDY inline. 2025-10-21 12:59:16 -04:00
Thomas Harte
2d6a0b3ed0 Add a branch to nowhere. 2025-10-20 23:08:04 -04:00
Thomas Harte
80f0ce78e0 Eliminate unused enum. 2025-10-20 22:51:12 -04:00
Thomas Harte
fde0e2434e Attempt to transcribe base 6502 instruction set. 2025-10-20 22:50:14 -04:00
Thomas Harte
fe2da7fd95 Merge branch 'master' into Turbo6502 2025-10-20 13:56:58 -04:00
Thomas Harte
25e783ff2f Merge pull request #1614 from TomHarte/6845Reading
Return 0 for write-only and nonexistent registers.
2025-10-20 13:56:27 -04:00
Thomas Harte
2eb94f1b66 Return 0 for write-only and nonexistent registers. 2025-10-20 13:26:22 -04:00
Thomas Harte
2cdf6ac8f9 Add interrupt, RDY and instruction fetch logic. 2025-10-20 13:16:03 -04:00
Thomas Harte
309c58a93d Include in CI builds; start implementation. 2025-10-19 23:29:27 -04:00
Thomas Harte
700bd0ddd4 Merge branch 'master' into Turbo6502 2025-10-19 22:21:19 -04:00
Thomas Harte
24fcbea6f2 Add TODO. 2025-10-19 19:28:38 -04:00
Thomas Harte
fddc9c8c48 Add base classes, reshuffle. 2025-10-18 22:45:09 -04:00
Thomas Harte
294893b7da Start transferring 6502 precepts. 2025-10-18 22:31:00 -04:00
10 changed files with 2478 additions and 2 deletions

View File

@@ -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),
};

View File

@@ -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) {

View File

@@ -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 */,

View 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

View 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 &registers() const { return registers_; }
void set_registers(const Registers &registers) {
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"

View 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};
}
}
};
}

View 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 &registers = 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
}
}

View 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; }
}

View 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 &registers, const uint8_t operand) {
registers.a = (registers.a | 0xee) & operand & registers.x;
registers.flags.set_nz(registers.a);
}
template <typename RegistersT>
void anc(RegistersT &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, uint8_t &operand) {
registers.flags.carry = operand >> 7;
operand <<= 1;
registers.flags.set_nz(operand);
}
template <typename RegistersT>
void aso(RegistersT &registers, 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 &registers, 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 &registers, 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 &registers, uint8_t &operand) {
registers.flags.carry = operand & 1;
operand >>= 1;
registers.flags.set_nz(operand);
}
template <typename RegistersT>
void lse(RegistersT &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, const uint8_t operand) {
registers.flags.zero_result = operand & registers.a;
}
template <typename RegistersT>
void trb(RegistersT &registers, uint8_t &operand) {
registers.flags.zero_result = operand & registers.a;
operand &= ~registers.a;
}
template <typename RegistersT>
void tsb(RegistersT &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers, 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 &registers) {
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 &registers,
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;
}
}
}

View 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; }
};
}