2016-10-12 02:20:13 +00:00
|
|
|
//
|
|
|
|
// Oric.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 11/10/2016.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
2016-10-12 02:20:13 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "Oric.hpp"
|
2017-08-03 15:42:31 +00:00
|
|
|
|
2017-10-21 14:52:35 +00:00
|
|
|
#include "Keyboard.hpp"
|
2017-10-13 02:25:02 +00:00
|
|
|
#include "Microdisc.hpp"
|
|
|
|
#include "Video.hpp"
|
2017-08-16 18:35:53 +00:00
|
|
|
|
2018-05-12 03:05:36 +00:00
|
|
|
#include "../../Activity/Source.hpp"
|
2018-07-11 01:32:28 +00:00
|
|
|
#include "../MediaTarget.hpp"
|
2018-03-09 20:19:02 +00:00
|
|
|
#include "../CRTMachine.hpp"
|
|
|
|
#include "../KeyboardMachine.hpp"
|
|
|
|
|
2017-10-21 14:30:02 +00:00
|
|
|
#include "../Utility/MemoryFuzzer.hpp"
|
2018-05-13 17:57:19 +00:00
|
|
|
#include "../Utility/StringSerialiser.hpp"
|
2016-10-12 02:20:13 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
#include "../../Processors/6502/6502.hpp"
|
|
|
|
#include "../../Components/6522/6522.hpp"
|
|
|
|
#include "../../Components/AY38910/AY38910.hpp"
|
2018-05-09 02:05:43 +00:00
|
|
|
#include "../../Components/DiskII/DiskII.hpp"
|
2017-08-03 15:42:31 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
#include "../../Storage/Tape/Tape.hpp"
|
|
|
|
#include "../../Storage/Tape/Parsers/Oric.hpp"
|
2016-10-12 02:20:13 +00:00
|
|
|
|
2017-08-22 01:56:42 +00:00
|
|
|
#include "../../ClockReceiver/ForceInline.hpp"
|
2017-11-21 02:55:32 +00:00
|
|
|
#include "../../Configurable/StandardOptions.hpp"
|
2017-12-19 02:39:23 +00:00
|
|
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
2017-08-22 01:56:42 +00:00
|
|
|
|
2018-03-09 21:07:29 +00:00
|
|
|
#include "../../Analyser/Static/Oric/Target.hpp"
|
|
|
|
|
2017-11-24 21:59:00 +00:00
|
|
|
#include <cstdint>
|
2017-08-16 18:35:53 +00:00
|
|
|
#include <memory>
|
2017-11-24 21:59:00 +00:00
|
|
|
#include <vector>
|
2017-08-16 18:35:53 +00:00
|
|
|
|
|
|
|
namespace Oric {
|
|
|
|
|
2017-11-24 21:59:00 +00:00
|
|
|
enum ROM {
|
|
|
|
BASIC10 = 0, BASIC11, Microdisc, Colour
|
|
|
|
};
|
|
|
|
|
2017-11-21 02:55:32 +00:00
|
|
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
|
|
|
return Configurable::standard_options(
|
2018-11-29 01:53:33 +00:00
|
|
|
static_cast<Configurable::StandardOptions>(
|
|
|
|
Configurable::DisplayRGB |
|
|
|
|
Configurable::DisplayCompositeColour |
|
|
|
|
Configurable::DisplayCompositeMonochrome |
|
|
|
|
Configurable::QuickLoadTape)
|
2017-11-21 02:55:32 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-09-04 22:22:14 +00:00
|
|
|
/*!
|
|
|
|
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) {
|
2017-10-22 01:50:53 +00:00
|
|
|
return static_cast<uint8_t>(parser_.get_next_byte(get_tape(), use_fast_encoding));
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2017-12-18 02:26:06 +00:00
|
|
|
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, GI::AY38910::AY38910 &ay8910, Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
|
|
|
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
|
2017-09-04 22:22:14 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
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();
|
2017-12-18 02:26:06 +00:00
|
|
|
ay8910_.set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
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();
|
2017-12-18 02:26:06 +00:00
|
|
|
ay8910_.set_data_input(value);
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
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) {
|
2017-12-18 02:26:06 +00:00
|
|
|
uint8_t column = ay8910_.get_port_output(false) ^ 0xff;
|
2017-09-04 22:22:14 +00:00
|
|
|
return keyboard_.query_column(column) ? 0x08 : 0x00;
|
|
|
|
} else {
|
2017-12-18 02:26:06 +00:00
|
|
|
return ay8910_.get_data_output();
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
|
|
|
|
*/
|
2019-06-01 18:39:40 +00:00
|
|
|
inline void run_for(const HalfCycles cycles) {
|
2017-09-04 22:22:14 +00:00
|
|
|
cycles_since_ay_update_ += cycles;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Flushes any queued behaviour (which, specifically, means on the AY).
|
|
|
|
void flush() {
|
2017-12-18 02:26:06 +00:00
|
|
|
update_ay();
|
|
|
|
audio_queue_.perform();
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void update_ay() {
|
2019-07-29 01:49:54 +00:00
|
|
|
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
|
2017-09-04 22:22:14 +00:00
|
|
|
}
|
|
|
|
bool ay_bdir_ = false;
|
|
|
|
bool ay_bc1_ = false;
|
2019-06-01 18:39:40 +00:00
|
|
|
HalfCycles cycles_since_ay_update_;
|
2017-09-04 22:22:14 +00:00
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
|
|
|
GI::AY38910::AY38910 &ay8910_;
|
|
|
|
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker_;
|
2017-09-04 22:22:14 +00:00
|
|
|
TapePlayer &tape_player_;
|
|
|
|
Keyboard &keyboard_;
|
|
|
|
};
|
|
|
|
|
2018-05-09 02:05:43 +00:00
|
|
|
template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine:
|
2018-03-09 20:19:02 +00:00
|
|
|
public CRTMachine::Machine,
|
2018-07-11 01:32:28 +00:00
|
|
|
public MediaTarget::Machine,
|
2018-10-25 01:59:30 +00:00
|
|
|
public KeyboardMachine::MappedMachine,
|
2018-03-09 20:19:02 +00:00
|
|
|
public Configurable::Device,
|
2017-08-16 18:35:53 +00:00
|
|
|
public CPU::MOS6502::BusHandler,
|
2017-09-04 18:26:04 +00:00
|
|
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
2017-08-16 18:35:53 +00:00
|
|
|
public Utility::TypeRecipient,
|
|
|
|
public Storage::Tape::BinaryTapePlayer::Delegate,
|
|
|
|
public Microdisc::Delegate,
|
2018-05-28 03:17:06 +00:00
|
|
|
public ClockingHint::Observer,
|
2018-05-12 03:05:36 +00:00
|
|
|
public Activity::Source,
|
2017-08-16 18:35:53 +00:00
|
|
|
public Machine {
|
|
|
|
|
|
|
|
public:
|
2018-07-11 00:00:46 +00:00
|
|
|
ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
2018-08-14 02:17:22 +00:00
|
|
|
m6502_(*this),
|
2018-11-29 02:16:13 +00:00
|
|
|
video_output_(ram_),
|
2017-12-18 02:26:06 +00:00
|
|
|
ay8910_(audio_queue_),
|
|
|
|
speaker_(ay8910_),
|
|
|
|
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
|
2018-05-28 21:19:29 +00:00
|
|
|
via_(via_port_handler_),
|
|
|
|
diskii_(2000000) {
|
2017-08-16 18:35:53 +00:00
|
|
|
set_clock_rate(1000000);
|
2019-02-06 02:42:39 +00:00
|
|
|
speaker_.set_input_rate(1000000.0f);
|
2017-09-04 18:26:04 +00:00
|
|
|
via_port_handler_.set_interrupt_delegate(this);
|
2017-09-04 22:22:14 +00:00
|
|
|
tape_player_.set_delegate(this);
|
2017-08-16 18:35:53 +00:00
|
|
|
Memory::Fuzz(ram_, sizeof(ram_));
|
2018-05-20 03:15:28 +00:00
|
|
|
|
|
|
|
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) {
|
2018-05-28 03:17:06 +00:00
|
|
|
diskii_.set_clocking_hint_observer(this);
|
2018-05-20 03:15:28 +00:00
|
|
|
}
|
2018-03-23 01:59:19 +00:00
|
|
|
|
2019-07-23 01:14:21 +00:00
|
|
|
const std::string machine_name = "Oric";
|
2019-07-23 02:15:44 +00:00
|
|
|
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
|
2018-07-11 00:00:46 +00:00
|
|
|
switch(target.rom) {
|
2019-07-23 02:15:44 +00:00
|
|
|
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
|
|
|
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
|
|
|
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
|
|
|
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
|
|
|
|
break;
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
2019-07-23 03:07:23 +00:00
|
|
|
size_t diskii_state_machine_index = 0;
|
2018-05-09 02:05:43 +00:00
|
|
|
switch(disk_interface) {
|
|
|
|
default: break;
|
2019-07-23 02:15:44 +00:00
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
rom_names.emplace_back(machine_name, "the ORIC Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
|
|
|
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
|
2019-07-23 03:07:23 +00:00
|
|
|
// These ROM details are coupled with those in the DiskIICard.
|
|
|
|
diskii_state_machine_index = rom_names.size();
|
|
|
|
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
|
2019-07-23 02:15:44 +00:00
|
|
|
break;
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
|
|
|
|
2019-07-23 01:14:21 +00:00
|
|
|
const auto roms = rom_fetcher(rom_names);
|
2017-11-08 02:19:51 +00:00
|
|
|
|
2017-11-11 20:28:40 +00:00
|
|
|
for(std::size_t index = 0; index < roms.size(); ++index) {
|
2018-07-11 00:00:46 +00:00
|
|
|
if(!roms[index]) {
|
|
|
|
throw ROMMachine::Error::MissingROMs;
|
|
|
|
}
|
2017-11-07 03:14:15 +00:00
|
|
|
}
|
2018-07-11 00:00:46 +00:00
|
|
|
|
2018-11-29 02:16:13 +00:00
|
|
|
video_output_.set_colour_rom(*roms[0]);
|
2018-05-09 02:05:43 +00:00
|
|
|
rom_ = std::move(*roms[1]);
|
|
|
|
|
|
|
|
switch(disk_interface) {
|
|
|
|
default: break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
microdisc_rom_ = std::move(*roms[2]);
|
|
|
|
microdisc_rom_.resize(8192);
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: {
|
|
|
|
pravetz_rom_ = std::move(*roms[2]);
|
|
|
|
pravetz_rom_.resize(512);
|
|
|
|
|
2019-07-23 03:07:23 +00:00
|
|
|
diskii_.set_state_machine(*roms[diskii_state_machine_index]);
|
2018-05-09 02:05:43 +00:00
|
|
|
} break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rom_.resize(16384);
|
|
|
|
paged_rom_ = rom_.data();
|
2017-11-25 03:22:32 +00:00
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
switch(target.disk_interface) {
|
2018-05-09 02:05:43 +00:00
|
|
|
default: break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
microdisc_did_change_paging_flags(µdisc_);
|
|
|
|
microdisc_.set_delegate(this);
|
|
|
|
break;
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-11-04 02:14:40 +00:00
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
if(!target.loading_command.empty()) {
|
|
|
|
type_string(target.loading_command);
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-12 02:20:13 +00:00
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
switch(target.rom) {
|
2018-05-09 02:05:43 +00:00
|
|
|
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
|
|
|
tape_get_byte_address_ = 0xe630;
|
|
|
|
tape_speed_address_ = 0x67;
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
|
|
|
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
|
|
|
tape_get_byte_address_ = 0xe6c9;
|
|
|
|
tape_speed_address_ = 0x024d;
|
|
|
|
break;
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2017-08-17 14:48:29 +00:00
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
insert_media(target.media);
|
|
|
|
}
|
|
|
|
|
|
|
|
~ConcreteMachine() {
|
|
|
|
audio_queue_.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_key_state(uint16_t key, bool is_pressed) override final {
|
|
|
|
if(key == KeyNMI) {
|
|
|
|
m6502_.set_nmi_line(is_pressed);
|
|
|
|
} else {
|
|
|
|
keyboard_.set_key_state(key, is_pressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_all_keys() override final {
|
|
|
|
keyboard_.clear_all_keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_use_fast_tape_hack(bool activate) {
|
|
|
|
use_fast_tape_hack_ = activate;
|
2017-08-17 14:48:29 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 02:48:44 +00:00
|
|
|
bool insert_media(const Analyser::Static::Media &media) override final {
|
2018-05-09 02:05:43 +00:00
|
|
|
bool inserted = false;
|
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
if(!media.tapes.empty()) {
|
2017-09-04 22:22:14 +00:00
|
|
|
tape_player_.set_tape(media.tapes.front());
|
2018-05-09 02:05:43 +00:00
|
|
|
inserted = true;
|
2017-08-17 14:48:29 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 02:05:43 +00:00
|
|
|
if(!media.disks.empty()) {
|
|
|
|
switch(disk_interface) {
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc: {
|
|
|
|
inserted = true;
|
2018-05-12 03:05:36 +00:00
|
|
|
size_t drive_index = 0;
|
2018-05-09 02:05:43 +00:00
|
|
|
for(auto &disk : media.disks) {
|
|
|
|
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
2018-05-12 03:05:36 +00:00
|
|
|
++drive_index;
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: {
|
|
|
|
inserted = true;
|
|
|
|
int drive_index = 0;
|
|
|
|
for(auto &disk : media.disks) {
|
|
|
|
if(drive_index < 2) diskii_.set_disk(disk, drive_index);
|
2018-05-12 03:05:36 +00:00
|
|
|
++drive_index;
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
2017-08-17 14:48:29 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 02:05:43 +00:00
|
|
|
return inserted;
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-12 02:20:13 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// to satisfy CPU::MOS6502::BusHandler
|
2017-08-22 01:56:42 +00:00
|
|
|
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
2017-08-16 18:35:53 +00:00
|
|
|
if(address > ram_top_) {
|
|
|
|
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
|
|
|
|
|
|
|
// 024D = 0 => fast; otherwise slow
|
|
|
|
// E6C9 = read byte: return byte in A
|
2017-09-04 22:22:14 +00:00
|
|
|
if( address == tape_get_byte_address_ &&
|
2018-05-09 02:05:43 +00:00
|
|
|
paged_rom_ == rom_.data() &&
|
2017-09-04 22:22:14 +00:00
|
|
|
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_]);
|
2017-08-16 18:35:53 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if((address & 0xff00) == 0x0300) {
|
2018-05-09 02:05:43 +00:00
|
|
|
if(address < 0x0310 || (disk_interface == Analyser::Static::Oric::Target::DiskInterface::None)) {
|
|
|
|
if(isReadOperation(operation)) *value = via_.get_register(address);
|
|
|
|
else via_.set_register(address, *value);
|
|
|
|
} else {
|
|
|
|
switch(disk_interface) {
|
|
|
|
default: break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
switch(address) {
|
|
|
|
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
|
|
|
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
|
|
|
else microdisc_.set_register(address, *value);
|
|
|
|
break;
|
|
|
|
case 0x314: case 0x315: case 0x316: case 0x317:
|
|
|
|
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
|
|
|
|
else microdisc_.set_control_register(*value);
|
|
|
|
break;
|
|
|
|
case 0x318: case 0x319: case 0x31a: case 0x31b:
|
|
|
|
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
|
|
|
break;
|
|
|
|
}
|
2017-08-16 18:35:53 +00:00
|
|
|
break;
|
2018-05-09 02:05:43 +00:00
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
|
|
|
if(address >= 0x0320) {
|
|
|
|
if(isReadOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
|
|
|
|
else {
|
|
|
|
switch(address) {
|
|
|
|
case 0x380: case 0x381: case 0x382: case 0x383:
|
2018-05-10 00:28:25 +00:00
|
|
|
ram_top_ = (address&1) ? basic_invisible_ram_top_ : basic_visible_ram_top_;
|
2018-05-09 02:05:43 +00:00
|
|
|
pravetz_rom_base_pointer_ = (address&2) ? 0x100 : 0x000;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-05-28 22:20:43 +00:00
|
|
|
flush_diskii();
|
2018-05-18 01:39:11 +00:00
|
|
|
const int disk_value = diskii_.read_address(address);
|
|
|
|
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value);
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
2017-08-16 18:35:53 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(isReadOperation(operation))
|
|
|
|
*value = ram_[address];
|
|
|
|
else {
|
2017-09-04 22:22:14 +00:00
|
|
|
if(address >= 0x9800 && address <= 0xc000) update_video();
|
2017-08-16 18:35:53 +00:00
|
|
|
ram_[address] = *value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-14 00:50:55 +00:00
|
|
|
|
2018-05-13 19:34:31 +00:00
|
|
|
// $02df is where the Oric ROMs; all of them, including BASIC 1.0, 1.1 and the Pravetz; have the
|
2018-05-13 18:02:46 +00:00
|
|
|
// IRQ routine store an incoming keystroke in order for reading to occur later. By capturing the
|
|
|
|
// read rather than the decode and write: (i) nothing is lost while BASIC is parsing; and
|
|
|
|
// (ii) keyboard input is much more rapid.
|
|
|
|
if(string_serialiser_ && address == 0x02df && operation == CPU::MOS6502::BusOperation::Read) {
|
|
|
|
*value = string_serialiser_->head() | 0x80;
|
2018-05-13 17:57:19 +00:00
|
|
|
if(!string_serialiser_->advance()) string_serialiser_.reset();
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-15 01:18:03 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
via_.run_for(Cycles(1));
|
2017-09-04 22:22:14 +00:00
|
|
|
tape_player_.run_for(Cycles(1));
|
2018-05-09 02:05:43 +00:00
|
|
|
switch(disk_interface) {
|
|
|
|
default: break;
|
2018-05-18 01:39:11 +00:00
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
microdisc_.run_for(Cycles(8));
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
2018-05-28 22:20:43 +00:00
|
|
|
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
2018-05-20 03:15:28 +00:00
|
|
|
diskii_.set_data_input(*value);
|
|
|
|
diskii_.run_for(Cycles(2));
|
2018-05-28 22:20:43 +00:00
|
|
|
} else {
|
|
|
|
cycles_since_diskii_update_ += Cycles(2);
|
2018-05-20 03:15:28 +00:00
|
|
|
}
|
2018-05-18 01:39:11 +00:00
|
|
|
break;
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
2017-08-16 18:35:53 +00:00
|
|
|
cycles_since_video_update_++;
|
|
|
|
return Cycles(1);
|
|
|
|
}
|
2016-10-15 01:18:03 +00:00
|
|
|
|
2017-08-22 01:56:42 +00:00
|
|
|
forceinline void flush() {
|
2017-08-16 18:35:53 +00:00
|
|
|
update_video();
|
2019-06-01 18:39:40 +00:00
|
|
|
via_.flush();
|
2018-05-28 22:20:43 +00:00
|
|
|
flush_diskii();
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-16 01:21:18 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// to satisfy CRTMachine::Machine
|
2018-11-15 02:52:57 +00:00
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
2018-11-29 02:16:13 +00:00
|
|
|
video_output_.set_scan_target(scan_target);
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-11-03 11:59:30 +00:00
|
|
|
|
2018-11-30 04:44:21 +00:00
|
|
|
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
|
|
|
video_output_.set_display_type(display_type);
|
|
|
|
}
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
Outputs::Speaker::Speaker *get_speaker() override final {
|
|
|
|
return &speaker_;
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-31 02:51:08 +00:00
|
|
|
|
2017-08-22 01:56:42 +00:00
|
|
|
void run_for(const Cycles cycles) override final {
|
2017-08-16 18:35:53 +00:00
|
|
|
m6502_.run_for(cycles);
|
|
|
|
}
|
2016-10-31 02:51:08 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// to satisfy MOS::MOS6522IRQDelegate::Delegate
|
2017-08-22 01:56:42 +00:00
|
|
|
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
2017-08-16 18:35:53 +00:00
|
|
|
set_interrupt_line();
|
|
|
|
}
|
2016-10-31 02:51:08 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
2017-08-22 01:56:42 +00:00
|
|
|
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
|
2017-08-16 18:35:53 +00:00
|
|
|
// set CB1
|
2017-09-04 18:26:04 +00:00
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-21 00:53:17 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// for Utility::TypeRecipient::Delegate
|
2017-12-29 20:26:03 +00:00
|
|
|
void type_string(const std::string &string) override final {
|
2018-05-13 17:57:19 +00:00
|
|
|
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2017-03-26 18:34:47 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// for Microdisc::Delegate
|
2017-08-22 01:56:42 +00:00
|
|
|
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
|
2017-08-16 18:35:53 +00:00
|
|
|
int flags = microdisc->get_paging_flags();
|
|
|
|
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
2018-05-24 22:53:02 +00:00
|
|
|
ram_top_ = basic_visible_ram_top_;
|
2018-05-09 02:05:43 +00:00
|
|
|
paged_rom_ = rom_.data();
|
2017-08-16 18:35:53 +00:00
|
|
|
} else {
|
2018-05-24 22:57:35 +00:00
|
|
|
if(flags&Microdisc::PagingFlags::MicrodiscDisable) {
|
2018-05-24 22:53:02 +00:00
|
|
|
ram_top_ = basic_invisible_ram_top_;
|
2017-08-16 18:35:53 +00:00
|
|
|
} else {
|
|
|
|
ram_top_ = 0xdfff;
|
|
|
|
paged_rom_ = microdisc_rom_.data();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-21 00:53:17 +00:00
|
|
|
|
2017-08-22 01:56:42 +00:00
|
|
|
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
|
2017-08-16 18:35:53 +00:00
|
|
|
set_interrupt_line();
|
|
|
|
}
|
2016-10-21 00:53:17 +00:00
|
|
|
|
2018-02-09 21:31:05 +00:00
|
|
|
KeyboardMapper *get_keyboard_mapper() override {
|
|
|
|
return &keyboard_mapper_;
|
2017-10-13 02:25:02 +00:00
|
|
|
}
|
|
|
|
|
2017-11-19 00:48:10 +00:00
|
|
|
// MARK: - Configuration options.
|
|
|
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
2017-11-21 02:55:32 +00:00
|
|
|
return Oric::get_options();
|
2017-11-19 00:48:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
2017-11-21 02:55:32 +00:00
|
|
|
bool quickload;
|
|
|
|
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
|
|
|
set_use_fast_tape_hack(quickload);
|
|
|
|
}
|
2017-11-19 00:48:10 +00:00
|
|
|
|
2017-11-21 02:55:32 +00:00
|
|
|
Configurable::Display display;
|
|
|
|
if(Configurable::get_display(selections_by_option, display)) {
|
2018-04-01 02:14:34 +00:00
|
|
|
set_video_signal_configurable(display);
|
2017-11-21 02:55:32 +00:00
|
|
|
}
|
2017-11-19 00:48:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Configurable::SelectionSet get_accurate_selections() override {
|
|
|
|
Configurable::SelectionSet selection_set;
|
2017-11-21 02:55:32 +00:00
|
|
|
Configurable::append_quick_load_tape_selection(selection_set, false);
|
2018-11-29 01:53:33 +00:00
|
|
|
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
2017-11-19 00:48:10 +00:00
|
|
|
return selection_set;
|
|
|
|
}
|
|
|
|
|
|
|
|
Configurable::SelectionSet get_user_friendly_selections() override {
|
|
|
|
Configurable::SelectionSet selection_set;
|
2017-11-21 02:55:32 +00:00
|
|
|
Configurable::append_quick_load_tape_selection(selection_set, true);
|
|
|
|
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
2017-11-19 00:48:10 +00:00
|
|
|
return selection_set;
|
|
|
|
}
|
|
|
|
|
2018-05-12 03:05:36 +00:00
|
|
|
void set_activity_observer(Activity::Observer *observer) override {
|
|
|
|
switch(disk_interface) {
|
|
|
|
default: break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
|
|
|
microdisc_.set_activity_observer(observer);
|
|
|
|
break;
|
|
|
|
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
|
|
|
diskii_.set_activity_observer(observer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-28 22:20:43 +00:00
|
|
|
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
|
|
|
|
diskii_clocking_preference_ = diskii_.preferred_clocking();
|
2018-05-20 03:15:28 +00:00
|
|
|
}
|
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
private:
|
2018-05-10 00:28:25 +00:00
|
|
|
const uint16_t basic_invisible_ram_top_ = 0xffff;
|
|
|
|
const uint16_t basic_visible_ram_top_ = 0xbfff;
|
|
|
|
|
2018-08-14 02:17:22 +00:00
|
|
|
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
2016-10-21 00:53:17 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// RAM and ROM
|
2018-11-29 02:16:13 +00:00
|
|
|
std::vector<uint8_t> rom_, microdisc_rom_;
|
2018-05-09 02:05:43 +00:00
|
|
|
uint8_t ram_[65536];
|
2017-08-16 18:35:53 +00:00
|
|
|
Cycles cycles_since_video_update_;
|
|
|
|
inline void update_video() {
|
2019-07-29 01:49:54 +00:00
|
|
|
video_output_.run_for(cycles_since_video_update_.flush<Cycles>());
|
2017-08-16 18:35:53 +00:00
|
|
|
}
|
2016-10-21 00:53:17 +00:00
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// ROM bookkeeping
|
2018-05-13 18:02:46 +00:00
|
|
|
uint16_t tape_get_byte_address_ = 0, tape_speed_address_ = 0;
|
2017-09-04 22:22:14 +00:00
|
|
|
int keyboard_read_count_ = 0;
|
2017-08-16 18:35:53 +00:00
|
|
|
|
|
|
|
// Outputs
|
2018-11-29 02:16:13 +00:00
|
|
|
VideoOutput video_output_;
|
2017-12-18 02:26:06 +00:00
|
|
|
|
|
|
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
|
|
|
GI::AY38910::AY38910 ay8910_;
|
|
|
|
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
2017-08-16 18:35:53 +00:00
|
|
|
|
2017-10-13 02:25:02 +00:00
|
|
|
// Inputs
|
|
|
|
Oric::KeyboardMapper keyboard_mapper_;
|
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
// The tape
|
2017-09-04 22:22:14 +00:00
|
|
|
TapePlayer tape_player_;
|
|
|
|
bool use_fast_tape_hack_ = false;
|
2017-08-16 18:35:53 +00:00
|
|
|
|
2017-09-04 22:22:14 +00:00
|
|
|
VIAPortHandler via_port_handler_;
|
|
|
|
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
|
|
|
Keyboard keyboard_;
|
2017-08-16 18:35:53 +00:00
|
|
|
|
|
|
|
// the Microdisc, if in use
|
|
|
|
class Microdisc microdisc_;
|
2018-05-09 02:05:43 +00:00
|
|
|
|
|
|
|
// the Pravetz/Disk II, if in use
|
|
|
|
Apple::DiskII diskii_;
|
2018-05-28 22:20:43 +00:00
|
|
|
Cycles cycles_since_diskii_update_;
|
|
|
|
void flush_diskii() {
|
2019-07-29 01:49:54 +00:00
|
|
|
diskii_.run_for(cycles_since_diskii_update_.flush<Cycles>());
|
2018-05-28 22:20:43 +00:00
|
|
|
}
|
2018-05-09 02:05:43 +00:00
|
|
|
std::vector<uint8_t> pravetz_rom_;
|
|
|
|
std::size_t pravetz_rom_base_pointer_ = 0;
|
2018-05-28 22:20:43 +00:00
|
|
|
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
2018-05-09 02:05:43 +00:00
|
|
|
|
|
|
|
// Overlay RAM
|
2018-05-10 00:28:25 +00:00
|
|
|
uint16_t ram_top_ = basic_visible_ram_top_;
|
2018-05-09 02:05:43 +00:00
|
|
|
uint8_t *paged_rom_ = nullptr;
|
2017-08-16 18:35:53 +00:00
|
|
|
|
2018-05-09 02:05:43 +00:00
|
|
|
// Helper to discern current IRQ state
|
2017-08-16 18:35:53 +00:00
|
|
|
inline void set_interrupt_line() {
|
2018-05-09 02:05:43 +00:00
|
|
|
bool irq_line = via_.get_interrupt_line();
|
|
|
|
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Microdisc)
|
|
|
|
irq_line |= microdisc_.get_interrupt_request_line();
|
|
|
|
m6502_.set_irq_line(irq_line);
|
2016-11-25 12:15:48 +00:00
|
|
|
}
|
2018-05-13 17:57:19 +00:00
|
|
|
|
|
|
|
// MARK - typing
|
|
|
|
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
2017-08-16 18:35:53 +00:00
|
|
|
};
|
2016-11-25 12:15:48 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-08-16 18:35:53 +00:00
|
|
|
using namespace Oric;
|
|
|
|
|
2018-07-11 00:00:46 +00:00
|
|
|
Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) {
|
2018-05-09 02:05:43 +00:00
|
|
|
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint);
|
|
|
|
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
|
|
|
|
switch(oric_target->disk_interface) {
|
2018-07-11 00:00:46 +00:00
|
|
|
default: return new ConcreteMachine<DiskInterface::None>(*oric_target, rom_fetcher);
|
|
|
|
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
|
|
|
|
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
|
2018-05-09 02:05:43 +00:00
|
|
|
}
|
2016-11-25 12:15:48 +00:00
|
|
|
}
|
2017-08-16 18:35:53 +00:00
|
|
|
|
|
|
|
Machine::~Machine() {}
|