From 236fdacb36275e8934d3a9f0de76b6b3caf5ed26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 14:14:25 -0500 Subject: [PATCH 1/9] Adapt Plus 4 to the newer 6502. --- Machines/Commodore/Plus4/Plus4.cpp | 364 ++++++++++++++--------------- Processors/6502Mk2/6502Mk2.hpp | 4 + 2 files changed, 183 insertions(+), 185 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 66f4e9dc0..28c82e6a4 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -16,7 +16,7 @@ #include "Machines/MachineTypes.hpp" #include "Machines/Utility/MemoryFuzzer.hpp" -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Analyser/Static/Commodore/Target.hpp" #include "Outputs/Log.hpp" #include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp" @@ -242,14 +242,11 @@ public: // HACK. NOCOMMIT. // int pulse_num_ = 0; - Cycles perform_bus_operation( - const CPU::MOS6502::BusOperation operation, - const uint16_t address, - uint8_t *const value - ) { + template + Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { // Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502, // relative to the master clock. - const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); + const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready); // Update other subsystems. advance_timers_and_tape(length); @@ -265,7 +262,7 @@ public: time_since_audio_update_ += length; } - if(operation == CPU::MOS6502::BusOperation::Ready) { + if(operation == CPU::MOS6502Mk2::BusOperation::Ready) { return length; } @@ -282,17 +279,17 @@ public: // b1 = serial clock out and cassette write; // b0 = serial data out. - if(is_read(operation)) { + if constexpr (is_read(operation)) { if(!address) { - *value = io_direction_; + value = io_direction_; } else { - *value = io_input(); + value = io_input(); } } else { if(!address) { - io_direction_ = *value; + io_direction_ = value; } else { - io_output_ = *value; + io_output_ = value; } const auto output = io_output_ | ~io_direction_; @@ -323,36 +320,36 @@ public: // ); // } - if( - use_fast_tape_hack_ && - operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && - ( - (use_hle && address == 0xe5fd) || - address == 0xe68b || - address == 0xe68d - ) - ) { -// ++pulse_num_; - if(use_hle) { - read_dipole(); - } + if constexpr (is_read(operation)) { + if( + use_fast_tape_hack_ && + operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode && + ( + (use_hle && address == 0xe5fd) || + address == 0xe68b || + address == 0xe68d + ) + ) { + // ++pulse_num_; + if(use_hle) { + read_dipole(); + } -// using Flag = CPU::MOS6502::Flag; -// using Register = CPU::MOS6502::Register; -// const auto flags = m6502_.value_of(Register::Flags); -// printf("to %lld: %c%c%c\n", -// tape_player_->serialiser()->offset(), -// flags & Flag::Sign ? 'n' : '-', -// flags & Flag::Overflow ? 'v' : '-', -// flags & Flag::Carry ? 'c' : '-' -// ); - *value = 0x60; - } else { - if(is_read(operation)) { - *value = map_.read(address); + // using Flag = CPU::MOS6502::Flag; + // using Register = CPU::MOS6502::Register; + // const auto flags = m6502_.value_of(Register::Flags); + // printf("to %lld: %c%c%c\n", + // tape_player_->serialiser()->offset(), + // flags & Flag::Sign ? 'n' : '-', + // flags & Flag::Overflow ? 'v' : '-', + // flags & Flag::Carry ? 'c' : '-' + // ); + value = 0x60; } else { - map_.write(address) = *value; + value = map_.read(address); } + } else { + map_.write(address) = value; } @@ -393,12 +390,12 @@ public: // ram_[0x90] = 0; // ram_[0x93] = 0; // -// *value = 0x0c; // NOP abs. +// value = 0x0c; // NOP abs. // } // } } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. - if(is_read(operation)) { + if constexpr (is_read(operation)) { switch(address & 0xfff0) { case 0xfd10: // 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies. @@ -406,7 +403,7 @@ public: // If play button is not currently pressed and this read is immediately followed by // an AND 4, press it. The kernel will deal with motor control subsequently. if(!play_button_) { - const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter); + const uint16_t pc = m6502_.registers().pc.full; const uint8_t next[] = { map_.read(pc+0), map_.read(pc+1), @@ -422,12 +419,12 @@ public: } } - *value = 0xff ^ (play_button_ ? 0x4 :0x0); + value = 0xff ^ (play_button_ ? 0x4 :0x0); break; case 0xfdd0: case 0xfdf0: - *value = uint8_t(address >> 8); + value = uint8_t(address >> 8); break; default: @@ -437,7 +434,7 @@ public: } else { switch(address & 0xfff0) { case 0xfd30: - keyboard_mask_ = *value; + keyboard_mask_ = value; break; case 0xfdd0: { @@ -447,28 +444,28 @@ public: } break; default: - Logger::info().append("TODO: write of %02x @ %04x", *value, address); + Logger::info().append("TODO: write of %02x @ %04x", value, address); break; } } } else { - const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter); + const auto pc = m6502_.registers().pc.full; const bool is_from_rom = (rom_is_paged_ && pc >= 0x8000) || (pc >= 0x400 && pc < 0x500) || (pc >= 0x700 && pc < 0x800); bool is_hit = true; - if(is_read(operation)) { + if constexpr (is_read(operation)) { switch(address) { - case 0xff00: *value = timers_.read<0>(); break; - case 0xff01: *value = timers_.read<1>(); break; - case 0xff02: *value = timers_.read<2>(); break; - case 0xff03: *value = timers_.read<3>(); break; - case 0xff04: *value = timers_.read<4>(); break; - case 0xff05: *value = timers_.read<5>(); break; - case 0xff06: *value = video_.read<0xff06>(); break; - case 0xff07: *value = video_.read<0xff07>(); break; + case 0xff00: value = timers_.read<0>(); break; + case 0xff01: value = timers_.read<1>(); break; + case 0xff02: value = timers_.read<2>(); break; + case 0xff03: value = timers_.read<3>(); break; + case 0xff04: value = timers_.read<4>(); break; + case 0xff05: value = timers_.read<5>(); break; + case 0xff06: value = video_.read<0xff06>(); break; + case 0xff07: value = video_.read<0xff07>(); break; case 0xff08: { const uint8_t keyboard_input = ~( @@ -487,36 +484,36 @@ public: ((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) & ((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80)); - *value = keyboard_input & joystick_mask; + value = keyboard_input & joystick_mask; } break; - case 0xff09: *value = interrupts_.status(); break; + case 0xff09: value = interrupts_.status(); break; case 0xff0a: - *value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0; + value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0; break; - case 0xff0b: *value = video_.read<0xff0b>(); break; - case 0xff0c: *value = video_.read<0xff0c>(); break; - case 0xff0d: *value = video_.read<0xff0d>(); break; - case 0xff0e: *value = ff0e_; break; - case 0xff0f: *value = ff0f_; break; - case 0xff10: *value = ff10_ | 0xfc; break; - case 0xff11: *value = ff11_; break; - case 0xff12: *value = ff12_ | 0xc0; break; - case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break; - case 0xff14: *value = video_.read<0xff14>(); break; - case 0xff15: *value = video_.read<0xff15>(); break; - case 0xff16: *value = video_.read<0xff16>(); break; - case 0xff17: *value = video_.read<0xff17>(); break; - case 0xff18: *value = video_.read<0xff18>(); break; - case 0xff19: *value = video_.read<0xff19>(); break; - case 0xff1a: *value = video_.read<0xff1a>(); break; - case 0xff1b: *value = video_.read<0xff1b>(); break; - case 0xff1c: *value = video_.read<0xff1c>(); break; - case 0xff1d: *value = video_.read<0xff1d>(); break; - case 0xff1e: *value = video_.read<0xff1e>(); break; - case 0xff1f: *value = video_.read<0xff1f>(); break; + case 0xff0b: value = video_.read<0xff0b>(); break; + case 0xff0c: value = video_.read<0xff0c>(); break; + case 0xff0d: value = video_.read<0xff0d>(); break; + case 0xff0e: value = ff0e_; break; + case 0xff0f: value = ff0f_; break; + case 0xff10: value = ff10_ | 0xfc; break; + case 0xff11: value = ff11_; break; + case 0xff12: value = ff12_ | 0xc0; break; + case 0xff13: value = ff13_ | (rom_is_paged_ ? 1 : 0); break; + case 0xff14: value = video_.read<0xff14>(); break; + case 0xff15: value = video_.read<0xff15>(); break; + case 0xff16: value = video_.read<0xff16>(); break; + case 0xff17: value = video_.read<0xff17>(); break; + case 0xff18: value = video_.read<0xff18>(); break; + case 0xff19: value = video_.read<0xff19>(); break; + case 0xff1a: value = video_.read<0xff1a>(); break; + case 0xff1b: value = video_.read<0xff1b>(); break; + case 0xff1c: value = video_.read<0xff1c>(); break; + case 0xff1d: value = video_.read<0xff1d>(); break; + case 0xff1e: value = video_.read<0xff1e>(); break; + case 0xff1f: value = video_.read<0xff1f>(); break; - case 0xff3e: *value = 0; break; - case 0xff3f: *value = 0; break; + case 0xff3e: value = 0; break; + case 0xff3f: value = 0; break; default: Logger::info().append("TODO: TED read at %04x", address); @@ -524,90 +521,90 @@ public: } } else { switch(address) { - case 0xff00: timers_.write<0>(*value); break; - case 0xff01: timers_.write<1>(*value); break; - case 0xff02: timers_.write<2>(*value); break; - case 0xff03: timers_.write<3>(*value); break; - case 0xff04: timers_.write<4>(*value); break; - case 0xff05: timers_.write<5>(*value); break; - case 0xff06: video_.write<0xff06>(*value); break; + case 0xff00: timers_.write<0>(value); break; + case 0xff01: timers_.write<1>(value); break; + case 0xff02: timers_.write<2>(value); break; + case 0xff03: timers_.write<3>(value); break; + case 0xff04: timers_.write<4>(value); break; + case 0xff05: timers_.write<5>(value); break; + case 0xff06: video_.write<0xff06>(value); break; case 0xff07: - video_.write<0xff07>(*value); + video_.write<0xff07>(value); update_audio(); - audio_.set_divider(*value); + audio_.set_divider(value); break; case 0xff08: // Observation here: the kernel posts a 0 to this // address upon completing each keyboard scan cycle, // once per frame. - if(typer_ && !*value) { + if(typer_ && !value) { if(!typer_->type_next_character()) { clear_all_keys(); typer_.reset(); } } - joystick_mask_ = *value; + joystick_mask_ = value; break; case 0xff09: - interrupts_.set_status(*value); + interrupts_.set_status(value); break; case 0xff0a: - interrupts_.set_mask(*value); - video_.write<0xff0a>(*value); + interrupts_.set_mask(value); + video_.write<0xff0a>(value); break; - case 0xff0b: video_.write<0xff0b>(*value); break; - case 0xff0c: video_.write<0xff0c>(*value); break; - case 0xff0d: video_.write<0xff0d>(*value); break; + case 0xff0b: video_.write<0xff0b>(value); break; + case 0xff0c: video_.write<0xff0c>(value); break; + case 0xff0d: video_.write<0xff0d>(value); break; case 0xff0e: - ff0e_ = *value; + ff0e_ = value; update_audio(); - audio_.set_frequency_low<0>(*value); + audio_.set_frequency_low<0>(value); break; case 0xff0f: - ff0f_ = *value; + ff0f_ = value; update_audio(); - audio_.set_frequency_low<1>(*value); + audio_.set_frequency_low<1>(value); break; case 0xff10: - ff10_ = *value; + ff10_ = value; update_audio(); - audio_.set_frequency_high<1>(*value); + audio_.set_frequency_high<1>(value); break; case 0xff11: - ff11_ = *value; + ff11_ = value; update_audio(); - audio_.set_control(*value); + audio_.set_control(value); break; case 0xff12: - ff12_ = *value & 0x3f; - video_.write<0xff12>(*value); + ff12_ = value & 0x3f; + video_.write<0xff12>(value); - if((*value & 4)) { + if((value & 4)) { page_video_rom(); } else { page_video_ram(); } update_audio(); - audio_.set_frequency_high<0>(*value); + audio_.set_frequency_high<0>(value); break; case 0xff13: - ff13_ = *value & 0xfe; - video_.write<0xff13>(*value); + ff13_ = value & 0xfe; + video_.write<0xff13>(value); break; - case 0xff14: video_.write<0xff14>(*value); break; - case 0xff15: video_.write<0xff15>(*value); break; - case 0xff16: video_.write<0xff16>(*value); break; - case 0xff17: video_.write<0xff17>(*value); break; - case 0xff18: video_.write<0xff18>(*value); break; - case 0xff19: video_.write<0xff19>(*value); break; - case 0xff1a: video_.write<0xff1a>(*value); break; - case 0xff1b: video_.write<0xff1b>(*value); break; - case 0xff1c: video_.write<0xff1c>(*value); break; - case 0xff1d: video_.write<0xff1d>(*value); break; - case 0xff1e: video_.write<0xff1e>(*value); break; - case 0xff1f: video_.write<0xff1f>(*value); break; + case 0xff14: video_.write<0xff14>(value); break; + case 0xff15: video_.write<0xff15>(value); break; + case 0xff16: video_.write<0xff16>(value); break; + case 0xff17: video_.write<0xff17>(value); break; + case 0xff18: video_.write<0xff18>(value); break; + case 0xff19: video_.write<0xff19>(value); break; + case 0xff1a: video_.write<0xff1a>(value); break; + case 0xff1b: video_.write<0xff1b>(value); break; + case 0xff1c: video_.write<0xff1c>(value); break; + case 0xff1d: video_.write<0xff1d>(value); break; + case 0xff1e: video_.write<0xff1e>(value); break; + case 0xff1f: video_.write<0xff1f>(value); break; case 0xff3e: page_cpu_rom(); break; case 0xff3f: page_cpu_ram(); break; @@ -626,8 +623,12 @@ public: } private: - using Processor = CPU::MOS6502::Processor; - Processor m6502_; + struct M6502Traits { + static constexpr auto uses_ready_line = true; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions; + using BusHandlerT = ConcreteMachine; + }; + CPU::MOS6502Mk2::Processor m6502_; Outputs::Speaker::Speaker *get_speaker() override { return &speaker_; @@ -637,11 +638,11 @@ private: if(c1541_) c1541_->set_activity_observer(observer); } - void set_irq_line(bool active) override { - m6502_.set_irq_line(active); + void set_irq_line(const bool active) override { + m6502_.template set(active); } - void set_ready_line(bool active) override { - m6502_.set_ready_line(active); + void set_ready_line(const bool active) override { + m6502_.template set(active); } void page_video_rom() { @@ -788,26 +789,19 @@ private: // TODO: substantially simplify the below; at the minute it's a // literal transcription of the original as a simple first step. void read_dipole() { - using Register = CPU::MOS6502::Register; - using Flag = CPU::MOS6502::Flag; + using Flag = CPU::MOS6502Mk2::Flag; // // Get registers now and ensure they'll be written back at function exit. // - CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags))); - uint8_t x, y, a; - uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer)); + auto registers = m6502_.registers(); struct ScopeGuard { ScopeGuard(std::function at_exit) : at_exit_(at_exit) {} ~ScopeGuard() { at_exit_(); } private: std::function at_exit_; - } registers([&] { - m6502_.set_value_of(Register::Flags, flags.get()); - m6502_.set_value_of(Register::A, a); - m6502_.set_value_of(Register::X, x); - m6502_.set_value_of(Register::Y, y); - m6502_.set_value_of(Register::StackPointer, s); + } store_registers([&] { + m6502_.set_registers(registers); }); // @@ -822,38 +816,38 @@ private: // 6502 pseudo-ops. // const auto ldabs = [&] (uint8_t &target, const uint16_t address) { - flags.set_nz(target = map_.read(address)); + registers.flags.set_per(target = map_.read(address)); }; const auto ldimm = [&] (uint8_t &target, const uint8_t value) { - flags.set_nz(target = value); + registers.flags.set_per(target = value); }; const auto pha = [&] () { - map_.write(0x100 + s) = a; - --s; + map_.write(0x100 + registers.s) = registers.a; + --registers.s; }; const auto pla = [&] () { - ++s; - a = map_.read(0x100 + s); + ++registers.s; + registers.a = map_.read(0x100 + registers.s); }; const auto bit = [&] (const uint8_t value) { - flags.zero_result = a & value; - flags.negative_result = value; - flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow; + registers.flags.set_per(registers.a & value); + registers.flags.set_per(value); + registers.flags.set_per(value); }; const auto cmp = [&] (const uint8_t value) { - const uint16_t temp16 = a - value; - flags.set_nz(uint8_t(temp16)); - flags.carry = ((~temp16) >> 8)&1; + const uint16_t temp16 = registers.a - value; + registers.flags.set_per(uint8_t(temp16)); + registers.flags.set_per(((~temp16) >> 8)&1); }; const auto andimm = [&] (const uint8_t value) { - a &= value; - flags.set_nz(a); + registers.a &= value; + registers.flags.set_per(registers.a); }; const auto ne = [&]() -> bool { - return flags.zero_result; + return !registers.flags.get(); }; const auto eq = [&]() -> bool { - return !flags.zero_result; + return registers.flags.get(); }; // @@ -862,7 +856,7 @@ private: const auto dipok = [&] { // clc ; everything's fine // rts - flags.carry = 0; + registers.flags.set_per(0); }; const auto rshort = [&] { // bit tshrtd ; got a short @@ -878,7 +872,7 @@ private: const auto rderr1 = [&] { // sec ; i'm confused // rts - flags.carry = Flag::Carry; + registers.flags.set_per(Flag::Carry); }; // @@ -891,8 +885,8 @@ private: //rddipl // ldx dsamp1 ; setup x,y with 1st sample point // ldy dsamp1+1 - ldabs(x, dsamp1); - ldabs(y, dsamp1 + 1); + ldabs(registers.x, dsamp1); + ldabs(registers.y, dsamp1 + 1); advance_cycles(8); //badeg1 @@ -901,9 +895,9 @@ private: // pha // lda dsamp2 // pha - ldabs(a, dsamp2 + 1); + ldabs(registers.a, dsamp2 + 1); pha(); - ldabs(a, dsamp2); + ldabs(registers.a, dsamp2); pha(); advance_cycles(14); @@ -911,7 +905,7 @@ private: //rwtl ; wait till rd line is high // bit port [= $0001] // beq rwtl ; !ls! - ldimm(a, 0x10); + ldimm(registers.a, 0x10); advance_cycles(2); do { bit(io_input()); @@ -933,8 +927,8 @@ private: // stx timr2l // sty timr2h - timers_.write<2>(x); - timers_.write<3>(y); + timers_.write<2>(registers.x); + timers_.write<3>(registers.y); advance_cycles(8); @@ -945,9 +939,9 @@ private: // pla // sta timr3h ;go! ...tb pla(); - timers_.write<4>(a); + timers_.write<4>(registers.a); pla(); - timers_.write<5>(a); + timers_.write<5>(registers.a); advance_cycles(14); @@ -955,8 +949,8 @@ private: // // lda #$50 ; clr ta,tb // sta tedirq - ldimm(a, 0x50); - interrupts_.set_status(a); + ldimm(registers.a, 0x50); + interrupts_.set_status(registers.a); advance_cycles(6); @@ -969,7 +963,7 @@ private: // and #$10 ; a look at that edge again // bne badeg1 ; woa! got a bad edge trigger !ls! do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; @@ -992,7 +986,7 @@ private: // lda #$10 //wata ; wait for ta to timeout - ldimm(a, 0x10); + ldimm(registers.a, 0x10); advance_cycles(3); do { // bit port ; kuldge, kludge, kludge !!! <<><>> @@ -1020,7 +1014,7 @@ private: do { // lda port // cmp port - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { @@ -1048,7 +1042,7 @@ private: // //; wait for tb to timeout //; now do the dipole sample #2 - ldimm(a, 0x40); + ldimm(registers.a, 0x40); advance_cycles(3); do { bit(interrupts_.status()); @@ -1063,7 +1057,7 @@ private: // cmp port // bne casdb3 do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; @@ -1084,10 +1078,10 @@ private: // sta timr2l // lda zcell+1 // sta timr2h - ldabs(a, zcell); - timers_.write<2>(a); - ldabs(a, zcell + 1); - timers_.write<3>(y); + ldabs(registers.a, zcell); + timers_.write<2>(registers.a); + ldabs(registers.a, zcell + 1); + timers_.write<3>(registers.y); advance_cycles(16); @@ -1096,9 +1090,9 @@ private: // lda #$10 // sta tedirq ; verify +180 half of word dipole // lda #$10 - ldimm(a, 0x10); - interrupts_.set_status(a); - ldimm(a, 0x10); + ldimm(registers.a, 0x10); + interrupts_.set_status(registers.a); + ldimm(registers.a, 0x10); advance_cycles(8); //wata2 @@ -1116,7 +1110,7 @@ private: // cmp port // bne casdb4 do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; diff --git a/Processors/6502Mk2/6502Mk2.hpp b/Processors/6502Mk2/6502Mk2.hpp index bd4388347..b4f0638ca 100644 --- a/Processors/6502Mk2/6502Mk2.hpp +++ b/Processors/6502Mk2/6502Mk2.hpp @@ -66,6 +66,7 @@ enum class Line { PowerOn, Overflow, NMI, + Ready, }; // MARK: - Address bus. @@ -193,6 +194,9 @@ public: case Line::Overflow: edge_sample(Inputs::InterruptRequest::Reset, inputs_.overflow); break; case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break; + // Merely stateful. + case Line::Ready: inputs_.ready = value; break; + default: __builtin_unreachable(); } From 67590cf06bc7850c4501675f0faf5d92aa2dead0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 14:23:05 -0500 Subject: [PATCH 2/9] Adapt Vic-20 to the newer 6502. --- Machines/Commodore/Vic-20/Vic20.cpp | 66 +++++++++++++++-------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index ea858f5f2..aca140c86 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -13,7 +13,7 @@ #include "Activity/Source.hpp" #include "Machines/MachineTypes.hpp" -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Components/6560/6560.hpp" #include "Components/6522/6522.hpp" @@ -487,22 +487,20 @@ public: return joysticks_; } - // to satisfy CPU::MOS6502::Processor - forceinline Cycles perform_bus_operation( - const CPU::MOS6502::BusOperation operation, - const uint16_t address, - uint8_t *const value - ) { + template + Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { // Tun the phase-1 part of this cycle, in which the VIC accesses memory. cycles_since_mos6560_update_++; // Run the phase-2 part of the cycle, which is whatever the 6502 said it should be. - const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000; - if(is_read(operation)) { + const auto is_from_rom = [&]() { + return m6502_.registers().pc.full > 0x8000; + }; + if constexpr (is_read(operation)) { const auto page = processor_read_memory_map_[address >> 10]; uint8_t result; if(!page) { - if(!is_from_rom) confidence_.add_miss(); + if(!is_from_rom()) confidence_.add_miss(); result = 0xff; } else { result = processor_read_memory_map_[address >> 10][address & 0x3ff]; @@ -515,7 +513,7 @@ public: if(address & 0x10) result &= user_port_via_.read(address); if(address & 0x20) result &= keyboard_via_.read(address); - if(!is_from_rom) { + if(!is_from_rom()) { if((address & 0x100) && !(address & 0x30)) { confidence_.add_miss(); } else { @@ -523,10 +521,10 @@ public: } } } - *value = result; + value = result; // Consider applying the fast tape hack. - if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { + if(use_fast_tape_hack_ && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) { if(address == 0xf7b2) { // Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the // next header. Skip that via a three-byte NOP and fill in the next header programmatically. @@ -551,10 +549,10 @@ public: ram_[0x90] = 0; ram_[0x93] = 0; - *value = 0x0c; // i.e. NOP abs, to swallow the entire JSR + value = 0x0c; // i.e. NOP abs, to swallow the entire JSR } else if(address == 0xf90b) { - const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); - if(x == 0xe) { + auto registers = m6502_.registers(); + if(registers.x == 0xe) { Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20); const auto tape_position = tape_->serialiser()->offset(); const std::unique_ptr data = parser.get_next_data(*tape_->serialiser()); @@ -576,14 +574,13 @@ public: // set tape status, carry and flag ram_[0x90] |= 0x40; - uint8_t flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)); - flags &= ~uint8_t((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt)); - m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); + registers.flags.set_per(0); + registers.flags.set_per(0); // to ensure that execution proceeds to 0xfccf, pretend a NOP was here and // ensure that the PC leaps to 0xfccf - m6502_.set_value_of(CPU::MOS6502::Register::ProgramCounter, 0xfccf); - *value = 0xea; // i.e. NOP implied + registers.pc.full = 0xfccf; + value = 0xea; // i.e. NOP implied hold_tape_ = true; Logger::info().append("Found data"); } else { @@ -592,27 +589,28 @@ public: Logger::info().append("Didn't find data"); } } + m6502_.set_registers(registers); } } } else { uint8_t *const ram = processor_write_memory_map_[address >> 10]; if(ram) { update_video(); - ram[address & 0x3ff] = *value; + ram[address & 0x3ff] = value; } // Anything between 0x9000 and 0x9400 is the IO area. if((address&0xfc00) == 0x9000) { // The VIC is selected by bit 8 = 0 if(!(address&0x100)) { update_video(); - mos6560_.write(address, *value); + mos6560_.write(address, value); } // The first VIA is selected by bit 4 = 1. - if(address & 0x10) user_port_via_.write(address, *value); + if(address & 0x10) user_port_via_.write(address, value); // The second VIA is selected by bit 5 = 1. - if(address & 0x20) keyboard_via_.write(address, *value); + if(address & 0x20) keyboard_via_.write(address, value); - if(!is_from_rom) { + if(!is_from_rom()) { if((address & 0x100) && !(address & 0x30)) { confidence_.add_miss(); } else { @@ -620,13 +618,13 @@ public: } } } else if(!ram) { - if(!is_from_rom) confidence_.add_miss(); + if(!is_from_rom()) confidence_.add_miss(); } } user_port_via_.run_for(Cycles(1)); keyboard_via_.run_for(Cycles(1)); - if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) { + if(typer_ && address == 0xeb1e && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) { if(!typer_->type_next_character()) { clear_all_keys(); typer_.reset(); @@ -672,8 +670,8 @@ public: } void mos6522_did_change_interrupt_status(void *) final { - m6502_.set_nmi_line(user_port_via_.get_interrupt_line()); - m6502_.set_irq_line(keyboard_via_.get_interrupt_line()); + m6502_.template set(user_port_via_.get_interrupt_line()); + m6502_.template set(keyboard_via_.get_interrupt_line()); } void type_string(const std::string &string) final { @@ -718,10 +716,16 @@ public: } private: + struct M6502Traits { + static constexpr auto uses_ready_line = false; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions; + using BusHandlerT = ConcreteMachine; + }; + CPU::MOS6502Mk2::Processor m6502_; + void update_video() { mos6560_.run_for(cycles_since_mos6560_update_.flush()); } - CPU::MOS6502::Processor m6502_; std::vector character_rom_; std::vector basic_rom_; From b3a9e39be3979021afa374235d3e683a1f063a0b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 14:39:35 -0500 Subject: [PATCH 3/9] Transfer C1540, ensure Plus 4 bus always holds _something_. --- .../Commodore/1540/Implementation/C1540.cpp | 35 +++++++++++-------- .../1540/Implementation/C1540Base.hpp | 13 ++++--- Machines/Commodore/Plus4/Plus4.cpp | 2 ++ Machines/Commodore/Vic-20/Vic20.cpp | 1 - Processors/6502Mk2/6502Mk2.hpp | 15 +++++--- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 7fd07a82c..55e800c40 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -65,7 +65,8 @@ void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { Commodore::Serial::attach(serial_port_, serial_bus); } -Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +template +Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { /* Memory map (given that I'm unsure yet on any potential mirroring): @@ -75,24 +76,28 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, 0xc000-0xffff ROM */ if(address < 0x800) { - if(is_read(operation)) - *value = ram_[address]; + if constexpr (is_read(operation)) + value = ram_[address]; else - ram_[address] = *value; + ram_[address] = value; } else if(address >= 0xc000) { - if(is_read(operation)) { - *value = rom_[address & 0x3fff]; + if constexpr (is_read(operation)) { + value = rom_[address & 0x3fff]; } } else if(address >= 0x1800 && address <= 0x180f) { - if(is_read(operation)) - *value = serial_port_VIA_.read(address); + if constexpr (is_read(operation)) + value = serial_port_VIA_.read(address); else - serial_port_VIA_.write(address, *value); + serial_port_VIA_.write(address, value); } else if(address >= 0x1c00 && address <= 0x1c0f) { - if(is_read(operation)) - *value = drive_VIA_.read(address); + if constexpr (is_read(operation)) + value = drive_VIA_.read(address); else - drive_VIA_.write(address, *value); + drive_VIA_.write(address, value); + } else { + if constexpr (is_read(operation)) { + value = 0xff; + } } serial_port_VIA_.run_for(Cycles(1)); @@ -123,7 +128,7 @@ void MachineBase::set_activity_observer(Activity::Observer *observer) { void MachineBase::mos6522_did_change_interrupt_status(void *) { // both VIAs are connected to the IRQ line - m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); + m6502_.set(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); } // MARK: - Disk drive @@ -141,10 +146,10 @@ void MachineBase::process_input_bit(int value) { drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_)); bit_window_offset_ = 0; if(drive_VIA_port_handler_.get_should_set_overflow()) { - m6502_.set_overflow_line(true); + m6502_.set(true); } } - else m6502_.set_overflow_line(false); + else m6502_.set(false); } // the 1540 does not recognise index holes diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 88dc21db8..b33be3e1b 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -8,7 +8,7 @@ #pragma once -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Components/6522/6522.hpp" #include "Machines/Commodore/SerialBus.hpp" @@ -122,7 +122,6 @@ private: }; class MachineBase: - public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public DriveVIA::Delegate, public Storage::Disk::Controller { @@ -134,7 +133,8 @@ public: void set_activity_observer(Activity::Observer *); // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value); + template + Cycles perform(const AddressT, CPU::MOS6502Mk2::data_t); protected: // to satisfy MOS::MOS6522::Delegate @@ -144,7 +144,12 @@ protected: void drive_via_did_step_head(DriveVIA &, int direction) override; void drive_via_did_set_data_density(DriveVIA &, int density) override; - CPU::MOS6502::Processor m6502_; + struct M6502Traits { + static constexpr auto uses_ready_line = false; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle; + using BusHandlerT = MachineBase; + }; + CPU::MOS6502Mk2::Processor m6502_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 28c82e6a4..a3c7cdc93 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -428,6 +428,7 @@ public: break; default: + value = 0xff; Logger::info().append("TODO: read @ %04x", address); break; } @@ -517,6 +518,7 @@ public: default: Logger::info().append("TODO: TED read at %04x", address); + value = 0xff; is_hit = false; } } else { diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index aca140c86..c7fc5f9af 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -280,7 +280,6 @@ class ConcreteMachine: public MachineTypes::MappedKeyboardMachine, public MachineTypes::JoystickMachine, public Configurable::Device, - public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, diff --git a/Processors/6502Mk2/6502Mk2.hpp b/Processors/6502Mk2/6502Mk2.hpp index b4f0638ca..888f5d505 100644 --- a/Processors/6502Mk2/6502Mk2.hpp +++ b/Processors/6502Mk2/6502Mk2.hpp @@ -186,17 +186,24 @@ public: (inputs_.interrupt_requests & Inputs::InterruptRequest::PowerOn); break; - // Level triggered. + // Level triggered interrupts. case Line::Reset: level_sample(Inputs::InterruptRequest::Reset); break; case Line::IRQ: level_sample(Inputs::InterruptRequest::IRQ); break; - // Edge triggered. - case Line::Overflow: edge_sample(Inputs::InterruptRequest::Reset, inputs_.overflow); break; + // Edge triggered interrupts. case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break; - // Merely stateful. + // Leval-capturing state. case Line::Ready: inputs_.ready = value; break; + // Edge-triggered state. + case Line::Overflow: + if(!inputs_.overflow && value) { + registers_.flags.set_per(Flag::Overflow); + } + inputs_.overflow = value; + break; + default: __builtin_unreachable(); } From 332b37063fadd9b6c1567f650a169e51aee81297 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 17:15:26 -0500 Subject: [PATCH 4/9] Adjust for style. --- .../Commodore/1540/Implementation/C1540.cpp | 82 +++++++++++-------- .../1540/Implementation/C1540Base.hpp | 4 +- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 55e800c40..e58763867 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -17,7 +17,10 @@ using namespace Commodore::C1540; namespace { -ROM::Name rom_name(Personality personality) { + +// MARK: - Construction, including ROM requests. + +ROM::Name rom_name(const Personality personality) { switch(personality) { default: case Personality::C1540: return ROM::Name::Commodore1540; @@ -26,11 +29,11 @@ ROM::Name rom_name(Personality personality) { } } -ROM::Request Machine::rom_request(Personality personality) { +ROM::Request Machine::rom_request(const Personality personality) { return ROM::Request(rom_name(personality)); } -MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : +MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), m6502_(*this), drive_VIA_(drive_VIA_port_handler_), @@ -58,12 +61,10 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size())); } -Machine::Machine(Personality personality, const ROM::Map &roms) : +Machine::Machine(const Personality personality, const ROM::Map &roms) : MachineBase(personality, roms) {} -void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { - Commodore::Serial::attach(serial_port_, serial_bus); -} +// MARK: - 6502 bus. template Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { @@ -106,34 +107,41 @@ Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t disk) { get_drive().set_disk(disk); } -void Machine::run_for(const Cycles cycles) { - m6502_.run_for(cycles); - - const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); - get_drive().set_motor_on(drive_motor); - if(drive_motor) - Storage::Disk::Controller::run_for(cycles); -} - -void MachineBase::set_activity_observer(Activity::Observer *observer) { +void MachineBase::set_activity_observer(Activity::Observer *const observer) { drive_VIA_.bus_handler().set_activity_observer(observer); get_drive().set_activity_observer(observer, "Drive", false); } -// MARK: - 6522 delegate +// MARK: - 6522 delegate. void MachineBase::mos6522_did_change_interrupt_status(void *) { // both VIAs are connected to the IRQ line m6502_.set(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); } -// MARK: - Disk drive +// MARK: - Disk drive. -void MachineBase::process_input_bit(int value) { +void MachineBase::process_input_bit(const int value) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { drive_VIA_port_handler_.set_sync_detected(true); @@ -145,7 +153,7 @@ void MachineBase::process_input_bit(int value) { if(bit_window_offset_ == 8) { drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_)); bit_window_offset_ = 0; - if(drive_VIA_port_handler_.get_should_set_overflow()) { + if(drive_VIA_port_handler_.should_set_overflow()) { m6502_.set(true); } } @@ -169,7 +177,9 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density) template uint8_t SerialPortVIA::get_port_input() const { - if(port) return port_b_; + if(port) { + return port_b_; + } return 0xff; } @@ -208,13 +218,15 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) { void SerialPortVIA::update_data_line() { // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" - serial_port_->set_output(::Commodore::Serial::Line::Data, - Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); + serial_port_->set_output( + ::Commodore::Serial::Line::Data, + Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) + ); } // MARK: - DriveVIA -void DriveVIA::set_delegate(Delegate *delegate) { +void DriveVIA::set_delegate(Delegate *const delegate) { delegate_ = delegate; } @@ -228,15 +240,15 @@ void DriveVIA::set_sync_detected(const bool sync_detected) { port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80); } -void DriveVIA::set_data_input(uint8_t value) { +void DriveVIA::set_data_input(const uint8_t value) { port_a_ = value; } -bool DriveVIA::get_should_set_overflow() { +bool DriveVIA::should_set_overflow() { return should_set_overflow_; } -bool DriveVIA::get_motor_enabled() { +bool DriveVIA::motor_enabled() { return drive_motor_; } @@ -256,8 +268,8 @@ void DriveVIA::set_port_output(const uint8_t value, uint8_t) { // Check for a head step. const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; - if(step_difference) { - if(delegate_) delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); + if(step_difference && delegate_) { + delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); } // Check for a change in density. @@ -267,14 +279,16 @@ void DriveVIA::set_port_output(const uint8_t value, uint8_t) { } // Post the LED status. - if(observer_) observer_->set_led_status("Drive", value&8); + if(observer_) { + observer_->set_led_status("Drive", value&8); + } previous_port_b_output_ = value; } } } -void DriveVIA::set_activity_observer(Activity::Observer *observer) { +void DriveVIA::set_activity_observer(Activity::Observer *const observer) { observer_ = observer; if(observer) { observer->register_led("Drive"); @@ -282,9 +296,9 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) { } } -// MARK: - SerialPort +// MARK: - SerialPort. -void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) { +void SerialPort::set_input(const Serial::Line line, const Serial::LineLevel level) { serial_port_via_->set_serial_line_state(line, bool(level), *via_); } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index b33be3e1b..79ae38a10 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -88,8 +88,8 @@ public: void set_sync_detected(bool); void set_data_input(uint8_t); - bool get_should_set_overflow(); - bool get_motor_enabled(); + bool should_set_overflow(); + bool motor_enabled(); template void set_control_line_output(bool value); From d011b10b5de03f87411f7984865a54956b08e684 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 17:54:09 -0500 Subject: [PATCH 5/9] Update comments, add note-to-self on write mode. --- .../Commodore/1540/Implementation/C1540.cpp | 58 ++++++++++++------- .../1540/Implementation/C1540Base.hpp | 9 ++- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index e58763867..3d56adcac 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -125,6 +125,7 @@ void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { void Machine::set_disk(std::shared_ptr disk) { get_drive().set_disk(disk); + drive_VIA_port_handler_.set_is_read_only(disk->is_read_only()); } void MachineBase::set_activity_observer(Activity::Observer *const observer) { @@ -237,7 +238,11 @@ uint8_t DriveVIA::get_port_input() const { } void DriveVIA::set_sync_detected(const bool sync_detected) { - port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80); + port_b_ = (port_b_ & ~0x80) | (sync_detected ? 0x00 : 0x80); +} + +void DriveVIA::set_is_read_only(const bool is_read_only) { + port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x10 : 0x80); } void DriveVIA::set_data_input(const uint8_t value) { @@ -257,34 +262,43 @@ void DriveVIA::set_control_line_output(const bool value) { if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { should_set_overflow_ = value; } + + if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) { + // TODO: 0 = write, 1 = read. + if(!value) { + printf("NOT IMPLEMENTED: write mode\n"); + } + } } template void DriveVIA::set_port_output(const uint8_t value, uint8_t) { - if(port) { - if(previous_port_b_output_ != value) { - // Record drive motor state. - drive_motor_ = value&4; + if(!port) { + return; + } - // Check for a head step. - const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; - if(step_difference && delegate_) { - delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); - } + if(previous_port_b_output_ != value) { + // Record drive motor state. + drive_motor_ = value&4; - // Check for a change in density. - const int density_difference = (previous_port_b_output_^value) & (3 << 5); - if(density_difference && delegate_) { - delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3); - } - - // Post the LED status. - if(observer_) { - observer_->set_led_status("Drive", value&8); - } - - previous_port_b_output_ = value; + // Check for a head step. + const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; + if(step_difference && delegate_) { + delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); } + + // Check for a change in density. + const int density_difference = (previous_port_b_output_^value) & (3 << 5); + if(density_difference && delegate_) { + delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3); + } + + // Post the LED status. + if(observer_) { + observer_->set_led_status("Drive", value&8); + } + + previous_port_b_output_ = value; } } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 79ae38a10..0ffd2940f 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -65,15 +65,17 @@ private: It is wired up such that Port B contains: Bits 0/1: head step direction Bit 2: motor control - Bit 3: LED control (TODO) + Bit 3: LED control Bit 4: write protect photocell status (TODO) Bits 5/6: read/write density Bit 7: 0 if sync marks are currently being detected, 1 otherwise. ... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. - It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on - whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. + Elsewhere: + * CA2 might is used to set processor overflow; + * CA1 a strobe for data input; and + * CB2 indicates read/write mode; 1 = read, 0 = write. */ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: @@ -88,6 +90,7 @@ public: void set_sync_detected(bool); void set_data_input(uint8_t); + void set_is_read_only(bool); bool should_set_overflow(); bool motor_enabled(); From aabfe7c284c7796b994a35de7ce446d7c378166d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 21:15:42 -0500 Subject: [PATCH 6/9] Observe that there is an attempt to output data. --- Machines/Commodore/1540/Implementation/C1540.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 3d56adcac..e9f9aebd1 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -271,12 +271,8 @@ void DriveVIA::set_control_line_output(const bool value) { } } -template -void DriveVIA::set_port_output(const uint8_t value, uint8_t) { - if(!port) { - return; - } - +template <> +void DriveVIA::set_port_output(const uint8_t value, uint8_t) { if(previous_port_b_output_ != value) { // Record drive motor state. drive_motor_ = value&4; @@ -302,6 +298,11 @@ void DriveVIA::set_port_output(const uint8_t value, uint8_t) { } } +template <> +void DriveVIA::set_port_output(const uint8_t value, uint8_t) { + printf("TODO: output is %02x\n", value); +} + void DriveVIA::set_activity_observer(Activity::Observer *const observer) { observer_ = observer; if(observer) { From fa8be26f9fdf4a13f327cace3827c5ae9ec2553f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 22:20:20 -0500 Subject: [PATCH 7/9] Fix read-only bit. --- Machines/Commodore/1540/Implementation/C1540.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index e9f9aebd1..7fe86dd47 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -242,7 +242,7 @@ void DriveVIA::set_sync_detected(const bool sync_detected) { } void DriveVIA::set_is_read_only(const bool is_read_only) { - port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x10 : 0x80); + port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x00 : 0x10); } void DriveVIA::set_data_input(const uint8_t value) { From 5359964feff00a70767eb890cfa7dccb83c9f9db Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 22:55:18 -0500 Subject: [PATCH 8/9] Make minor style improvements, fix cursor keys. --- Machines/Commodore/Vic-20/Vic20.cpp | 39 +++++++++++++++++++---------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index c7fc5f9af..71de49bbd 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -79,19 +79,23 @@ public: } /// Receives announcements of control line output change from the 6522. - template void set_control_line_output(const bool value) { + template + void set_control_line_output(const bool) {} + + template <> void set_control_line_output(const bool value) { // The CA2 output is used to control the tape motor. - if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { - tape_->set_motor_control(!value); - } + tape_->set_motor_control(!value); } /// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A. - void set_serial_line_state(Commodore::Serial::Line line, const bool value) { + void set_serial_line_state(const Commodore::Serial::Line line, const bool value) { + const auto set = [&](const uint8_t bit) { + port_a_ = (port_a_ & ~bit) | (value ? bit : 0x00); + }; switch(line) { default: break; - case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break; - case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break; + case ::Commodore::Serial::Line::Data: set(0x02); break; + case ::Commodore::Serial::Line::Clock: set(0x01); break; } } @@ -146,7 +150,7 @@ public: /// Sets all keys as unpressed. void clear_all_keys() { - memset(columns_, 0xff, sizeof(columns_)); + std::fill(std::begin(columns_), std::end(columns_), 0xff); } /// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. @@ -230,8 +234,6 @@ struct Vic6560BusHandler { // It is assumed that these pointers have been filled in by the machine. const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions. const uint8_t *colour_memory{}; // Colour memory must be contiguous. - - // TODO: make the above const. }; /*! @@ -454,7 +456,7 @@ public: } void set_key_state(const uint16_t key, const bool is_pressed) final { - if(key < 0xfff0) { + if(key < KeyUp) { keyboard_via_port_handler_.set_key_state(key, is_pressed); } else { switch(key) { @@ -480,6 +482,7 @@ public: void clear_all_keys() final { keyboard_via_port_handler_.clear_all_keys(); + set_key_state(KeyRestore, false); } const std::vector> &get_joysticks() final { @@ -748,12 +751,22 @@ private: ++address; } } - void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, size_t length) { + void write_to_map( + const uint8_t **const map, + const uint8_t *const area, + const uint16_t address, + const size_t length + ) { write_to_map([&](const uint16_t address, const size_t offset) { map[address] = &area[offset]; }, address, length); } - void write_to_map(uint8_t **const map, uint8_t *area, uint16_t address, size_t length) { + void write_to_map( + uint8_t **const map, + uint8_t *const area, + const uint16_t address, + const size_t length + ) { write_to_map([&](const uint16_t address, const size_t offset) { map[address] = &area[offset]; }, address, length); From 5e465f1ff40ceb8652955552305eab11fda932d3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Nov 2025 23:04:33 -0500 Subject: [PATCH 9/9] Avoid function specialisation. --- Machines/Commodore/Vic-20/Vic20.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 71de49bbd..e9452d6d8 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -72,7 +72,7 @@ public: // Port A provides information about the presence or absence of a tape, and parts of // the joystick and serial port state, both of which have been statefully collected // into port_a_. - if(!port) { + if constexpr (port == MOS::MOS6522::Port::A) { return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); } return 0xff; @@ -80,11 +80,11 @@ public: /// Receives announcements of control line output change from the 6522. template - void set_control_line_output(const bool) {} - - template <> void set_control_line_output(const bool value) { - // The CA2 output is used to control the tape motor. - tape_->set_motor_control(!value); + void set_control_line_output(const bool value) { + // CA2: control the tape motor. + if constexpr (port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { + tape_->set_motor_control(!value); + } } /// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.