1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-23 20:29:42 +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:
Thomas Harte 2018-05-21 20:54:53 -04:00
parent e482929da8
commit 80d34f5511
4 changed files with 123 additions and 32 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
};
}

View File

@ -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<uint8_t>(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<uint8_t>(disk_value);
}
} break;
case Device:
if(is_read) *value = boot_[address & 0xff];
break;
}
}

View File

@ -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);