mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-10 00:29:40 +00:00
Merge pull request #1434 from TomHarte/Plus4Startup
Add a simulacrum of C16+4 emulation.
This commit is contained in:
commit
1ed550d7f9
@ -14,6 +14,7 @@
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
@ -37,7 +38,7 @@ public:
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(const uint8_t track, const uint8_t sector) {
|
||||
const Sector *sector(const uint8_t track, const uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
@ -68,7 +69,7 @@ private:
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
|
||||
|
||||
void process_input_bit(const int value) override {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
@ -111,23 +112,24 @@ private:
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(const uint8_t sector) {
|
||||
const Sector *get_sector(const uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
auto existing = sector_cache_.find(sector_address);
|
||||
if(existing != sector_cache_.end()) return existing->second.get();
|
||||
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
const auto first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1) {
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
while(true) {
|
||||
const auto next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
auto sector = std::make_shared<Sector>();
|
||||
const Sector *get_next_sector() {
|
||||
auto sector = std::make_unique<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
@ -160,8 +162,8 @@ private:
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
auto pair = sector_cache_.emplace(sector_address, std::move(sector));
|
||||
return pair.first->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,21 +173,29 @@ private:
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
std::vector<File> files;
|
||||
auto parser = std::make_unique<CommodoreGCRParser>();
|
||||
parser->set_disk(disk);
|
||||
CommodoreGCRParser parser;
|
||||
parser.set_disk(disk);
|
||||
|
||||
// Assemble directory.
|
||||
std::vector<uint8_t> directory;
|
||||
uint8_t next_track = 18;
|
||||
uint8_t next_sector = 1;
|
||||
directory.reserve(20 * 1024); // Probably more than plenty.
|
||||
std::set<std::pair<uint8_t, uint8_t>> visited;
|
||||
while(true) {
|
||||
auto sector = parser->sector(next_track, next_sector);
|
||||
// Don't be fooled by disks that are encoded with a looping directory.
|
||||
const auto key = std::make_pair(next_track, next_sector);
|
||||
if(visited.find(key) != visited.end()) break;
|
||||
visited.insert(key);
|
||||
|
||||
// Append sector to directory and follow next link.
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
// Check for end-of-directory.
|
||||
if(!next_track) break;
|
||||
}
|
||||
|
||||
@ -222,7 +232,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
auto sector = parser->sector(next_track, next_sector);
|
||||
const auto sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
|
||||
next_track = sector->data[0];
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
@ -108,7 +109,7 @@ std::optional<BASICAnalysis> analyse(const File &file) {
|
||||
while(true) {
|
||||
// Analysis has failed if there isn't at least one complete BASIC line from here.
|
||||
// Fall back on guessing the start address as a machine code entrypoint.
|
||||
if(size_t(line_address - file.starting_address) + 5 >= file.data.size()) {
|
||||
if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
|
||||
analysis.machine_code_addresses.push_back(file.starting_address);
|
||||
break;
|
||||
}
|
||||
@ -209,10 +210,6 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
|
||||
// Inspect discovered files to try to divine machine and memory model.
|
||||
auto vic_memory_model = Target::MemoryModel::Unexpanded;
|
||||
|
||||
if(files.size() > 1) {
|
||||
printf("");
|
||||
}
|
||||
|
||||
auto it = files.begin();
|
||||
while(it != files.end()) {
|
||||
const auto &file = *it;
|
||||
|
@ -127,7 +127,10 @@ Video::Video() :
|
||||
|
||||
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
|
||||
// usual output height of 200 lines.
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f));
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(
|
||||
33, 260,
|
||||
480, 1280,
|
||||
4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void Video::set_ram(uint16_t *ram, size_t size) {
|
||||
|
@ -36,20 +36,20 @@ namespace Commodore::C1540 {
|
||||
Provides an emulation of the C1540.
|
||||
*/
|
||||
class Machine final: public MachineBase {
|
||||
public:
|
||||
static ROM::Request rom_request(Personality personality);
|
||||
Machine(Personality personality, const ROM::Map &roms);
|
||||
public:
|
||||
static ROM::Request rom_request(Personality personality);
|
||||
Machine(Personality personality, const ROM::Map &roms);
|
||||
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
*/
|
||||
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
*/
|
||||
void set_serial_bus(Commodore::Serial::Bus &);
|
||||
|
||||
/// Advances time.
|
||||
void run_for(const Cycles cycles);
|
||||
/// Advances time.
|
||||
void run_for(Cycles);
|
||||
|
||||
/// Inserts @c disk into the drive.
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
/// Inserts @c disk into the drive.
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -16,56 +16,54 @@
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
ROM::Request Machine::rom_request(Personality personality) {
|
||||
namespace {
|
||||
ROM::Name rom_name(Personality personality) {
|
||||
switch(personality) {
|
||||
default:
|
||||
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
|
||||
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
|
||||
case Personality::C1540: return ROM::Name::Commodore1540;
|
||||
case Personality::C1541: return ROM::Name::Commodore1541;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ROM::Request Machine::rom_request(Personality personality) {
|
||||
return ROM::Request(rom_name(personality));
|
||||
}
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_port_handler_(serial_port_VIA_),
|
||||
drive_VIA_(drive_VIA_port_handler_),
|
||||
serial_port_VIA_(*serial_port_VIA_port_handler_) {
|
||||
// attach the serial port to its VIA and vice versa
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
||||
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
||||
serial_port_VIA_(serial_port_VIA_port_handler_) {
|
||||
// Attach the serial port to its VIA and vice versa.
|
||||
serial_port_.set_serial_port_via(serial_port_VIA_port_handler_);
|
||||
serial_port_VIA_port_handler_.set_serial_port(serial_port_);
|
||||
|
||||
// set this instance as the delegate to receive interrupt requests from both VIAs
|
||||
serial_port_VIA_port_handler_->set_interrupt_delegate(this);
|
||||
// Set this instance as the delegate to receive interrupt requests from both VIAs.
|
||||
serial_port_VIA_port_handler_.set_interrupt_delegate(this);
|
||||
drive_VIA_port_handler_.set_interrupt_delegate(this);
|
||||
drive_VIA_port_handler_.set_delegate(this);
|
||||
|
||||
// set a bit rate
|
||||
// Set a bit rate.
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
|
||||
// attach the only drive there is
|
||||
// Attach the only drive there is.
|
||||
emplace_drive(1000000, 300, 2);
|
||||
set_drive(1);
|
||||
|
||||
ROM::Name rom_name;
|
||||
switch(personality) {
|
||||
default:
|
||||
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
|
||||
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
|
||||
}
|
||||
|
||||
auto rom = roms.find(rom_name);
|
||||
const auto rom = roms.find(rom_name(personality));
|
||||
if(rom == roms.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
|
||||
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
|
||||
}
|
||||
|
||||
Machine::Machine(Personality personality, const ROM::Map &roms) :
|
||||
MachineBase(personality, roms) {}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
|
||||
Commodore::Serial::attach(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
@ -155,11 +153,11 @@ void MachineBase::process_index_hole() {}
|
||||
|
||||
// MARK: - Drive VIA delegate
|
||||
|
||||
void MachineBase::drive_via_did_step_head(void *, int direction) {
|
||||
void MachineBase::drive_via_did_step_head(void *, const int direction) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction, 2));
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_did_set_data_density(void *, int density) {
|
||||
void MachineBase::drive_via_did_set_data_density(void *, const int density) {
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density)));
|
||||
}
|
||||
|
||||
@ -174,14 +172,11 @@ uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
|
||||
|
||||
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
|
||||
if(port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
data_level_output_ = (value&0x02);
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
data_level_output_ = (value&0x02);
|
||||
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08)));
|
||||
update_data_line();
|
||||
}
|
||||
serial_port_->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08)));
|
||||
update_data_line();
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,17 +194,14 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
|
||||
serial_port_ = &port;
|
||||
}
|
||||
|
||||
void SerialPortVIA::update_data_line() {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data,
|
||||
::Commodore::Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
}
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serial_port_->set_output(::Commodore::Serial::Line::Data,
|
||||
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
}
|
||||
|
||||
// MARK: - DriveVIA
|
||||
@ -248,23 +240,23 @@ void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Li
|
||||
void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
|
||||
if(port) {
|
||||
if(previous_port_b_output_ != value) {
|
||||
// record drive motor state
|
||||
drive_motor_ = !!(value&4);
|
||||
// Record drive motor state.
|
||||
drive_motor_ = value&4;
|
||||
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
// Check for a head step.
|
||||
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference) {
|
||||
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
// Check for a change in density.
|
||||
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// post the LED status
|
||||
if(observer_) observer_->set_led_status("Drive", !!(value&8));
|
||||
// Post the LED status.
|
||||
if(observer_) observer_->set_led_status("Drive", value&8);
|
||||
|
||||
previous_port_b_output_ = value;
|
||||
}
|
||||
@ -275,17 +267,16 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
|
||||
observer_ = observer;
|
||||
if(observer) {
|
||||
observer->register_led("Drive");
|
||||
observer->set_led_status("Drive", !!(previous_port_b_output_&8));
|
||||
observer->set_led_status("Drive", previous_port_b_output_&8);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SerialPort
|
||||
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
|
||||
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, bool(level));
|
||||
void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) {
|
||||
serial_port_VIA_->set_serial_line_state(line, bool(level));
|
||||
}
|
||||
|
||||
void SerialPort::set_serial_port_via(const std::shared_ptr<SerialPortVIA> &serialPortVIA) {
|
||||
serial_port_VIA_ = serialPortVIA;
|
||||
void SerialPort::set_serial_port_via(SerialPortVIA &serialPortVIA) {
|
||||
serial_port_VIA_ = &serialPortVIA;
|
||||
}
|
||||
|
@ -46,12 +46,12 @@ public:
|
||||
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask);
|
||||
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
||||
|
||||
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
||||
void set_serial_port(Commodore::Serial::Port &);
|
||||
|
||||
private:
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> &via_;
|
||||
uint8_t port_b_ = 0x0;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
Commodore::Serial::Port *serial_port_ = nullptr;
|
||||
bool attention_acknowledge_level_ = false;
|
||||
bool attention_level_input_ = true;
|
||||
bool data_level_output_ = false;
|
||||
@ -84,7 +84,7 @@ public:
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port);
|
||||
uint8_t get_port_input(MOS::MOS6522::Port);
|
||||
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
@ -95,7 +95,7 @@ public:
|
||||
|
||||
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask);
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
private:
|
||||
uint8_t port_b_ = 0xff, port_a_ = 0xff;
|
||||
@ -111,11 +111,11 @@ private:
|
||||
*/
|
||||
class SerialPort : public ::Commodore::Serial::Port {
|
||||
public:
|
||||
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
||||
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
||||
void set_input(Commodore::Serial::Line, Commodore::Serial::LineLevel);
|
||||
void set_serial_port_via(SerialPortVIA &);
|
||||
|
||||
private:
|
||||
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
SerialPortVIA *serial_port_VIA_ = nullptr;
|
||||
};
|
||||
|
||||
class MachineBase:
|
||||
@ -125,37 +125,37 @@ class MachineBase:
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
public:
|
||||
MachineBase(Personality personality, const ROM::Map &roms);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(void *driveVIA, int direction);
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
MachineBase(Personality, const ROM::Map &);
|
||||
|
||||
/// Attaches the activity observer to this C1540.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value);
|
||||
|
||||
protected:
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override;
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(void *driveVIA, int direction) override;
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density) override;
|
||||
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
SerialPortVIA serial_port_VIA_port_handler_;
|
||||
SerialPort serial_port_;
|
||||
DriveVIA drive_VIA_port_handler_;
|
||||
|
||||
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||
|
||||
int shift_register_ = 0, bit_window_offset_;
|
||||
virtual void process_input_bit(int value);
|
||||
virtual void process_index_hole();
|
||||
void process_input_bit(int value) override;
|
||||
void process_index_hole() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
113
Machines/Commodore/Plus4/Audio.hpp
Normal file
113
Machines/Commodore/Plus4/Audio.hpp
Normal file
@ -0,0 +1,113 @@
|
||||
//
|
||||
// Audio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
// PAL: / 160 i.e. 5*32
|
||||
// NTSC: / 128 i.e. 4*32
|
||||
|
||||
// 111860.78125 = NTSC
|
||||
// 110840.46875 = PAL
|
||||
|
||||
class Audio: public Outputs::Speaker::BufferSource<Audio, false> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t size, Outputs::Speaker::MonoSample *const target) {
|
||||
const auto count_frequency = [&](int index) {
|
||||
++counts_[index];
|
||||
if(counts_[index] == (frequencies_[index] ^ 1023) * frequency_multiplier_) {
|
||||
states_[index] ^= 1;
|
||||
counts_[index] = 0;
|
||||
} else if(counts_[index] == 1024 * frequency_multiplier_) {
|
||||
counts_[index] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
if(sound_dc_) {
|
||||
Outputs::Speaker::fill<action>(target, target + size, Outputs::Speaker::MonoSample(volume_ * 2));
|
||||
} else {
|
||||
// TODO: noise generation.
|
||||
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
count_frequency(0);
|
||||
count_frequency(1);
|
||||
|
||||
Outputs::Speaker::apply<action>(
|
||||
target[c],
|
||||
Outputs::Speaker::MonoSample(
|
||||
(
|
||||
((states_[0] & masks_[0]) * external_volume_) +
|
||||
((states_[1] & masks_[1]) * external_volume_)
|
||||
) * volume_
|
||||
));
|
||||
}
|
||||
}
|
||||
r_ += size;
|
||||
}
|
||||
|
||||
void set_sample_volume_range(const std::int16_t range) {
|
||||
external_volume_ = range / (2 * 9); // Two channels and nine output levels.
|
||||
}
|
||||
|
||||
bool is_zero_level() const {
|
||||
return !(masks_[0] || masks_[1] || sound2_noise_on_) || !volume_;
|
||||
}
|
||||
|
||||
template <int channel> void set_frequency_low(uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
frequencies_[channel] = (frequencies_[channel] & 0xff00) | value;
|
||||
});
|
||||
}
|
||||
|
||||
template <int channel> void set_frequency_high(uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
frequencies_[channel] = (frequencies_[channel] & 0x00ff) | ((value&3) << 8);
|
||||
});
|
||||
}
|
||||
|
||||
void set_constrol(uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
volume_ = std::min(value & 0xf, 8); // Only nine volumes are available.
|
||||
masks_[0] = (value & 0x10) ? 1 : 0;
|
||||
|
||||
masks_[1] = (value & 0x20) ? 1 : 0;
|
||||
sound2_noise_on_ = (value & 0x40) && !(value & 0x20);
|
||||
|
||||
sound_dc_ = value & 0x80;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
// Calling-thread state.
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
// Audio-thread state.
|
||||
int16_t external_volume_ = 0;
|
||||
int frequencies_[2]{};
|
||||
int frequency_multiplier_ = 5;
|
||||
int counts_[2]{};
|
||||
|
||||
int states_[2]{};
|
||||
int masks_[2]{};
|
||||
|
||||
bool sound2_noise_on_ = false;
|
||||
bool sound_dc_ = false;
|
||||
int volume_ = 0;
|
||||
|
||||
int r_ = 0;
|
||||
};
|
||||
|
||||
}
|
72
Machines/Commodore/Plus4/Interrupts.hpp
Normal file
72
Machines/Commodore/Plus4/Interrupts.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Interrupts.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/12/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
struct BusController {
|
||||
virtual void set_irq_line(bool) = 0;
|
||||
virtual void set_ready_line(bool) = 0;
|
||||
};
|
||||
|
||||
struct Interrupts {
|
||||
public:
|
||||
Interrupts(BusController &delegate) : delegate_(delegate) {}
|
||||
BusController &bus() {
|
||||
return delegate_;
|
||||
}
|
||||
|
||||
enum Flag {
|
||||
Timer3 = 0x40,
|
||||
Timer2 = 0x10,
|
||||
Timer1 = 0x08,
|
||||
Raster = 0x02,
|
||||
};
|
||||
|
||||
uint8_t status() const {
|
||||
return status_ | ((status_ & mask_) ? 0x80 : 0x00);
|
||||
}
|
||||
|
||||
uint8_t mask() const {
|
||||
return mask_;
|
||||
}
|
||||
|
||||
void set_status(const uint8_t status) {
|
||||
status_ &= ~status;
|
||||
update_output();
|
||||
}
|
||||
|
||||
void apply(const uint8_t interrupt) {
|
||||
status_ |= interrupt;
|
||||
update_output();
|
||||
}
|
||||
|
||||
void set_mask(const uint8_t mask) {
|
||||
mask_ = mask;
|
||||
update_output();
|
||||
}
|
||||
|
||||
private:
|
||||
void update_output() {
|
||||
const bool set = status_ & mask_;
|
||||
if(set != last_set_) {
|
||||
delegate_.set_irq_line(set);
|
||||
last_set_ = set;
|
||||
}
|
||||
}
|
||||
|
||||
BusController &delegate_;
|
||||
uint8_t status_;
|
||||
uint8_t mask_;
|
||||
bool last_set_ = false;
|
||||
};
|
||||
|
||||
}
|
134
Machines/Commodore/Plus4/Keyboard.cpp
Normal file
134
Machines/Commodore/Plus4/Keyboard.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
//
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/12/2024.
|
||||
// Copyright 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Commodore::Plus4;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Plus4::dest
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
BIND(k0, k0); BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4);
|
||||
BIND(k5, k5); BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9);
|
||||
BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T);
|
||||
BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P);
|
||||
BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G);
|
||||
BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L);
|
||||
BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V);
|
||||
BIND(B, B); BIND(N, N); BIND(M, M);
|
||||
|
||||
BIND(Backspace, InsDel);
|
||||
BIND(Escape, Escape);
|
||||
BIND(F1, F1_F4);
|
||||
BIND(F2, F2_F5);
|
||||
BIND(F3, F3_F6);
|
||||
BIND(F4, Help_F7);
|
||||
BIND(Enter, Return);
|
||||
BIND(Space, Space);
|
||||
|
||||
BIND(Up, Up); BIND(Down, Down); BIND(Left, Left); BIND(Right, Right);
|
||||
|
||||
BIND(LeftShift, Shift); BIND(RightShift, Shift);
|
||||
BIND(LeftControl, Control); BIND(RightControl, Control);
|
||||
BIND(LeftOption, Commodore); BIND(RightOption, Commodore);
|
||||
|
||||
BIND(FullStop, FullStop); BIND(Comma, Comma);
|
||||
BIND(Semicolon, Semicolon); BIND(Quote, Colon);
|
||||
BIND(Equals, Equals); BIND(ForwardSlash, Slash);
|
||||
|
||||
BIND(OpenSquareBracket, At);
|
||||
BIND(CloseSquareBracket, Plus);
|
||||
BIND(Backslash, Clear_Home);
|
||||
BIND(BackTick, Asterisk);
|
||||
|
||||
BIND(F11, Clear_Home);
|
||||
BIND(F12, Run_Stop);
|
||||
|
||||
// TODO:
|
||||
// GBP
|
||||
}
|
||||
#undef BIND
|
||||
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
|
||||
//const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
//#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
|
||||
//#define SHIFT(...) {KeyLShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
|
||||
//#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
|
||||
// static KeySequence key_sequences[] = {
|
||||
// /* NUL */ X, /* SOH */ X,
|
||||
// /* STX */ X, /* ETX */ X,
|
||||
// /* EOT */ X, /* ENQ */ X,
|
||||
// /* ACK */ X, /* BEL */ X,
|
||||
// /* BS */ KEYS(KeyDelete), /* HT */ X,
|
||||
// /* LF */ KEYS(KeyReturn), /* VT */ X,
|
||||
// /* FF */ X, /* CR */ X,
|
||||
// /* SO */ X, /* SI */ X,
|
||||
// /* DLE */ X, /* DC1 */ X,
|
||||
// /* DC2 */ X, /* DC3 */ X,
|
||||
// /* DC4 */ X, /* NAK */ X,
|
||||
// /* SYN */ X, /* ETB */ X,
|
||||
// /* CAN */ X, /* EM */ X,
|
||||
// /* SUB */ X, /* ESC */ X,
|
||||
// /* FS */ X, /* GS */ X,
|
||||
// /* RS */ X, /* US */ X,
|
||||
// /* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
|
||||
// /* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
|
||||
// /* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
|
||||
// /* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
|
||||
// /* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
|
||||
// /* * */ KEYS(KeyAsterisk), /* + */ KEYS(KeyPlus),
|
||||
// /* , */ KEYS(KeyComma), /* - */ KEYS(KeyDash),
|
||||
// /* . */ KEYS(KeyFullStop), /* / */ KEYS(KeySlash),
|
||||
// /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
|
||||
// /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
|
||||
// /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
|
||||
// /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
|
||||
// /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
|
||||
// /* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
|
||||
// /* < */ SHIFT(KeyComma), /* = */ KEYS(KeyEquals),
|
||||
// /* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeySlash),
|
||||
// /* @ */ KEYS(KeyAt), /* A */ KEYS(KeyA),
|
||||
// /* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
|
||||
// /* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
|
||||
// /* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
|
||||
// /* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
|
||||
// /* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
|
||||
// /* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
|
||||
// /* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
|
||||
// /* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
|
||||
// /* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
|
||||
// /* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
|
||||
// /* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
|
||||
// /* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
|
||||
// /* Z */ KEYS(KeyZ), /* [ */ SHIFT(KeyColon),
|
||||
// /* \ */ X, /* ] */ SHIFT(KeySemicolon),
|
||||
// /* ^ */ X, /* _ */ X,
|
||||
// /* ` */ X, /* a */ KEYS(KeyA),
|
||||
// /* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
|
||||
// /* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
|
||||
// /* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
|
||||
// /* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
|
||||
// /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
|
||||
// /* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
|
||||
// /* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
|
||||
// /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
|
||||
// /* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
|
||||
// /* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
// /* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
// /* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
// /* z */ KEYS(KeyZ)
|
||||
// };
|
||||
//#undef KEYS
|
||||
//#undef SHIFT
|
||||
//#undef X
|
||||
//
|
||||
// return table_lookup_sequence_for_character(key_sequences, character);
|
||||
//}
|
82
Machines/Commodore/Plus4/Keyboard.hpp
Normal file
82
Machines/Commodore/Plus4/Keyboard.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/12/2024.
|
||||
// Copyright 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../Utility/Typer.hpp"
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
static constexpr uint16_t key(const int line, const int mask) {
|
||||
return uint16_t((mask << 3) | line);
|
||||
}
|
||||
static constexpr size_t line(const uint16_t key) {
|
||||
return key & 7;
|
||||
}
|
||||
static constexpr uint8_t mask(const uint16_t key) {
|
||||
return uint8_t(key >> 3);
|
||||
}
|
||||
|
||||
enum Key: uint16_t {
|
||||
InsDel = key(0, 0x01), Return = key(0, 0x02),
|
||||
GBP = key(0, 0x04), Help_F7 = key(0, 0x08),
|
||||
F1_F4 = key(0, 0x10), F2_F5 = key(0, 0x20),
|
||||
F3_F6 = key(0, 0x40), At = key(0, 0x80),
|
||||
|
||||
k3 = key(1, 0x01), W = key(1, 0x02),
|
||||
A = key(1, 0x04), k4 = key(1, 0x08),
|
||||
Z = key(1, 0x10), S = key(1, 0x20),
|
||||
E = key(1, 0x40), Shift = key(1, 0x80),
|
||||
|
||||
k5 = key(2, 0x01), R = key(2, 0x02),
|
||||
D = key(2, 0x04), k6 = key(2, 0x08),
|
||||
C = key(2, 0x10), F = key(2, 0x20),
|
||||
T = key(2, 0x40), X = key(2, 0x80),
|
||||
|
||||
k7 = key(3, 0x01), Y = key(3, 0x02),
|
||||
G = key(3, 0x04), k8 = key(3, 0x08),
|
||||
B = key(3, 0x10), H = key(3, 0x20),
|
||||
U = key(3, 0x40), V = key(3, 0x80),
|
||||
|
||||
k9 = key(4, 0x01), I = key(4, 0x02),
|
||||
J = key(4, 0x04), k0 = key(4, 0x08),
|
||||
M = key(4, 0x10), K = key(4, 0x20),
|
||||
O = key(4, 0x40), N = key(4, 0x80),
|
||||
|
||||
Down = key(5, 0x01), P = key(5, 0x02),
|
||||
L = key(5, 0x04), Up = key(5, 0x08),
|
||||
FullStop = key(5, 0x10), Colon = key(5, 0x20),
|
||||
Minus = key(5, 0x40), Comma = key(5, 0x80),
|
||||
|
||||
Left = key(6, 0x01), Asterisk = key(6, 0x02),
|
||||
Semicolon = key(6, 0x04), Right = key(6, 0x08),
|
||||
Escape = key(6, 0x10), Equals = key(6, 0x20),
|
||||
Plus = key(6, 0x40), Slash = key(6, 0x80),
|
||||
|
||||
k1 = key(7, 0x01), Clear_Home = key(7, 0x02),
|
||||
Control = key(7, 0x04), k2 = key(7, 0x08),
|
||||
Space = key(7, 0x10), Commodore = key(7, 0x20),
|
||||
Q = key(7, 0x40), Run_Stop = key(7, 0x80),
|
||||
|
||||
// Bonus virtual keys:
|
||||
F4 = 0xfe00,
|
||||
F5 = 0xfe01,
|
||||
F6 = 0xfe02,
|
||||
F7 = 0xfe03,
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
|
||||
};
|
||||
|
||||
//struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
// const uint16_t *sequence_for_character(char character) const final;
|
||||
//};
|
||||
|
||||
}
|
62
Machines/Commodore/Plus4/Pager.hpp
Normal file
62
Machines/Commodore/Plus4/Pager.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// Pager.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/12/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
enum PagerSide {
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = Read | Write,
|
||||
};
|
||||
|
||||
template <typename AddressT, typename DataT, int NumPages>
|
||||
class Pager {
|
||||
public:
|
||||
DataT read(AddressT address) const {
|
||||
return read_[address >> Shift][address];
|
||||
}
|
||||
DataT &write(AddressT address) {
|
||||
return write_[address >> Shift][address];
|
||||
}
|
||||
|
||||
template <int side, size_t start, size_t length>
|
||||
void page(uint8_t *data) {
|
||||
static_assert(!(start % PageSize), "Start address must be a multiple of the page size");
|
||||
static_assert(!(length % PageSize), "Data length must be a multiple of the page size");
|
||||
|
||||
for(size_t slot = start >> Shift; slot < (start + length) >> Shift; slot++) {
|
||||
if constexpr (side & PagerSide::Write) write_[slot] = data - (slot << Shift);
|
||||
if constexpr (side & PagerSide::Read) read_[slot] = data - (slot << Shift);
|
||||
data += PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<DataT *, NumPages> write_{};
|
||||
std::array<const DataT *, NumPages> read_{};
|
||||
|
||||
static constexpr auto AddressBits = sizeof(AddressT) * 8;
|
||||
static constexpr auto PageSize = (1 << AddressBits) / NumPages;
|
||||
static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size");
|
||||
|
||||
static constexpr int ln2(int value) {
|
||||
int result = 0;
|
||||
while(value != 1) {
|
||||
value >>= 1;
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static constexpr auto Shift = ln2(PageSize);
|
||||
};
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
using Pager = Pager<uint16_t, uint8_t, 4>;
|
||||
|
||||
}
|
@ -8,37 +8,524 @@
|
||||
|
||||
#include "Plus4.hpp"
|
||||
|
||||
#include "Audio.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Pager.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../MachineTypes.hpp"
|
||||
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Analyser/Static/Commodore/Target.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "../SerialBus.hpp"
|
||||
#include "../1540/C1540.hpp"
|
||||
|
||||
using namespace Commodore;
|
||||
using namespace Commodore::Plus4;
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteMachine:
|
||||
public MachineTypes::TimedMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
public Machine {
|
||||
class Timers {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
(void)target;
|
||||
(void)rom_fetcher;
|
||||
Timers(Interrupts &interrupts) : interrupts_(interrupts) {}
|
||||
|
||||
template <int offset>
|
||||
void write(const uint8_t value) {
|
||||
const auto load_low = [&](uint16_t &target) {
|
||||
target = uint16_t((target & 0xff00) | (value << 0));
|
||||
};
|
||||
const auto load_high = [&](uint16_t &target) {
|
||||
target = uint16_t((target & 0x00ff) | (value << 8));
|
||||
};
|
||||
|
||||
constexpr auto timer = offset >> 1;
|
||||
paused_[timer] = ~offset & 1;
|
||||
if constexpr (offset & 1) {
|
||||
load_high(timers_[timer]);
|
||||
if constexpr (!timer) {
|
||||
load_high(timer0_reload_);
|
||||
}
|
||||
} else {
|
||||
load_low(timers_[timer]);
|
||||
if constexpr (!timer) {
|
||||
load_low(timer0_reload_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int offset>
|
||||
uint8_t read() {
|
||||
constexpr auto timer = offset >> 1;
|
||||
if constexpr (offset & 1) {
|
||||
return uint8_t(timers_[timer] >> 8);
|
||||
} else {
|
||||
return uint8_t(timers_[timer] >> 0);
|
||||
}
|
||||
}
|
||||
|
||||
void tick(int count) {
|
||||
// Quick hack here; do better than stepping through one at a time.
|
||||
while(count--) {
|
||||
decrement<0>();
|
||||
decrement<1>();
|
||||
decrement<2>();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const) final {
|
||||
template <int timer>
|
||||
void decrement() {
|
||||
if(paused_[timer]) return;
|
||||
|
||||
// Check for reload.
|
||||
if(!timer && !timers_[timer]) {
|
||||
timers_[timer] = timer0_reload_;
|
||||
}
|
||||
|
||||
-- timers_[timer];
|
||||
|
||||
// Check for interrupt.
|
||||
if(!timers_[timer]) {
|
||||
switch(timer) {
|
||||
case 0: interrupts_.apply(Interrupts::Flag::Timer1); break;
|
||||
case 1: interrupts_.apply(Interrupts::Flag::Timer2); break;
|
||||
case 2: interrupts_.apply(Interrupts::Flag::Timer3); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t timers_[3]{};
|
||||
uint16_t timer0_reload_ = 0xffff;
|
||||
bool paused_[3]{};
|
||||
|
||||
Interrupts &interrupts_;
|
||||
};
|
||||
|
||||
class SerialPort: public Serial::Port {
|
||||
public:
|
||||
void set_input(const Serial::Line line, const Serial::LineLevel value) override {
|
||||
levels_[size_t(line)] = value;
|
||||
}
|
||||
|
||||
Serial::LineLevel level(const Serial::Line line) const {
|
||||
return levels_[size_t(line)];
|
||||
}
|
||||
|
||||
private:
|
||||
Serial::LineLevel levels_[5];
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public BusController,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::TimedMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::MediaTarget,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
interrupts_(*this),
|
||||
timers_(interrupts_),
|
||||
video_(video_map_, interrupts_),
|
||||
audio_(audio_queue_),
|
||||
speaker_(audio_)
|
||||
{
|
||||
const auto clock = clock_rate(false);
|
||||
media_divider_ = Cycles(clock);
|
||||
set_clock_rate(clock);
|
||||
speaker_.set_input_rate(float(clock) / 32.0f);
|
||||
|
||||
// TODO: decide whether to attach a 1541 for real.
|
||||
const bool has_c1541 = true;
|
||||
|
||||
const auto kernel = ROM::Name::Plus4KernelPALv5;
|
||||
const auto basic = ROM::Name::Plus4BASIC;
|
||||
ROM::Request request = ROM::Request(basic) && ROM::Request(kernel);
|
||||
if(has_c1541) {
|
||||
request = request && C1540::Machine::rom_request(C1540::Personality::C1541);
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
kernel_ = roms.find(kernel)->second;
|
||||
basic_ = roms.find(basic)->second;
|
||||
|
||||
Memory::Fuzz(ram_);
|
||||
map_.page<PagerSide::ReadWrite, 0, 65536>(ram_.data());
|
||||
page_cpu_rom();
|
||||
|
||||
video_map_.page<PagerSide::ReadWrite, 0, 65536>(ram_.data());
|
||||
|
||||
if(has_c1541) {
|
||||
c1541_ = std::make_unique<C1540::Machine>(C1540::Personality::C1541, roms);
|
||||
c1541_->set_serial_bus(serial_bus_);
|
||||
Serial::attach(serial_port_, serial_bus_);
|
||||
}
|
||||
|
||||
tape_player_ = std::make_unique<Storage::Tape::BinaryTapePlayer>(clock);
|
||||
|
||||
insert_media(target.media);
|
||||
printf("Loading command is: %s\n", target.loading_command.c_str());
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
Cycles perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
|
||||
// relative to the master clock.
|
||||
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
|
||||
|
||||
// Update other subsystems.
|
||||
timers_subcycles_ += length;
|
||||
const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length());
|
||||
timers_.tick(timers_cycles.as<int>());
|
||||
|
||||
video_.run_for(length);
|
||||
tape_player_->run_for(length);
|
||||
|
||||
if(c1541_) {
|
||||
c1541_cycles_ += length * Cycles(1'000'000);
|
||||
c1541_->run_for(c1541_cycles_.divide(media_divider_));
|
||||
}
|
||||
|
||||
time_since_audio_update_ += length;
|
||||
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready) {
|
||||
return length;
|
||||
}
|
||||
|
||||
// Perform actual access.
|
||||
if(address < 0x0002) {
|
||||
// 0x0000: data directions for parallel IO; 1 = output.
|
||||
// 0x0001:
|
||||
// b7 = serial data in;
|
||||
// b6 = serial clock in and cassette write;
|
||||
// b5 = [unconnected];
|
||||
// b4 = cassette read;
|
||||
// b3 = cassette motor, 1 = off;
|
||||
// b2 = serial ATN out;
|
||||
// b1 = serial clock out and cassette write;
|
||||
// b0 = serial data out.
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
if(!address) {
|
||||
*value = io_direction_;
|
||||
} else {
|
||||
const uint8_t all_inputs =
|
||||
(tape_player_->input() ? 0x00 : 0x10) |
|
||||
(serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) |
|
||||
(serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00);
|
||||
*value =
|
||||
(io_direction_ & io_output_) |
|
||||
(~io_direction_ & all_inputs);
|
||||
}
|
||||
} else {
|
||||
if(!address) {
|
||||
io_direction_ = *value;
|
||||
} else {
|
||||
io_output_ = *value;
|
||||
}
|
||||
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
// tape_player_->set_motor_control(~output & 0x08);
|
||||
serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01));
|
||||
serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02));
|
||||
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
|
||||
}
|
||||
|
||||
// printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w');
|
||||
} else if(address < 0xfd00 || address >= 0xff40) {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = map_.read(address);
|
||||
} else {
|
||||
map_.write(address) = *value;
|
||||
}
|
||||
} else if(address < 0xff00) {
|
||||
// Miscellaneous hardware. All TODO.
|
||||
if(isReadOperation(operation)) {
|
||||
// printf("TODO: read @ %04x\n", address);
|
||||
if((address & 0xfff0) == 0xfd10) {
|
||||
tape_player_->set_motor_control(true);
|
||||
*value = 0xff ^ 0x4; // Seems to detect the play button.
|
||||
} else {
|
||||
*value = 0xff;
|
||||
}
|
||||
} else {
|
||||
// printf("TODO: write of %02x @ %04x\n", *value, address);
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation)) {
|
||||
switch(address) {
|
||||
case 0xff00: *value = timers_.read<0>(); break;
|
||||
case 0xff01: *value = timers_.read<1>(); break;
|
||||
case 0xff02: *value = timers_.read<2>(); break;
|
||||
case 0xff03: *value = timers_.read<3>(); break;
|
||||
case 0xff04: *value = timers_.read<4>(); break;
|
||||
case 0xff05: *value = timers_.read<5>(); break;
|
||||
|
||||
case 0xff08: *value = keyboard_latch_; break;
|
||||
|
||||
case 0xff09: *value = interrupts_.status(); break;
|
||||
case 0xff0a: *value = interrupts_.mask(); break;
|
||||
|
||||
case 0xff06: *value = video_.read<0xff06>(); break;
|
||||
case 0xff07: *value = video_.read<0xff07>(); break;
|
||||
case 0xff0b: *value = video_.read<0xff0b>(); break;
|
||||
case 0xff1c: *value = video_.read<0xff1c>(); break;
|
||||
case 0xff1d: *value = video_.read<0xff1d>(); break;
|
||||
|
||||
case 0xff0e: *value = ff0e_; break;
|
||||
case 0xff0f: *value = ff0f_; break;
|
||||
case 0xff10: *value = ff10_; break;
|
||||
case 0xff11: *value = ff11_; break;
|
||||
case 0xff12: *value = ff12_; break;
|
||||
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
|
||||
|
||||
case 0xff0c: *value = video_.read<0xff0c>(); break;
|
||||
case 0xff0d: *value = video_.read<0xff0d>(); break;
|
||||
case 0xff14: *value = video_.read<0xff14>(); break;
|
||||
case 0xff15: *value = video_.read<0xff15>(); break;
|
||||
case 0xff16: *value = video_.read<0xff16>(); break;
|
||||
case 0xff17: *value = video_.read<0xff17>(); break;
|
||||
case 0xff18: *value = video_.read<0xff18>(); break;
|
||||
case 0xff19: *value = video_.read<0xff19>(); break;
|
||||
|
||||
default:
|
||||
printf("TODO: TED read at %04x\n", address);
|
||||
}
|
||||
} else {
|
||||
switch(address) {
|
||||
case 0xff00: timers_.write<0>(*value); break;
|
||||
case 0xff01: timers_.write<1>(*value); break;
|
||||
case 0xff02: timers_.write<2>(*value); break;
|
||||
case 0xff03: timers_.write<3>(*value); break;
|
||||
case 0xff04: timers_.write<4>(*value); break;
|
||||
case 0xff05: timers_.write<5>(*value); break;
|
||||
|
||||
case 0xff08:
|
||||
keyboard_latch_ = ~(
|
||||
((*value & 0x01) ? 0x00 : key_states_[0]) |
|
||||
((*value & 0x02) ? 0x00 : key_states_[1]) |
|
||||
((*value & 0x04) ? 0x00 : key_states_[2]) |
|
||||
((*value & 0x08) ? 0x00 : key_states_[3]) |
|
||||
((*value & 0x10) ? 0x00 : key_states_[4]) |
|
||||
((*value & 0x20) ? 0x00 : key_states_[5]) |
|
||||
((*value & 0x40) ? 0x00 : key_states_[6]) |
|
||||
((*value & 0x80) ? 0x00 : key_states_[7])
|
||||
);
|
||||
break;
|
||||
|
||||
case 0xff09:
|
||||
interrupts_.set_status(*value);
|
||||
break;
|
||||
case 0xff0a:
|
||||
interrupts_.set_mask(*value);
|
||||
video_.write<0xff0a>(*value);
|
||||
break;
|
||||
case 0xff0b: video_.write<0xff0b>(*value); break;
|
||||
|
||||
case 0xff06: video_.write<0xff06>(*value); break;
|
||||
case 0xff07: video_.write<0xff07>(*value); break;
|
||||
case 0xff0c: video_.write<0xff0c>(*value); break;
|
||||
case 0xff0d: video_.write<0xff0d>(*value); break;
|
||||
case 0xff0e:
|
||||
ff0e_ = *value;
|
||||
update_audio();
|
||||
audio_.set_frequency_low<0>(*value);
|
||||
break;
|
||||
case 0xff0f:
|
||||
ff0f_ = *value;
|
||||
update_audio();
|
||||
audio_.set_frequency_low<1>(*value);
|
||||
break;
|
||||
case 0xff10:
|
||||
ff10_ = *value;
|
||||
update_audio();
|
||||
audio_.set_frequency_high<1>(*value);
|
||||
break;
|
||||
case 0xff11:
|
||||
ff11_ = *value;
|
||||
update_audio();
|
||||
audio_.set_constrol(*value);
|
||||
break;
|
||||
case 0xff12:
|
||||
ff12_ = *value & 0x3f;
|
||||
video_.write<0xff12>(*value);
|
||||
|
||||
if((*value & 4)) {
|
||||
page_video_rom();
|
||||
} else {
|
||||
page_video_ram();
|
||||
}
|
||||
|
||||
update_audio();
|
||||
audio_.set_frequency_high<0>(*value);
|
||||
break;
|
||||
case 0xff13:
|
||||
ff13_ = *value & 0xfe;
|
||||
video_.write<0xff13>(*value);
|
||||
break;
|
||||
case 0xff14: video_.write<0xff14>(*value); break;
|
||||
case 0xff1a: video_.write<0xff1a>(*value); break;
|
||||
case 0xff1b: video_.write<0xff1b>(*value); break;
|
||||
|
||||
case 0xff15: video_.write<0xff15>(*value); break;
|
||||
case 0xff16: video_.write<0xff16>(*value); break;
|
||||
case 0xff17: video_.write<0xff17>(*value); break;
|
||||
case 0xff18: video_.write<0xff18>(*value); break;
|
||||
case 0xff19: video_.write<0xff19>(*value); break;
|
||||
|
||||
case 0xff3e: page_cpu_rom(); break;
|
||||
case 0xff3f: page_cpu_ram(); break;
|
||||
|
||||
// TODO: audio is 0xff10, 0xff11, 0xff0e, 0xff0f and shares 0xff18.
|
||||
|
||||
default:
|
||||
printf("TODO: TED write at %04x\n", address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true> m6502_;
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *const observer) final {
|
||||
if(c1541_) c1541_->set_activity_observer(observer);
|
||||
}
|
||||
|
||||
void set_irq_line(bool active) override {
|
||||
m6502_.set_irq_line(active);
|
||||
}
|
||||
void set_ready_line(bool active) override {
|
||||
m6502_.set_ready_line(active);
|
||||
}
|
||||
|
||||
void page_video_rom() {
|
||||
video_map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
|
||||
video_map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
|
||||
}
|
||||
void page_video_ram() {
|
||||
video_map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
|
||||
}
|
||||
|
||||
void page_cpu_rom() {
|
||||
// TODO: allow other ROM selection. And no ROM?
|
||||
map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
|
||||
map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
|
||||
rom_is_paged_ = true;
|
||||
}
|
||||
void page_cpu_ram() {
|
||||
map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
|
||||
rom_is_paged_ = false;
|
||||
}
|
||||
bool rom_is_paged_ = false;
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const target) final {
|
||||
video_.set_scan_target(target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return {};
|
||||
return video_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void run_for(const Cycles) final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
|
||||
// I don't know why.
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
void flush_output(int outputs) override {
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_->set_tape(media.tapes[0]);
|
||||
}
|
||||
|
||||
if(!media.disks.empty() && c1541_) {
|
||||
c1541_->set_disk(media.disks[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Plus4::Pager map_;
|
||||
Plus4::Pager video_map_;
|
||||
std::array<uint8_t, 65536> ram_;
|
||||
std::vector<uint8_t> kernel_;
|
||||
std::vector<uint8_t> basic_;
|
||||
uint8_t ff0e_, ff0f_, ff10_, ff11_, ff12_, ff13_;
|
||||
|
||||
Interrupts interrupts_;
|
||||
Cycles timers_subcycles_;
|
||||
Timers timers_;
|
||||
Video video_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
Audio audio_;
|
||||
Cycles time_since_audio_update_;
|
||||
Outputs::Speaker::PullLowpass<Audio> speaker_;
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide(Cycles(32)));
|
||||
}
|
||||
|
||||
// MARK: - MappedKeyboardMachine.
|
||||
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
|
||||
static Plus4::KeyboardMapper keyboard_mapper_;
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
if(is_pressed) {
|
||||
key_states_[line(key)] |= mask(key);
|
||||
} else {
|
||||
key_states_[line(key)] &= ~mask(key);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> key_states_{};
|
||||
uint8_t keyboard_latch_ = 0xff;
|
||||
|
||||
Cycles media_divider_, c1541_cycles_;
|
||||
std::unique_ptr<C1540::Machine> c1541_;
|
||||
Serial::Bus serial_bus_;
|
||||
SerialPort serial_port_;
|
||||
|
||||
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<Machine> Machine::Plus4(
|
||||
const Analyser::Static::Target *target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
|
1003
Machines/Commodore/Plus4/Video.hpp
Normal file
1003
Machines/Commodore/Plus4/Video.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,52 +13,55 @@
|
||||
|
||||
using namespace Commodore::Serial;
|
||||
|
||||
const char *::Commodore::Serial::StringForLine(Line line) {
|
||||
const char *::Commodore::Serial::to_string(Line line) {
|
||||
switch(line) {
|
||||
case ServiceRequest: return "Service request";
|
||||
case Attention: return "Attention";
|
||||
case Clock: return "Clock";
|
||||
case Data: return "Data";
|
||||
case Reset: return "Reset";
|
||||
case Line::ServiceRequest: return "Service request";
|
||||
case Line::Attention: return "Attention";
|
||||
case Line::Clock: return "Clock";
|
||||
case Line::Data: return "Data";
|
||||
case Line::Reset: return "Reset";
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
|
||||
port->set_serial_bus(bus);
|
||||
bus->add_port(port);
|
||||
const char *::Commodore::Serial::to_string(LineLevel level) {
|
||||
switch(level) {
|
||||
case LineLevel::High : return "high";
|
||||
case LineLevel::Low : return "low";
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::add_port(std::shared_ptr<Port> port) {
|
||||
ports_.push_back(port);
|
||||
for(int line = int(ServiceRequest); line <= int(Reset); line++) {
|
||||
// the addition of a new device may change the line output...
|
||||
void ::Commodore::Serial::attach(Port &port, Bus &bus) {
|
||||
port.set_bus(bus);
|
||||
bus.add_port(port);
|
||||
}
|
||||
|
||||
void Bus::add_port(Port &port) {
|
||||
ports_.push_back(&port);
|
||||
for(int line = int(Line::ServiceRequest); line <= int(Line::Reset); line++) {
|
||||
// The addition of a new device may change the line output...
|
||||
set_line_output_did_change(Line(line));
|
||||
|
||||
// ... but the new device will need to be told the current state regardless
|
||||
port->set_input(Line(line), line_levels_[line]);
|
||||
// ... but the new device will need to be told the current state regardless.
|
||||
port.set_input(Line(line), line_levels_[line]);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::set_line_output_did_change(Line line) {
|
||||
// i.e. I believe these lines to be open collector
|
||||
LineLevel new_line_level = High;
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port) {
|
||||
new_line_level = (LineLevel)(bool(new_line_level) & bool(locked_port->get_output(line)));
|
||||
}
|
||||
// Treat lines as open collector.
|
||||
auto new_line_level = LineLevel::High;
|
||||
for(auto port : ports_) {
|
||||
new_line_level = LineLevel(bool(new_line_level) & bool(port->get_output(line)));
|
||||
}
|
||||
|
||||
// post an update only if one occurred
|
||||
if(new_line_level != line_levels_[line]) {
|
||||
line_levels_[line] = new_line_level;
|
||||
// Post an update only if one occurred.
|
||||
const auto index = size_t(line);
|
||||
if(new_line_level != line_levels_[index]) {
|
||||
line_levels_[index] = new_line_level;
|
||||
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port) {
|
||||
locked_port->set_input(line, new_line_level);
|
||||
}
|
||||
for(auto port : ports_) {
|
||||
port->set_input(line, new_line_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,14 +69,15 @@ void Bus::set_line_output_did_change(Line line) {
|
||||
// MARK: - The debug port
|
||||
|
||||
void DebugPort::set_input(Line line, LineLevel value) {
|
||||
input_levels_[line] = value;
|
||||
const auto index = size_t(line);
|
||||
input_levels_[index] = value;
|
||||
|
||||
std::cout << "[Bus] " << StringForLine(line) << " is " << (value ? "high" : "low");
|
||||
std::cout << "[Bus] " << to_string(line) << " is " << to_string(value);
|
||||
if(!incoming_count_) {
|
||||
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
|
||||
incoming_count_ = (!input_levels_[size_t(Line::Clock)] && !input_levels_[size_t(Line::Data)]) ? 8 : 0;
|
||||
} else {
|
||||
if(line == Line::Clock && value) {
|
||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[size_t(Line::Data)] ? 0x80 : 0x00);
|
||||
}
|
||||
incoming_count_--;
|
||||
if(incoming_count_ == 0) std::cout << "[Bus] Observed value " << std::hex << int(incoming_byte_);
|
||||
|
@ -14,115 +14,123 @@
|
||||
|
||||
namespace Commodore::Serial {
|
||||
|
||||
enum Line {
|
||||
ServiceRequest = 0,
|
||||
Attention,
|
||||
Clock,
|
||||
Data,
|
||||
Reset
|
||||
};
|
||||
enum class Line {
|
||||
ServiceRequest = 0,
|
||||
Attention,
|
||||
Clock,
|
||||
Data,
|
||||
Reset
|
||||
};
|
||||
const char *to_string(Line line);
|
||||
|
||||
enum LineLevel: bool {
|
||||
High = true,
|
||||
Low = false
|
||||
};
|
||||
enum LineLevel: bool {
|
||||
High = true,
|
||||
Low = false
|
||||
};
|
||||
const char *to_string(LineLevel line);
|
||||
|
||||
class Port;
|
||||
class Bus;
|
||||
class Port;
|
||||
class Bus;
|
||||
|
||||
/*!
|
||||
Calls both of the necessary methods to (i) set @c bus as the target for @c port outputs; and
|
||||
(ii) add @c port as one of the targets to which @c bus propagates line changes.
|
||||
*/
|
||||
void attach(Port &port, Bus &bus);
|
||||
|
||||
/*!
|
||||
A serial bus is responsible for retaining a weakly-held collection of attached ports and for deciding the
|
||||
current bus levels based upon the net result of each port's output, and for communicating changes in bus
|
||||
levels to every port.
|
||||
*/
|
||||
class Bus {
|
||||
public:
|
||||
Bus() : line_levels_{
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High
|
||||
} {}
|
||||
|
||||
/*!
|
||||
Returns a C string giving a human-readable name for the supplied line.
|
||||
Adds the supplied port to the bus.
|
||||
*/
|
||||
const char *StringForLine(Line line);
|
||||
void add_port(Port &);
|
||||
|
||||
/*!
|
||||
Calls both of the necessary methods to (i) set @c bus as the target for @c port outputs; and
|
||||
(ii) add @c port as one of the targets to which @c bus propagates line changes.
|
||||
Communicates to the bus that one of its attached port has changed its output level for the given line.
|
||||
The bus will therefore recalculate bus state and propagate as necessary.
|
||||
*/
|
||||
void AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus);
|
||||
void set_line_output_did_change(Line);
|
||||
|
||||
private:
|
||||
LineLevel line_levels_[5];
|
||||
std::vector<Port *> ports_;
|
||||
};
|
||||
|
||||
/*!
|
||||
A serial port is an endpoint on a serial bus; this class provides a direct setter for current line outputs and
|
||||
expects to be subclassed in order for specific port-housing devices to deal with input.
|
||||
*/
|
||||
class Port {
|
||||
public:
|
||||
Port() : line_levels_{
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High,
|
||||
LineLevel::High
|
||||
} {}
|
||||
virtual ~Port() = default;
|
||||
|
||||
/*!
|
||||
A serial bus is responsible for retaining a weakly-held collection of attached ports and for deciding the
|
||||
current bus levels based upon the net result of each port's output, and for communicating changes in bus
|
||||
levels to every port.
|
||||
Sets the current level of an output line on this serial port.
|
||||
*/
|
||||
class Bus {
|
||||
public:
|
||||
Bus() : line_levels_{High, High, High, High, High} {}
|
||||
|
||||
/*!
|
||||
Adds the supplied port to the bus.
|
||||
*/
|
||||
void add_port(std::shared_ptr<Port> port);
|
||||
|
||||
/*!
|
||||
Communicates to the bus that one of its attached port has changed its output level for the given line.
|
||||
The bus will therefore recalculate bus state and propagate as necessary.
|
||||
*/
|
||||
void set_line_output_did_change(Line line);
|
||||
|
||||
private:
|
||||
LineLevel line_levels_[5];
|
||||
std::vector<std::weak_ptr<Port>> ports_;
|
||||
};
|
||||
void set_output(Line line, LineLevel level) {
|
||||
if(line_levels_[size_t(line)] != level) {
|
||||
line_levels_[size_t(line)] = level;
|
||||
if(serial_bus_) serial_bus_->set_line_output_did_change(line);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
A serial port is an endpoint on a serial bus; this class provides a direct setter for current line outputs and
|
||||
expects to be subclassed in order for specific port-housing devices to deal with input.
|
||||
Gets the previously set level of an output line.
|
||||
*/
|
||||
class Port {
|
||||
public:
|
||||
Port() : line_levels_{High, High, High, High, High} {}
|
||||
virtual ~Port() = default;
|
||||
|
||||
/*!
|
||||
Sets the current level of an output line on this serial port.
|
||||
*/
|
||||
void set_output(Line line, LineLevel level) {
|
||||
if(line_levels_[line] != level) {
|
||||
line_levels_[line] = level;
|
||||
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
||||
if(bus) bus->set_line_output_did_change(line);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the previously set level of an output line.
|
||||
*/
|
||||
LineLevel get_output(Line line) {
|
||||
return line_levels_[line];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called by the bus to signal a change in any input line level. Subclasses should implement this.
|
||||
*/
|
||||
virtual void set_input(Line line, LineLevel value) = 0;
|
||||
|
||||
/*!
|
||||
Sets the supplied serial bus as that to which line levels will be communicated.
|
||||
*/
|
||||
inline void set_serial_bus(std::shared_ptr<Bus> serial_bus) {
|
||||
serial_bus_ = serial_bus;
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<Bus> serial_bus_;
|
||||
LineLevel line_levels_[5];
|
||||
};
|
||||
LineLevel get_output(Line line) const {
|
||||
return line_levels_[size_t(line)];
|
||||
}
|
||||
|
||||
/*!
|
||||
A debugging port, which makes some attempt to log bus activity. Incomplete. TODO: complete.
|
||||
Called by the bus to signal a change in any input line level. Subclasses should implement this.
|
||||
*/
|
||||
class DebugPort: public Port {
|
||||
public:
|
||||
void set_input(Line line, LineLevel value);
|
||||
virtual void set_input(Line line, LineLevel value) = 0;
|
||||
|
||||
DebugPort() : incoming_count_(0) {}
|
||||
/*!
|
||||
Sets the supplied serial bus as that to which line levels will be communicated.
|
||||
*/
|
||||
inline void set_bus(Bus &serial_bus) {
|
||||
serial_bus_ = &serial_bus;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t incoming_byte_;
|
||||
int incoming_count_;
|
||||
LineLevel input_levels_[5];
|
||||
};
|
||||
private:
|
||||
Bus *serial_bus_ = nullptr;
|
||||
LineLevel line_levels_[5];
|
||||
};
|
||||
|
||||
/*!
|
||||
A debugging port, which makes some attempt to log bus activity. Incomplete. TODO: complete.
|
||||
*/
|
||||
class DebugPort: public Port {
|
||||
public:
|
||||
void set_input(Line line, LineLevel value);
|
||||
|
||||
DebugPort() : incoming_count_(0) {}
|
||||
|
||||
private:
|
||||
uint8_t incoming_byte_;
|
||||
int incoming_count_;
|
||||
LineLevel input_levels_[5];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -105,14 +105,13 @@ public:
|
||||
void set_port_output(const MOS::MOS6522::Port port, const uint8_t value, uint8_t) {
|
||||
// Line 7 of port A is inverted and output as serial ATN.
|
||||
if(!port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
||||
serial_port_->set_output(Serial::Line::Attention, Serial::LineLevel(!(value&0x80)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets @serial_port as this VIA's connection to the serial bus.
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) {
|
||||
serial_port_ = serial_port;
|
||||
void set_serial_port(Serial::Port &serial_port) {
|
||||
serial_port_ = &serial_port;
|
||||
}
|
||||
|
||||
/// Sets @tape as the tape player connected to this VIA.
|
||||
@ -122,7 +121,7 @@ public:
|
||||
|
||||
private:
|
||||
uint8_t port_a_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
Serial::Port *serial_port_ = nullptr;
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
};
|
||||
|
||||
@ -171,14 +170,11 @@ public:
|
||||
/// Called by the 6522 to set control line output. Which affects the serial port.
|
||||
void set_control_line_output(const MOS::MOS6522::Port port, const MOS::MOS6522::Line line, const bool value) {
|
||||
if(line == MOS::MOS6522::Line::Two) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
if(port == MOS::MOS6522::Port::A)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
||||
else
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
|
||||
}
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
if(port == MOS::MOS6522::Port::A)
|
||||
serial_port_->set_output(Serial::Line::Clock, Serial::LineLevel(!value));
|
||||
else
|
||||
serial_port_->set_output(Serial::Line::Data, Serial::LineLevel(!value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,15 +186,15 @@ public:
|
||||
}
|
||||
|
||||
/// Sets the serial port to which this VIA is connected.
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
void set_serial_port(Serial::Port &port) {
|
||||
serial_port_ = &port;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t port_b_;
|
||||
uint8_t columns_[8];
|
||||
uint8_t activation_mask_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
Serial::Port *serial_port_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -208,17 +204,16 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
public:
|
||||
/// Receives an input change from the base serial port class, and communicates it to the user-port VIA.
|
||||
void set_input(const ::Commodore::Serial::Line line, const ::Commodore::Serial::LineLevel level) {
|
||||
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, bool(level));
|
||||
if(user_port_via_) user_port_via_->set_serial_line_state(line, bool(level));
|
||||
}
|
||||
|
||||
/// Sets the user-port VIA with which this serial port communicates.
|
||||
void set_user_port_via(const std::shared_ptr<UserPortVIA> userPortVIA) {
|
||||
user_port_via_ = userPortVIA;
|
||||
void set_user_port_via(UserPortVIA &via) {
|
||||
user_port_via_ = &via;
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<UserPortVIA> user_port_via_;
|
||||
UserPortVIA *user_port_via_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -293,32 +288,28 @@ public:
|
||||
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
mos6560_(mos6560_bus_handler_),
|
||||
user_port_via_port_handler_(new UserPortVIA),
|
||||
keyboard_via_port_handler_(new KeyboardVIA),
|
||||
serial_port_(new SerialPort),
|
||||
serial_bus_(new ::Commodore::Serial::Bus),
|
||||
user_port_via_(*user_port_via_port_handler_),
|
||||
keyboard_via_(*keyboard_via_port_handler_),
|
||||
user_port_via_(user_port_via_port_handler_),
|
||||
keyboard_via_(keyboard_via_port_handler_),
|
||||
tape_(new Storage::Tape::BinaryTapePlayer(1022727)) {
|
||||
// communicate the tape to the user-port VIA
|
||||
user_port_via_port_handler_->set_tape(tape_);
|
||||
user_port_via_port_handler_.set_tape(tape_);
|
||||
|
||||
// wire up the serial bus and serial port
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||
Commodore::Serial::attach(serial_port_, serial_bus_);
|
||||
|
||||
// wire up 6522s and serial port
|
||||
user_port_via_port_handler_->set_serial_port(serial_port_);
|
||||
keyboard_via_port_handler_->set_serial_port(serial_port_);
|
||||
serial_port_->set_user_port_via(user_port_via_port_handler_);
|
||||
user_port_via_port_handler_.set_serial_port(serial_port_);
|
||||
keyboard_via_port_handler_.set_serial_port(serial_port_);
|
||||
serial_port_.set_user_port_via(user_port_via_port_handler_);
|
||||
|
||||
// wire up the 6522s, tape and machine
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
user_port_via_port_handler_.set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
tape_->set_clocking_hint_observer(this);
|
||||
|
||||
// Install a joystick.
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
joysticks_.emplace_back(new Joystick(user_port_via_port_handler_, keyboard_via_port_handler_));
|
||||
|
||||
ROM::Request request(ROM::Name::Vic20BASIC);
|
||||
ROM::Name kernel, character;
|
||||
@ -361,7 +352,7 @@ public:
|
||||
|
||||
if(target.has_c1540) {
|
||||
// construct the 1540
|
||||
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
|
||||
c1540_ = std::make_unique<C1540::Machine>(C1540::Personality::C1540, roms);
|
||||
|
||||
// attach it to the serial bus
|
||||
c1540_->set_serial_bus(serial_bus_);
|
||||
@ -472,7 +463,7 @@ public:
|
||||
|
||||
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
||||
if(key < 0xfff0) {
|
||||
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||
keyboard_via_port_handler_.set_key_state(key, is_pressed);
|
||||
} else {
|
||||
switch(key) {
|
||||
case KeyRestore:
|
||||
@ -480,8 +471,8 @@ public:
|
||||
break;
|
||||
#define ShiftedMap(source, target) \
|
||||
case source: \
|
||||
keyboard_via_port_handler_->set_key_state(KeyLShift, is_pressed); \
|
||||
keyboard_via_port_handler_->set_key_state(target, is_pressed); \
|
||||
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed); \
|
||||
keyboard_via_port_handler_.set_key_state(target, is_pressed); \
|
||||
break;
|
||||
|
||||
ShiftedMap(KeyUp, KeyDown);
|
||||
@ -496,7 +487,7 @@ public:
|
||||
}
|
||||
|
||||
void clear_all_keys() final {
|
||||
keyboard_via_port_handler_->clear_all_keys();
|
||||
keyboard_via_port_handler_.clear_all_keys();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
@ -741,10 +732,10 @@ private:
|
||||
Cycles cycles_since_mos6560_update_;
|
||||
Vic6560BusHandler mos6560_bus_handler_;
|
||||
MOS::MOS6560::MOS6560<Vic6560BusHandler> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
UserPortVIA user_port_via_port_handler_;
|
||||
KeyboardVIA keyboard_via_port_handler_;
|
||||
SerialPort serial_port_;
|
||||
Serial::Bus serial_bus_;
|
||||
|
||||
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
|
||||
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
|
||||
@ -760,7 +751,7 @@ private:
|
||||
}
|
||||
|
||||
// Disk
|
||||
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
||||
std::unique_ptr<::Commodore::C1540::Machine> c1540_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../MachineTypes.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../../Analyser/Static/Enterprise/Target.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "PIT.hpp"
|
||||
#include "RTC.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
|
||||
#include "../../InstructionSets/x86/Decoder.hpp"
|
||||
#include "../../InstructionSets/x86/Flags.hpp"
|
||||
#include "../../InstructionSets/x86/Instruction.hpp"
|
||||
|
@ -256,7 +256,7 @@ struct Request {
|
||||
bool empty();
|
||||
|
||||
/// @returns what remains of this ROM request given that everything in @c map has been found.
|
||||
Request subtract(const ROM::Map &map) const;
|
||||
Request subtract(const Map &map) const;
|
||||
|
||||
enum class ListType {
|
||||
Any, All, Single
|
||||
@ -303,7 +303,7 @@ private:
|
||||
const std::function<void(void)> &exit_list,
|
||||
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
|
||||
) const;
|
||||
bool subtract(const ROM::Map &map);
|
||||
bool subtract(const Map &map);
|
||||
void sort() {
|
||||
// Don't do a full sort, but move anything optional to the back.
|
||||
// This makes them print more nicely; it's a human-facing tweak only.
|
||||
|
62
Numeric/UpperBound.hpp
Normal file
62
Numeric/UpperBound.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// UpperBound.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/12/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
/// @returns The element that is `index - offset` into the list given by the rest of the
|
||||
/// variadic arguments or the final element if `offset` is out of bounds.
|
||||
///
|
||||
/// E.g. @c at_index<0, 3, 5, 6, 7, 8, 9>() returns the `3 - 0` = 4th element from the
|
||||
/// list 5, 6, 7, 8, 9, i.e. 8.
|
||||
template<int origin, int index, int T, int... Args>
|
||||
constexpr int at_index() {
|
||||
if constexpr (origin == index || sizeof...(Args) == 0) {
|
||||
return T;
|
||||
} else {
|
||||
return at_index<origin + 1, index, Args...>();
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The result of binary searching for the first thing in the range `[left, right)` within
|
||||
/// the other template arguments that is strictly greater than @c location.
|
||||
template <int left, int right, int... Args>
|
||||
int upper_bound_bounded(int location) {
|
||||
if constexpr (left + 1 == right) {
|
||||
return at_index<0, left+1, Args...>();
|
||||
}
|
||||
|
||||
constexpr auto midpoint = (left + right) >> 1;
|
||||
if(location >= at_index<0, midpoint, Args...>()) {
|
||||
return upper_bound_bounded<midpoint, right, Args...>(location);
|
||||
} else {
|
||||
return upper_bound_bounded<left, midpoint, Args...>(location);
|
||||
}
|
||||
}
|
||||
|
||||
template <int index, int... Args>
|
||||
constexpr int is_ordered() {
|
||||
if constexpr (sizeof...(Args) == index + 1) {
|
||||
return true;
|
||||
} else {
|
||||
return
|
||||
(at_index<0, index, Args...>() < at_index<0, index+1, Args...>()) &&
|
||||
is_ordered<index+1, Args...>();
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The result of binary searching for the first thing in the template arguments
|
||||
/// is strictly greater than @c location.
|
||||
template <int... Args>
|
||||
int upper_bound(int location) {
|
||||
static_assert(is_ordered<0, Args...>(), "Template arguments must be in ascending order.");
|
||||
return upper_bound_bounded<0, sizeof...(Args), Args...>(location);
|
||||
}
|
||||
|
||||
}
|
@ -595,6 +595,9 @@
|
||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
||||
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
|
||||
4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
|
||||
4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
|
||||
4B80CD6F2568A82C00176FCC /* DiskIIDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */; };
|
||||
4B80CD76256CA16400176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; };
|
||||
4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; };
|
||||
@ -1296,6 +1299,10 @@
|
||||
42EB812C2B47008D00429AF4 /* MemoryMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryMap.cpp; sourceTree = "<group>"; };
|
||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
|
||||
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
|
||||
4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UpperBound.hpp; sourceTree = "<group>"; };
|
||||
4B0329212D0A8C4700C51EB5 /* Pager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Pager.hpp; sourceTree = "<group>"; };
|
||||
4B0329222D0BD32500C51EB5 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
|
||||
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
|
||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
|
||||
4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = "<group>"; };
|
||||
@ -1710,6 +1717,9 @@
|
||||
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B7F1896215486A100388727 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B80214322EE7C3E00068002 /* JustInTime.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JustInTime.hpp; sourceTree = "<group>"; };
|
||||
4B8058012D0D0B55007EAD46 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4B8058022D0D0B55007EAD46 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B8058062D23902B007EAD46 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
|
||||
4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIIDrive.cpp; sourceTree = "<group>"; };
|
||||
4B80CD6E2568A82900176FCC /* DiskIIDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskIIDrive.hpp; sourceTree = "<group>"; };
|
||||
4B80CD74256CA15E00176FCC /* 2MG.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 2MG.cpp; sourceTree = "<group>"; };
|
||||
@ -3391,8 +3401,14 @@
|
||||
4B596EB12D037E8800FBF4B1 /* Plus4 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */,
|
||||
4B8058022D0D0B55007EAD46 /* Keyboard.cpp */,
|
||||
4B596EB02D037E8800FBF4B1 /* Plus4.cpp */,
|
||||
4B0329222D0BD32500C51EB5 /* Interrupts.hpp */,
|
||||
4B8058012D0D0B55007EAD46 /* Keyboard.hpp */,
|
||||
4B0329212D0A8C4700C51EB5 /* Pager.hpp */,
|
||||
4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */,
|
||||
4B03291F2D0923E300C51EB5 /* Video.hpp */,
|
||||
4B8058062D23902B007EAD46 /* Audio.hpp */,
|
||||
);
|
||||
path = Plus4;
|
||||
sourceTree = "<group>";
|
||||
@ -3635,13 +3651,14 @@
|
||||
children = (
|
||||
4B43984129674943006B0BFC /* BitReverse.hpp */,
|
||||
4BD155312716362A00410C6E /* BitSpread.hpp */,
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
|
||||
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
|
||||
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
|
||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
||||
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */,
|
||||
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */,
|
||||
);
|
||||
name = Numeric;
|
||||
path = ../../Numeric;
|
||||
@ -5468,7 +5485,7 @@
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 1610;
|
||||
LastUpgradeCheck = 1620;
|
||||
ORGANIZATIONNAME = "Thomas Harte";
|
||||
TargetAttributes = {
|
||||
4B055A691FAE763F0060FFFF = {
|
||||
@ -6096,6 +6113,7 @@
|
||||
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */,
|
||||
4BB505792B962DDF0031C43C /* SoundGenerator.cpp in Sources */,
|
||||
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
|
||||
4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
|
||||
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
|
||||
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
|
||||
4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
|
||||
@ -6362,6 +6380,7 @@
|
||||
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
|
||||
4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */,
|
||||
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
|
||||
4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
|
||||
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
|
||||
4B0DA67B282DCDF100C12F17 /* Instruction.cpp in Sources */,
|
||||
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
|
||||
@ -6696,6 +6715,7 @@
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
|
||||
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
|
||||
4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
|
||||
4B06AADF2C645F830034D014 /* Video.cpp in Sources */,
|
||||
4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */,
|
||||
4B7752BF28217F250073E2C5 /* Sprites.cpp in Sources */,
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@ -23,7 +23,7 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
@ -74,7 +74,7 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUShaderValidationMode = "2"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@ -18,7 +18,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="590" height="369"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1470" height="924"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="590" height="369"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -125,7 +125,7 @@ Gw
|
||||
<rect key="frame" x="110" y="210" width="80" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="512 kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="512" imageScaling="axesIndependently" inset="2" selectedItem="Zev-ku-jDG" id="vdO-VR-mUx">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="Zvi-ox-ip4">
|
||||
<items>
|
||||
<menuItem title="512 kb" state="on" tag="512" id="Zev-ku-jDG"/>
|
||||
@ -155,7 +155,7 @@ Gw
|
||||
<rect key="frame" x="108" y="180" width="72" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="zV7-V8-c7s" id="39D-ms-pf9">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="yIx-1y-Ab0">
|
||||
<items>
|
||||
<menuItem title="None" state="on" id="zV7-V8-c7s"/>
|
||||
@ -192,7 +192,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="96" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="LgZ-9j-YQl" id="yH2-Vm-hiD">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="FdB-a4-Cox">
|
||||
<items>
|
||||
<menuItem title="CPC464" tag="464" id="VSM-l2-mWa"/>
|
||||
@ -246,7 +246,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="117" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="esp-ir-7iH">
|
||||
<items>
|
||||
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
|
||||
@ -261,7 +261,7 @@ Gw
|
||||
<rect key="frame" x="119" y="180" width="134" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="udQ-NK-FG8">
|
||||
<items>
|
||||
<menuItem title="Sixteen Sector" state="on" tag="16" id="QaV-Yr-k9o"/>
|
||||
@ -321,7 +321,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="89" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="4rn-K4-QgV">
|
||||
<items>
|
||||
<menuItem title="ROM 00" id="2LO-US-byb"/>
|
||||
@ -335,7 +335,7 @@ Gw
|
||||
<rect key="frame" x="108" y="180" width="82" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="Scm-d9-EU9">
|
||||
<items>
|
||||
<menuItem title="256 kb" tag="256" id="PZE-8h-MNs"/>
|
||||
@ -400,7 +400,7 @@ Gw
|
||||
<rect key="frame" x="79" y="210" width="80" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="512 kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="512" imageScaling="axesIndependently" inset="2" selectedItem="LVX-CI-lo9" id="dSS-yv-CDV">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="jsD-9U-bwN">
|
||||
<items>
|
||||
<menuItem title="512 kb" state="on" tag="512" id="LVX-CI-lo9"/>
|
||||
@ -481,7 +481,7 @@ Gw
|
||||
<rect key="frame" x="80" y="210" width="129" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="ojI-Vb-iHz">
|
||||
<items>
|
||||
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
|
||||
@ -495,7 +495,7 @@ Gw
|
||||
<rect key="frame" x="67" y="180" width="77" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="4 Mhz" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="axesIndependently" inset="2" selectedItem="5N6-tD-uN7" id="BU2-NJ-Kii">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="ANK-p0-M77">
|
||||
<items>
|
||||
<menuItem title="4 Mhz" state="on" tag="4" id="5N6-tD-uN7"/>
|
||||
@ -508,7 +508,7 @@ Gw
|
||||
<rect key="frame" x="64" y="150" width="107" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="uNC-hA-d5z">
|
||||
<items>
|
||||
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
|
||||
@ -522,7 +522,7 @@ Gw
|
||||
<rect key="frame" x="67" y="120" width="105" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="VcH-Xm-1hY">
|
||||
<items>
|
||||
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
|
||||
@ -537,7 +537,7 @@ Gw
|
||||
<rect key="frame" x="57" y="90" width="83" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" title="DOS" id="sdr-al-7mi">
|
||||
<items>
|
||||
<menuItem title="EXDOS" state="on" tag="1" id="8rP-2w-PdU"/>
|
||||
@ -636,7 +636,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="75" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="ofy-j9-YnU">
|
||||
<items>
|
||||
<menuItem title="128k" id="WCG-6u-ANQ"/>
|
||||
@ -674,7 +674,7 @@ Gw
|
||||
<rect key="frame" x="71" y="180" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="BCR-rp-Kpc">
|
||||
<items>
|
||||
<menuItem title="European (PAL)" state="on" id="xAh-Ch-tby"/>
|
||||
@ -704,7 +704,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="79" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="MSX 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="G9U-vd-Cjk" id="2TO-f6-Gmp">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="v3T-Jv-VLK">
|
||||
<items>
|
||||
<menuItem title="MSX 1" state="on" tag="1" id="G9U-vd-Cjk"/>
|
||||
@ -759,7 +759,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="107" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="E9d-fH-Eak">
|
||||
<items>
|
||||
<menuItem title="Oric-1" state="on" id="jGN-1a-biF"/>
|
||||
@ -773,7 +773,7 @@ Gw
|
||||
<rect key="frame" x="113" y="180" width="130" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="hy3-Li-nlW">
|
||||
<items>
|
||||
<menuItem title="None" state="on" id="XhK-Jh-oTW"/>
|
||||
@ -818,7 +818,7 @@ Gw
|
||||
<rect key="frame" x="116" y="210" width="68" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="MDA" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="oIy-If-5bQ" id="xz8-mu-ynU">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="dju-ZY-2DH">
|
||||
<items>
|
||||
<menuItem title="MDA" state="on" id="oIy-If-5bQ"/>
|
||||
@ -847,7 +847,7 @@ Gw
|
||||
<rect key="frame" x="68" y="180" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Similar to Original" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8086" imageScaling="axesIndependently" inset="2" selectedItem="R4W-s4-KFx" id="9i0-UG-B2c">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="KQw-8g-SrI">
|
||||
<items>
|
||||
<menuItem title="Similar to Original" state="on" tag="8086" id="R4W-s4-KFx"/>
|
||||
@ -881,7 +881,7 @@ Gw
|
||||
<rect key="frame" x="71" y="210" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="uCQ-9l-eBb">
|
||||
<items>
|
||||
<menuItem title="European (PAL)" state="on" id="45i-0n-gau"/>
|
||||
@ -897,7 +897,7 @@ Gw
|
||||
<rect key="frame" x="108" y="180" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="e9J-Ie-PjH">
|
||||
<items>
|
||||
<menuItem title="Unexpanded" state="on" id="fOl-8Q-fsA"/>
|
||||
@ -958,7 +958,7 @@ Gw
|
||||
<rect key="frame" x="108" y="210" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="Of4-DY-mE5">
|
||||
<items>
|
||||
<menuItem title="Unexpanded" state="on" id="4Sa-jR-xOd"/>
|
||||
@ -1006,7 +1006,7 @@ Gw
|
||||
<rect key="frame" x="108" y="210" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="mua-Lp-9wl">
|
||||
<items>
|
||||
<menuItem title="Unexpanded" state="on" id="7QC-Ij-hES"/>
|
||||
@ -1044,7 +1044,7 @@ Gw
|
||||
<rect key="frame" x="67" y="210" width="76" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="8lt-dk-zPr">
|
||||
<items>
|
||||
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>
|
||||
|
@ -390,6 +390,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
|
||||
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||
[encoder endEncoding];
|
||||
encoder = nil;
|
||||
[commandBuffer commit];
|
||||
|
||||
return commandBuffer;
|
||||
@ -885,6 +886,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
|
||||
// Complete encoding.
|
||||
[encoder endEncoding];
|
||||
encoder = nil;
|
||||
}
|
||||
|
||||
- (void)outputFrameCleanerToCommandBuffer:(id<MTLCommandBuffer>)commandBuffer {
|
||||
@ -901,11 +903,12 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
|
||||
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||
[encoder endEncoding];
|
||||
encoder = nil;
|
||||
}
|
||||
|
||||
- (void)composeOutputArea:(const BufferingScanTarget::OutputArea &)outputArea commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
|
||||
// Output all scans to the composition buffer.
|
||||
const id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass];
|
||||
id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass];
|
||||
[encoder setRenderPipelineState:_composePipeline];
|
||||
|
||||
[encoder setVertexBuffer:_scansBuffer offset:0 atIndex:0];
|
||||
@ -919,6 +922,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
RangePerform(outputArea.start.scan, outputArea.end.scan, NumBufferedScans, OutputScans);
|
||||
#undef OutputScans
|
||||
[encoder endEncoding];
|
||||
encoder = nil;
|
||||
}
|
||||
|
||||
- (id<MTLBuffer>)bufferForOffset:(size_t)offset {
|
||||
@ -1141,6 +1145,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
|
||||
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||
[encoder endEncoding];
|
||||
encoder = nil;
|
||||
|
||||
[commandBuffer presentDrawable:view.currentDrawable];
|
||||
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {
|
||||
|
@ -8,10 +8,10 @@
|
||||
|
||||
#include "CRT.hpp"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
@ -458,40 +458,58 @@ void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
|
||||
|
||||
// MARK: - Getters.
|
||||
|
||||
Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) const {
|
||||
Outputs::Display::Rect CRT::get_rect_for_area(
|
||||
int first_line_after_sync,
|
||||
int number_of_lines,
|
||||
int first_cycle_after_sync,
|
||||
int number_of_cycles,
|
||||
float aspect_ratio
|
||||
) const {
|
||||
assert(number_of_cycles > 0);
|
||||
assert(number_of_lines > 0);
|
||||
assert(first_line_after_sync >= 0);
|
||||
assert(first_cycle_after_sync >= 0);
|
||||
|
||||
// Scale up x coordinates and add a little extra leeway to y.
|
||||
first_cycle_after_sync *= time_multiplier_;
|
||||
number_of_cycles *= time_multiplier_;
|
||||
|
||||
first_line_after_sync -= 2;
|
||||
number_of_lines += 4;
|
||||
|
||||
// determine prima facie x extent
|
||||
// Determine prima facie x extent.
|
||||
const int horizontal_period = horizontal_flywheel_->get_standard_period();
|
||||
const int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
|
||||
const int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
|
||||
|
||||
// make sure that the requested range is visible
|
||||
if(int(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = int(horizontal_retrace_period);
|
||||
if(int(first_cycle_after_sync + number_of_cycles) > horizontal_scan_period) number_of_cycles = int(horizontal_scan_period - int(first_cycle_after_sync));
|
||||
// Ensure requested range is within visible region.
|
||||
first_cycle_after_sync = std::max(horizontal_retrace_period, first_cycle_after_sync);
|
||||
number_of_cycles = std::min(horizontal_period - first_cycle_after_sync, number_of_cycles);
|
||||
|
||||
float start_x = float(int(first_cycle_after_sync) - horizontal_retrace_period) / float(horizontal_scan_period);
|
||||
float start_x = float(first_cycle_after_sync - horizontal_retrace_period) / float(horizontal_scan_period);
|
||||
float width = float(number_of_cycles) / float(horizontal_scan_period);
|
||||
|
||||
// determine prima facie y extent
|
||||
// Determine prima facie y extent.
|
||||
const int vertical_period = vertical_flywheel_->get_standard_period();
|
||||
const int vertical_scan_period = vertical_flywheel_->get_scan_period();
|
||||
const int vertical_retrace_period = vertical_period - vertical_scan_period;
|
||||
|
||||
// make sure that the requested range is visible
|
||||
// if(int(first_line_after_sync) * horizontal_period < vertical_retrace_period)
|
||||
// first_line_after_sync = (vertical_retrace_period + horizontal_period - 1) / horizontal_period;
|
||||
// if((first_line_after_sync + number_of_lines) * horizontal_period > vertical_scan_period)
|
||||
// number_of_lines = int(horizontal_scan_period - int(first_cycle_after_sync));
|
||||
// Ensure range is visible.
|
||||
first_line_after_sync = std::max(
|
||||
first_line_after_sync * horizontal_period,
|
||||
vertical_retrace_period
|
||||
) / horizontal_period;
|
||||
number_of_lines = std::min(
|
||||
vertical_period - first_line_after_sync * horizontal_period,
|
||||
number_of_lines * horizontal_period
|
||||
) / horizontal_period;
|
||||
|
||||
float start_y = float((int(first_line_after_sync) * horizontal_period) - vertical_retrace_period) / float(vertical_scan_period);
|
||||
float height = float(int(number_of_lines) * horizontal_period) / vertical_scan_period;
|
||||
float start_y =
|
||||
float(first_line_after_sync * horizontal_period - vertical_retrace_period) /
|
||||
float(vertical_scan_period);
|
||||
float height = float(number_of_lines * horizontal_period) / vertical_scan_period;
|
||||
|
||||
// adjust to ensure aspect ratio is correct
|
||||
// Pick a zoom that includes the entire requested visible area given the aspect ratio constraints.
|
||||
const float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
|
||||
const float ideal_width = height * adjusted_aspect_ratio;
|
||||
if(ideal_width > width) {
|
||||
@ -503,6 +521,8 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num
|
||||
height = ideal_height;
|
||||
}
|
||||
|
||||
// TODO: apply absolute clipping constraints now.
|
||||
|
||||
return Outputs::Display::Rect(start_x, start_y, width, height);
|
||||
}
|
||||
|
||||
|
12
ROMImages/Plus4/readme.txt
Normal file
12
ROMImages/Plus4/readme.txt
Normal file
@ -0,0 +1,12 @@
|
||||
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
|
||||
|
||||
Expected files:
|
||||
|
||||
At least one of:
|
||||
kernal.318004-03.bin — the PAL kernel, v3;
|
||||
kernal.318004-04.bin — the PAL kernel, v4; and
|
||||
kernal.318004-05.bin — the PAL kernel, v5.
|
||||
|
||||
|
||||
And:
|
||||
basic.318006-01.bin — the BASIC 3.5 ROM.
|
@ -12,7 +12,7 @@ using namespace Storage::Tape;
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
TapePlayer::TapePlayer(int input_clock_rate) :
|
||||
TapePlayer::TapePlayer(const int input_clock_rate) :
|
||||
TimedEventLoop(input_clock_rate)
|
||||
{}
|
||||
|
||||
@ -129,7 +129,7 @@ void TapePlayer::process_next_event() {
|
||||
|
||||
// MARK: - Binary Player
|
||||
|
||||
BinaryTapePlayer::BinaryTapePlayer(int input_clock_rate) :
|
||||
BinaryTapePlayer::BinaryTapePlayer(const int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate)
|
||||
{}
|
||||
|
||||
@ -138,7 +138,7 @@ ClockingHint::Preference BinaryTapePlayer::preferred_clocking() const {
|
||||
return TapePlayer::preferred_clocking();
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
void BinaryTapePlayer::set_motor_control(const bool enabled) {
|
||||
if(motor_is_running_ != enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
update_clocking_observer();
|
||||
@ -149,7 +149,7 @@ void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_activity_observer(Activity::Observer *observer) {
|
||||
void BinaryTapePlayer::set_activity_observer(Activity::Observer *const observer) {
|
||||
observer_ = observer;
|
||||
if(observer) {
|
||||
observer->register_led("Tape motor");
|
||||
@ -173,7 +173,7 @@ void BinaryTapePlayer::run_for(const Cycles cycles) {
|
||||
if(motor_is_running_) TapePlayer::run_for(cycles);
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_delegate(Delegate *delegate) {
|
||||
void BinaryTapePlayer::set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,10 @@
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
#include "../TargetPlatforms.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Storage::Tape {
|
||||
|
||||
@ -112,7 +111,7 @@ public:
|
||||
bool has_tape() const;
|
||||
std::shared_ptr<Storage::Tape::Tape> tape();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
void run_for(Cycles);
|
||||
void run_for_input_pulse();
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const override;
|
||||
@ -148,12 +147,12 @@ public:
|
||||
void set_tape_output(bool);
|
||||
bool input() const;
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
void run_for(Cycles);
|
||||
|
||||
struct Delegate {
|
||||
virtual void tape_did_change_input(BinaryTapePlayer *) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
|
@ -118,6 +118,7 @@ set(CLK_SOURCES
|
||||
Machines/Atari/ST/Video.cpp
|
||||
Machines/ColecoVision/ColecoVision.cpp
|
||||
Machines/Commodore/1540/Implementation/C1540.cpp
|
||||
Machines/Commodore/Plus4/Keyboard.cpp
|
||||
Machines/Commodore/Plus4/Plus4.cpp
|
||||
Machines/Commodore/SerialBus.cpp
|
||||
Machines/Commodore/Vic-20/Keyboard.cpp
|
||||
|
Loading…
x
Reference in New Issue
Block a user