mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-19 23:32:28 +00:00
Specs out a new AppleII::Card
interface.
Doesn't yet fully implement it on the Apple II side though.
This commit is contained in:
parent
e482929da8
commit
80d34f5511
@ -286,25 +286,29 @@ class ConcreteMachine:
|
||||
break;
|
||||
}
|
||||
|
||||
if(address >= 0xc100 && address < 0xc800) {
|
||||
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.
|
||||
*/
|
||||
const size_t card_number = (address - 0xc100) >> 8;
|
||||
if(cards_[card_number]) {
|
||||
update_cards();
|
||||
cards_[card_number]->perform_bus_operation(operation, address & 0xff, value);
|
||||
}
|
||||
} else if(address >= 0xc090 && address < 0xc100) {
|
||||
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.
|
||||
*/
|
||||
const size_t card_number = (address - 0xc090) >> 4;
|
||||
card_number = (address - 0xc090) >> 4;
|
||||
select = AppleII::Card::IO;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
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(isReadOperation(operation)) {
|
||||
if(is_read) {
|
||||
if(disk_value != diskii_.DidNotLoad)
|
||||
*value = static_cast<uint8_t>(disk_value);
|
||||
}
|
||||
} break;
|
||||
case Device:
|
||||
if(is_read) *value = boot_[address & 0xff];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@ -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<Storage::Disk::Disk> &disk, int drive);
|
||||
|
Loading…
Reference in New Issue
Block a user