From bc7ab0eba13b9a6fe15e32e902b9e1790027adb6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Jan 2025 22:37:10 -0500 Subject: [PATCH] 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 a78318998..992aa6c76 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 0c6926a61..888a23105 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 c0c2967e4..4f3613514 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 6662f1f0d..ce60e2b2b 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 @@ public: 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 @@ private: 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 f77efcdb5..395b59872 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -537,9 +537,9 @@ public: // 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 @@ public: } 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 ff304c27b..eed63b809 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; } +} + +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 72c1bf57c..3c6221232 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 @@ public: std::unique_ptr 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.