mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +00:00
Reformulates the Oric more cleanly into the modern world.
Specifically: now that the implementation is contained within the CPP file, there's no need to embed the keyboard, tape player and VIA port handler as private classes. Also the pain of additional syntax is reduced, so the keyboard has been bumped up to a fully data-hiding class. I've also transferred overall ownership of the tape player, AY and keyboard up to the Oric itself, with the VIA merely being wired to them, and added a whole bunch of extra documentation.
This commit is contained in:
parent
da09098e49
commit
a42ca290cb
@ -24,12 +24,153 @@
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<Outputs::CRT::CRT> get_crt() override final {
|
||||
@ -220,7 +361,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> 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<VideoOutput> video_output_;
|
||||
|
||||
// Keyboard
|
||||
class Keyboard {
|
||||
public:
|
||||
uint8_t row;
|
||||
uint8_t rows[8];
|
||||
};
|
||||
int typer_delay_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> 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<GI::AY38910::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> 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> via_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
VIAPortHandler via_port_handler_;
|
||||
MOS::MOS6522::MOS6522<VIAPortHandler> 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() {
|
||||
|
Loading…
Reference in New Issue
Block a user