diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 5d978e9d4..e863a2b03 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -24,12 +24,153 @@ #include "../../ClockReceiver/ForceInline.hpp" -#include #include -#include namespace Oric { +/*! + Models the Oric's keyboard: eight key rows, containing a bitfield of keys set. + + Active line is selected through a port on the Oric's VIA, and a column mask is + selected via a port on the AY, returning a single Boolean representation of the + logical OR of every key selected by the column mask on the active row. +*/ +class Keyboard { + public: + Keyboard() { + clear_all_keys(); + } + + /// Sets whether @c key is or is not pressed, per @c is_pressed. + void set_key_state(uint16_t key, bool is_pressed) { + uint8_t mask = key & 0xff; + int line = key >> 8; + + if(is_pressed) rows_[line] |= mask; + else rows_[line] &= ~mask; + } + + /// Sets all keys as unpressed. + void clear_all_keys() { + memset(rows_, 0, sizeof(rows_)); + } + + /// Selects the active row. + void set_active_row(uint8_t row) { + row_ = row & 7; + } + + /// Queries the keys on the active row specified by @c mask. + bool query_column(uint8_t column_mask) { + return !!(rows_[row_] & column_mask); + } + + private: + uint8_t row_ = 0; + uint8_t rows_[8]; +}; + +/*! + Provide's the Oric's tape player: a standard binary-sampled tape which also holds + an instance of the Oric tape parser, to provide fast-tape loading. +*/ +class TapePlayer: public Storage::Tape::BinaryTapePlayer { + public: + TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {} + + /*! + Parses the incoming tape event stream to obtain the next stored byte. + + @param use_fast_encoding If set to @c true , inspects the tape as though it + is encoded in the Oric's fast-loading scheme. Otherwise looks for a slow-encoded byte. + + @returns The next byte from the tape. + */ + uint8_t get_next_byte(bool use_fast_encoding) { + return (uint8_t)parser_.get_next_byte(get_tape(), use_fast_encoding); + } + + private: + Storage::Tape::Oric::Parser parser_; +}; + +/*! + Implements the Oric's VIA's port handler. On the Oric the VIA's ports connect + to the AY, the tape's motor control signal and the keyboard. +*/ +class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { + public: + VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {} + + /*! + Reponds to the 6522's control line output change signal; on an Oric A2 is connected to + the AY's BDIR, and B2 is connected to the AY's A2. + */ + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { + if(line) { + if(port) ay_bdir_ = value; else ay_bc1_ = value; + update_ay(); + ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); + } + } + + /*! + Reponds to changes in the 6522's port output. On an Oric port B sets the tape motor control + and the keyboard's active row. Port A is connected to the AY's data bus. + */ + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { + if(port) { + keyboard_.set_active_row(value); + tape_player_.set_motor_control(value & 0x40); + } else { + update_ay(); + ay8910_->set_data_input(value); + } + } + + /*! + Provides input data for the 6522. Port B reads the keyboard, and port A reads from the AY. + */ + uint8_t get_port_input(MOS::MOS6522::Port port) { + if(port) { + uint8_t column = ay8910_->get_port_output(false) ^ 0xff; + return keyboard_.query_column(column) ? 0x08 : 0x00; + } else { + return ay8910_->get_data_output(); + } + } + + /*! + Advances time. This class manages the AY's concept of time to permit updating-on-demand. + */ + inline void run_for(const Cycles cycles) { + cycles_since_ay_update_ += cycles; + } + + /// Flushes any queued behaviour (which, specifically, means on the AY). + void flush() { + ay8910_->run_for(cycles_since_ay_update_.flush()); + ay8910_->flush(); + } + + /// Sets the AY in use by the machine the VIA that uses this port handler sits within. + void set_ay(GI::AY38910::AY38910 *ay) { + ay8910_ = ay; + } + + private: + void update_ay() { + ay8910_->run_for(cycles_since_ay_update_.flush()); + } + bool ay_bdir_ = false; + bool ay_bc1_ = false; + Cycles cycles_since_ay_update_; + + GI::AY38910::AY38910 *ay8910_ = nullptr; + TapePlayer &tape_player_; + Keyboard &keyboard_; +}; + class ConcreteMachine: public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, @@ -41,19 +182,12 @@ class ConcreteMachine: public: ConcreteMachine() : m6502_(*this), - use_fast_tape_hack_(false), - typer_delay_(2500000), - keyboard_read_count_(0), - keyboard_(new Keyboard), - ram_top_(0xbfff), paged_rom_(rom_), - microdisc_is_enabled_(false), - via_(via_port_handler_) { + via_(via_port_handler_), + via_port_handler_(tape_player_, keyboard_) { set_clock_rate(1000000); via_port_handler_.set_interrupt_delegate(this); - via_port_handler_.keyboard = keyboard_; - clear_all_keys(); - via_port_handler_.tape->set_delegate(this); + tape_player_.set_delegate(this); Memory::Fuzz(ram_, sizeof(ram_)); } @@ -69,19 +203,16 @@ class ConcreteMachine: } } - void set_key_state(uint16_t key, bool isPressed) override final { + void set_key_state(uint16_t key, bool is_pressed) override final { if(key == KeyNMI) { - m6502_.set_nmi_line(isPressed); + m6502_.set_nmi_line(is_pressed); } else { - if(isPressed) - keyboard_->rows[key >> 8] |= (key & 0xff); - else - keyboard_->rows[key >> 8] &= ~(key & 0xff); + keyboard_.set_key_state(key, is_pressed); } } void clear_all_keys() override final { - memset(keyboard_->rows, 0, sizeof(keyboard_->rows)); + keyboard_.clear_all_keys(); } void set_use_fast_tape_hack(bool activate) override final { @@ -125,7 +256,7 @@ class ConcreteMachine: bool insert_media(const StaticAnalyser::Media &media) override final { if(media.tapes.size()) { - via_port_handler_.tape->set_tape(media.tapes.front()); + tape_player_.set_tape(media.tapes.front()); } int drive_index = 0; @@ -144,8 +275,14 @@ class ConcreteMachine: // 024D = 0 => fast; otherwise slow // E6C9 = read byte: return byte in A - if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_port_handler_.tape->has_tape() && !via_port_handler_.tape->get_tape()->is_at_end()) { - uint8_t next_byte = via_port_handler_.tape->get_next_byte(!ram_[tape_speed_address_]); + if( address == tape_get_byte_address_ && + paged_rom_ == rom_ && + use_fast_tape_hack_ && + operation == CPU::MOS6502::BusOperation::ReadOpcode && + tape_player_.has_tape() && + !tape_player_.get_tape()->is_at_end()) { + + uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]); m6502_.set_value_of_register(CPU::MOS6502::A, next_byte); m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); *value = 0x60; // i.e. RTS @@ -174,7 +311,7 @@ class ConcreteMachine: if(isReadOperation(operation)) *value = ram_[address]; else { - if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; } + if(address >= 0x9800 && address <= 0xc000) update_video(); ram_[address] = *value; } } @@ -192,6 +329,7 @@ class ConcreteMachine: via_.run_for(Cycles(1)); via_port_handler_.run_for(Cycles(1)); + tape_player_.run_for(Cycles(1)); if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8)); cycles_since_video_update_++; return Cycles(1); @@ -204,15 +342,18 @@ class ConcreteMachine: // to satisfy CRTMachine::Machine void setup_output(float aspect_ratio) override final { - via_port_handler_.ay8910.reset(new GI::AY38910::AY38910()); - via_port_handler_.ay8910->set_clock_rate(1000000); + ay8910_.reset(new GI::AY38910::AY38910()); + ay8910_->set_clock_rate(1000000); + via_port_handler_.set_ay(ay8910_.get()); + video_output_.reset(new VideoOutput(ram_)); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); } void close_output() override final { video_output_.reset(); - via_port_handler_.ay8910.reset(); + ay8910_.reset(); + via_port_handler_.set_ay(nullptr); } std::shared_ptr get_crt() override final { @@ -220,7 +361,7 @@ class ConcreteMachine: } std::shared_ptr get_speaker() override final { - return via_port_handler_.ay8910; + return ay8910_; } void run_for(const Cycles cycles) override final { @@ -276,95 +417,26 @@ class ConcreteMachine: } // ROM bookkeeping - bool is_using_basic11_; - uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_; - int keyboard_read_count_; + bool is_using_basic11_ = false; + uint16_t tape_get_byte_address_ = 0, scan_keyboard_address_ = 0, tape_speed_address_ = 0; + int keyboard_read_count_ = 0; // Outputs std::unique_ptr video_output_; - - // Keyboard - class Keyboard { - public: - uint8_t row; - uint8_t rows[8]; - }; - int typer_delay_; + std::shared_ptr ay8910_; // The tape - class TapePlayer: public Storage::Tape::BinaryTapePlayer { - public: - TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {} + TapePlayer tape_player_; + bool use_fast_tape_hack_ = false; - inline uint8_t get_next_byte(bool fast) { - return (uint8_t)parser_.get_next_byte(get_tape(), fast); - } - - private: - Storage::Tape::Oric::Parser parser_; - }; - bool use_fast_tape_hack_; - - // VIA (which owns the tape and the AY) - class VIA: public MOS::MOS6522::IRQDelegatePortHandler { - public: - VIA() : tape(new TapePlayer) {} - - void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { - if(line) { - if(port) ay_bdir_ = value; else ay_bc1_ = value; - update_ay(); - } - } - - void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { - if(port) { - keyboard->row = value; - tape->set_motor_control(value & 0x40); - } else { - ay8910->set_data_input(value); - } - } - - uint8_t get_port_input(MOS::MOS6522::Port port) { - if(port) { - uint8_t column = ay8910->get_port_output(false) ^ 0xff; - return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; - } else { - return ay8910->get_data_output(); - } - } - - inline void run_for(const Cycles cycles) { - cycles_since_ay_update_ += cycles; - tape->run_for(cycles); - } - - void flush() { - ay8910->run_for(cycles_since_ay_update_.flush()); - ay8910->flush(); - } - - std::shared_ptr ay8910; - std::unique_ptr tape; - std::shared_ptr keyboard; - - private: - void update_ay() { - ay8910->run_for(cycles_since_ay_update_.flush()); - ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); - } - bool ay_bdir_, ay_bc1_; - Cycles cycles_since_ay_update_; - }; - VIA via_port_handler_; - MOS::MOS6522::MOS6522 via_; - std::shared_ptr keyboard_; + VIAPortHandler via_port_handler_; + MOS::MOS6522::MOS6522 via_; + Keyboard keyboard_; // the Microdisc, if in use class Microdisc microdisc_; - bool microdisc_is_enabled_; - uint16_t ram_top_; + bool microdisc_is_enabled_ = false; + uint16_t ram_top_ = 0xbfff; uint8_t *paged_rom_; inline void set_interrupt_line() {