diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index b5aa63744..60fda9a7d 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -286,25 +286,29 @@ class ConcreteMachine: break; } - if(address >= 0xc100 && address < 0xc800) { - /* - Decode the area conventionally used by cards for ROMs: - 0xCn00 to 0xCnff: card n. - */ - const size_t card_number = (address - 0xc100) >> 8; - if(cards_[card_number]) { - update_cards(); - cards_[card_number]->perform_bus_operation(operation, address & 0xff, value); + if(address >= 0xc090 && address < 0xc800) { + size_t card_number = 0; + AppleII::Card::Select select = AppleII::Card::None; + + if(address >= 0xc100) { + /* + Decode the area conventionally used by cards for ROMs: + 0xCn00 to 0xCnff: card n. + */ + card_number = (address - 0xc100) >> 8; + select = AppleII::Card::Device; + } else { + /* + Decode the area conventionally used by cards for registers: + C0n0 to C0nF: card n - 8. + */ + card_number = (address - 0xc090) >> 4; + select = AppleII::Card::IO; } - } else if(address >= 0xc090 && address < 0xc100) { - /* - Decode the area conventionally used by cards for registers: - C0n0 to C0nF: card n - 8. - */ - const size_t card_number = (address - 0xc090) >> 4; + if(cards_[card_number]) { update_cards(); - cards_[card_number]->perform_bus_operation(operation, 0x100 | (address&0xf), value); + cards_[card_number]->perform_bus_operation(select, isReadOperation(operation), address, value); } } } diff --git a/Machines/AppleII/Card.hpp b/Machines/AppleII/Card.hpp index 4a8d7d864..42fb4fd23 100644 --- a/Machines/AppleII/Card.hpp +++ b/Machines/AppleII/Card.hpp @@ -15,16 +15,97 @@ namespace AppleII { +/*! + This provides a small subset of the interface offered to cards installed in + an Apple II, oriented pragmatically around the cards that are implemented. + + The main underlying rule is as it is elsewhere in the emulator: no + _inaccurate_ simplifications — no provision of information that shouldn't + actually commute, no interfaces that say they do one thing but which by both + both sides are coupled through an unwritten understanding of abuse. + + Special notes: + + Devices that announce a select constraint, being interested in acting only + when their IO or Device select is active will receive just-in-time @c run_for + notifications, as well as being updated at the end of each of the Apple's + @c run_for periods, prior to a @c flush. + + Devices that do not announce a select constraint will prima facie receive a + @c perform_bus_operation every cycle. They'll also receive a @c flush. + It is **highly** recomended that such devices also implement @c Sleeper + as they otherwise prima facie require a virtual method call every + single cycle. +*/ class Card { public: - /*! Advances time by @c cycles, of which @c stretches were stretched. */ + enum Select: int { + None = 0, // No select line is active + IO = 1 << 0, // IO select is active + Device = 1 << 1, // Device select is active + }; + + /*! + Advances time by @c cycles, of which @c stretches were stretched. + + This is posted only to cards that announced a select constraint. Cards with + no constraints, that want to be informed of every machine cycle, will receive + a call to perform_bus_operation every cycle and should use that for time keeping. + */ virtual void run_for(Cycles half_cycles, int stretches) {} - /*! Performs a bus operation; the card is implicitly selected. */ - virtual void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) = 0; + /// Requests a flush of any pending audio or video output. + virtual void flush() {} - /*! Supplies a target for observers. */ + /*! + Performs a bus operation. + + @param select The state of the card's select lines: indicates whether the Apple II + thinks this card should respond as though this were an IO access, a Device access, + or it thinks that the card shouldn't respond. + @param is_read @c true if this is a read cycle; @c false otherwise. + @param address The current value of the address bus. + @param value A pointer to the value of the data bus, not accounting input from cards. + If this is a read cycle, the card is permitted to replace this value with the value + output by the card, if any. If this is a write cycle, the card should only read + this value. + */ + virtual void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) = 0; + + /*! + Returns the type of bus selects this card is actually interested in. + As specified, the default is that cards will ask to receive perform_bus_operation + only when their select lines are active. + + There's a substantial caveat here: cards that register to receive @c None + will receive a perform_bus_operation every cycle. To reduce the number of + virtual method calls, they **will not** receive run_for. run_for will propagate + only to cards that register for IO and/or Device accesses only. + + + */ + int get_select_constraints() { + return select_constraints_; + } + + /*! Cards may supply a target for activity observation if desired. */ virtual void set_activity_observer(Activity::Observer *observer) {} + + struct Delegate { + virtual void card_did_change_select_constraints(Card *card) = 0; + }; + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } + + protected: + int select_constraints_ = IO | Device; + Delegate *delegate_ = nullptr; + void set_select_constraints(int constraints) { + if(constraints == select_constraints_) return; + select_constraints_ = constraints; + if(delegate_) delegate_->card_did_change_select_constraints(this); + } }; } diff --git a/Machines/AppleII/DiskIICard.cpp b/Machines/AppleII/DiskIICard.cpp index 6a0fb6c0f..b2dd411ba 100644 --- a/Machines/AppleII/DiskIICard.cpp +++ b/Machines/AppleII/DiskIICard.cpp @@ -19,19 +19,23 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec }); boot_ = std::move(*roms[0]); diskii_.set_state_machine(*roms[1]); + set_select_constraints(None); } -void DiskIICard::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - if(address < 0x100) { - if(isReadOperation(operation)) *value = boot_[address]; - } else { - // TODO: data input really shouldn't happen only upon a write. - diskii_.set_data_input(*value); - const int disk_value = diskii_.read_address(address); - if(isReadOperation(operation)) { - if(disk_value != diskii_.DidNotLoad) - *value = static_cast(disk_value); - } +void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) { + diskii_.set_data_input(*value); + switch(select) { + default: break; + case IO: { + const int disk_value = diskii_.read_address(address); + if(is_read) { + if(disk_value != diskii_.DidNotLoad) + *value = static_cast(disk_value); + } + } break; + case Device: + if(is_read) *value = boot_[address & 0xff]; + break; } } diff --git a/Machines/AppleII/DiskIICard.hpp b/Machines/AppleII/DiskIICard.hpp index 63953164d..48d30d021 100644 --- a/Machines/AppleII/DiskIICard.hpp +++ b/Machines/AppleII/DiskIICard.hpp @@ -14,6 +14,7 @@ #include "../../Components/DiskII/DiskII.hpp" #include "../../Storage/Disk/Disk.hpp" +#include "../../ClockReceiver/Sleeper.hpp" #include #include @@ -25,8 +26,9 @@ class DiskIICard: public Card { public: DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) override; + void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) override; void run_for(Cycles cycles, int stretches) override; + void set_activity_observer(Activity::Observer *observer) override; void set_disk(const std::shared_ptr &disk, int drive);