1
0
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:
Thomas Harte 2025-01-03 20:11:08 -05:00 committed by GitHub
commit 1ed550d7f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2437 additions and 358 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
};
}

View 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;
};
}

View 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);
//}

View 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;
//};
}

View 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>;
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -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"

View File

@ -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"

View File

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

View File

@ -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 */,

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -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">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -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"/>

View File

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

View File

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

View 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.

View File

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

View File

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

View File

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