From 348a593dc11738df67e8dd7edb31563835779351 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Jan 2025 14:13:42 -0500 Subject: [PATCH 01/13] Flail in attempt to implement fast loading. --- Machines/Commodore/Plus4/Plus4.cpp | 163 +++++++++++++++----- Outputs/ScanTargets/BufferingScanTarget.cpp | 2 +- Storage/TimedEventLoop.cpp | 6 + Storage/TimedEventLoop.hpp | 10 ++ 4 files changed, 144 insertions(+), 37 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index ae86d676c4..b23351344e 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -30,6 +30,7 @@ #include "../1540/C1540.hpp" #include +#include #include using namespace Commodore; @@ -248,15 +249,17 @@ class ConcreteMachine: const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length()); timers_.tick(timers_cycles.as()); - video_.run_for(length); tape_player_->run_for(length); + if(!is_fast_loading_) { + video_.run_for(length); - if(c1541_) { - c1541_cycles_ += length * Cycles(1'000'000); - c1541_->run_for(c1541_cycles_.divide(media_divider_)); - } + if(c1541_) { + c1541_cycles_ += length * Cycles(1'000'000); + c1541_->run_for(c1541_cycles_.divide(media_divider_)); + } - time_since_audio_update_ += length; + time_since_audio_update_ += length; + } if(operation == CPU::MOS6502::BusOperation::Ready) { return length; @@ -301,38 +304,122 @@ class ConcreteMachine: serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); } } else if(address < 0xfd00 || address >= 0xff40) { - if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) { - // TODO: - // - // ; read a dipole from tape (and then RTS) - // ; - // ; if c=1 then error - // ; else if v=1 then short - // ; else if n=0 then long - // ; else word - // ; end - // ; end - // ; end - - // Compare with: - // - // dsamp1 *=*+2 ;time constant for x cell sample 07B8 - // dsamp2 *=*+2 ;time constant for y cell sample - // zcell *=*+2 ;time constant for z cell verify - -// const uint8_t dsamp1 = map_.read(0x7b8); -// const uint8_t dsamp2 = map_.read(0x7b9); -// const uint8_t zcell = map_.read(0x7ba); +// if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) { +// ++pulse_num; +// const bool is_interesting = pulse_num >= 15220; // +// if(pulse_num == 15224) { +// printf(""); +// } // -// logger.info().append("rddipl: %d / %d / %d", dsamp1, dsamp2, zcell); - } +//// if(address == 0xe68a) { +//// } +// +// if(address == 0xe5fd) { +// // TODO: +// // +// // ; read a dipole from tape (and then RTS) +// // ; +// // ; if c=1 then error +// // ; else if v=1 then short +// // ; else if n=0 then long +// // ; else word +// // ; end +// // ; end +// // ; end +// +// // 76 = V, not N or C +// // b7 = N +// +// // Compare with: +// // +// // dsamp1 *=*+2 ;time constant for x cell sample 07B8 +// // dsamp2 *=*+2 ;time constant for y cell sample +// // zcell *=*+2 ;time constant for z cell verify +// +// const auto read16 = [&](uint16_t address) { +// // These constants are defined in terms of the timer clocks; convert them to +// // fractions of a second. +// const auto constant = uint16_t( map_.read(address) | (map_.read(address + 1) << 8) ); +// return (float(constant) * video_.timer_cycle_length().as()) / float(get_clock_rate()); +// }; +// const auto dsamp1 = read16(0x7b8); +// const auto dsamp2 = read16(0x7ba); +// const auto zcell = read16(0x7bc); +// using Pulse = Storage::Tape::Pulse; +// +// // Wait until tape input is high (i.e. input is low). +//// while(tape_player_->current_pulse().type != Pulse::Type::Low) { +//// tape_player_->complete_pulse(); +//// } +// +// // Wait until tape input is low. +// while(tape_player_->current_pulse().type != Pulse::Type::High) { +// tape_player_->complete_pulse(); +// } +// +// // Count time of low high, and classify. +// const auto length1 = tape_player_->current_pulse().length.get(); +// tape_player_->complete_pulse(); // Consume High. +// const auto length2 = tape_player_->current_pulse().length.get(); +// tape_player_->complete_pulse(); // Consume Low. +// +// uint8_t flags = +// uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)) & +// ~(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Overflow | CPU::MOS6502::Flag::Sign); +// +// if(std::abs(length1 - length2) > 0.00025f) { +// // Lengths are too dissimilar; call that an error. +// flags |= CPU::MOS6502::Flag::Carry; +// } +// +//// const auto dsamp1_difference = std::abs(length1 - dsamp1); +//// const auto dsamp2_difference = std::abs(length1 - dsamp2); +//// const auto zcell_difference = std::abs(length1 - zcell); +// +// if(length2 < dsamp1) { +// flags |= CPU::MOS6502::Flag::Overflow; +// } else if(length2 > zcell) { +// flags |= CPU::MOS6502::Flag::Sign; +// } +// +// m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); +// +// if(is_interesting) { +// logger.info().append("Read: %d %d", +// int(length1 * 1'000'000), +// int(length2 * 1'000'000) +// ); +// } +// +// *value = 0x60; // i.e. RTS. +// } +// +// if(is_interesting) { +// const auto flags = m6502_.value_of(CPU::MOS6502::Register::Flags); +// logger.info().append("%d @ %d dipole result: %c%c%c", +// pulse_num, +// tape_player_->event_count(), +// flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', +// flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', +// flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); +//} +// } else { + if(is_read(operation)) { + *value = map_.read(address); + } else { + map_.write(address) = *value; + } - if(is_read(operation)) { - *value = map_.read(address); - } else { - map_.write(address) = *value; - } + // If fast loading is enabled, zero-rate anything in the function rddipl, which reads + // dipoles from tape. + if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) { + is_fast_loading_ = (address >= 0xe5fd) && (address <= 0xeb71); + } +// if(is_in_rddipl) { +// return Cycles(0); +// } +// } } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. if(is_read(operation)) { @@ -559,7 +646,7 @@ class ConcreteMachine: } } - return length; + return is_fast_loading_ ? Cycles(0) : length; } private: @@ -618,6 +705,8 @@ class ConcreteMachine: return video_.get_display_type(); } + int pulse_num = 0; + void run_for(const Cycles cycles) final { m6502_.run_for(cycles); @@ -704,8 +793,10 @@ class ConcreteMachine: bool play_button_ = false; bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack. bool use_fast_tape_hack_ = false; + bool is_fast_loading_ = false; void set_use_fast_tape() { use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_; + is_fast_loading_ &= use_fast_tape_hack_; } void update_tape_motor() { const auto output = io_output_ | ~io_direction_; diff --git a/Outputs/ScanTargets/BufferingScanTarget.cpp b/Outputs/ScanTargets/BufferingScanTarget.cpp index 021f1981b0..2ce4e233ba 100644 --- a/Outputs/ScanTargets/BufferingScanTarget.cpp +++ b/Outputs/ScanTargets/BufferingScanTarget.cpp @@ -273,7 +273,7 @@ void BufferingScanTarget::will_change_owner() { std::lock_guard lock_guard(producer_mutex_); allocation_has_failed_ = true; vended_scan_ = nullptr; -#ifdef DEBUG +#ifndef NDEBUG data_is_allocated_ = false; #endif } diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index b083ef0609..35e80e0da7 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -30,6 +30,9 @@ void TimedEventLoop::run_for(const Cycles cycles) { advance(cycles_until_event_); remaining_cycles -= cycles_until_event_; cycles_until_event_ = 0; +#ifndef NDEBUG + ++event_count_; +#endif process_next_event(); } @@ -60,6 +63,9 @@ void TimedEventLoop::reset_timer() { void TimedEventLoop::jump_to_next_event() { reset_timer(); +#ifndef NDEBUG + ++event_count_; +#endif process_next_event(); } diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index 97860fa979..293b7c64e1 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -58,6 +58,13 @@ class TimedEventLoop { */ Cycles::IntType get_input_clock_rate() const; +#ifndef NDEBUG + /*! + For debugging purposes only, returns the number of events that have so far passed. + */ + int event_count() { return event_count_; } +#endif + protected: /*! Sets the time interval, as a proportion of a second, until the next event should be triggered. @@ -103,6 +110,9 @@ class TimedEventLoop { Cycles::IntType input_clock_rate_ = 0; Cycles::IntType cycles_until_event_ = 0; float subcycles_until_event_ = 0.0f; +#ifndef NDEBUG + int event_count_ = 0; +#endif }; } From 11190cff1d9c620f74ab43448be4145936d941df Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Jan 2025 16:45:05 -0500 Subject: [PATCH 02/13] Eject zero-cost execution in favour of faulty HLE. --- Machines/Commodore/Plus4/Plus4.cpp | 448 +++++++++++++++++++++-------- 1 file changed, 324 insertions(+), 124 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index b23351344e..8cc9a26570 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -235,6 +235,9 @@ class ConcreteMachine: audio_queue_.flush(); } + // HACK. NOCOMMIT. + int pulse_num_ = 0; + Cycles perform_bus_operation( const CPU::MOS6502::BusOperation operation, const uint16_t address, @@ -250,17 +253,15 @@ class ConcreteMachine: timers_.tick(timers_cycles.as()); tape_player_->run_for(length); - if(!is_fast_loading_) { - video_.run_for(length); - - if(c1541_) { - c1541_cycles_ += length * Cycles(1'000'000); - c1541_->run_for(c1541_cycles_.divide(media_divider_)); - } + video_.run_for(length); - time_since_audio_update_ += length; + if(c1541_) { + c1541_cycles_ += length * Cycles(1'000'000); + c1541_->run_for(c1541_cycles_.divide(media_divider_)); } + time_since_audio_update_ += length; + if(operation == CPU::MOS6502::BusOperation::Ready) { return length; } @@ -304,122 +305,33 @@ class ConcreteMachine: serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); } } else if(address < 0xfd00 || address >= 0xff40) { -// if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) { -// ++pulse_num; -// const bool is_interesting = pulse_num >= 15220; -// -// if(pulse_num == 15224) { -// printf(""); -// } -// -//// if(address == 0xe68a) { -//// } -// -// if(address == 0xe5fd) { -// // TODO: -// // -// // ; read a dipole from tape (and then RTS) -// // ; -// // ; if c=1 then error -// // ; else if v=1 then short -// // ; else if n=0 then long -// // ; else word -// // ; end -// // ; end -// // ; end -// -// // 76 = V, not N or C -// // b7 = N -// -// // Compare with: -// // -// // dsamp1 *=*+2 ;time constant for x cell sample 07B8 -// // dsamp2 *=*+2 ;time constant for y cell sample -// // zcell *=*+2 ;time constant for z cell verify -// -// const auto read16 = [&](uint16_t address) { -// // These constants are defined in terms of the timer clocks; convert them to -// // fractions of a second. -// const auto constant = uint16_t( map_.read(address) | (map_.read(address + 1) << 8) ); -// return (float(constant) * video_.timer_cycle_length().as()) / float(get_clock_rate()); -// }; -// const auto dsamp1 = read16(0x7b8); -// const auto dsamp2 = read16(0x7ba); -// const auto zcell = read16(0x7bc); -// using Pulse = Storage::Tape::Pulse; -// -// // Wait until tape input is high (i.e. input is low). -//// while(tape_player_->current_pulse().type != Pulse::Type::Low) { -//// tape_player_->complete_pulse(); -//// } -// -// // Wait until tape input is low. -// while(tape_player_->current_pulse().type != Pulse::Type::High) { -// tape_player_->complete_pulse(); -// } -// -// // Count time of low high, and classify. -// const auto length1 = tape_player_->current_pulse().length.get(); -// tape_player_->complete_pulse(); // Consume High. -// const auto length2 = tape_player_->current_pulse().length.get(); -// tape_player_->complete_pulse(); // Consume Low. -// -// uint8_t flags = -// uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)) & -// ~(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Overflow | CPU::MOS6502::Flag::Sign); -// -// if(std::abs(length1 - length2) > 0.00025f) { -// // Lengths are too dissimilar; call that an error. -// flags |= CPU::MOS6502::Flag::Carry; -// } -// -//// const auto dsamp1_difference = std::abs(length1 - dsamp1); -//// const auto dsamp2_difference = std::abs(length1 - dsamp2); -//// const auto zcell_difference = std::abs(length1 - zcell); -// -// if(length2 < dsamp1) { -// flags |= CPU::MOS6502::Flag::Overflow; -// } else if(length2 > zcell) { -// flags |= CPU::MOS6502::Flag::Sign; -// } -// -// m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); -// -// if(is_interesting) { -// logger.info().append("Read: %d %d", -// int(length1 * 1'000'000), -// int(length2 * 1'000'000) -// ); -// } -// -// *value = 0x60; // i.e. RTS. -// } -// -// if(is_interesting) { -// const auto flags = m6502_.value_of(CPU::MOS6502::Register::Flags); -// logger.info().append("%d @ %d dipole result: %c%c%c", -// pulse_num, -// tape_player_->event_count(), -// flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', -// flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', -// flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); -//} -// } else { + constexpr bool use_hle = true; + constexpr uint16_t trap = use_hle ? 0xe5fd : 0xe68a; + + if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == trap) { + ++pulse_num_; + + if(use_hle) { + read_dipole(); + } + *value = 0x60; + + if(!tape_player_->is_at_end()) { + const auto flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)); + logger.info().append("%d @ %d dipole result: %c%c%c", + pulse_num_, + tape_player_->event_count(), + flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', + flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', + flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); + } + } else { if(is_read(operation)) { *value = map_.read(address); } else { map_.write(address) = *value; } - - // If fast loading is enabled, zero-rate anything in the function rddipl, which reads - // dipoles from tape. - if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) { - is_fast_loading_ = (address >= 0xe5fd) && (address <= 0xeb71); - } -// if(is_in_rddipl) { -// return Cycles(0); -// } -// } + } } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. if(is_read(operation)) { @@ -646,7 +558,7 @@ class ConcreteMachine: } } - return is_fast_loading_ ? Cycles(0) : length; + return length; } private: @@ -705,8 +617,6 @@ class ConcreteMachine: return video_.get_display_type(); } - int pulse_num = 0; - void run_for(const Cycles cycles) final { m6502_.run_for(cycles); @@ -793,16 +703,306 @@ class ConcreteMachine: bool play_button_ = false; bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack. bool use_fast_tape_hack_ = false; - bool is_fast_loading_ = false; void set_use_fast_tape() { use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_; - is_fast_loading_ &= use_fast_tape_hack_; } void update_tape_motor() { const auto output = io_output_ | ~io_direction_; tape_player_->set_motor_control(play_button_ && (~output & 0x08)); set_use_fast_tape(); } + void read_dipole() { + if(pulse_num_ == 15225) { + printf(""); + } + + // Provides an HLE implementation of the routine beginning at address + // 0xe5fd in the ROM, i.e. rddipl (read dipole) as that's the one that + // spins awaiting changes in tape input. + using Register = CPU::MOS6502::Register; + using Pulse = Storage::Tape::Pulse; + using Flag = CPU::MOS6502::Flag; + + auto s = uint8_t(m6502_.value_of(Register::StackPointer)); + auto flags = uint8_t(m6502_.value_of(Register::Flags)); + const unsigned int timer_clock_rate = + static_cast(get_clock_rate()) / video_.timer_cycle_length().as(); + + //; trigger on negative edge (beginning) of dipole + //; + //rddipl + // ldx dsamp1 ; setup x,y with 1st sample point + // ldy dsamp1+1 + //badeg1 + // lda dsamp2+1 ; put 2nd samp value on stack in reverse order + // pha + // lda dsamp2 + // pha + const auto read16 = [&](uint16_t address) { + return uint16_t( map_.read(address) | (map_.read(address + 1) << 8) ); + }; + const auto dsamp1 = read16(0x7b8); + const auto dsamp2 = read16(0x7ba); + const auto zcell = read16(0x7bc); + + map_.write(0x100 + s) = uint8_t(dsamp2 >> 8); + --s; + map_.write(0x100 + s) = uint8_t (dsamp2); + + // + // lda #$10 + //rwtl ; wait till rd line is high + // bit port + // beq rwtl ; !ls! + // + const auto wait_for = [&](Pulse::Type type) -> bool { + while(!tape_player_->is_at_end() && tape_player_->current_pulse().type != type) { + tape_player_->complete_pulse(); + } + + return tape_player_->current_pulse().type == type; + }; + if(!wait_for(Pulse::Low)) { + flags |= Flag::Carry; + return; + } + + // + //rwth ;it's high...now wait till it's low + // bit port + // bne rwth ; caught the edge + if(!wait_for(Pulse::High)) { + flags |= Flag::Carry; + return; + } + + // stx timr2l + // sty timr2h + // + //; go! ...ta + // + // pla ;go! ...ta + // sta timr3l + // pla + // sta timr3h ;go! ...tb + // + //; clear timer flags + // + // lda #$50 ; clr ta,tb + // sta tedirq + // + //; um...check that edge again + // + //casdb1 + // lda port + // cmp port + // bne casdb1 ; something is going on here... + // and #$10 ; a look at that edge again + // bne badeg1 ; woa! got a bad edge trigger !ls! + // + //; must have been a valid edge + //; + //; do stop key check here + // + // jsr balout + // lda #$10 + //wata ; wait for ta to timeout + // bit port ; kuldge, kludge, kludge !!! <<><>> + // bne rshort ; kuldge, kludge, kludge !!! <<><>> + // bit tedirq + // beq wata + // + //; now do the dipole sample #1 + // + //casdb2 + // lda port + // cmp port + // bne casdb2 + // and #$10 + // bne rshort ; shorts anyone? + // + + const auto pulse_timer_cycles = [&] { + const auto length = tape_player_->current_pulse().length; + return (length.length * timer_clock_rate) / length.clock_rate; + }; + const auto pulse_length = pulse_timer_cycles(); + if(pulse_length <= dsamp1) { + // Goto rshort, i.e. ... + flags |= Flag::Overflow; + flags &= ~Flag::Carry; + m6502_.set_value_of(Register::Flags, flags); + return; + } + + if(pulse_length <= dsamp2) { + // Goto rlong, i.e. ... + flags &= ~(Flag::Carry | Flag::Sign); + m6502_.set_value_of(Register::Flags, flags); + return; + } + + // Test for a word by looking at the second part of the pulse. + tape_player_->complete_pulse(); + const auto second_pulse_length = pulse_timer_cycles(); + if(second_pulse_length <= zcell) { + flags |= Flag::Sign; + flags &= ~(Flag::Carry | Flag::Overflow); + m6502_.set_value_of(Register::Flags, flags); + return; + } + + flags |= Flag::Carry; + m6502_.set_value_of(Register::Flags, flags); + + //; perhaps a long or a word? + // + // lda #$40 + //watb + // bit tedirq + // beq watb + // + //; wait for tb to timeout + //; now do the dipole sample #2 + // + //casdb3 + // lda port + // cmp port + // bne casdb3 + // and #$10 + // bne rlong ; looks like a long from here !ls! + // ; or could it be a word? + // lda zcell + // sta timr2l + // lda zcell+1 + // sta timr2h + // ; go! z-cell check + // ; clear ta flag + // lda #$10 + // sta tedirq ; verify +180 half of word dipole + // lda #$10 + //wata2 + // bit tedirq + // beq wata2 ; check z-cell is low + //casdb4 + // lda port + // cmp port + // bne casdb4 + // and #$10 + // beq rderr1 ; !ls! + // bit twordd ; got a word dipole + // bmi dipok ; !bra + // + //rshort + // bit tshrtd ; got a short + // bvs dipok ; !bra + // + //rlong + // bit tlongd ; got a long + // + //dipok + // clc ; everything's fine + // rts + // + //rderr1 + // sec ; i'm confused + // rts + + + +// ++pulse_num; +// const bool is_interesting = pulse_num >= 15220; +// +// if(pulse_num == 15224) { +// printf(""); +// } +// +//// if(address == 0xe68a) { +//// } +// +// if(address == 0xe5fd) { +// // TODO: +// // +// // ; read a dipole from tape (and then RTS) +// // ; +// // ; if c=1 then error +// // ; else if v=1 then short +// // ; else if n=0 then long +// // ; else word +// // ; end +// // ; end +// // ; end +// +// // 76 = V, not N or C +// // b7 = N +// +// // Compare with: +// // +// // dsamp1 *=*+2 ;time constant for x cell sample 07B8 +// // dsamp2 *=*+2 ;time constant for y cell sample +// // zcell *=*+2 ;time constant for z cell verify +// +// const auto zcell = read16(0x7bc); +// using Pulse = Storage::Tape::Pulse; +// +// // Wait until tape input is high (i.e. input is low). +//// while(tape_player_->current_pulse().type != Pulse::Type::Low) { +//// tape_player_->complete_pulse(); +//// } +// +// // Wait until tape input is low. +// while(tape_player_->current_pulse().type != Pulse::Type::High) { +// tape_player_->complete_pulse(); +// } +// +// // Count time of low high, and classify. +// const auto length1 = tape_player_->current_pulse().length.get(); +// tape_player_->complete_pulse(); // Consume High. +// const auto length2 = tape_player_->current_pulse().length.get(); +// tape_player_->complete_pulse(); // Consume Low. +// +// uint8_t flags = +// uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)) & +// ~(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Overflow | CPU::MOS6502::Flag::Sign); +// +// if(std::abs(length1 - length2) > 0.00025f) { +// // Lengths are too dissimilar; call that an error. +// flags |= CPU::MOS6502::Flag::Carry; +// } +// +//// const auto dsamp1_difference = std::abs(length1 - dsamp1); +//// const auto dsamp2_difference = std::abs(length1 - dsamp2); +//// const auto zcell_difference = std::abs(length1 - zcell); +// +// if(length2 < dsamp1) { +// flags |= CPU::MOS6502::Flag::Overflow; +// } else if(length2 > zcell) { +// flags |= CPU::MOS6502::Flag::Sign; +// } +// +// m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); +// +// if(is_interesting) { +// logger.info().append("Read: %d %d", +// int(length1 * 1'000'000), +// int(length2 * 1'000'000) +// ); +// } +// +// *value = 0x60; // i.e. RTS. +// } +// +// if(is_interesting) { +// const auto flags = m6502_.value_of(CPU::MOS6502::Register::Flags); +// logger.info().append("%d @ %d dipole result: %c%c%c", +// pulse_num, +// tape_player_->event_count(), +// flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', +// flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', +// flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); +//} +// } else { + } uint8_t io_direction_ = 0x00, io_output_ = 0x00; From 56f271c8ad2727c81118eb905c4aabd3c14cbe75 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Jan 2025 17:24:12 -0500 Subject: [PATCH 03/13] Continue failing. This is the story of my life. --- Machines/Commodore/Plus4/Plus4.cpp | 267 ++++------------------------- 1 file changed, 35 insertions(+), 232 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 8cc9a26570..6662f1f0d5 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -712,10 +712,9 @@ class ConcreteMachine: set_use_fast_tape(); } void read_dipole() { - if(pulse_num_ == 15225) { + if(pulse_num_ >= 15225) { printf(""); } - // Provides an HLE implementation of the routine beginning at address // 0xe5fd in the ROM, i.e. rddipl (read dipole) as that's the one that // spins awaiting changes in tape input. @@ -728,19 +727,24 @@ class ConcreteMachine: const unsigned int timer_clock_rate = static_cast(get_clock_rate()) / video_.timer_cycle_length().as(); - //; trigger on negative edge (beginning) of dipole - //; - //rddipl - // ldx dsamp1 ; setup x,y with 1st sample point - // ldy dsamp1+1 - //badeg1 - // lda dsamp2+1 ; put 2nd samp value on stack in reverse order - // pha - // lda dsamp2 - // pha + const auto wait_for = [&](Pulse::Type type) -> bool { + while(!tape_player_->is_at_end() && tape_player_->current_pulse().type != type) { + tape_player_->complete_pulse(); + } + + return tape_player_->current_pulse().type == type; + }; const auto read16 = [&](uint16_t address) { return uint16_t( map_.read(address) | (map_.read(address + 1) << 8) ); }; + const auto pulse_timer_cycles = [&] { + const auto length = tape_player_->current_pulse().length; + return (length.length * timer_clock_rate) / length.clock_rate; + }; + + // + // Grab timing constants, and put one into memory as it had been on the stack. + // const auto dsamp1 = read16(0x7b8); const auto dsamp2 = read16(0x7ba); const auto zcell = read16(0x7bc); @@ -750,258 +754,57 @@ class ConcreteMachine: map_.write(0x100 + s) = uint8_t (dsamp2); // - // lda #$10 - //rwtl ; wait till rd line is high - // bit port - // beq rwtl ; !ls! + // Busy wait for high input from tape (Elaboration: exiting with error if the tape ends). // - const auto wait_for = [&](Pulse::Type type) -> bool { - while(!tape_player_->is_at_end() && tape_player_->current_pulse().type != type) { - tape_player_->complete_pulse(); - } - - return tape_player_->current_pulse().type == type; - }; if(!wait_for(Pulse::Low)) { flags |= Flag::Carry; + m6502_.set_value_of(Register::Flags, flags); return; } // - //rwth ;it's high...now wait till it's low - // bit port - // bne rwth ; caught the edge + // Busy wait for low input from tape (Elaboration: exiting with error if the tape ends). + // if(!wait_for(Pulse::High)) { flags |= Flag::Carry; + m6502_.set_value_of(Register::Flags, flags); return; } - // stx timr2l - // sty timr2h - // - //; go! ...ta - // - // pla ;go! ...ta - // sta timr3l - // pla - // sta timr3h ;go! ...tb - // - //; clear timer flags - // - // lda #$50 ; clr ta,tb - // sta tedirq - // - //; um...check that edge again // - //casdb1 - // lda port - // cmp port - // bne casdb1 ; something is going on here... - // and #$10 ; a look at that edge again - // bne badeg1 ; woa! got a bad edge trigger !ls! + // If tape input goes high before dsamp1 cycles, report a short. // - //; must have been a valid edge - //; - //; do stop key check here - // - // jsr balout - // lda #$10 - //wata ; wait for ta to timeout - // bit port ; kuldge, kludge, kludge !!! <<><>> - // bne rshort ; kuldge, kludge, kludge !!! <<><>> - // bit tedirq - // beq wata - // - //; now do the dipole sample #1 - // - //casdb2 - // lda port - // cmp port - // bne casdb2 - // and #$10 - // bne rshort ; shorts anyone? - // - - const auto pulse_timer_cycles = [&] { - const auto length = tape_player_->current_pulse().length; - return (length.length * timer_clock_rate) / length.clock_rate; - }; const auto pulse_length = pulse_timer_cycles(); if(pulse_length <= dsamp1) { - // Goto rshort, i.e. ... flags |= Flag::Overflow; flags &= ~Flag::Carry; m6502_.set_value_of(Register::Flags, flags); return; } - if(pulse_length <= dsamp2) { + // + // Wait until end of dsamp1 period, then wait on until input is high, then wait until end of dsamp2 period. + // If input is then low, it's a long. + // + tape_player_->complete_pulse(); + const auto second_pulse_length = pulse_timer_cycles(); + if(second_pulse_length + pulse_length <= dsamp2) { // Goto rlong, i.e. ... flags &= ~(Flag::Carry | Flag::Sign); m6502_.set_value_of(Register::Flags, flags); return; } - // Test for a word by looking at the second part of the pulse. - tape_player_->complete_pulse(); - const auto second_pulse_length = pulse_timer_cycles(); - if(second_pulse_length <= zcell) { + // + // Now wait an additional zcell clocks. If input is still low, that's an error. Otherwise it's a word. + // + if(second_pulse_length + pulse_length > dsamp2 + zcell) { + flags |= Flag::Carry; + } else { flags |= Flag::Sign; flags &= ~(Flag::Carry | Flag::Overflow); - m6502_.set_value_of(Register::Flags, flags); - return; } - - flags |= Flag::Carry; m6502_.set_value_of(Register::Flags, flags); - - //; perhaps a long or a word? - // - // lda #$40 - //watb - // bit tedirq - // beq watb - // - //; wait for tb to timeout - //; now do the dipole sample #2 - // - //casdb3 - // lda port - // cmp port - // bne casdb3 - // and #$10 - // bne rlong ; looks like a long from here !ls! - // ; or could it be a word? - // lda zcell - // sta timr2l - // lda zcell+1 - // sta timr2h - // ; go! z-cell check - // ; clear ta flag - // lda #$10 - // sta tedirq ; verify +180 half of word dipole - // lda #$10 - //wata2 - // bit tedirq - // beq wata2 ; check z-cell is low - //casdb4 - // lda port - // cmp port - // bne casdb4 - // and #$10 - // beq rderr1 ; !ls! - // bit twordd ; got a word dipole - // bmi dipok ; !bra - // - //rshort - // bit tshrtd ; got a short - // bvs dipok ; !bra - // - //rlong - // bit tlongd ; got a long - // - //dipok - // clc ; everything's fine - // rts - // - //rderr1 - // sec ; i'm confused - // rts - - - -// ++pulse_num; -// const bool is_interesting = pulse_num >= 15220; -// -// if(pulse_num == 15224) { -// printf(""); -// } -// -//// if(address == 0xe68a) { -//// } -// -// if(address == 0xe5fd) { -// // TODO: -// // -// // ; read a dipole from tape (and then RTS) -// // ; -// // ; if c=1 then error -// // ; else if v=1 then short -// // ; else if n=0 then long -// // ; else word -// // ; end -// // ; end -// // ; end -// -// // 76 = V, not N or C -// // b7 = N -// -// // Compare with: -// // -// // dsamp1 *=*+2 ;time constant for x cell sample 07B8 -// // dsamp2 *=*+2 ;time constant for y cell sample -// // zcell *=*+2 ;time constant for z cell verify -// -// const auto zcell = read16(0x7bc); -// using Pulse = Storage::Tape::Pulse; -// -// // Wait until tape input is high (i.e. input is low). -//// while(tape_player_->current_pulse().type != Pulse::Type::Low) { -//// tape_player_->complete_pulse(); -//// } -// -// // Wait until tape input is low. -// while(tape_player_->current_pulse().type != Pulse::Type::High) { -// tape_player_->complete_pulse(); -// } -// -// // Count time of low high, and classify. -// const auto length1 = tape_player_->current_pulse().length.get(); -// tape_player_->complete_pulse(); // Consume High. -// const auto length2 = tape_player_->current_pulse().length.get(); -// tape_player_->complete_pulse(); // Consume Low. -// -// uint8_t flags = -// uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)) & -// ~(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Overflow | CPU::MOS6502::Flag::Sign); -// -// if(std::abs(length1 - length2) > 0.00025f) { -// // Lengths are too dissimilar; call that an error. -// flags |= CPU::MOS6502::Flag::Carry; -// } -// -//// const auto dsamp1_difference = std::abs(length1 - dsamp1); -//// const auto dsamp2_difference = std::abs(length1 - dsamp2); -//// const auto zcell_difference = std::abs(length1 - zcell); -// -// if(length2 < dsamp1) { -// flags |= CPU::MOS6502::Flag::Overflow; -// } else if(length2 > zcell) { -// flags |= CPU::MOS6502::Flag::Sign; -// } -// -// m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); -// -// if(is_interesting) { -// logger.info().append("Read: %d %d", -// int(length1 * 1'000'000), -// int(length2 * 1'000'000) -// ); -// } -// -// *value = 0x60; // i.e. RTS. -// } -// -// if(is_interesting) { -// const auto flags = m6502_.value_of(CPU::MOS6502::Register::Flags); -// logger.info().append("%d @ %d dipole result: %c%c%c", -// pulse_num, -// tape_player_->event_count(), -// flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', -// flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', -// flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); -//} -// } else { } uint8_t io_direction_ = 0x00, io_output_ = 0x00; From bc7ab0eba13b9a6fe15e32e902b9e1790027adb6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Jan 2025 22:37:10 -0500 Subject: [PATCH 04/13] Extend parser, accelerate headers. --- Analyser/Static/Commodore/StaticAnalyser.cpp | 4 +- Analyser/Static/Commodore/Tape.cpp | 4 +- Analyser/Static/Commodore/Tape.hpp | 3 +- Machines/Commodore/Plus4/Plus4.cpp | 64 +++++++++++--------- Machines/Commodore/Vic-20/Vic20.cpp | 8 +-- Storage/Tape/Parsers/Commodore.cpp | 49 ++++++++++----- Storage/Tape/Parsers/Commodore.hpp | 9 ++- 7 files changed, 89 insertions(+), 52 deletions(-) diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index a783189985..992aa6c76b 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -204,7 +204,7 @@ struct FileAnalysis { Analyser::Static::Media media; }; -template +template FileAnalysis analyse_files(const Analyser::Static::Media &media) { FileAnalysis analysis; @@ -226,7 +226,7 @@ FileAnalysis analyse_files(const Analyser::Static::Media &media) { // Find all valid Commodore files on tapes. for(auto &tape : media.tapes) { auto serialiser = tape->serialiser(); - std::vector tape_files = GetFiles(*serialiser); + std::vector tape_files = GetFiles(*serialiser, platform); if(!tape_files.empty()) { analysis.files.insert( analysis.files.end(), diff --git a/Analyser/Static/Commodore/Tape.cpp b/Analyser/Static/Commodore/Tape.cpp index 0c6926a615..888a23105b 100644 --- a/Analyser/Static/Commodore/Tape.cpp +++ b/Analyser/Static/Commodore/Tape.cpp @@ -12,8 +12,8 @@ using namespace Analyser::Static::Commodore; -std::vector Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser) { - Storage::Tape::Commodore::Parser parser; +std::vector Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) { + Storage::Tape::Commodore::Parser parser(type); std::vector file_list; std::unique_ptr header = parser.get_next_header(serialiser); diff --git a/Analyser/Static/Commodore/Tape.hpp b/Analyser/Static/Commodore/Tape.hpp index c0c2967e4e..4f3613514a 100644 --- a/Analyser/Static/Commodore/Tape.hpp +++ b/Analyser/Static/Commodore/Tape.hpp @@ -9,10 +9,11 @@ #pragma once #include "../../../Storage/Tape/Tape.hpp" +#include "../../../Storage/TargetPlatforms.hpp" #include "File.hpp" namespace Analyser::Static::Commodore { -std::vector GetFiles(Storage::Tape::TapeSerialiser &); +std::vector GetFiles(Storage::Tape::TapeSerialiser &, TargetPlatform::Type); } diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 6662f1f0d5..ce60e2b2b7 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -25,6 +25,7 @@ #include "../../../Analyser/Dynamic/ConfidenceCounter.hpp" #include "../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../Storage/Tape/Parsers/Commodore.hpp" #include "../../../Storage/Tape/Tape.hpp" #include "../SerialBus.hpp" #include "../1540/C1540.hpp" @@ -305,31 +306,40 @@ class ConcreteMachine: serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); } } else if(address < 0xfd00 || address >= 0xff40) { - constexpr bool use_hle = true; - constexpr uint16_t trap = use_hle ? 0xe5fd : 0xe68a; - - if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == trap) { - ++pulse_num_; - - if(use_hle) { - read_dipole(); - } - *value = 0x60; - - if(!tape_player_->is_at_end()) { - const auto flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)); - logger.info().append("%d @ %d dipole result: %c%c%c", - pulse_num_, - tape_player_->event_count(), - flags & CPU::MOS6502::Flag::Sign ? 'n' : '-', - flags & CPU::MOS6502::Flag::Overflow ? 'v' : '-', - flags & CPU::MOS6502::Flag::Carry ? 'c' : '-'); - } + if(is_read(operation)) { + *value = map_.read(address); } else { - if(is_read(operation)) { - *value = map_.read(address); - } else { - map_.write(address) = *value; + map_.write(address) = *value; + } + + if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) { + if(address == 0xe9cc) { + // Skip the `jsr rdblok` that opens `fah` (i.e. find any header), performing + // its function as a high-level emulation. + Storage::Tape::Commodore::Parser parser(TargetPlatform::Plus4); + auto header = parser.get_next_header(*tape_player_->serialiser()); + + const auto tape_position = tape_player_->serialiser()->offset(); + if(header) { + // Copy to in-memory buffer and set type. + std::memcpy(&ram_[0x0333], header->data.data(), 191); + map_.write(0xb6) = 0x33; + map_.write(0xb7) = 0x03; + map_.write(0xf8) = header->type_descriptor(); +// hold_tape_ = true; + logger.info().append("Found header"); + } else { + // no header found, so pretend this hack never interceded + tape_player_->serialiser()->set_offset(tape_position); +// hold_tape_ = false; + logger.info().append("Didn't find header"); + } + + // Clear status and the verify flags. + ram_[0x90] = 0; + ram_[0x93] = 0; + + *value = 0x0c; // NOP abs. } } } else if(address < 0xff00) { @@ -712,9 +722,9 @@ class ConcreteMachine: set_use_fast_tape(); } void read_dipole() { - if(pulse_num_ >= 15225) { - printf(""); - } +// if(pulse_num_ >= 15225) { +// printf(""); +// } // Provides an HLE implementation of the routine beginning at address // 0xe5fd in the ROM, i.e. rddipl (read dipole) as that's the one that // spins awaiting changes in tape input. diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index f77efcdb5a..395b598726 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -537,9 +537,9 @@ class ConcreteMachine: // Consider applying the fast tape hack. if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { if(address == 0xf7b2) { - // Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. - // So cancel that via a double NOP and fill in the next header programmatically. - Storage::Tape::Commodore::Parser parser; + // 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. + Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20); std::unique_ptr header = parser.get_next_header(*tape_->serialiser()); const auto tape_position = tape_->serialiser()->offset(); @@ -564,7 +564,7 @@ class ConcreteMachine: } else if(address == 0xf90b) { uint8_t x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); if(x == 0xe) { - Storage::Tape::Commodore::Parser parser; + 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()); if(data) { diff --git a/Storage/Tape/Parsers/Commodore.cpp b/Storage/Tape/Parsers/Commodore.cpp index ff304c27ba..eed63b809a 100644 --- a/Storage/Tape/Parsers/Commodore.cpp +++ b/Storage/Tape/Parsers/Commodore.cpp @@ -13,8 +13,9 @@ using namespace Storage::Tape::Commodore; -Parser::Parser() : - Storage::Tape::PulseClassificationParser() {} +Parser::Parser(TargetPlatform::Type target_platform) : + Storage::Tape::PulseClassificationParser(), + target_platform_(target_platform) {} /*! Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. @@ -113,18 +114,21 @@ std::unique_ptr
Parser::get_next_header_body(Storage::Tape::TapeSerialis return header; } -void Header::serialise(uint8_t *target, [[maybe_unused]] uint16_t length) { +uint8_t Header::type_descriptor() const { switch(type) { - default: target[0] = 0xff; break; - case Header::RelocatableProgram: target[0] = 0x01; break; - case Header::DataBlock: target[0] = 0x02; break; - case Header::NonRelocatableProgram: target[0] = 0x03; break; - case Header::DataSequenceHeader: target[0] = 0x04; break; - case Header::EndOfTape: target[0] = 0x05; break; + default: return 0xff; + case Header::RelocatableProgram: return 0x01; + case Header::DataBlock: return 0x02; + case Header::NonRelocatableProgram: return 0x03; + case Header::DataSequenceHeader: return 0x04; + case Header::EndOfTape: return 0x05; } +} - // TODO: validate length. +void Header::serialise(uint8_t *target, [[maybe_unused]] uint16_t length) const { + target[0] = type_descriptor(); + // TODO: validate length. std::memcpy(&target[1], data.data(), 191); } @@ -238,12 +242,29 @@ void Parser::process_pulse(const Storage::Tape::Pulse &pulse) { // short: 182us => 0.000364s cycle // medium: 262us => 0.000524s cycle // long: 342us => 0.000684s cycle + + // The C16, which polls for tape level around lengthy bad line pauses, instead uses these timings: + // short: 240us => 0.000480s cycle + // medium: 480us => 0.000960s cycle + // long: 960us => 0.001920s cycle + const bool is_high = pulse.type == Storage::Tape::Pulse::High; if(!is_high && previous_was_high_) { - if(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised); - else if(wave_period_ >= 0.000604) push_wave(WaveType::Long); - else if(wave_period_ >= 0.000444) push_wave(WaveType::Medium); - else if(wave_period_ >= 0.000284) push_wave(WaveType::Short); + const bool is_plus4 = target_platform_ == TargetPlatform::Plus4; + const float short_ms = is_plus4 ? 240.0f : 182.0f; + const float medium_ms = is_plus4 ? 480.0f : 262.0f; + const float long_ms = is_plus4 ? 960.0f : 342.0f; + + constexpr float to_s = 2.0f / 1'000'000.0f; + const float overlong_threshold = (long_ms + long_ms - medium_ms) * to_s; + const float long_threshold = ((long_ms + medium_ms) * 0.5f) * to_s; + const float medium_threshold = ((medium_ms + short_ms) * 0.5f) * to_s; + const float short_threshold = (short_ms * 0.5f) * to_s; + + if(wave_period_ >= overlong_threshold) push_wave(WaveType::Unrecognised); + else if(wave_period_ >= long_threshold) push_wave(WaveType::Long); + else if(wave_period_ >= medium_threshold) push_wave(WaveType::Medium); + else if(wave_period_ >= short_threshold) push_wave(WaveType::Short); else push_wave(WaveType::Unrecognised); wave_period_ = 0.0f; diff --git a/Storage/Tape/Parsers/Commodore.hpp b/Storage/Tape/Parsers/Commodore.hpp index 72c1bf57ca..3c6221232d 100644 --- a/Storage/Tape/Parsers/Commodore.hpp +++ b/Storage/Tape/Parsers/Commodore.hpp @@ -9,6 +9,7 @@ #pragma once #include "TapeParser.hpp" +#include "../../TargetPlatforms.hpp" #include #include @@ -44,7 +45,9 @@ struct Header { Writes a byte serialised version of this header to @c target, writing at most @c length bytes. */ - void serialise(uint8_t *target, uint16_t length); + void serialise(uint8_t *target, uint16_t length) const; + + uint8_t type_descriptor() const; }; struct Data { @@ -55,7 +58,7 @@ struct Data { class Parser: public Storage::Tape::PulseClassificationParser { public: - Parser(); + Parser(TargetPlatform::Type); /*! Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. @@ -70,6 +73,8 @@ class Parser: public Storage::Tape::PulseClassificationParser get_next_data(Storage::Tape::TapeSerialiser &); private: + TargetPlatform::Type target_platform_; + /*! Template for the logic in selecting which of two copies of something to consider authoritative, including setting the duplicate_matched flag. From 609aba7c737618f90615999bc2b4b5f5b446d49d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 22 Jan 2025 13:47:25 -0500 Subject: [PATCH 05/13] Made various additional style improvements. --- Machines/Commodore/Vic-20/Vic20.cpp | 114 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 395b598726..00b8c05973 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -68,7 +68,7 @@ class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { UserPortVIA() : port_a_(0xbf) {} /// Reports the current input to the 6522 port @c port. - uint8_t get_port_input(const MOS::MOS6522::Port port) { + uint8_t get_port_input(const MOS::MOS6522::Port port) const { // 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_. @@ -150,7 +150,7 @@ class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler { } /// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. - uint8_t get_port_input(const MOS::MOS6522::Port port) { + uint8_t get_port_input(const MOS::MOS6522::Port port) const { if(!port) { uint8_t result = 0xff; for(int c = 0; c < 8; c++) { @@ -222,14 +222,14 @@ class SerialPort : public ::Commodore::Serial::Port { */ struct Vic6560BusHandler { /// Performs a read on behalf of the 6560; in practice uses @c video_memory_map and @c colour_memory to find data. - forceinline void perform_read(const uint16_t address, uint8_t *const pixel_data, uint8_t *const colour_data) { + forceinline void perform_read(const uint16_t address, uint8_t *const pixel_data, uint8_t *const colour_data) const { *pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO *colour_data = colour_memory[address & 0x03ff]; } // It is assumed that these pointers have been filled in by the machine. - uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions. - uint8_t *colour_memory{}; // Colour memory must be contiguous. + 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. }; @@ -251,18 +251,20 @@ class Joystick: public Inputs::ConcreteJoystick { keyboard_via_port_handler_(keyboard_via_port_handler) {} void did_set_input(const Input &digital_input, const bool is_active) final { - JoystickInput mapped_input; - switch(digital_input.type) { - default: return; - case Input::Up: mapped_input = Up; break; - case Input::Down: mapped_input = Down; break; - case Input::Left: mapped_input = Left; break; - case Input::Right: mapped_input = Right; break; - case Input::Fire: mapped_input = Fire; break; + if(const auto mapped_input = [&]() -> std::optional { + switch(digital_input.type) { + default: return std::nullopt; + case Input::Up: return Up; + case Input::Down: return Down; + case Input::Left: return Left; + case Input::Right: return Right; + case Input::Fire: return Fire; + } + }(); mapped_input.has_value() + ) { + user_port_via_port_handler_.set_joystick_state(*mapped_input, is_active); + keyboard_via_port_handler_.set_joystick_state(*mapped_input, is_active); } - - user_port_via_port_handler_.set_joystick_state(mapped_input, is_active); - keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active); } private: @@ -292,18 +294,18 @@ class ConcreteMachine: user_port_via_(user_port_via_port_handler_), keyboard_via_(keyboard_via_port_handler_), tape_(new Storage::Tape::BinaryTapePlayer(1022727)) { - // communicate the tape to the user-port VIA + // Connect tape and user-port VIA. user_port_via_port_handler_.set_tape(tape_); - // wire up the serial bus and serial port + // Connect serial bus and serial port. Commodore::Serial::attach(serial_port_, serial_bus_); - // wire up 6522s and serial port + // Connect 6522s and serial port. user_port_via_port_handler_.set_serial_port(serial_port_); keyboard_via_port_handler_.set_serial_port(serial_port_); serial_port_.set_user_port_via(user_port_via_port_handler_); - // wire up the 6522s, tape and machine + // Connect 6522s, tape and machine. user_port_via_port_handler_.set_interrupt_delegate(this); keyboard_via_port_handler_.set_interrupt_delegate(this); tape_->set_delegate(this); @@ -312,6 +314,7 @@ class ConcreteMachine: // Install a joystick. joysticks_.emplace_back(new Joystick(user_port_via_port_handler_, keyboard_via_port_handler_)); + // Obtain and distribute ROMs. ROM::Request request(ROM::Name::Vic20BASIC); ROM::Name kernel, character; using Region = Analyser::Static::Commodore::Vic20Target::Region; @@ -363,7 +366,7 @@ class ConcreteMachine: c1540_->run_for(Cycles(2000000)); } - // Determine PAL/NTSC + // Determine PAL/NTSC. if(target.region == Region::American || target.region == Region::Japanese) { // NTSC set_clock_rate(1022727); @@ -377,17 +380,11 @@ class ConcreteMachine: mos6560_.set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20. mos6560_.set_clock_rate(get_clock_rate()); - // Initialise the memory maps as all pointing to nothing - memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); - memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); - memset(mos6560_bus_handler_.video_memory_map, 0, sizeof(mos6560_bus_handler_.video_memory_map)); - -#define set_ram(baseaddr, length) { \ - write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \ - write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); \ -} - // Add 6502-visible RAM as requested. + const auto set_ram = [&](uint16_t base_address, uint16_t length) { + write_to_map(processor_read_memory_map_, &ram_[base_address], base_address, length); + write_to_map(processor_write_memory_map_, &ram_[base_address], base_address, length); + }; set_ram(0x0000, 0x0400); set_ram(0x1000, 0x1000); // Built-in RAM. if(target.enabled_ram.bank0) set_ram(0x0400, 0x0c00); // Bank 0: 0x0400 -> 0x1000. @@ -396,9 +393,7 @@ class ConcreteMachine: if(target.enabled_ram.bank3) set_ram(0x6000, 0x2000); // Bank 3: 0x6000 -> 0x8000. if(target.enabled_ram.bank5) set_ram(0xa000, 0x2000); // Bank 5: 0xa000 -> 0xc000. -#undef set_ram - - // all expansions also have colour RAM visible at 0x9400. + // All expansions also have colour RAM visible at 0x9400. write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_)); write_to_map(processor_write_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_)); @@ -407,32 +402,29 @@ class ConcreteMachine: // memory bus. It can access only internal memory, so the first 1kb, then the 4kb from 0x1000. struct Range { const std::size_t start, end; - Range(std::size_t start, std::size_t end) : start(start), end(end) {} }; const std::array video_ranges = {{ - Range(0x0000, 0x0400), - Range(0x1000, 0x2000), + {0x0000, 0x0400}, + {0x1000, 0x2000}, }}; for(const auto &video_range : video_ranges) { for(auto addr = video_range.start; addr < video_range.end; addr += 0x400) { auto destination_address = (addr & 0x1fff) | (((addr & 0x8000) >> 2) ^ 0x2000); if(processor_read_memory_map_[addr >> 10]) { - write_to_map(mos6560_bus_handler_.video_memory_map, &ram_[addr], uint16_t(destination_address), 0x400); + write_to_map( + mos6560_bus_handler_.video_memory_map, &ram_[addr], uint16_t(destination_address), 0x400); } } } mos6560_bus_handler_.colour_memory = colour_ram_; - // install the BASIC ROM - write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, uint16_t(basic_rom_.size())); + // Install ROMs. + write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, basic_rom_.size()); + write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, character_rom_.size()); + write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, character_rom_.size()); + write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, kernel_rom_.size()); - // install the system ROM - write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, uint16_t(character_rom_.size())); - write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, uint16_t(character_rom_.size())); - write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, uint16_t(kernel_rom_.size())); - - // The insert_media occurs last, so if there's a conflict between cartridges and RAM, - // the cartridge wins. + // Insert media last so that if there's a conflict between cartridges and RAM, the cartridge wins. insert_media(target.media); if(!target.loading_command.empty()) { type_string(target.loading_command); @@ -459,7 +451,6 @@ class ConcreteMachine: } set_use_fast_tape(); - return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); } @@ -562,7 +553,7 @@ class ConcreteMachine: *value = 0x0c; // i.e. NOP abs, to swallow the entire JSR } else if(address == 0xf90b) { - uint8_t x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); + const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); if(x == 0xe) { Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20); const auto tape_position = tape_->serialiser()->offset(); @@ -711,7 +702,6 @@ class ConcreteMachine: void set_options(const std::unique_ptr &str) final { const auto options = dynamic_cast(str.get()); - set_video_signal_configurable(options->output); allow_fast_tape_hack_ = options->quickload; set_use_fast_tape(); @@ -742,17 +732,29 @@ class ConcreteMachine: uint8_t ram_[0x10000]; uint8_t colour_ram_[0x0400]; - uint8_t *processor_read_memory_map_[64]; - uint8_t *processor_write_memory_map_[64]; - void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { + const uint8_t *processor_read_memory_map_[64]{}; + uint8_t *processor_write_memory_map_[64]{}; + + void write_to_map(const std::function &store, uint16_t address, size_t length) { address >>= 10; length >>= 10; + size_t offset = 0; while(length--) { - map[address] = area; - area += 0x400; - address++; + store(address, offset); + offset += 0x400; + ++address; } } + void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, 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) { + write_to_map([&](const uint16_t address, const size_t offset) { + map[address] = &area[offset]; + }, address, length); + } Commodore::Vic20::KeyboardMapper keyboard_mapper_; std::vector> joysticks_; From 8ba57dec03309fd5f7a276419a9befcd1dc553c6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2025 22:07:17 -0500 Subject: [PATCH 06/13] Take another stab at read_dipole. --- Machines/Commodore/Plus4/Plus4.cpp | 399 ++++++++++++++---- .../Clock Signal.xcodeproj/project.pbxproj | 13 +- .../6502Esque/Implementation/LazyFlags.hpp | 14 +- 3 files changed, 339 insertions(+), 87 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index ce60e2b2b7..6a268f3525 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -30,6 +30,8 @@ #include "../SerialBus.hpp" #include "../1540/C1540.hpp" +#include "../../../Processors/6502Esque/Implementation/LazyFlags.hpp" + #include #include #include @@ -249,11 +251,7 @@ class ConcreteMachine: const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); // Update other subsystems. - timers_subcycles_ += length; - const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length()); - timers_.tick(timers_cycles.as()); - - tape_player_->run_for(length); + advance_timers_and_tape(length); video_.run_for(length); if(c1541_) { @@ -284,13 +282,7 @@ class ConcreteMachine: if(!address) { *value = io_direction_; } else { - const uint8_t all_inputs = - (tape_player_->input() ? 0x00 : 0x10) | - (serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) | - (serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00); - *value = - (io_direction_ & io_output_) | - (~io_direction_ & all_inputs); + *value = io_input(); } } else { if(!address) { @@ -306,13 +298,18 @@ class ConcreteMachine: serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); } } else if(address < 0xfd00 || address >= 0xff40) { - if(is_read(operation)) { - *value = map_.read(address); + if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) { + read_dipole(); + *value = 0x60; } else { - map_.write(address) = *value; + if(is_read(operation)) { + *value = map_.read(address); + } else { + map_.write(address) = *value; + } } - if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) { +/* if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) { if(address == 0xe9cc) { // Skip the `jsr rdblok` that opens `fah` (i.e. find any header), performing // its function as a high-level emulation. @@ -341,7 +338,7 @@ class ConcreteMachine: *value = 0x0c; // NOP abs. } - } + }*/ } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. if(is_read(operation)) { @@ -572,7 +569,8 @@ class ConcreteMachine: } private: - CPU::MOS6502::Processor m6502_; + using Processor = CPU::MOS6502::Processor; + Processor m6502_; Outputs::Speaker::Speaker *get_speaker() override { return &speaker_; @@ -721,103 +719,342 @@ class ConcreteMachine: tape_player_->set_motor_control(play_button_ && (~output & 0x08)); set_use_fast_tape(); } + void advance_timers_and_tape(const Cycles length) { + timers_subcycles_ += length; + const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length()); + timers_.tick(timers_cycles.as()); + + tape_player_->run_for(length); + } void read_dipole() { -// if(pulse_num_ >= 15225) { -// printf(""); -// } - // Provides an HLE implementation of the routine beginning at address - // 0xe5fd in the ROM, i.e. rddipl (read dipole) as that's the one that - // spins awaiting changes in tape input. using Register = CPU::MOS6502::Register; using Pulse = Storage::Tape::Pulse; using Flag = CPU::MOS6502::Flag; - auto s = uint8_t(m6502_.value_of(Register::StackPointer)); - auto flags = uint8_t(m6502_.value_of(Register::Flags)); - const unsigned int timer_clock_rate = - static_cast(get_clock_rate()) / video_.timer_cycle_length().as(); + // + // 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)); + 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); + }); - const auto wait_for = [&](Pulse::Type type) -> bool { - while(!tape_player_->is_at_end() && tape_player_->current_pulse().type != type) { - tape_player_->complete_pulse(); - } + // + // Time advancement. + // + const auto advance_cycles = [&](int cycles) { + advance_timers_and_tape(video_.cycle_length(false) * cycles); + }; - return tape_player_->current_pulse().type == type; + // + // 6502 pseudo-ops. + // + const auto ldabs = [&] (uint8_t &target, const uint16_t address) { + flags.set_nz(target = map_.read(address)); + }; + const auto ldimm = [&] (uint8_t &target, const uint8_t value) { + flags.set_nz(target = value); + }; + const auto pha = [&] () { + map_.write(0x100 + s) = a; + --s; + }; + const auto pla = [&] () { + ++s; + a = map_.read(0x100 + s); }; - const auto read16 = [&](uint16_t address) { - return uint16_t( map_.read(address) | (map_.read(address + 1) << 8) ); + const auto bit = [&] (const uint8_t value) { + flags.zero_result = a & value; + flags.negative_result = value; + flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow; }; - const auto pulse_timer_cycles = [&] { - const auto length = tape_player_->current_pulse().length; - return (length.length * timer_clock_rate) / length.clock_rate; + 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 auto andimm = [&] (const uint8_t value) { + a &= value; + flags.set_nz(a); + }; + const auto ne = [&]() -> bool { + return flags.zero_result; + }; + const auto eq = [&]() -> bool { + return !flags.zero_result; }; // - // Grab timing constants, and put one into memory as it had been on the stack. + // Common branch points. // - const auto dsamp1 = read16(0x7b8); - const auto dsamp2 = read16(0x7ba); - const auto zcell = read16(0x7bc); - - map_.write(0x100 + s) = uint8_t(dsamp2 >> 8); - --s; - map_.write(0x100 + s) = uint8_t (dsamp2); + const auto dipok = [&] { + // clc ; everything's fine + // rts + flags.carry = 0; + }; + const auto rshort = [&] { + // bit tshrtd ; got a short + // bvs dipok ; !bra + bit(0x40); + dipok(); + }; + const auto rlong = [&] { + // bit tlongd ; got a long + bit(0x00); + dipok(); + }; + const auto rderr1 = [&] { + // sec ; i'm confused + // rts + flags.carry = Flag::Carry; + }; // - // Busy wait for high input from tape (Elaboration: exiting with error if the tape ends). + // Labels. // - if(!wait_for(Pulse::Low)) { - flags |= Flag::Carry; - m6502_.set_value_of(Register::Flags, flags); - return; - } + static constexpr uint16_t dsamp1 = 0x7b8; + static constexpr uint16_t dsamp2 = 0x7ba; + static constexpr uint16_t zcell = 0x07bc; + + //rddipl + // ldx dsamp1 ; setup x,y with 1st sample point + // ldy dsamp1+1 + ldabs(x, dsamp1); + ldabs(y, dsamp1 + 1); + + //badeg1 + do { + // lda dsamp2+1 ; put 2nd samp value on stack in reverse order + // pha + // lda dsamp2 + // pha + ldabs(a, dsamp2 + 1); + pha(); + ldabs(a, dsamp2); + pha(); + + // lda #$10 + //rwtl ; wait till rd line is high + // bit port [= $0001] + // beq rwtl ; !ls! + ldimm(a, 0x10); + do { + bit(io_input()); + advance_cycles(7); + } while(eq()); + + //rwth ;it's high...now wait till it's low + // bit port + // bne rwth ; caught the edge + do { + bit(io_input()); + advance_cycles(7); + } while(ne()); + + + // stx timr2l + // sty timr2h + timers_.write<0xff02>(x); + timers_.write<0xff03>(y); + + //; go! ...ta + // + // pla ;go! ...ta + // sta timr3l + // pla + // sta timr3h ;go! ...tb + timers_.tick(4); + pla(); + timers_.write<0xff04>(a); + pla(); + timers_.write<0xff05>(a); + + + //; clear timer flags + // + // lda #$50 ; clr ta,tb + // sta tedirq + ldimm(a, 0x50); + interrupts_.set_status(a); + + + //; um...check that edge again + // + //casdb1 + // lda port + // cmp port + // bne casdb1 ; something is going on here... + // and #$10 ; a look at that edge again + // bne badeg1 ; woa! got a bad edge trigger !ls! + do { + ldimm(a, io_input()); + cmp(io_input()); + advance_cycles(11); + } while(ne()); + andimm(0x10); + } while(ne()); + // - // Busy wait for low input from tape (Elaboration: exiting with error if the tape ends). + //; must have been a valid edge + //; + //; do stop key check here // - if(!wait_for(Pulse::High)) { - flags |= Flag::Carry; - m6502_.set_value_of(Register::Flags, flags); - return; - } + // jsr balout + + /* balout not checked */ + + + // lda #$10 + //wata ; wait for ta to timeout + ldimm(a, 0x10); + do { + advance_cycles(13); + + // bit port ; kuldge, kludge, kludge !!! <<><>> + // bne rshort ; kuldge, kludge, kludge !!! <<><>> + bit(io_input()); + if(ne()) { + rshort(); + return; + } + + // bit tedirq + // beq wata + bit(interrupts_.status()); + } while(eq()); + // - // If tape input goes high before dsamp1 cycles, report a short. + //; now do the dipole sample #1 // - const auto pulse_length = pulse_timer_cycles(); - if(pulse_length <= dsamp1) { - flags |= Flag::Overflow; - flags &= ~Flag::Carry; - m6502_.set_value_of(Register::Flags, flags); + //casdb2 + do { + advance_cycles(11); + + // lda port + // cmp port + ldimm(a, io_input()); + cmp(io_input()); + + // bne casdb2 + } while(ne()); + + // and #$10 + // bne rshort ; shorts anyone? + andimm(0x10); + if(ne()) { + rshort(); return; } // - // Wait until end of dsamp1 period, then wait on until input is high, then wait until end of dsamp2 period. - // If input is then low, it's a long. + //; perhaps a long or a word? // - tape_player_->complete_pulse(); - const auto second_pulse_length = pulse_timer_cycles(); - if(second_pulse_length + pulse_length <= dsamp2) { - // Goto rlong, i.e. ... - flags &= ~(Flag::Carry | Flag::Sign); - m6502_.set_value_of(Register::Flags, flags); + // lda #$40 + //watb + // bit tedirq + // beq watb + // + //; wait for tb to timeout + //; now do the dipole sample #2 + ldimm(a, 0x40); + do { + advance_cycles(7); + bit(interrupts_.status()); + } while(eq()); + + + //casdb3 + // lda port + // cmp port + // bne casdb3 + do { + advance_cycles(11); + ldimm(a, io_input()); + cmp(io_input()); + } while(ne()); + + // and #$10 + // bne rlong ; looks like a long from here !ls! + andimm(0x10); + if(ne()) { + rlong(); return; } - // - // Now wait an additional zcell clocks. If input is still low, that's an error. Otherwise it's a word. - // - if(second_pulse_length + pulse_length > dsamp2 + zcell) { - flags |= Flag::Carry; - } else { - flags |= Flag::Sign; - flags &= ~(Flag::Carry | Flag::Overflow); + // ; or could it be a word? + // lda zcell + // sta timr2l + // lda zcell+1 + // sta timr2h + ldabs(a, zcell); + timers_.write<0xff02>(a); + ldabs(a, zcell + 1); + timers_.write<0xff03>(y); + + + // ; go! z-cell check + // ; clear ta flag + // lda #$10 + // sta tedirq ; verify +180 half of word dipole + // lda #$10 + ldimm(a, 0x10); + interrupts_.set_status(a); + ldimm(a, 0x10); + + //wata2 + // bit tedirq + // beq wata2 ; check z-cell is low + do { + advance_cycles(7); + bit(interrupts_.status()); + } while(eq()); + + //casdb4 + // lda port + // cmp port + // bne casdb4 + do { + advance_cycles(7); + ldimm(a, io_input()); + cmp(io_input()); + } while(ne()); + + // and #$10 + // beq rderr1 ; !ls! + // bit twordd ; got a word dipole + // bmi dipok ; !bra + andimm(0x10); + if(eq()) { + rderr1(); + return; } - m6502_.set_value_of(Register::Flags, flags); + bit(0x80); + dipok(); } uint8_t io_direction_ = 0x00, io_output_ = 0x00; + uint8_t io_input() const { + const uint8_t all_inputs = + (tape_player_->input() ? 0x00 : 0x10) | + (serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) | + (serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00); + return + (io_direction_ & io_output_) | + (~io_direction_ & all_inputs); + } std::vector> &get_joysticks() override { return joysticks_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 18ab9883a2..6b7ed73e1f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -2468,6 +2468,10 @@ 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EmuTOSTests.mm; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 4B24B3ED2D49BA4400691F28 /* 65x02 */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = 65x02; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 4B055A671FAE763F0060FFFF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -5289,6 +5293,7 @@ 4BEDA3B225B25563000C2DBD /* InstructionSets */ = { isa = PBXGroup; children = ( + 4B24B3ED2D49BA4400691F28 /* 65x02 */, 4BEDA42925B3C26B000C2DBD /* AccessType.hpp */, 4BEDA45425B5ECAB000C2DBD /* CachingExecutor.hpp */, 4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */, @@ -5454,6 +5459,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 4B24B3ED2D49BA4400691F28 /* 65x02 */, + ); name = "Clock Signal"; productName = "Clock Signal"; productReference = 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */; @@ -5472,6 +5480,9 @@ dependencies = ( 4BB73EB41B587A5100552FC2 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 4B24B3ED2D49BA4400691F28 /* 65x02 */, + ); name = "Clock SignalTests"; productName = "Clock SignalTests"; productReference = 4BB73EB21B587A5100552FC2 /* Clock SignalTests.xctest */; diff --git a/Processors/6502Esque/Implementation/LazyFlags.hpp b/Processors/6502Esque/Implementation/LazyFlags.hpp index bc44dd299b..dc96614c14 100644 --- a/Processors/6502Esque/Implementation/LazyFlags.hpp +++ b/Processors/6502Esque/Implementation/LazyFlags.hpp @@ -32,27 +32,27 @@ struct LazyFlags { uint8_t inverse_interrupt = 0; /// Sets N and Z flags per the 8-bit value @c value. - void set_nz(uint8_t 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(uint16_t value, int shift) { + 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(uint16_t value, int shift) { + 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(uint16_t value, int shift) { + void set_n(const uint16_t value, const int shift) { negative_result = uint8_t(value >> shift); } - void set(uint8_t flags) { + void set(const uint8_t flags) { carry = flags & Flag::Carry; negative_result = flags & Flag::Sign; zero_result = (~flags) & Flag::Zero; @@ -72,6 +72,10 @@ struct LazyFlags { decimal &= Flag::Decimal; overflow &= Flag::Overflow; } + + LazyFlags(const uint8_t value) { + set(value); + } }; } From 0ff6a0bb530ca0899d75c20ace794c3c45a07cda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2025 22:51:02 -0500 Subject: [PATCH 07/13] Slightly simplify template arguments. --- Machines/Commodore/Plus4/Plus4.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 6a268f3525..163d032838 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -865,8 +865,8 @@ class ConcreteMachine: // stx timr2l // sty timr2h - timers_.write<0xff02>(x); - timers_.write<0xff03>(y); + timers_.write<2>(x); + timers_.write<3>(y); //; go! ...ta // @@ -874,11 +874,10 @@ class ConcreteMachine: // sta timr3l // pla // sta timr3h ;go! ...tb - timers_.tick(4); pla(); - timers_.write<0xff04>(a); + timers_.write<4>(a); pla(); - timers_.write<0xff05>(a); + timers_.write<5>(a); //; clear timer flags @@ -1000,9 +999,9 @@ class ConcreteMachine: // lda zcell+1 // sta timr2h ldabs(a, zcell); - timers_.write<0xff02>(a); + timers_.write<2>(a); ldabs(a, zcell + 1); - timers_.write<0xff03>(y); + timers_.write<3>(y); // ; go! z-cell check From 6cb3bbaa2db44842bf165602842be9433b18f9ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2025 23:30:16 -0500 Subject: [PATCH 08/13] Ensure tape ending != infinite loop. --- Machines/Commodore/Plus4/Plus4.cpp | 50 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 163d032838..0bc73e0ed3 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -170,6 +170,7 @@ class SerialPort: public Serial::Port { class ConcreteMachine: public Activity::Source, public BusController, + public ClockingHint::Observer, public Configurable::Device, public CPU::MOS6502::BusHandler, public MachineTypes::AudioProducer, @@ -223,6 +224,7 @@ class ConcreteMachine: } tape_player_ = std::make_unique(clock); + tape_player_->set_clocking_hint_observer(this); joysticks_.emplace_back(std::make_unique()); joysticks_.emplace_back(std::make_unique()); @@ -712,12 +714,12 @@ class ConcreteMachine: bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack. bool use_fast_tape_hack_ = false; void set_use_fast_tape() { - use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_; + use_fast_tape_hack_ = + allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_ && !tape_player_->is_at_end(); } void update_tape_motor() { const auto output = io_output_ | ~io_direction_; tape_player_->set_motor_control(play_button_ && (~output & 0x08)); - set_use_fast_tape(); } void advance_timers_and_tape(const Cycles length) { timers_subcycles_ += length; @@ -753,8 +755,9 @@ class ConcreteMachine: // // Time advancement. // - const auto advance_cycles = [&](int cycles) { + const auto advance_cycles = [&](int cycles) -> bool { advance_timers_and_tape(video_.cycle_length(false) * cycles); + return !use_fast_tape_hack_; }; // @@ -851,7 +854,9 @@ class ConcreteMachine: ldimm(a, 0x10); do { bit(io_input()); - advance_cycles(7); + if(advance_cycles(7)) { + return; + } } while(eq()); //rwth ;it's high...now wait till it's low @@ -859,7 +864,9 @@ class ConcreteMachine: // bne rwth ; caught the edge do { bit(io_input()); - advance_cycles(7); + if(advance_cycles(7)) { + return; + } } while(ne()); @@ -899,7 +906,9 @@ class ConcreteMachine: do { ldimm(a, io_input()); cmp(io_input()); - advance_cycles(11); + if(advance_cycles(11)) { + return; + } } while(ne()); andimm(0x10); } while(ne()); @@ -919,7 +928,9 @@ class ConcreteMachine: //wata ; wait for ta to timeout ldimm(a, 0x10); do { - advance_cycles(13); + if(advance_cycles(13)) { + return; + } // bit port ; kuldge, kludge, kludge !!! <<><>> // bne rshort ; kuldge, kludge, kludge !!! <<><>> @@ -940,7 +951,9 @@ class ConcreteMachine: // //casdb2 do { - advance_cycles(11); + if(advance_cycles(11)) { + return; + } // lda port // cmp port @@ -970,7 +983,9 @@ class ConcreteMachine: //; now do the dipole sample #2 ldimm(a, 0x40); do { - advance_cycles(7); + if(advance_cycles(7)) { + return; + } bit(interrupts_.status()); } while(eq()); @@ -980,7 +995,9 @@ class ConcreteMachine: // cmp port // bne casdb3 do { - advance_cycles(11); + if(advance_cycles(11)) { + return; + } ldimm(a, io_input()); cmp(io_input()); } while(ne()); @@ -1017,7 +1034,9 @@ class ConcreteMachine: // bit tedirq // beq wata2 ; check z-cell is low do { - advance_cycles(7); + if(advance_cycles(7)) { + return; + } bit(interrupts_.status()); } while(eq()); @@ -1026,7 +1045,9 @@ class ConcreteMachine: // cmp port // bne casdb4 do { - advance_cycles(7); + if(advance_cycles(7)) { + return; + } ldimm(a, io_input()); cmp(io_input()); } while(ne()); @@ -1063,6 +1084,11 @@ class ConcreteMachine: } std::vector> joysticks_; + // MARK: - ClockingHint::Observer. + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) override { + set_use_fast_tape(); + } + // MARK: - Confidence. Analyser::Dynamic::ConfidenceCounter confidence_; float get_confidence() final { return confidence_.get_confidence(); } From 9cecccf5da0b0f84f1ade055c63d3fa06da6f166 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 30 Jan 2025 21:04:36 -0500 Subject: [PATCH 09/13] Correct TAP type check. --- Storage/Tape/Formats/CommodoreTAP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Tape/Formats/CommodoreTAP.cpp b/Storage/Tape/Formats/CommodoreTAP.cpp index 80fe080e40..7978f50493 100644 --- a/Storage/Tape/Formats/CommodoreTAP.cpp +++ b/Storage/Tape/Formats/CommodoreTAP.cpp @@ -86,7 +86,7 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() { const auto read_next_length = [&]() -> bool { uint32_t next_length; const uint8_t next_byte = file_.get8(); - if(updated_layout_ || next_byte > 0) { + if(!updated_layout_ || next_byte > 0) { next_length = uint32_t(next_byte) << 3; } else { next_length = file_.get24le(); From a9fe5d5c87ca32cab0d7bc4e21e58388e702f641 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 30 Jan 2025 22:48:11 -0500 Subject: [PATCH 10/13] Manually revert Xcode project version. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6b7ed73e1f..eb1e195a7d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ From 3a53c349a084f9cba737474568d711a5573b9a6e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 31 Jan 2025 11:45:43 -0500 Subject: [PATCH 11/13] Abandon attempts to build on older macOS for now. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e504c9dc55..bf8348b605 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ jobs: name: Mac UI / xcodebuild / ${{ matrix.os }} strategy: matrix: - os: [macos-13, macos-14, macos-15] + os: [macos-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout From 4dbd63de0833d81794a7a6f9c5f739d15d65086d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 31 Jan 2025 12:33:21 -0500 Subject: [PATCH 12/13] Attempt version rebump. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index eb1e195a7d..6b7ed73e1f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ From 49d931f5cc66ff30384b4d2e362e72c2c049d978 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 2 Feb 2025 21:41:37 -0500 Subject: [PATCH 13/13] Disable Mac job for now. --- .github/workflows/build.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf8348b605..c86fab42f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,18 +1,18 @@ name: Build on: [pull_request] jobs: - build-mac-xcodebuild: - name: Mac UI / xcodebuild / ${{ matrix.os }} - strategy: - matrix: - os: [macos-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Make - working-directory: OSBindings/Mac - run: xcodebuild CODE_SIGN_IDENTITY=- +# build-mac-xcodebuild: +# name: Mac UI / xcodebuild / ${{ matrix.os }} +# strategy: +# matrix: +# os: [macos-13, macos-14, macos-15] +# runs-on: ${{ matrix.os }} +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# - name: Make +# working-directory: OSBindings/Mac +# run: xcodebuild CODE_SIGN_IDENTITY=- build-sdl-cmake: name: SDL UI / cmake / ${{ matrix.os }} strategy: