mirror of
https://github.com/TomHarte/CLK.git
synced 2025-11-23 21:17:42 +00:00
Compare commits
116 Commits
6845LinesP
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d340a01513 | ||
|
|
2dd2b279c8 | ||
|
|
88c124aa9f | ||
|
|
e2ef3226af | ||
|
|
507b81a8a4 | ||
|
|
31d7639761 | ||
|
|
10847b3e0b | ||
|
|
7b513a95a1 | ||
|
|
411b96128c | ||
|
|
5ef5478861 | ||
|
|
50adbaefc8 | ||
|
|
424d57c2c1 | ||
|
|
76ed9d1703 | ||
|
|
c4bab00c6d | ||
|
|
ac9fc15981 | ||
|
|
1b977cae64 | ||
|
|
0cd1921971 | ||
|
|
c193315e17 | ||
|
|
364996021e | ||
|
|
f287c80e39 | ||
|
|
f4a9a64c93 | ||
|
|
c464ffaeac | ||
|
|
822aa4155d | ||
|
|
3d192e22f2 | ||
|
|
6e8e2b6201 | ||
|
|
16e4144409 | ||
|
|
6dacc50163 | ||
|
|
6d71ad9bcc | ||
|
|
fc5d93f9cc | ||
|
|
3196840b05 | ||
|
|
51eea4dea3 | ||
|
|
314154e9fd | ||
|
|
4a93264dc5 | ||
|
|
39a96a7f73 | ||
|
|
9ee7425627 | ||
|
|
923fdd42ec | ||
|
|
50cd28f882 | ||
|
|
7bc865a2e0 | ||
|
|
1cf3c77ae9 | ||
|
|
d3cda5d878 | ||
|
|
4f09f38f2e | ||
|
|
4980caee1b | ||
|
|
ae89c66b17 | ||
|
|
bb7059a9e1 | ||
|
|
05de67ba76 | ||
|
|
25bf7df4d1 | ||
|
|
e44cbcc1d5 | ||
|
|
c876bcb849 | ||
|
|
febff84421 | ||
|
|
1ca261986e | ||
|
|
71e319a815 | ||
|
|
a67e222c35 | ||
|
|
e173a93b57 | ||
|
|
3dbf62ca08 | ||
|
|
bb5239e553 | ||
|
|
faec5c3f84 | ||
|
|
9c359627f3 | ||
|
|
2a0208c554 | ||
|
|
f513edc006 | ||
|
|
9e39be282b | ||
|
|
a9d945d6d2 | ||
|
|
5e465f1ff4 | ||
|
|
5359964fef | ||
|
|
fa8be26f9f | ||
|
|
aabfe7c284 | ||
|
|
d011b10b5d | ||
|
|
332b37063f | ||
|
|
b3a9e39be3 | ||
|
|
67590cf06b | ||
|
|
236fdacb36 | ||
|
|
176bda9eb8 | ||
|
|
9f0a0443a8 | ||
|
|
fd1a7e78c5 | ||
|
|
909fa57b27 | ||
|
|
5630b1c351 | ||
|
|
c4fe38a61f | ||
|
|
5b4f303e35 | ||
|
|
c9c1bde6e2 | ||
|
|
d01e1f3bb1 | ||
|
|
fd32e63459 | ||
|
|
dbbb1d60fc | ||
|
|
1ce013bcf7 | ||
|
|
86bf019aac | ||
|
|
d00546dd77 | ||
|
|
cf33e17688 | ||
|
|
c5c6c5ff72 | ||
|
|
fa0835abd8 | ||
|
|
f232b179ed | ||
|
|
a4a0026cab | ||
|
|
eac7493180 | ||
|
|
989fb32fba | ||
|
|
735afcfabb | ||
|
|
37152a1fad | ||
|
|
4e86184955 | ||
|
|
d23dbb96c2 | ||
|
|
4586e4b4c1 | ||
|
|
de5cdbf18c | ||
|
|
8c2294fc0d | ||
|
|
b0b82782ad | ||
|
|
b9f5802c89 | ||
|
|
29235f1276 | ||
|
|
8c74e2a323 | ||
|
|
ae2936b9c3 | ||
|
|
0d295a6338 | ||
|
|
3ebd6c6871 | ||
|
|
6e2cd0ace6 | ||
|
|
af82a0bcda | ||
|
|
6fe208ae77 | ||
|
|
f569b86c90 | ||
|
|
7dfd5ea0d0 | ||
|
|
0cc5a9d74f | ||
|
|
5e98e6502d | ||
|
|
fe7a206fc5 | ||
|
|
d628f75244 | ||
|
|
cd09b5d356 | ||
|
|
bb2cf0170d |
@@ -44,6 +44,7 @@ struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::Str
|
|||||||
bool has_1770dfs = false;
|
bool has_1770dfs = false;
|
||||||
bool has_adfs = false;
|
bool has_adfs = false;
|
||||||
bool has_sideways_ram = true;
|
bool has_sideways_ram = true;
|
||||||
|
bool has_beebsid = false;
|
||||||
|
|
||||||
ReflectableEnum(TubeProcessor, None, WDC65C02, Z80);
|
ReflectableEnum(TubeProcessor, None, WDC65C02, Z80);
|
||||||
TubeProcessor tube_processor = TubeProcessor::None;
|
TubeProcessor tube_processor = TubeProcessor::None;
|
||||||
@@ -56,6 +57,7 @@ private:
|
|||||||
DeclareField(has_1770dfs);
|
DeclareField(has_1770dfs);
|
||||||
DeclareField(has_adfs);
|
DeclareField(has_adfs);
|
||||||
DeclareField(has_sideways_ram);
|
DeclareField(has_sideways_ram);
|
||||||
|
DeclareField(has_beebsid);
|
||||||
AnnounceEnum(TubeProcessor);
|
AnnounceEnum(TubeProcessor);
|
||||||
DeclareField(tube_processor);
|
DeclareField(tube_processor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
|||||||
bool
|
bool
|
||||||
) {
|
) {
|
||||||
// This analyser can comprehend disks only.
|
// This analyser can comprehend disks only.
|
||||||
if(media.disks.empty()) return {};
|
if(media.disks.empty() && media.file_bundles.empty()) return {};
|
||||||
|
|
||||||
// Otherwise, assume a return will happen.
|
// Otherwise, assume a return will happen.
|
||||||
Analyser::Static::TargetList targets;
|
Analyser::Static::TargetList targets;
|
||||||
@@ -86,7 +86,36 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
if(!media.file_bundles.empty()) {
|
||||||
|
auto &bundle = *media.file_bundles.front();
|
||||||
|
const auto key = bundle.key_file();
|
||||||
|
|
||||||
|
if(key.has_value()) {
|
||||||
|
auto file = bundle.open(*key, Storage::FileMode::Read);
|
||||||
|
|
||||||
|
enum class FileType: uint16_t {
|
||||||
|
COM = 0x0500,
|
||||||
|
BAS = 0x0400,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for a .COM by inspecting the header.
|
||||||
|
const auto type = FileType(file.get_le<uint16_t>());
|
||||||
|
const uint16_t size = file.get_le<uint16_t>();
|
||||||
|
// There are then 12 bytes of 0 padding that could be tested for.
|
||||||
|
|
||||||
|
if((type != FileType::COM && type != FileType::BAS) || size > file.stats().st_size - 16) {
|
||||||
|
target->media.file_bundles.clear();
|
||||||
|
} else {
|
||||||
|
target->loading_command = "run \"file:\"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: look for a key file, similar logic to above.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!target->media.empty()) {
|
||||||
|
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||||
|
}
|
||||||
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,9 @@
|
|||||||
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
|
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||||
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||||
|
|
||||||
|
// File Bundles.
|
||||||
|
#include "Storage/FileBundle/FileBundle.hpp"
|
||||||
|
|
||||||
// Mass Storage Devices (i.e. usually, hard disks)
|
// Mass Storage Devices (i.e. usually, hard disks)
|
||||||
#include "Storage/MassStorage/Formats/DAT.hpp"
|
#include "Storage/MassStorage/Formats/DAT.hpp"
|
||||||
#include "Storage/MassStorage/Formats/DSK.hpp"
|
#include "Storage/MassStorage/Formats/DSK.hpp"
|
||||||
@@ -123,6 +126,8 @@ public:
|
|||||||
media.cartridges.push_back(instance);
|
media.cartridges.push_back(instance);
|
||||||
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
|
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
|
||||||
media.mass_storage_devices.push_back(instance);
|
media.mass_storage_devices.push_back(instance);
|
||||||
|
} else if constexpr (std::is_base_of_v<Storage::FileBundle::FileBundle, InstanceT>) {
|
||||||
|
media.file_bundles.push_back(instance);
|
||||||
} else {
|
} else {
|
||||||
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
|
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
|
||||||
}
|
}
|
||||||
@@ -208,13 +213,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
|||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
|
||||||
|
|
||||||
|
accumulator.try_standard<FileBundle::LocalFSFileBundle>(TargetPlatform::Enterprise, "bas");
|
||||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
||||||
|
|
||||||
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
|
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
|
||||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
|
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
|
||||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
||||||
|
accumulator.try_standard<FileBundle::LocalFSFileBundle>(TargetPlatform::Enterprise, "com");
|
||||||
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
||||||
|
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
|
||||||
@@ -257,6 +263,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
|||||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
|
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
|
||||||
"ipf");
|
"ipf");
|
||||||
|
|
||||||
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
|
||||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
|
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
|
||||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
|
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "Storage/Cartridge/Cartridge.hpp"
|
#include "Storage/Cartridge/Cartridge.hpp"
|
||||||
#include "Storage/Disk/Disk.hpp"
|
#include "Storage/Disk/Disk.hpp"
|
||||||
|
#include "Storage/FileBundle/FileBundle.hpp"
|
||||||
#include "Storage/MassStorage/MassStorageDevice.hpp"
|
#include "Storage/MassStorage/MassStorageDevice.hpp"
|
||||||
#include "Storage/Tape/Tape.hpp"
|
#include "Storage/Tape/Tape.hpp"
|
||||||
#include "Storage/TargetPlatforms.hpp"
|
#include "Storage/TargetPlatforms.hpp"
|
||||||
@@ -33,9 +34,15 @@ struct Media {
|
|||||||
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||||
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
|
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
|
||||||
|
std::vector<std::shared_ptr<Storage::FileBundle::FileBundle>> file_bundles;
|
||||||
|
|
||||||
bool empty() const {
|
bool empty() const {
|
||||||
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
|
return
|
||||||
|
disks.empty() &&
|
||||||
|
tapes.empty() &&
|
||||||
|
cartridges.empty() &&
|
||||||
|
mass_storage_devices.empty() &&
|
||||||
|
file_bundles.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Media &operator +=(const Media &rhs) {
|
Media &operator +=(const Media &rhs) {
|
||||||
@@ -47,6 +54,7 @@ struct Media {
|
|||||||
append(tapes, rhs.tapes);
|
append(tapes, rhs.tapes);
|
||||||
append(cartridges, rhs.cartridges);
|
append(cartridges, rhs.cartridges);
|
||||||
append(mass_storage_devices, rhs.mass_storage_devices);
|
append(mass_storage_devices, rhs.mass_storage_devices);
|
||||||
|
append(file_bundles, rhs.file_bundles);
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ public:
|
|||||||
/// Flushes all accumulated time.
|
/// Flushes all accumulated time.
|
||||||
inline void flush() {
|
inline void flush() {
|
||||||
if(!is_flushed_) {
|
if(!is_flushed_) {
|
||||||
task_queue_.flush();
|
task_queue_.lock_flush();
|
||||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||||
is_flushed_ = true;
|
is_flushed_ = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,7 +562,7 @@ void WD1770::posit_event(const int new_event_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_data_mode(DataMode::Writing);
|
set_data_mode(DataMode::Writing);
|
||||||
begin_writing(false);
|
begin_writing(false, false);
|
||||||
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
|
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
|
||||||
write_byte(0);
|
write_byte(0);
|
||||||
}
|
}
|
||||||
@@ -755,7 +755,7 @@ void WD1770::posit_event(const int new_event_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
|
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
|
||||||
begin_writing(true);
|
begin_writing(true, false);
|
||||||
index_hole_count_ = 0;
|
index_hole_count_ = 0;
|
||||||
|
|
||||||
write_track_write_loop:
|
write_track_write_loop:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using namespace MOS::MOS6560;
|
|||||||
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||||
audio_queue_(audio_queue) {}
|
audio_queue_(audio_queue) {}
|
||||||
|
|
||||||
|
|
||||||
void AudioGenerator::set_volume(const uint8_t volume) {
|
void AudioGenerator::set_volume(const uint8_t volume) {
|
||||||
audio_queue_.enqueue([this, volume]() {
|
audio_queue_.enqueue([this, volume]() {
|
||||||
volume_ = int16_t(volume) * range_multiplier_;
|
volume_ = int16_t(volume) * range_multiplier_;
|
||||||
@@ -148,7 +147,7 @@ template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(
|
|||||||
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(
|
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(
|
||||||
std::size_t, Outputs::Speaker::MonoSample *);
|
std::size_t, Outputs::Speaker::MonoSample *);
|
||||||
|
|
||||||
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
|
void AudioGenerator::set_sample_volume_range(const std::int16_t range) {
|
||||||
range_multiplier_ = int16_t(range / 64);
|
range_multiplier_ = int16_t(range / 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~MOS6560() {
|
~MOS6560() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_clock_rate(const double clock_rate) {
|
void set_clock_rate(const double clock_rate) {
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ void i8272::posit_event(const int event_type) {
|
|||||||
|
|
||||||
write_data_found_header:
|
write_data_found_header:
|
||||||
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
|
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
|
||||||
begin_writing(true);
|
begin_writing(true, false);
|
||||||
|
|
||||||
write_id_data_joiner(command_.command() == Command::WriteDeletedData, true);
|
write_id_data_joiner(command_.command() == Command::WriteDeletedData, true);
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ void i8272::posit_event(const int event_type) {
|
|||||||
// Wait for the index hole.
|
// Wait for the index hole.
|
||||||
WAIT_FOR_EVENT(Event::IndexHole);
|
WAIT_FOR_EVENT(Event::IndexHole);
|
||||||
index_hole_count_ = 0;
|
index_hole_count_ = 0;
|
||||||
begin_writing(true);
|
begin_writing(true, false);
|
||||||
|
|
||||||
// Write start-of-track.
|
// Write start-of-track.
|
||||||
write_start_of_track();
|
write_start_of_track();
|
||||||
|
|||||||
@@ -296,8 +296,9 @@ int DiskII::read_address(int address) {
|
|||||||
inputs_ &= ~input_mode;
|
inputs_ &= ~input_mode;
|
||||||
break;
|
break;
|
||||||
case 0xf:
|
case 0xf:
|
||||||
if(!(inputs_ & input_mode))
|
if(!(inputs_ & input_mode)) {
|
||||||
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
|
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false, false);
|
||||||
|
}
|
||||||
inputs_ |= input_mode;
|
inputs_ |= input_mode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,7 +346,9 @@ void IWM::select_shift_mode() {
|
|||||||
|
|
||||||
// If writing mode just began, set the drive into write mode and cue up the first output byte.
|
// If writing mode just began, set the drive into write mode and cue up the first output byte.
|
||||||
if(old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
if(old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
||||||
if(drives_[active_drive_]) drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
|
if(drives_[active_drive_]) {
|
||||||
|
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false, false);
|
||||||
|
}
|
||||||
shift_register_ = next_output_;
|
shift_register_ = next_output_;
|
||||||
write_handshake_ |= 0x80 | 0x40;
|
write_handshake_ |= 0x80 | 0x40;
|
||||||
output_bits_remaining_ = 8;
|
output_bits_remaining_ = 8;
|
||||||
|
|||||||
433
Components/SID/SID.cpp
Normal file
433
Components/SID/SID.cpp
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
//
|
||||||
|
// SID.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "SID.hpp"
|
||||||
|
|
||||||
|
// Sources used:
|
||||||
|
//
|
||||||
|
// (1) SID Article v0.2 at https://github.com/ImreOlajos/SID-Article
|
||||||
|
// (2) Technical SID Information/Software stuff at http://www.sidmusic.org/sid/sidtech2.html
|
||||||
|
// (3) SID 6581/8580 (Sound Interface Device) reference at https://oxyron.de/html/registers_sid.html
|
||||||
|
|
||||||
|
using namespace MOS::SID;
|
||||||
|
|
||||||
|
SID::SID(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||||
|
audio_queue_(audio_queue),
|
||||||
|
output_filter_(
|
||||||
|
SignalProcessing::BiquadFilter::Type::LowPass,
|
||||||
|
1000000.0f,
|
||||||
|
15000.0f
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// MARK: - Programmer interface.
|
||||||
|
|
||||||
|
void SID::write(const Numeric::SizedInt<5> address, const uint8_t value) {
|
||||||
|
last_write_ = value;
|
||||||
|
audio_queue_.enqueue([=, this] {
|
||||||
|
const auto voice = [&]() -> Voice & {
|
||||||
|
return voices_[address.get() / 7];
|
||||||
|
};
|
||||||
|
const auto oscillator = [&]() -> Voice::Oscillator & {
|
||||||
|
return voice().oscillator;
|
||||||
|
};
|
||||||
|
const auto adsr = [&]() -> Voice::ADSR & {
|
||||||
|
return voice().adsr;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch(address.get()) {
|
||||||
|
case 0x00: case 0x07: case 0x0e:
|
||||||
|
oscillator().pitch = (oscillator().pitch & 0xff'00'00) | uint32_t(value << 8);
|
||||||
|
break;
|
||||||
|
case 0x01: case 0x08: case 0x0f:
|
||||||
|
oscillator().pitch = (oscillator().pitch & 0x00'ff'00) | uint32_t(value << 16);
|
||||||
|
break;
|
||||||
|
case 0x02: case 0x09: case 0x10:
|
||||||
|
oscillator().pulse_width = (oscillator().pitch & 0xf0'00'00'00) | uint32_t(value << 20);
|
||||||
|
break;
|
||||||
|
case 0x03: case 0x0a: case 0x11:
|
||||||
|
// The top bit of the phase counter is inverted; since it'll be compared directly with the
|
||||||
|
// pulse width, invert that bit too.
|
||||||
|
oscillator().pulse_width =
|
||||||
|
(
|
||||||
|
(oscillator().pitch & 0x0f'f0'00'00) |
|
||||||
|
uint32_t(value << 28)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 0x04: case 0x0b: case 0x12:
|
||||||
|
voice().set_control(value);
|
||||||
|
break;
|
||||||
|
case 0x05: case 0x0c: case 0x13:
|
||||||
|
adsr().attack = value >> 4;
|
||||||
|
adsr().decay = value;
|
||||||
|
adsr().set_phase(adsr().phase);
|
||||||
|
break;
|
||||||
|
case 0x06: case 0x0d: case 0x14:
|
||||||
|
adsr().sustain = (value >> 4) | (value & 0xf0);
|
||||||
|
adsr().release = value;
|
||||||
|
adsr().set_phase(adsr().phase);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x15:
|
||||||
|
filter_cutoff_.load<0, 3>(value);
|
||||||
|
update_filter();
|
||||||
|
break;
|
||||||
|
case 0x16:
|
||||||
|
filter_cutoff_.load<3>(value);
|
||||||
|
update_filter();
|
||||||
|
break;
|
||||||
|
case 0x17:
|
||||||
|
filter_channels_ = value;
|
||||||
|
filter_resonance_ = value >> 4;
|
||||||
|
update_filter();
|
||||||
|
break;
|
||||||
|
case 0x18:
|
||||||
|
volume_ = value & 0x0f;
|
||||||
|
filter_mode_ = value >> 4;
|
||||||
|
voice3_disable_ = value & 0x80;
|
||||||
|
update_filter();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::set_potentometer_input(const int index, const uint8_t value) {
|
||||||
|
potentometers_[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::update_filter() {
|
||||||
|
using Type = SignalProcessing::BiquadFilter::Type;
|
||||||
|
Type type = Type::AllPass;
|
||||||
|
|
||||||
|
switch(filter_mode_.get()) {
|
||||||
|
case 0:
|
||||||
|
filter_ = SignalProcessing::BiquadFilter();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
case 3: type = Type::LowPass; break;
|
||||||
|
|
||||||
|
case 2: type = Type::BandPass; break;
|
||||||
|
case 5: type = Type::Notch; break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
case 6: type = Type::HighPass; break;
|
||||||
|
|
||||||
|
case 7: type = Type::AllPass; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_.configure(
|
||||||
|
type,
|
||||||
|
1'000'000.0f,
|
||||||
|
30.0f + float(filter_cutoff_.get()) * 5.8f,
|
||||||
|
0.707f + float(filter_resonance_.get()) * 0.2862f,
|
||||||
|
6.0f,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filter cutoff: the data sheet provides that it is linear, and "approximate Cutoff Frequency
|
||||||
|
// ranges between 30Hz and 12KHz [with recommended externally-supplied capacitors]."
|
||||||
|
//
|
||||||
|
// It's an 11-bit number, so the above is "approximate"ly right.
|
||||||
|
|
||||||
|
// Resonance: a complete from-thin-air guess. The data sheet says merely:
|
||||||
|
//
|
||||||
|
// "There are 16 Resonance settings ranging from about 0.707 (Critical Damping) for a count of 0
|
||||||
|
// to a maximum for a count of 15"
|
||||||
|
//
|
||||||
|
// i.e. no information is given on the maximum. I've taken it to be 5-ish per commentary on more general sites
|
||||||
|
// that 5 is a typical ceiling for the resonance factor.
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SID::read(const Numeric::SizedInt<5> address) {
|
||||||
|
switch(address.get()) {
|
||||||
|
default: return last_write_;
|
||||||
|
|
||||||
|
case 0x19: return potentometers_[0];
|
||||||
|
case 0x1a: return potentometers_[1];
|
||||||
|
|
||||||
|
case 0x1b:
|
||||||
|
case 0x1c:
|
||||||
|
// Ensure all channels are entirely up to date.
|
||||||
|
audio_queue_.spin_flush();
|
||||||
|
return (address == 0x1c) ? voices_[2].adsr.envelope : uint8_t(voices_[2].output(voices_[1]) >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Oscillators.
|
||||||
|
|
||||||
|
void Voice::Oscillator::reset_phase() {
|
||||||
|
phase = PhaseReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Voice::Oscillator::did_raise_b23() const {
|
||||||
|
return previous_phase > phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Voice::Oscillator::did_raise_b19() const {
|
||||||
|
static constexpr int NoiseBit = 1 << (19 + 8);
|
||||||
|
return (previous_phase ^ phase) & phase & NoiseBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Voice::Oscillator::sawtooth_output() const {
|
||||||
|
return (phase >> 20) ^ 0x800;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Noise generator.
|
||||||
|
|
||||||
|
uint16_t Voice::NoiseGenerator::output() const {
|
||||||
|
// Uses bits: 20, 18, 14, 11, 9, 5, 2 and 0, plus four more zero bits.
|
||||||
|
const uint16_t output =
|
||||||
|
((noise >> 9) & 0b1000'0000'0000) | // b20 -> b11
|
||||||
|
((noise >> 8) & 0b0100'0000'0000) | // b18 -> b10
|
||||||
|
((noise >> 5) & 0b0010'0000'0000) | // b14 -> b9
|
||||||
|
((noise >> 3) & 0b0001'0000'0000) | // b11 -> b8
|
||||||
|
((noise >> 2) & 0b0000'1000'0000) | // b9 -> b7
|
||||||
|
((noise << 1) & 0b0000'0100'0000) | // b5 -> b6
|
||||||
|
((noise << 3) & 0b0000'0010'0000) | // b2 -> b5
|
||||||
|
((noise << 4) & 0b0000'0001'0000); // b0 -> b4
|
||||||
|
|
||||||
|
assert(output <= Voice::MaxWaveformValue);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::NoiseGenerator::update(const bool test) {
|
||||||
|
noise =
|
||||||
|
(noise << 1) |
|
||||||
|
(((noise >> 17) ^ ((noise >> 22) | test)) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ADSR.
|
||||||
|
|
||||||
|
void Voice::ADSR::set_phase(const Phase new_phase) {
|
||||||
|
static constexpr uint16_t rate_prescaler[] = {
|
||||||
|
9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251
|
||||||
|
};
|
||||||
|
static_assert(sizeof(rate_prescaler) / sizeof(*rate_prescaler) == 16);
|
||||||
|
|
||||||
|
phase = new_phase;
|
||||||
|
switch(phase) {
|
||||||
|
case Phase::Attack: rate_counter_target = rate_prescaler[attack.get()]; break;
|
||||||
|
case Phase::DecayAndHold: rate_counter_target = rate_prescaler[decay.get()]; break;
|
||||||
|
case Phase::Release: rate_counter_target = rate_prescaler[release.get()]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Voices.
|
||||||
|
|
||||||
|
void Voice::set_control(const uint8_t new_control) {
|
||||||
|
const bool old_gate = gate();
|
||||||
|
control = new_control;
|
||||||
|
if(gate() && !old_gate) {
|
||||||
|
adsr.set_phase(ADSR::Phase::Attack);
|
||||||
|
} else if(!gate() && old_gate) {
|
||||||
|
adsr.set_phase(ADSR::Phase::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Voice::noise() const { return control.bit<7>(); }
|
||||||
|
bool Voice::pulse() const { return control.bit<6>(); }
|
||||||
|
bool Voice::sawtooth() const { return control.bit<5>(); }
|
||||||
|
bool Voice::triangle() const { return control.bit<4>(); }
|
||||||
|
bool Voice::test() const { return control.bit<3>(); }
|
||||||
|
bool Voice::ring_mod() const { return control.bit<2>(); }
|
||||||
|
bool Voice::sync() const { return control.bit<1>(); }
|
||||||
|
bool Voice::gate() const { return control.bit<0>(); }
|
||||||
|
|
||||||
|
void Voice::update() {
|
||||||
|
// Oscillator.
|
||||||
|
oscillator.previous_phase = oscillator.phase;
|
||||||
|
if(test()) {
|
||||||
|
oscillator.phase = 0;
|
||||||
|
} else {
|
||||||
|
oscillator.phase += oscillator.pitch;
|
||||||
|
|
||||||
|
if(oscillator.did_raise_b19()) {
|
||||||
|
noise_generator.update(test());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADSR.
|
||||||
|
|
||||||
|
// First prescalar, which is a function of the programmer-set rate.
|
||||||
|
++ adsr.rate_counter;
|
||||||
|
if(adsr.rate_counter == adsr.rate_counter_target) {
|
||||||
|
adsr.rate_counter = 0;
|
||||||
|
|
||||||
|
// Second prescalar, which approximates an exponential.
|
||||||
|
static constexpr uint8_t exponential_prescaler[] = {
|
||||||
|
1, // 0
|
||||||
|
30, 30, 30, 30, 30, 30, // 1–6
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, // 7–14
|
||||||
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 15–26
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 27–54
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 55–94
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1,
|
||||||
|
};
|
||||||
|
static_assert(sizeof(exponential_prescaler) == 256);
|
||||||
|
static_assert(exponential_prescaler[0] == 1);
|
||||||
|
static_assert(exponential_prescaler[1] == 30);
|
||||||
|
static_assert(exponential_prescaler[6] == 30);
|
||||||
|
static_assert(exponential_prescaler[7] == 16);
|
||||||
|
static_assert(exponential_prescaler[14] == 16);
|
||||||
|
static_assert(exponential_prescaler[15] == 8);
|
||||||
|
static_assert(exponential_prescaler[26] == 8);
|
||||||
|
static_assert(exponential_prescaler[27] == 4);
|
||||||
|
static_assert(exponential_prescaler[54] == 4);
|
||||||
|
static_assert(exponential_prescaler[55] == 2);
|
||||||
|
static_assert(exponential_prescaler[94] == 2);
|
||||||
|
static_assert(exponential_prescaler[95] == 1);
|
||||||
|
static_assert(exponential_prescaler[255] == 1);
|
||||||
|
|
||||||
|
if(adsr.phase == ADSR::Phase::Attack) {
|
||||||
|
++adsr.envelope;
|
||||||
|
// TODO: what really resets the exponential counter? If anything?
|
||||||
|
adsr.exponential_counter = 0;
|
||||||
|
|
||||||
|
if(adsr.envelope == 0xff) {
|
||||||
|
adsr.set_phase(ADSR::Phase::DecayAndHold);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
++adsr.exponential_counter;
|
||||||
|
if(adsr.exponential_counter == exponential_prescaler[adsr.envelope]) {
|
||||||
|
adsr.exponential_counter = 0;
|
||||||
|
|
||||||
|
if(adsr.envelope && (adsr.envelope != adsr.sustain || adsr.phase != ADSR::Phase::DecayAndHold)) {
|
||||||
|
--adsr.envelope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::synchronise(const Voice &prior) {
|
||||||
|
// Only oscillator work to do here.
|
||||||
|
if(
|
||||||
|
sync() &&
|
||||||
|
prior.oscillator.did_raise_b23()
|
||||||
|
) {
|
||||||
|
oscillator.phase = Oscillator::PhaseReload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Voice::pulse_output() const {
|
||||||
|
return (
|
||||||
|
(oscillator.phase ^ 0x8000'0000) < oscillator.pulse_width
|
||||||
|
) ? 0 : MaxWaveformValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Voice::triangle_output(const Voice &prior) const {
|
||||||
|
const uint16_t sawtooth = oscillator.sawtooth_output();
|
||||||
|
const uint16_t xor_mask1 = sawtooth;
|
||||||
|
const uint16_t xor_mask2 = ring_mod() ? prior.sawtooth() : 0;
|
||||||
|
const uint16_t xor_mask = ((xor_mask1 ^ xor_mask2) & 0x800) ? 0xfff : 0x000;
|
||||||
|
return ((sawtooth << 1) ^ xor_mask) & 0xfff;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Voice::output(const Voice &prior) const {
|
||||||
|
// TODO: true composite waves.
|
||||||
|
//
|
||||||
|
// My current understanding on this: if multiple waveforms are enabled, the pull to zero beats the
|
||||||
|
// pull to one on any line where the two compete. But the twist is that the lines are not necessarily
|
||||||
|
// one per bit since they lead to a common ground. Ummm, I think.
|
||||||
|
//
|
||||||
|
// Anyway, first pass: logical AND. It's not right. It will temporarily do.
|
||||||
|
|
||||||
|
uint16_t output = MaxWaveformValue;
|
||||||
|
|
||||||
|
if(pulse()) output &= pulse_output();
|
||||||
|
if(sawtooth()) output &= oscillator.sawtooth_output();
|
||||||
|
if(triangle()) output &= triangle_output(prior);
|
||||||
|
if(noise()) output &= noise_generator.output();
|
||||||
|
|
||||||
|
return (output * adsr.envelope) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Wave generation
|
||||||
|
|
||||||
|
void SID::set_sample_volume_range(const std::int16_t range) {
|
||||||
|
range_ = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SID::is_zero_level() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Outputs::Speaker::Action action>
|
||||||
|
void SID::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
|
||||||
|
for(std::size_t c = 0; c < number_of_samples; c++) {
|
||||||
|
// Advance phase.
|
||||||
|
voices_[0].update();
|
||||||
|
voices_[1].update();
|
||||||
|
voices_[2].update();
|
||||||
|
|
||||||
|
// Apply hard synchronisations.
|
||||||
|
voices_[0].synchronise(voices_[2]);
|
||||||
|
voices_[1].synchronise(voices_[0]);
|
||||||
|
voices_[2].synchronise(voices_[1]);
|
||||||
|
|
||||||
|
// Construct filtered and unfiltered output.
|
||||||
|
const uint16_t outputs[3] = {
|
||||||
|
voices_[0].output(voices_[2]),
|
||||||
|
voices_[1].output(voices_[0]),
|
||||||
|
voices_[2].output(voices_[1]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint16_t direct_sample =
|
||||||
|
(filter_channels_.bit<0>() ? 0 : outputs[0]) +
|
||||||
|
(filter_channels_.bit<1>() ? 0 : outputs[1]) +
|
||||||
|
(filter_channels_.bit<2>() || voice3_disable_ ? 0 : outputs[2]);
|
||||||
|
|
||||||
|
const int16_t filtered_sample =
|
||||||
|
filter_.apply(
|
||||||
|
(filter_channels_.bit<0>() ? outputs[0] : 0) +
|
||||||
|
(filter_channels_.bit<1>() ? outputs[1] : 0) +
|
||||||
|
(filter_channels_.bit<2>() ? outputs[2] : 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sum, apply volume and output.
|
||||||
|
const auto sample = output_filter_.apply(int16_t(
|
||||||
|
(
|
||||||
|
volume_ * (
|
||||||
|
direct_sample +
|
||||||
|
filtered_sample
|
||||||
|
- 227 // DC offset.
|
||||||
|
)
|
||||||
|
- 88732
|
||||||
|
) / 3
|
||||||
|
));
|
||||||
|
// Maximum range of above: 15 * (4095 * 3 - 227) = [-3405, 180870]
|
||||||
|
// So subtracting 88732 will move to the centre of the range, and 3 is the smallest
|
||||||
|
// integer that avoids clipping.
|
||||||
|
|
||||||
|
Outputs::Speaker::apply<action>(
|
||||||
|
target[c],
|
||||||
|
Outputs::Speaker::MonoSample((sample * range_) >> 16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template void SID::apply_samples<Outputs::Speaker::Action::Mix>(
|
||||||
|
std::size_t, Outputs::Speaker::MonoSample *);
|
||||||
|
template void SID::apply_samples<Outputs::Speaker::Action::Store>(
|
||||||
|
std::size_t, Outputs::Speaker::MonoSample *);
|
||||||
|
template void SID::apply_samples<Outputs::Speaker::Action::Ignore>(
|
||||||
|
std::size_t, Outputs::Speaker::MonoSample *);
|
||||||
129
Components/SID/SID.hpp
Normal file
129
Components/SID/SID.hpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
//
|
||||||
|
// SID.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Numeric/SizedInt.hpp"
|
||||||
|
|
||||||
|
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||||
|
#include "SignalProcessing/BiquadFilter.hpp"
|
||||||
|
|
||||||
|
namespace MOS::SID {
|
||||||
|
|
||||||
|
struct Voice {
|
||||||
|
static constexpr uint16_t MaxWaveformValue = (1 << 12) - 1;
|
||||||
|
|
||||||
|
struct Oscillator {
|
||||||
|
// Programmer inputs.
|
||||||
|
uint32_t pitch = 0;
|
||||||
|
uint32_t pulse_width = 0;
|
||||||
|
|
||||||
|
// State.
|
||||||
|
//
|
||||||
|
// A real SID has a 24-bit phase counter and does various things when the top bit transitions from 0 to 1.
|
||||||
|
// This implementation maintains a 32-bit phase counter in which the low byte is unused and the top bit
|
||||||
|
// is inverted. That saves the cost of any masking and makes the 0 -> 1 transition test actually a 1 -> 0
|
||||||
|
// transition test, which can be phrased simply as after < before. Sadly overflow of signed integers is
|
||||||
|
// still undefined behaviour in C++ at the time of writing.
|
||||||
|
static constexpr uint32_t PhaseReload = 0x8000'0000;
|
||||||
|
uint32_t phase = PhaseReload;
|
||||||
|
uint32_t previous_phase = PhaseReload;
|
||||||
|
|
||||||
|
void reset_phase();
|
||||||
|
bool did_raise_b23() const;
|
||||||
|
bool did_raise_b19() const;
|
||||||
|
uint16_t sawtooth_output() const;
|
||||||
|
} oscillator;
|
||||||
|
struct ADSR {
|
||||||
|
// Programmer inputs.
|
||||||
|
Numeric::SizedInt<4> attack;
|
||||||
|
Numeric::SizedInt<4> decay;
|
||||||
|
Numeric::SizedInt<4> release;
|
||||||
|
|
||||||
|
Numeric::SizedInt<8> sustain;
|
||||||
|
|
||||||
|
// State.
|
||||||
|
enum class Phase {
|
||||||
|
Attack,
|
||||||
|
DecayAndHold,
|
||||||
|
Release,
|
||||||
|
} phase = Phase::Release;
|
||||||
|
Numeric::SizedInt<15> rate_counter;
|
||||||
|
Numeric::SizedInt<15> rate_counter_target;
|
||||||
|
|
||||||
|
uint8_t exponential_counter;
|
||||||
|
uint8_t envelope;
|
||||||
|
|
||||||
|
void set_phase(const Phase);
|
||||||
|
} adsr;
|
||||||
|
struct NoiseGenerator {
|
||||||
|
static constexpr uint32_t NoiseReload = 0x7'ffff;
|
||||||
|
uint32_t noise = NoiseReload;
|
||||||
|
|
||||||
|
uint16_t output() const;
|
||||||
|
void update(const bool test);
|
||||||
|
} noise_generator;
|
||||||
|
|
||||||
|
void set_control(const uint8_t);
|
||||||
|
void update();
|
||||||
|
void synchronise(const Voice &prior);
|
||||||
|
uint16_t output(const Voice &prior) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Numeric::SizedInt<8> control;
|
||||||
|
bool noise() const;
|
||||||
|
bool pulse() const;
|
||||||
|
bool sawtooth() const;
|
||||||
|
bool triangle() const;
|
||||||
|
bool test() const;
|
||||||
|
bool ring_mod() const;
|
||||||
|
bool sync() const;
|
||||||
|
bool gate() const;
|
||||||
|
|
||||||
|
uint16_t pulse_output() const;
|
||||||
|
uint16_t triangle_output(const Voice &prior) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SID: public Outputs::Speaker::BufferSource<SID, false> {
|
||||||
|
public:
|
||||||
|
SID(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||||
|
|
||||||
|
void write(Numeric::SizedInt<5> address, uint8_t value);
|
||||||
|
uint8_t read(Numeric::SizedInt<5> address);
|
||||||
|
void set_potentometer_input(int index, uint8_t value);
|
||||||
|
|
||||||
|
// Outputs::Speaker::BufferSource.
|
||||||
|
template <Outputs::Speaker::Action action>
|
||||||
|
void apply_samples(std::size_t, Outputs::Speaker::MonoSample *);
|
||||||
|
bool is_zero_level() const;
|
||||||
|
void set_sample_volume_range(std::int16_t);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||||
|
Voice voices_[3];
|
||||||
|
|
||||||
|
uint8_t last_write_;
|
||||||
|
|
||||||
|
int16_t range_ = 0;
|
||||||
|
uint8_t volume_ = 0;
|
||||||
|
|
||||||
|
SignalProcessing::BiquadFilter filter_;
|
||||||
|
Numeric::SizedInt<11> filter_cutoff_;
|
||||||
|
Numeric::SizedInt<4> filter_resonance_;
|
||||||
|
Numeric::SizedInt<4> filter_channels_;
|
||||||
|
Numeric::SizedInt<3> filter_mode_;
|
||||||
|
bool voice3_disable_;
|
||||||
|
void update_filter();
|
||||||
|
|
||||||
|
SignalProcessing::BiquadFilter output_filter_;
|
||||||
|
|
||||||
|
uint8_t potentometers_[2]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -97,6 +97,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @returns The number of items currently enqueued.
|
||||||
|
size_t size() {
|
||||||
|
const std::lock_guard guard(condition_mutex_);
|
||||||
|
return actions_.size();
|
||||||
|
}
|
||||||
|
|
||||||
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
|
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
|
||||||
void perform() {
|
void perform() {
|
||||||
static_assert(!perform_automatically);
|
static_assert(!perform_automatically);
|
||||||
@@ -131,7 +137,7 @@ public:
|
|||||||
|
|
||||||
/// Schedules any remaining unscheduled work, then blocks synchronously
|
/// Schedules any remaining unscheduled work, then blocks synchronously
|
||||||
/// until all scheduled work has been performed.
|
/// until all scheduled work has been performed.
|
||||||
void flush() {
|
void lock_flush() {
|
||||||
std::mutex flush_mutex;
|
std::mutex flush_mutex;
|
||||||
std::condition_variable flush_condition;
|
std::condition_variable flush_condition;
|
||||||
bool has_run = false;
|
bool has_run = false;
|
||||||
@@ -150,6 +156,23 @@ public:
|
|||||||
flush_condition.wait(lock, [&has_run] { return has_run; });
|
flush_condition.wait(lock, [&has_run] { return has_run; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedules any remaining unscheduled work, then spins
|
||||||
|
/// until all scheduled work has been performed, placing a memory barrier
|
||||||
|
/// in between.
|
||||||
|
void spin_flush() {
|
||||||
|
std::atomic<bool> has_run = false;
|
||||||
|
|
||||||
|
enqueue([&has_run] () {
|
||||||
|
has_run.store(true, std::memory_order::release);
|
||||||
|
});
|
||||||
|
|
||||||
|
if constexpr (!perform_automatically) {
|
||||||
|
perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!has_run.load(std::memory_order::acquire));
|
||||||
|
}
|
||||||
|
|
||||||
~AsyncTaskQueue() {
|
~AsyncTaskQueue() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "Components/6522/6522.hpp"
|
#include "Components/6522/6522.hpp"
|
||||||
#include "Components/6845/CRTC6845.hpp"
|
#include "Components/6845/CRTC6845.hpp"
|
||||||
#include "Components/6850/6850.hpp"
|
#include "Components/6850/6850.hpp"
|
||||||
|
#include "Components/SID/SID.hpp"
|
||||||
#include "Components/SAA5050/SAA5050.hpp"
|
#include "Components/SAA5050/SAA5050.hpp"
|
||||||
#include "Components/SN76489/SN76489.hpp"
|
#include "Components/SN76489/SN76489.hpp"
|
||||||
#include "Components/uPD7002/uPD7002.hpp"
|
#include "Components/uPD7002/uPD7002.hpp"
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
#include "Outputs/Log.hpp"
|
#include "Outputs/Log.hpp"
|
||||||
|
|
||||||
#include "Outputs/CRT/CRT.hpp"
|
#include "Outputs/CRT/CRT.hpp"
|
||||||
|
#include "Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
@@ -107,22 +109,48 @@ private:
|
|||||||
/*!
|
/*!
|
||||||
Combines an SN76489 with an appropriate asynchronous queue and filtering speaker.
|
Combines an SN76489 with an appropriate asynchronous queue and filtering speaker.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: generalise the below and clean up across the project.
|
||||||
|
template <bool has_beebsid>
|
||||||
struct Audio {
|
struct Audio {
|
||||||
|
private:
|
||||||
|
using CompoundSource = Outputs::Speaker::CompoundSource<TI::SN76489, MOS::SID::SID>;
|
||||||
|
using Source = std::conditional_t<has_beebsid, CompoundSource, TI::SN76489>;
|
||||||
|
using Speaker = Outputs::Speaker::PullLowpass<Source>;
|
||||||
|
|
||||||
|
Source &speaker_source() {
|
||||||
|
if constexpr (has_beebsid) {
|
||||||
|
return compound_;
|
||||||
|
} else {
|
||||||
|
return sn76489_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
Audio() :
|
Audio() :
|
||||||
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, 2),
|
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, 4),
|
||||||
speaker_(sn76489_)
|
sid_(audio_queue_),
|
||||||
|
compound_(sn76489_, sid_),
|
||||||
|
speaker_(speaker_source())
|
||||||
{
|
{
|
||||||
// Combined with the additional divider specified above, implies this chip is clocked at 4Mhz.
|
// Combined with the additional divider specified above, implies the SN76489 is clocked at 4Mhz.
|
||||||
speaker_.set_input_rate(2'000'000.0f);
|
speaker_.set_input_rate(1'000'000.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Audio() {
|
~Audio() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
TI::SN76489 *operator ->() {
|
template <typename TargetT>
|
||||||
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
|
TargetT &get() {
|
||||||
return &sn76489_;
|
post_time();
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<TargetT, TI::SN76489>) {
|
||||||
|
return sn76489_;
|
||||||
|
}
|
||||||
|
if constexpr (std::is_same_v<TargetT, MOS::SID::SID>) {
|
||||||
|
return sid_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator +=(const Cycles duration) {
|
void operator +=(const Cycles duration) {
|
||||||
@@ -130,7 +158,7 @@ struct Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void flush() {
|
void flush() {
|
||||||
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
|
post_time();
|
||||||
audio_queue_.perform();
|
audio_queue_.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +166,20 @@ struct Audio {
|
|||||||
return &speaker_;
|
return &speaker_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t queue_size() {
|
||||||
|
return audio_queue_.size();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void post_time() {
|
||||||
|
speaker_.run_for(audio_queue_, time_since_update_.divide(Cycles(2)));
|
||||||
|
}
|
||||||
|
|
||||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||||
TI::SN76489 sn76489_;
|
TI::SN76489 sn76489_;
|
||||||
Outputs::Speaker::PullLowpass<TI::SN76489> speaker_;
|
MOS::SID::SID sid_;
|
||||||
|
CompoundSource compound_;
|
||||||
|
Outputs::Speaker::PullLowpass<Source> speaker_;
|
||||||
Cycles time_since_update_;
|
Cycles time_since_update_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -174,19 +212,20 @@ protected:
|
|||||||
/*!
|
/*!
|
||||||
Models the system VIA, which connects to the SN76489 and the keyboard.
|
Models the system VIA, which connects to the SN76489 and the keyboard.
|
||||||
*/
|
*/
|
||||||
struct SystemVIAPortHandler;
|
struct VSyncReceiver {
|
||||||
using SystemVIA = MOS::MOS6522::MOS6522<SystemVIAPortHandler>;
|
virtual void set_vsync(bool) = 0;
|
||||||
|
};
|
||||||
struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
struct SystemVIADelegate {
|
||||||
struct Delegate {
|
virtual void strobe_lightpen() = 0;
|
||||||
virtual void strobe_lightpen() = 0;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
template <typename AudioT>
|
||||||
|
struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler, public VSyncReceiver {
|
||||||
SystemVIAPortHandler(
|
SystemVIAPortHandler(
|
||||||
Audio &audio,
|
AudioT &audio,
|
||||||
VideoBaseAddress &video_base,
|
VideoBaseAddress &video_base,
|
||||||
SystemVIA &via,
|
MOS::MOS6522::MOS6522<SystemVIAPortHandler<AudioT>> &via,
|
||||||
Delegate &delegate,
|
SystemVIADelegate &delegate,
|
||||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks,
|
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks,
|
||||||
const bool run_disk
|
const bool run_disk
|
||||||
) :
|
) :
|
||||||
@@ -221,7 +260,7 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
|||||||
|
|
||||||
// Check for a strobe on the audio output.
|
// Check for a strobe on the audio output.
|
||||||
if((old_latch^latch_) & old_latch & LatchFlags::WriteToSN76489) {
|
if((old_latch^latch_) & old_latch & LatchFlags::WriteToSN76489) {
|
||||||
audio_->write(port_a_output_);
|
audio_.template get<TI::SN76489>().write(port_a_output_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass on the video wraparound/base.
|
// Pass on the video wraparound/base.
|
||||||
@@ -335,10 +374,10 @@ private:
|
|||||||
uint8_t port_a_output_ = 0;
|
uint8_t port_a_output_ = 0;
|
||||||
bool previous_cb2_ = false;
|
bool previous_cb2_ = false;
|
||||||
|
|
||||||
Audio &audio_;
|
AudioT &audio_;
|
||||||
VideoBaseAddress &video_base_;
|
VideoBaseAddress &video_base_;
|
||||||
|
|
||||||
SystemVIA &via_;
|
MOS::MOS6522::MOS6522<SystemVIAPortHandler<AudioT>> &via_;
|
||||||
|
|
||||||
// MARK: - Keyboard state and helpers.
|
// MARK: - Keyboard state and helpers.
|
||||||
|
|
||||||
@@ -369,7 +408,7 @@ private:
|
|||||||
}
|
}
|
||||||
} ()).to_ulong() & 0xfe; // Discard the first row.
|
} ()).to_ulong() & 0xfe; // Discard the first row.
|
||||||
|
|
||||||
via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state);
|
via_.template set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline const std::string caps_led = "CAPS";
|
static inline const std::string caps_led = "CAPS";
|
||||||
@@ -379,7 +418,11 @@ private:
|
|||||||
Activity::Observer *activity_observer_ = nullptr;
|
Activity::Observer *activity_observer_ = nullptr;
|
||||||
|
|
||||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks_;
|
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks_;
|
||||||
Delegate &delegate_;
|
SystemVIADelegate &delegate_;
|
||||||
|
|
||||||
|
void set_vsync(const bool vsync) override {
|
||||||
|
via_.template set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(vsync);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: - CRTC output.
|
// MARK: - CRTC output.
|
||||||
@@ -389,10 +432,10 @@ private:
|
|||||||
*/
|
*/
|
||||||
class CRTCBusHandler: public VideoBaseAddress {
|
class CRTCBusHandler: public VideoBaseAddress {
|
||||||
public:
|
public:
|
||||||
CRTCBusHandler(const uint8_t *const ram, SystemVIA &system_via) :
|
CRTCBusHandler(const uint8_t *const ram, VSyncReceiver &vsync_receiver) :
|
||||||
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
|
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
|
||||||
ram_(ram),
|
ram_(ram),
|
||||||
system_via_(system_via)
|
vsync_receiver_(vsync_receiver)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void set_dynamic_framing(const bool enable) {
|
void set_dynamic_framing(const bool enable) {
|
||||||
@@ -440,7 +483,10 @@ public:
|
|||||||
static_assert(!(PixelAllocationUnit % 16));
|
static_assert(!(PixelAllocationUnit % 16));
|
||||||
static_assert(!(PixelAllocationUnit % 12));
|
static_assert(!(PixelAllocationUnit % 12));
|
||||||
|
|
||||||
system_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(state.vsync);
|
if(state.vsync != vsync_) {
|
||||||
|
vsync_receiver_.set_vsync(state.vsync);
|
||||||
|
vsync_ = state.vsync;
|
||||||
|
}
|
||||||
|
|
||||||
// Count cycles since horizontal sync to insert a colour burst.
|
// Count cycles since horizontal sync to insert a colour burst.
|
||||||
// TODO: this is copy/pasted from the CPC. How does the BBC do it?
|
// TODO: this is copy/pasted from the CPC. How does the BBC do it?
|
||||||
@@ -675,7 +721,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *const ram_ = nullptr;
|
const uint8_t *const ram_ = nullptr;
|
||||||
SystemVIA &system_via_;
|
VSyncReceiver &vsync_receiver_;
|
||||||
|
bool vsync_ = false;
|
||||||
|
|
||||||
Mullard::SAA5050Serialiser saa5050_serialiser_;
|
Mullard::SAA5050Serialiser saa5050_serialiser_;
|
||||||
};
|
};
|
||||||
@@ -705,7 +752,7 @@ struct Tube<HostT, TubeProcessor::None> {
|
|||||||
|
|
||||||
// MARK: - ConcreteMachine.
|
// MARK: - ConcreteMachine.
|
||||||
|
|
||||||
template <TubeProcessor tube_processor, bool has_1770>
|
template <TubeProcessor tube_processor, bool has_1770, bool has_beebsid>
|
||||||
class ConcreteMachine:
|
class ConcreteMachine:
|
||||||
public Activity::Source,
|
public Activity::Source,
|
||||||
public Configurable::Device,
|
public Configurable::Device,
|
||||||
@@ -718,7 +765,7 @@ class ConcreteMachine:
|
|||||||
public MachineTypes::TimedMachine,
|
public MachineTypes::TimedMachine,
|
||||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
public NEC::uPD7002::Delegate,
|
public NEC::uPD7002::Delegate,
|
||||||
public SystemVIAPortHandler::Delegate,
|
public SystemVIADelegate,
|
||||||
public Utility::TypeRecipient<CharacterMapper>,
|
public Utility::TypeRecipient<CharacterMapper>,
|
||||||
public WD::WD1770::Delegate
|
public WD::WD1770::Delegate
|
||||||
{
|
{
|
||||||
@@ -731,7 +778,7 @@ public:
|
|||||||
system_via_port_handler_(audio_, crtc_bus_handler_, system_via_, *this, joysticks_, target.should_shift_restart),
|
system_via_port_handler_(audio_, crtc_bus_handler_, system_via_, *this, joysticks_, target.should_shift_restart),
|
||||||
user_via_(user_via_port_handler_),
|
user_via_(user_via_port_handler_),
|
||||||
system_via_(system_via_port_handler_),
|
system_via_(system_via_port_handler_),
|
||||||
crtc_bus_handler_(ram_.data(), system_via_),
|
crtc_bus_handler_(ram_.data(), system_via_port_handler_),
|
||||||
crtc_(crtc_bus_handler_),
|
crtc_(crtc_bus_handler_),
|
||||||
acia_(HalfCycles(2'000'000)), // TODO: look up real ACIA clock rate.
|
acia_(HalfCycles(2'000'000)), // TODO: look up real ACIA clock rate.
|
||||||
adc_(HalfCycles(2'000'000)),
|
adc_(HalfCycles(2'000'000)),
|
||||||
@@ -990,6 +1037,12 @@ public:
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else if(has_beebsid && address >= 0xfc20 && address < 0xfc40) {
|
||||||
|
if constexpr (is_read(operation)) {
|
||||||
|
value = audio_.template get<MOS::SID::SID>().read(+address);
|
||||||
|
} else {
|
||||||
|
audio_.template get<MOS::SID::SID>().write(+address, value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger::error()
|
Logger::error()
|
||||||
.append("Unhandled IO %s at %04x", is_read(operation) ? "read" : "write", address)
|
.append("Unhandled IO %s at %04x", is_read(operation) ? "read" : "write", address)
|
||||||
@@ -1045,8 +1098,7 @@ private:
|
|||||||
return crtc_bus_handler_.get_scaled_scan_status();
|
return crtc_bus_handler_.get_scaled_scan_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - SystemVIADelegate.
|
||||||
// MARK: - SystemVIAPortHandler::Delegate.
|
|
||||||
void strobe_lightpen() override {
|
void strobe_lightpen() override {
|
||||||
crtc_.trigger_light_pen();
|
crtc_.trigger_light_pen();
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1181,10 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void flush_output(const int outputs) final {
|
void flush_output(const int outputs) final {
|
||||||
if(outputs & Output::Audio) {
|
// TODO: I think there's an infrastructural bug here on macOS; if the audio output has stalled out,
|
||||||
|
// the outer wrapper won't ask for an audio flush, which means the queue will never try to start,
|
||||||
|
// and the audio queue will just fill indefinitely. Could this be the mythical 'leak'?
|
||||||
|
if(outputs & Output::Audio || audio_.queue_size() > 200) {
|
||||||
audio_.flush();
|
audio_.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,7 +1196,7 @@ private:
|
|||||||
|
|
||||||
// MARK: - uPD7002::Delegate.
|
// MARK: - uPD7002::Delegate.
|
||||||
void did_change_interrupt_status(NEC::uPD7002 &) override {
|
void did_change_interrupt_status(NEC::uPD7002 &) override {
|
||||||
system_via_.set_control_line_input<MOS::MOS6522::Port::B, MOS::MOS6522::Line::One>(adc_.interrupt());
|
system_via_.template set_control_line_input<MOS::MOS6522::Port::B, MOS::MOS6522::Line::One>(adc_.interrupt());
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MediaTarget.
|
// MARK: - MediaTarget.
|
||||||
@@ -1200,9 +1255,9 @@ private:
|
|||||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||||
|
|
||||||
UserVIAPortHandler user_via_port_handler_;
|
UserVIAPortHandler user_via_port_handler_;
|
||||||
SystemVIAPortHandler system_via_port_handler_;
|
SystemVIAPortHandler<Audio<has_beebsid>> system_via_port_handler_;
|
||||||
UserVIA user_via_;
|
UserVIA user_via_;
|
||||||
SystemVIA system_via_;
|
MOS::MOS6522::MOS6522<SystemVIAPortHandler<Audio<has_beebsid>>> system_via_;
|
||||||
|
|
||||||
void update_irq_line() {
|
void update_irq_line() {
|
||||||
const bool tube_irq =
|
const bool tube_irq =
|
||||||
@@ -1221,7 +1276,7 @@ private:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Audio audio_;
|
Audio<has_beebsid> audio_;
|
||||||
|
|
||||||
CRTCBusHandler crtc_bus_handler_;
|
CRTCBusHandler crtc_bus_handler_;
|
||||||
CRTC crtc_;
|
CRTC crtc_;
|
||||||
@@ -1275,12 +1330,21 @@ using namespace BBCMicro;
|
|||||||
namespace {
|
namespace {
|
||||||
using Target = Analyser::Static::Acorn::BBCMicroTarget;
|
using Target = Analyser::Static::Acorn::BBCMicroTarget;
|
||||||
|
|
||||||
|
template <Target::TubeProcessor processor, bool has_1770>
|
||||||
|
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||||
|
if(target.has_beebsid) {
|
||||||
|
return std::make_unique<BBCMicro::ConcreteMachine<processor, has_1770, true>>(target, rom_fetcher);
|
||||||
|
} else {
|
||||||
|
return std::make_unique<BBCMicro::ConcreteMachine<processor, has_1770, false>>(target, rom_fetcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <Target::TubeProcessor processor>
|
template <Target::TubeProcessor processor>
|
||||||
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||||
if(target.has_1770dfs || target.has_adfs) {
|
if(target.has_1770dfs || target.has_adfs) {
|
||||||
return std::make_unique<BBCMicro::ConcreteMachine<processor, true>>(target, rom_fetcher);
|
return machine<processor, true>(target, rom_fetcher);
|
||||||
} else {
|
} else {
|
||||||
return std::make_unique<BBCMicro::ConcreteMachine<processor, false>>(target, rom_fetcher);
|
return machine<processor, false>(target, rom_fetcher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool isPressed) final {
|
void set_key_state(uint16_t key, bool isPressed) final {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~AYDeferrer() {
|
~AYDeferrer() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for(const Cycles cycles) override {
|
void run_for(const Cycles cycles) override {
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_.queue.flush();
|
audio_.queue.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public:
|
|||||||
speaker_(tia_sound_) {}
|
speaker_(tia_sound_) {}
|
||||||
|
|
||||||
virtual ~Bus() {
|
virtual ~Bus() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void run_for(const Cycles cycles) = 0;
|
virtual void run_for(const Cycles cycles) = 0;
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: CRTMachine::Machine
|
// MARK: CRTMachine::Machine
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
using namespace Commodore::C1540;
|
using namespace Commodore::C1540;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
ROM::Name rom_name(Personality personality) {
|
|
||||||
|
// MARK: - Construction, including ROM requests.
|
||||||
|
|
||||||
|
ROM::Name rom_name(const Personality personality) {
|
||||||
switch(personality) {
|
switch(personality) {
|
||||||
default:
|
default:
|
||||||
case Personality::C1540: return ROM::Name::Commodore1540;
|
case Personality::C1540: return ROM::Name::Commodore1540;
|
||||||
@@ -26,11 +29,11 @@ ROM::Name rom_name(Personality personality) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ROM::Request Machine::rom_request(Personality personality) {
|
ROM::Request Machine::rom_request(const Personality personality) {
|
||||||
return ROM::Request(rom_name(personality));
|
return ROM::Request(rom_name(personality));
|
||||||
}
|
}
|
||||||
|
|
||||||
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) :
|
||||||
Storage::Disk::Controller(1000000),
|
Storage::Disk::Controller(1000000),
|
||||||
m6502_(*this),
|
m6502_(*this),
|
||||||
drive_VIA_(drive_VIA_port_handler_),
|
drive_VIA_(drive_VIA_port_handler_),
|
||||||
@@ -58,14 +61,13 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
|||||||
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
|
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::Machine(Personality personality, const ROM::Map &roms) :
|
Machine::Machine(const Personality personality, const ROM::Map &roms) :
|
||||||
MachineBase(personality, roms) {}
|
MachineBase(personality, roms) {}
|
||||||
|
|
||||||
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
|
// MARK: - 6502 bus.
|
||||||
Commodore::Serial::attach(serial_port_, serial_bus);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||||
|
Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||||
/*
|
/*
|
||||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||||
|
|
||||||
@@ -75,24 +77,28 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
|||||||
0xc000-0xffff ROM
|
0xc000-0xffff ROM
|
||||||
*/
|
*/
|
||||||
if(address < 0x800) {
|
if(address < 0x800) {
|
||||||
if(is_read(operation))
|
if constexpr (is_read(operation))
|
||||||
*value = ram_[address];
|
value = ram_[address];
|
||||||
else
|
else
|
||||||
ram_[address] = *value;
|
ram_[address] = value;
|
||||||
} else if(address >= 0xc000) {
|
} else if(address >= 0xc000) {
|
||||||
if(is_read(operation)) {
|
if constexpr (is_read(operation)) {
|
||||||
*value = rom_[address & 0x3fff];
|
value = rom_[address & 0x3fff];
|
||||||
}
|
}
|
||||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||||
if(is_read(operation))
|
if constexpr (is_read(operation))
|
||||||
*value = serial_port_VIA_.read(address);
|
value = serial_port_VIA_.read(address);
|
||||||
else
|
else
|
||||||
serial_port_VIA_.write(address, *value);
|
serial_port_VIA_.write(address, value);
|
||||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||||
if(is_read(operation))
|
if constexpr (is_read(operation))
|
||||||
*value = drive_VIA_.read(address);
|
value = drive_VIA_.read(address);
|
||||||
else
|
else
|
||||||
drive_VIA_.write(address, *value);
|
drive_VIA_.write(address, value);
|
||||||
|
} else {
|
||||||
|
if constexpr (is_read(operation)) {
|
||||||
|
value = 0xff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serial_port_VIA_.run_for(Cycles(1));
|
serial_port_VIA_.run_for(Cycles(1));
|
||||||
@@ -101,34 +107,39 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
|||||||
return Cycles(1);
|
return Cycles(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
|
||||||
get_drive().set_disk(disk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::run_for(const Cycles cycles) {
|
void Machine::run_for(const Cycles cycles) {
|
||||||
m6502_.run_for(cycles);
|
m6502_.run_for(cycles);
|
||||||
|
if(get_drive().get_motor_on()) {
|
||||||
const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
|
||||||
get_drive().set_motor_on(drive_motor);
|
|
||||||
if(drive_motor)
|
|
||||||
Storage::Disk::Controller::run_for(cycles);
|
Storage::Disk::Controller::run_for(cycles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MachineBase::set_activity_observer(Activity::Observer *observer) {
|
// MARK: - External attachments.
|
||||||
|
|
||||||
|
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
|
||||||
|
Commodore::Serial::attach(serial_port_, serial_bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||||
|
get_drive().set_disk(disk);
|
||||||
|
drive_VIA_port_handler_.set_is_read_only(disk->is_read_only());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::set_activity_observer(Activity::Observer *const observer) {
|
||||||
drive_VIA_.bus_handler().set_activity_observer(observer);
|
drive_VIA_.bus_handler().set_activity_observer(observer);
|
||||||
get_drive().set_activity_observer(observer, "Drive", false);
|
get_drive().set_activity_observer(observer, "Drive", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 6522 delegate
|
// MARK: - 6522 delegate.
|
||||||
|
|
||||||
void MachineBase::mos6522_did_change_interrupt_status(void *) {
|
void MachineBase::mos6522_did_change_interrupt_status(void *) {
|
||||||
// both VIAs are connected to the IRQ line
|
// both VIAs are connected to the IRQ line
|
||||||
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
m6502_.set<CPU::MOS6502Mk2::Line::IRQ>(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Disk drive
|
// MARK: - Disk drive.
|
||||||
|
|
||||||
void MachineBase::process_input_bit(int value) {
|
void MachineBase::process_input_bit(const int value) {
|
||||||
shift_register_ = (shift_register_ << 1) | value;
|
shift_register_ = (shift_register_ << 1) | value;
|
||||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||||
drive_VIA_port_handler_.set_sync_detected(true);
|
drive_VIA_port_handler_.set_sync_detected(true);
|
||||||
@@ -136,18 +147,40 @@ void MachineBase::process_input_bit(int value) {
|
|||||||
} else {
|
} else {
|
||||||
drive_VIA_port_handler_.set_sync_detected(false);
|
drive_VIA_port_handler_.set_sync_detected(false);
|
||||||
}
|
}
|
||||||
bit_window_offset_++;
|
|
||||||
|
++bit_window_offset_;
|
||||||
if(bit_window_offset_ == 8) {
|
if(bit_window_offset_ == 8) {
|
||||||
drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_));
|
drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_));
|
||||||
bit_window_offset_ = 0;
|
bit_window_offset_ = 0;
|
||||||
if(drive_VIA_port_handler_.get_should_set_overflow()) {
|
if(set_cpu_overflow_) {
|
||||||
m6502_.set_overflow_line(true);
|
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||||
}
|
}
|
||||||
else m6502_.set_overflow_line(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the 1540 does not recognise index holes
|
void MachineBase::is_writing_final_bit() {
|
||||||
|
if(set_cpu_overflow_) {
|
||||||
|
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::process_write_completed() {
|
||||||
|
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||||
|
serialise_shift_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::serialise_shift_output() {
|
||||||
|
auto &drive = get_drive();
|
||||||
|
uint8_t value = port_a_output_;
|
||||||
|
for(int c = 0; c < 8; c++) {
|
||||||
|
drive.write_bit(value & 0x80);
|
||||||
|
value <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 1540 does not recognise index holes.
|
||||||
void MachineBase::process_index_hole() {}
|
void MachineBase::process_index_hole() {}
|
||||||
|
|
||||||
// MARK: - Drive VIA delegate
|
// MARK: - Drive VIA delegate
|
||||||
@@ -160,11 +193,36 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density)
|
|||||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density)));
|
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MachineBase::drive_via_did_set_drive_motor(DriveVIA &, const bool enabled) {
|
||||||
|
get_drive().set_motor_on(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::drive_via_did_set_write_mode(DriveVIA &, const bool enabled) {
|
||||||
|
if(enabled) {
|
||||||
|
begin_writing(false, true);
|
||||||
|
} else {
|
||||||
|
end_writing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::drive_via_set_to_shifter_output(DriveVIA &, const uint8_t value) {
|
||||||
|
port_a_output_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineBase::drive_via_should_set_cpu_overflow(DriveVIA &, const bool overflow) {
|
||||||
|
set_cpu_overflow_ = overflow;
|
||||||
|
if(!overflow) {
|
||||||
|
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - SerialPortVIA
|
// MARK: - SerialPortVIA
|
||||||
|
|
||||||
template <MOS::MOS6522::Port port>
|
template <MOS::MOS6522::Port port>
|
||||||
uint8_t SerialPortVIA::get_port_input() const {
|
uint8_t SerialPortVIA::get_port_input() const {
|
||||||
if(port) return port_b_;
|
if(port) {
|
||||||
|
return port_b_;
|
||||||
|
}
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,13 +242,17 @@ void SerialPortVIA::set_serial_line_state(
|
|||||||
const bool value,
|
const bool value,
|
||||||
MOS::MOS6522::MOS6522<SerialPortVIA> &via
|
MOS::MOS6522::MOS6522<SerialPortVIA> &via
|
||||||
) {
|
) {
|
||||||
|
const auto set = [&](const uint8_t mask) {
|
||||||
|
port_b_ = (port_b_ & ~mask) | (value ? 0x00 : mask);
|
||||||
|
};
|
||||||
|
|
||||||
switch(line) {
|
switch(line) {
|
||||||
default: break;
|
default: break;
|
||||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
case ::Commodore::Serial::Line::Data: set(0x01); break;
|
||||||
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
|
case ::Commodore::Serial::Line::Clock: set(0x04); break;
|
||||||
case ::Commodore::Serial::Line::Attention:
|
case ::Commodore::Serial::Line::Attention:
|
||||||
|
set(0x80);
|
||||||
attention_level_input_ = !value;
|
attention_level_input_ = !value;
|
||||||
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
|
||||||
via.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!value);
|
via.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!value);
|
||||||
update_data_line();
|
update_data_line();
|
||||||
break;
|
break;
|
||||||
@@ -202,14 +264,17 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SerialPortVIA::update_data_line() {
|
void SerialPortVIA::update_data_line() {
|
||||||
// "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"
|
// "ATN (Attention) is an input on pin 3 of P2 and P3 that
|
||||||
serial_port_->set_output(::Commodore::Serial::Line::Data,
|
// is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||||
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
serial_port_->set_output(
|
||||||
|
::Commodore::Serial::Line::Data,
|
||||||
|
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - DriveVIA
|
// MARK: - DriveVIA
|
||||||
|
|
||||||
void DriveVIA::set_delegate(Delegate *delegate) {
|
void DriveVIA::set_delegate(Delegate *const delegate) {
|
||||||
delegate_ = delegate;
|
delegate_ = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,56 +285,80 @@ uint8_t DriveVIA::get_port_input() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DriveVIA::set_sync_detected(const bool sync_detected) {
|
void DriveVIA::set_sync_detected(const bool sync_detected) {
|
||||||
port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80);
|
port_b_ = (port_b_ & ~0x80) | (sync_detected ? 0x00 : 0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DriveVIA::set_data_input(uint8_t value) {
|
void DriveVIA::set_is_read_only(const bool is_read_only) {
|
||||||
|
port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x00 : 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveVIA::set_data_input(const uint8_t value) {
|
||||||
port_a_ = value;
|
port_a_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DriveVIA::get_should_set_overflow() {
|
|
||||||
return should_set_overflow_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DriveVIA::get_motor_enabled() {
|
|
||||||
return drive_motor_;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
|
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
|
||||||
void DriveVIA::set_control_line_output(const bool value) {
|
void DriveVIA::set_control_line_output(const bool value) {
|
||||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||||
should_set_overflow_ = value;
|
if(set_cpu_overflow_ != value) {
|
||||||
|
set_cpu_overflow_ = value;
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->drive_via_should_set_cpu_overflow(*this, set_cpu_overflow_);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
template <MOS::MOS6522::Port port>
|
if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) {
|
||||||
void DriveVIA::set_port_output(const uint8_t value, uint8_t) {
|
const bool new_write_mode = !value;
|
||||||
if(port) {
|
if(new_write_mode != write_mode_) {
|
||||||
if(previous_port_b_output_ != value) {
|
write_mode_ = new_write_mode;
|
||||||
// Record drive motor state.
|
if(delegate_) {
|
||||||
drive_motor_ = value&4;
|
delegate_->drive_via_did_set_write_mode(*this, write_mode_);
|
||||||
|
|
||||||
// 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.
|
|
||||||
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);
|
|
||||||
|
|
||||||
previous_port_b_output_ = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DriveVIA::set_activity_observer(Activity::Observer *observer) {
|
template <>
|
||||||
|
void DriveVIA::set_port_output<MOS::MOS6522::Port::B>(const uint8_t value, uint8_t) {
|
||||||
|
if(previous_port_b_output_ != value) {
|
||||||
|
// Record drive motor state.
|
||||||
|
const bool new_drive_motor = value & 4;
|
||||||
|
if(new_drive_motor != drive_motor_) {
|
||||||
|
drive_motor_ = new_drive_motor;
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->drive_via_did_set_drive_motor(*this, drive_motor_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a head step.
|
||||||
|
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||||
|
if(step_difference && delegate_) {
|
||||||
|
delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
previous_port_b_output_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void DriveVIA::set_port_output<MOS::MOS6522::Port::A>(const uint8_t value, uint8_t) {
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->drive_via_set_to_shifter_output(*this, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveVIA::set_activity_observer(Activity::Observer *const observer) {
|
||||||
observer_ = observer;
|
observer_ = observer;
|
||||||
if(observer) {
|
if(observer) {
|
||||||
observer->register_led("Drive");
|
observer->register_led("Drive");
|
||||||
@@ -277,9 +366,9 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SerialPort
|
// MARK: - SerialPort.
|
||||||
|
|
||||||
void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) {
|
void SerialPort::set_input(const Serial::Line line, const Serial::LineLevel level) {
|
||||||
serial_port_via_->set_serial_line_state(line, bool(level), *via_);
|
serial_port_via_->set_serial_line_state(line, bool(level), *via_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Processors/6502/6502.hpp"
|
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||||
#include "Components/6522/6522.hpp"
|
#include "Components/6522/6522.hpp"
|
||||||
|
|
||||||
#include "Machines/Commodore/SerialBus.hpp"
|
#include "Machines/Commodore/SerialBus.hpp"
|
||||||
@@ -65,21 +65,27 @@ private:
|
|||||||
It is wired up such that Port B contains:
|
It is wired up such that Port B contains:
|
||||||
Bits 0/1: head step direction
|
Bits 0/1: head step direction
|
||||||
Bit 2: motor control
|
Bit 2: motor control
|
||||||
Bit 3: LED control (TODO)
|
Bit 3: LED control
|
||||||
Bit 4: write protect photocell status (TODO)
|
Bit 4: write protect photocell status (TODO)
|
||||||
Bits 5/6: read/write density
|
Bits 5/6: read/write density
|
||||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||||
|
|
||||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||||
|
|
||||||
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
Elsewhere:
|
||||||
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
* CA2 might is used to set processor overflow;
|
||||||
|
* CA1 a strobe for data input; and
|
||||||
|
* CB2 indicates read/write mode; 1 = read, 0 = write.
|
||||||
*/
|
*/
|
||||||
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||||
public:
|
public:
|
||||||
struct Delegate {
|
struct Delegate {
|
||||||
virtual void drive_via_did_step_head(DriveVIA &, int direction) = 0;
|
virtual void drive_via_did_step_head(DriveVIA &, int direction) = 0;
|
||||||
virtual void drive_via_did_set_data_density(DriveVIA &, int density) = 0;
|
virtual void drive_via_did_set_data_density(DriveVIA &, int density) = 0;
|
||||||
|
virtual void drive_via_did_set_drive_motor(DriveVIA &, bool enabled) = 0;
|
||||||
|
virtual void drive_via_did_set_write_mode(DriveVIA &, bool write) = 0;
|
||||||
|
virtual void drive_via_should_set_cpu_overflow(DriveVIA &, bool overflow) = 0;
|
||||||
|
virtual void drive_via_set_to_shifter_output(DriveVIA &, uint8_t) = 0;
|
||||||
};
|
};
|
||||||
void set_delegate(Delegate *);
|
void set_delegate(Delegate *);
|
||||||
|
|
||||||
@@ -88,8 +94,7 @@ public:
|
|||||||
|
|
||||||
void set_sync_detected(bool);
|
void set_sync_detected(bool);
|
||||||
void set_data_input(uint8_t);
|
void set_data_input(uint8_t);
|
||||||
bool get_should_set_overflow();
|
void set_is_read_only(bool);
|
||||||
bool get_motor_enabled();
|
|
||||||
|
|
||||||
template <MOS::MOS6522::Port, MOS::MOS6522::Line>
|
template <MOS::MOS6522::Port, MOS::MOS6522::Line>
|
||||||
void set_control_line_output(bool value);
|
void set_control_line_output(bool value);
|
||||||
@@ -101,8 +106,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t port_b_ = 0xff, port_a_ = 0xff;
|
uint8_t port_b_ = 0xff, port_a_ = 0xff;
|
||||||
bool should_set_overflow_ = false;
|
|
||||||
|
bool set_cpu_overflow_ = false;
|
||||||
bool drive_motor_ = false;
|
bool drive_motor_ = false;
|
||||||
|
bool write_mode_ = false;
|
||||||
|
|
||||||
uint8_t previous_port_b_output_ = 0;
|
uint8_t previous_port_b_output_ = 0;
|
||||||
Delegate *delegate_ = nullptr;
|
Delegate *delegate_ = nullptr;
|
||||||
Activity::Observer *observer_ = nullptr;
|
Activity::Observer *observer_ = nullptr;
|
||||||
@@ -122,7 +130,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class MachineBase:
|
class MachineBase:
|
||||||
public CPU::MOS6502::BusHandler,
|
|
||||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
public DriveVIA::Delegate,
|
public DriveVIA::Delegate,
|
||||||
public Storage::Disk::Controller {
|
public Storage::Disk::Controller {
|
||||||
@@ -134,17 +141,27 @@ public:
|
|||||||
void set_activity_observer(Activity::Observer *);
|
void set_activity_observer(Activity::Observer *);
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
// to satisfy CPU::MOS6502::Processor
|
||||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value);
|
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||||
|
Cycles perform(const AddressT, CPU::MOS6502Mk2::data_t<operation>);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// to satisfy MOS::MOS6522::Delegate
|
// to satisfy MOS::MOS6522::Delegate
|
||||||
void mos6522_did_change_interrupt_status(void *mos6522) override;
|
void mos6522_did_change_interrupt_status(void *mos6522) override;
|
||||||
|
|
||||||
// to satisfy DriveVIA::Delegate
|
// to satisfy DriveVIA::Delegate
|
||||||
void drive_via_did_step_head(DriveVIA &, int direction) override;
|
void drive_via_did_step_head(DriveVIA &, int) override;
|
||||||
void drive_via_did_set_data_density(DriveVIA &, int density) override;
|
void drive_via_did_set_data_density(DriveVIA &, int) override;
|
||||||
|
void drive_via_did_set_drive_motor(DriveVIA &, bool) override;
|
||||||
|
void drive_via_did_set_write_mode(DriveVIA &, bool) override;
|
||||||
|
void drive_via_should_set_cpu_overflow(DriveVIA &, bool) override;
|
||||||
|
void drive_via_set_to_shifter_output(DriveVIA &, uint8_t) override;
|
||||||
|
|
||||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
struct M6502Traits {
|
||||||
|
static constexpr auto uses_ready_line = false;
|
||||||
|
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
|
||||||
|
using BusHandlerT = MachineBase;
|
||||||
|
};
|
||||||
|
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||||
|
|
||||||
uint8_t ram_[0x800];
|
uint8_t ram_[0x800];
|
||||||
uint8_t rom_[0x4000];
|
uint8_t rom_[0x4000];
|
||||||
@@ -156,9 +173,15 @@ protected:
|
|||||||
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||||
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||||
|
|
||||||
|
bool set_cpu_overflow_ = false;
|
||||||
int shift_register_ = 0, bit_window_offset_;
|
int shift_register_ = 0, bit_window_offset_;
|
||||||
void process_input_bit(int value) override;
|
void process_input_bit(int value) override;
|
||||||
void process_index_hole() override;
|
void process_index_hole() override;
|
||||||
|
void process_write_completed() override;
|
||||||
|
void is_writing_final_bit() override;
|
||||||
|
|
||||||
|
uint8_t port_a_output_ = 0xff;
|
||||||
|
void serialise_shift_output();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#include "Machines/MachineTypes.hpp"
|
#include "Machines/MachineTypes.hpp"
|
||||||
#include "Machines/Utility/MemoryFuzzer.hpp"
|
#include "Machines/Utility/MemoryFuzzer.hpp"
|
||||||
#include "Processors/6502/6502.hpp"
|
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||||
#include "Analyser/Static/Commodore/Target.hpp"
|
#include "Analyser/Static/Commodore/Target.hpp"
|
||||||
#include "Outputs/Log.hpp"
|
#include "Outputs/Log.hpp"
|
||||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
@@ -236,20 +236,17 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK. NOCOMMIT.
|
// HACK. NOCOMMIT.
|
||||||
// int pulse_num_ = 0;
|
// int pulse_num_ = 0;
|
||||||
|
|
||||||
Cycles perform_bus_operation(
|
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||||
const CPU::MOS6502::BusOperation operation,
|
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||||
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,
|
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
|
||||||
// relative to the master clock.
|
// relative to the master clock.
|
||||||
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
|
const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready);
|
||||||
|
|
||||||
// Update other subsystems.
|
// Update other subsystems.
|
||||||
advance_timers_and_tape(length);
|
advance_timers_and_tape(length);
|
||||||
@@ -265,7 +262,7 @@ public:
|
|||||||
time_since_audio_update_ += length;
|
time_since_audio_update_ += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(operation == CPU::MOS6502::BusOperation::Ready) {
|
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,17 +279,17 @@ public:
|
|||||||
// b1 = serial clock out and cassette write;
|
// b1 = serial clock out and cassette write;
|
||||||
// b0 = serial data out.
|
// b0 = serial data out.
|
||||||
|
|
||||||
if(is_read(operation)) {
|
if constexpr (is_read(operation)) {
|
||||||
if(!address) {
|
if(!address) {
|
||||||
*value = io_direction_;
|
value = io_direction_;
|
||||||
} else {
|
} else {
|
||||||
*value = io_input();
|
value = io_input();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(!address) {
|
if(!address) {
|
||||||
io_direction_ = *value;
|
io_direction_ = value;
|
||||||
} else {
|
} else {
|
||||||
io_output_ = *value;
|
io_output_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto output = io_output_ | ~io_direction_;
|
const auto output = io_output_ | ~io_direction_;
|
||||||
@@ -323,36 +320,36 @@ public:
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if(
|
if constexpr (is_read(operation)) {
|
||||||
use_fast_tape_hack_ &&
|
if(
|
||||||
operation == CPU::MOS6502Esque::BusOperation::ReadOpcode &&
|
use_fast_tape_hack_ &&
|
||||||
(
|
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||||
(use_hle && address == 0xe5fd) ||
|
(
|
||||||
address == 0xe68b ||
|
(use_hle && address == 0xe5fd) ||
|
||||||
address == 0xe68d
|
address == 0xe68b ||
|
||||||
)
|
address == 0xe68d
|
||||||
) {
|
)
|
||||||
// ++pulse_num_;
|
) {
|
||||||
if(use_hle) {
|
// ++pulse_num_;
|
||||||
read_dipole();
|
if(use_hle) {
|
||||||
}
|
read_dipole();
|
||||||
|
}
|
||||||
|
|
||||||
// using Flag = CPU::MOS6502::Flag;
|
// using Flag = CPU::MOS6502::Flag;
|
||||||
// using Register = CPU::MOS6502::Register;
|
// using Register = CPU::MOS6502::Register;
|
||||||
// const auto flags = m6502_.value_of(Register::Flags);
|
// const auto flags = m6502_.value_of(Register::Flags);
|
||||||
// printf("to %lld: %c%c%c\n",
|
// printf("to %lld: %c%c%c\n",
|
||||||
// tape_player_->serialiser()->offset(),
|
// tape_player_->serialiser()->offset(),
|
||||||
// flags & Flag::Sign ? 'n' : '-',
|
// flags & Flag::Sign ? 'n' : '-',
|
||||||
// flags & Flag::Overflow ? 'v' : '-',
|
// flags & Flag::Overflow ? 'v' : '-',
|
||||||
// flags & Flag::Carry ? 'c' : '-'
|
// flags & Flag::Carry ? 'c' : '-'
|
||||||
// );
|
// );
|
||||||
*value = 0x60;
|
value = 0x60;
|
||||||
} else {
|
|
||||||
if(is_read(operation)) {
|
|
||||||
*value = map_.read(address);
|
|
||||||
} else {
|
} else {
|
||||||
map_.write(address) = *value;
|
value = map_.read(address);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
map_.write(address) = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -393,12 +390,12 @@ public:
|
|||||||
// ram_[0x90] = 0;
|
// ram_[0x90] = 0;
|
||||||
// ram_[0x93] = 0;
|
// ram_[0x93] = 0;
|
||||||
//
|
//
|
||||||
// *value = 0x0c; // NOP abs.
|
// value = 0x0c; // NOP abs.
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
} else if(address < 0xff00) {
|
} else if(address < 0xff00) {
|
||||||
// Miscellaneous hardware. All TODO.
|
// Miscellaneous hardware. All TODO.
|
||||||
if(is_read(operation)) {
|
if constexpr (is_read(operation)) {
|
||||||
switch(address & 0xfff0) {
|
switch(address & 0xfff0) {
|
||||||
case 0xfd10:
|
case 0xfd10:
|
||||||
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
|
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
|
||||||
@@ -406,7 +403,7 @@ public:
|
|||||||
// If play button is not currently pressed and this read is immediately followed by
|
// If play button is not currently pressed and this read is immediately followed by
|
||||||
// an AND 4, press it. The kernel will deal with motor control subsequently.
|
// an AND 4, press it. The kernel will deal with motor control subsequently.
|
||||||
if(!play_button_) {
|
if(!play_button_) {
|
||||||
const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
|
const uint16_t pc = m6502_.registers().pc.full;
|
||||||
const uint8_t next[] = {
|
const uint8_t next[] = {
|
||||||
map_.read(pc+0),
|
map_.read(pc+0),
|
||||||
map_.read(pc+1),
|
map_.read(pc+1),
|
||||||
@@ -422,22 +419,23 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*value = 0xff ^ (play_button_ ? 0x4 :0x0);
|
value = 0xff ^ (play_button_ ? 0x4 :0x0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xfdd0:
|
case 0xfdd0:
|
||||||
case 0xfdf0:
|
case 0xfdf0:
|
||||||
*value = uint8_t(address >> 8);
|
value = uint8_t(address >> 8);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
value = 0xff;
|
||||||
Logger::info().append("TODO: read @ %04x", address);
|
Logger::info().append("TODO: read @ %04x", address);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch(address & 0xfff0) {
|
switch(address & 0xfff0) {
|
||||||
case 0xfd30:
|
case 0xfd30:
|
||||||
keyboard_mask_ = *value;
|
keyboard_mask_ = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xfdd0: {
|
case 0xfdd0: {
|
||||||
@@ -447,28 +445,28 @@ public:
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Logger::info().append("TODO: write of %02x @ %04x", *value, address);
|
Logger::info().append("TODO: write of %02x @ %04x", value, address);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
|
const auto pc = m6502_.registers().pc.full;
|
||||||
const bool is_from_rom =
|
const bool is_from_rom =
|
||||||
(rom_is_paged_ && pc >= 0x8000) ||
|
(rom_is_paged_ && pc >= 0x8000) ||
|
||||||
(pc >= 0x400 && pc < 0x500) ||
|
(pc >= 0x400 && pc < 0x500) ||
|
||||||
(pc >= 0x700 && pc < 0x800);
|
(pc >= 0x700 && pc < 0x800);
|
||||||
bool is_hit = true;
|
bool is_hit = true;
|
||||||
|
|
||||||
if(is_read(operation)) {
|
if constexpr (is_read(operation)) {
|
||||||
switch(address) {
|
switch(address) {
|
||||||
case 0xff00: *value = timers_.read<0>(); break;
|
case 0xff00: value = timers_.read<0>(); break;
|
||||||
case 0xff01: *value = timers_.read<1>(); break;
|
case 0xff01: value = timers_.read<1>(); break;
|
||||||
case 0xff02: *value = timers_.read<2>(); break;
|
case 0xff02: value = timers_.read<2>(); break;
|
||||||
case 0xff03: *value = timers_.read<3>(); break;
|
case 0xff03: value = timers_.read<3>(); break;
|
||||||
case 0xff04: *value = timers_.read<4>(); break;
|
case 0xff04: value = timers_.read<4>(); break;
|
||||||
case 0xff05: *value = timers_.read<5>(); break;
|
case 0xff05: value = timers_.read<5>(); break;
|
||||||
case 0xff06: *value = video_.read<0xff06>(); break;
|
case 0xff06: value = video_.read<0xff06>(); break;
|
||||||
case 0xff07: *value = video_.read<0xff07>(); break;
|
case 0xff07: value = video_.read<0xff07>(); break;
|
||||||
case 0xff08: {
|
case 0xff08: {
|
||||||
const uint8_t keyboard_input =
|
const uint8_t keyboard_input =
|
||||||
~(
|
~(
|
||||||
@@ -487,127 +485,128 @@ public:
|
|||||||
((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) &
|
((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) &
|
||||||
((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80));
|
((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80));
|
||||||
|
|
||||||
*value = keyboard_input & joystick_mask;
|
value = keyboard_input & joystick_mask;
|
||||||
} break;
|
} break;
|
||||||
case 0xff09: *value = interrupts_.status(); break;
|
case 0xff09: value = interrupts_.status(); break;
|
||||||
case 0xff0a:
|
case 0xff0a:
|
||||||
*value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
|
value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
|
||||||
break;
|
break;
|
||||||
case 0xff0b: *value = video_.read<0xff0b>(); break;
|
case 0xff0b: value = video_.read<0xff0b>(); break;
|
||||||
case 0xff0c: *value = video_.read<0xff0c>(); break;
|
case 0xff0c: value = video_.read<0xff0c>(); break;
|
||||||
case 0xff0d: *value = video_.read<0xff0d>(); break;
|
case 0xff0d: value = video_.read<0xff0d>(); break;
|
||||||
case 0xff0e: *value = ff0e_; break;
|
case 0xff0e: value = ff0e_; break;
|
||||||
case 0xff0f: *value = ff0f_; break;
|
case 0xff0f: value = ff0f_; break;
|
||||||
case 0xff10: *value = ff10_ | 0xfc; break;
|
case 0xff10: value = ff10_ | 0xfc; break;
|
||||||
case 0xff11: *value = ff11_; break;
|
case 0xff11: value = ff11_; break;
|
||||||
case 0xff12: *value = ff12_ | 0xc0; break;
|
case 0xff12: value = ff12_ | 0xc0; break;
|
||||||
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
|
case 0xff13: value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
|
||||||
case 0xff14: *value = video_.read<0xff14>(); break;
|
case 0xff14: value = video_.read<0xff14>(); break;
|
||||||
case 0xff15: *value = video_.read<0xff15>(); break;
|
case 0xff15: value = video_.read<0xff15>(); break;
|
||||||
case 0xff16: *value = video_.read<0xff16>(); break;
|
case 0xff16: value = video_.read<0xff16>(); break;
|
||||||
case 0xff17: *value = video_.read<0xff17>(); break;
|
case 0xff17: value = video_.read<0xff17>(); break;
|
||||||
case 0xff18: *value = video_.read<0xff18>(); break;
|
case 0xff18: value = video_.read<0xff18>(); break;
|
||||||
case 0xff19: *value = video_.read<0xff19>(); break;
|
case 0xff19: value = video_.read<0xff19>(); break;
|
||||||
case 0xff1a: *value = video_.read<0xff1a>(); break;
|
case 0xff1a: value = video_.read<0xff1a>(); break;
|
||||||
case 0xff1b: *value = video_.read<0xff1b>(); break;
|
case 0xff1b: value = video_.read<0xff1b>(); break;
|
||||||
case 0xff1c: *value = video_.read<0xff1c>(); break;
|
case 0xff1c: value = video_.read<0xff1c>(); break;
|
||||||
case 0xff1d: *value = video_.read<0xff1d>(); break;
|
case 0xff1d: value = video_.read<0xff1d>(); break;
|
||||||
case 0xff1e: *value = video_.read<0xff1e>(); break;
|
case 0xff1e: value = video_.read<0xff1e>(); break;
|
||||||
case 0xff1f: *value = video_.read<0xff1f>(); break;
|
case 0xff1f: value = video_.read<0xff1f>(); break;
|
||||||
|
|
||||||
case 0xff3e: *value = 0; break;
|
case 0xff3e: value = 0; break;
|
||||||
case 0xff3f: *value = 0; break;
|
case 0xff3f: value = 0; break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Logger::info().append("TODO: TED read at %04x", address);
|
Logger::info().append("TODO: TED read at %04x", address);
|
||||||
|
value = 0xff;
|
||||||
is_hit = false;
|
is_hit = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch(address) {
|
switch(address) {
|
||||||
case 0xff00: timers_.write<0>(*value); break;
|
case 0xff00: timers_.write<0>(value); break;
|
||||||
case 0xff01: timers_.write<1>(*value); break;
|
case 0xff01: timers_.write<1>(value); break;
|
||||||
case 0xff02: timers_.write<2>(*value); break;
|
case 0xff02: timers_.write<2>(value); break;
|
||||||
case 0xff03: timers_.write<3>(*value); break;
|
case 0xff03: timers_.write<3>(value); break;
|
||||||
case 0xff04: timers_.write<4>(*value); break;
|
case 0xff04: timers_.write<4>(value); break;
|
||||||
case 0xff05: timers_.write<5>(*value); break;
|
case 0xff05: timers_.write<5>(value); break;
|
||||||
case 0xff06: video_.write<0xff06>(*value); break;
|
case 0xff06: video_.write<0xff06>(value); break;
|
||||||
case 0xff07:
|
case 0xff07:
|
||||||
video_.write<0xff07>(*value);
|
video_.write<0xff07>(value);
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_divider(*value);
|
audio_.set_divider(value);
|
||||||
break;
|
break;
|
||||||
case 0xff08:
|
case 0xff08:
|
||||||
// Observation here: the kernel posts a 0 to this
|
// Observation here: the kernel posts a 0 to this
|
||||||
// address upon completing each keyboard scan cycle,
|
// address upon completing each keyboard scan cycle,
|
||||||
// once per frame.
|
// once per frame.
|
||||||
if(typer_ && !*value) {
|
if(typer_ && !value) {
|
||||||
if(!typer_->type_next_character()) {
|
if(!typer_->type_next_character()) {
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
typer_.reset();
|
typer_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
joystick_mask_ = *value;
|
joystick_mask_ = value;
|
||||||
break;
|
break;
|
||||||
case 0xff09:
|
case 0xff09:
|
||||||
interrupts_.set_status(*value);
|
interrupts_.set_status(value);
|
||||||
break;
|
break;
|
||||||
case 0xff0a:
|
case 0xff0a:
|
||||||
interrupts_.set_mask(*value);
|
interrupts_.set_mask(value);
|
||||||
video_.write<0xff0a>(*value);
|
video_.write<0xff0a>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff0b: video_.write<0xff0b>(*value); break;
|
case 0xff0b: video_.write<0xff0b>(value); break;
|
||||||
case 0xff0c: video_.write<0xff0c>(*value); break;
|
case 0xff0c: video_.write<0xff0c>(value); break;
|
||||||
case 0xff0d: video_.write<0xff0d>(*value); break;
|
case 0xff0d: video_.write<0xff0d>(value); break;
|
||||||
case 0xff0e:
|
case 0xff0e:
|
||||||
ff0e_ = *value;
|
ff0e_ = value;
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_frequency_low<0>(*value);
|
audio_.set_frequency_low<0>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff0f:
|
case 0xff0f:
|
||||||
ff0f_ = *value;
|
ff0f_ = value;
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_frequency_low<1>(*value);
|
audio_.set_frequency_low<1>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff10:
|
case 0xff10:
|
||||||
ff10_ = *value;
|
ff10_ = value;
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_frequency_high<1>(*value);
|
audio_.set_frequency_high<1>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff11:
|
case 0xff11:
|
||||||
ff11_ = *value;
|
ff11_ = value;
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_control(*value);
|
audio_.set_control(value);
|
||||||
break;
|
break;
|
||||||
case 0xff12:
|
case 0xff12:
|
||||||
ff12_ = *value & 0x3f;
|
ff12_ = value & 0x3f;
|
||||||
video_.write<0xff12>(*value);
|
video_.write<0xff12>(value);
|
||||||
|
|
||||||
if((*value & 4)) {
|
if((value & 4)) {
|
||||||
page_video_rom();
|
page_video_rom();
|
||||||
} else {
|
} else {
|
||||||
page_video_ram();
|
page_video_ram();
|
||||||
}
|
}
|
||||||
|
|
||||||
update_audio();
|
update_audio();
|
||||||
audio_.set_frequency_high<0>(*value);
|
audio_.set_frequency_high<0>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff13:
|
case 0xff13:
|
||||||
ff13_ = *value & 0xfe;
|
ff13_ = value & 0xfe;
|
||||||
video_.write<0xff13>(*value);
|
video_.write<0xff13>(value);
|
||||||
break;
|
break;
|
||||||
case 0xff14: video_.write<0xff14>(*value); break;
|
case 0xff14: video_.write<0xff14>(value); break;
|
||||||
case 0xff15: video_.write<0xff15>(*value); break;
|
case 0xff15: video_.write<0xff15>(value); break;
|
||||||
case 0xff16: video_.write<0xff16>(*value); break;
|
case 0xff16: video_.write<0xff16>(value); break;
|
||||||
case 0xff17: video_.write<0xff17>(*value); break;
|
case 0xff17: video_.write<0xff17>(value); break;
|
||||||
case 0xff18: video_.write<0xff18>(*value); break;
|
case 0xff18: video_.write<0xff18>(value); break;
|
||||||
case 0xff19: video_.write<0xff19>(*value); break;
|
case 0xff19: video_.write<0xff19>(value); break;
|
||||||
case 0xff1a: video_.write<0xff1a>(*value); break;
|
case 0xff1a: video_.write<0xff1a>(value); break;
|
||||||
case 0xff1b: video_.write<0xff1b>(*value); break;
|
case 0xff1b: video_.write<0xff1b>(value); break;
|
||||||
case 0xff1c: video_.write<0xff1c>(*value); break;
|
case 0xff1c: video_.write<0xff1c>(value); break;
|
||||||
case 0xff1d: video_.write<0xff1d>(*value); break;
|
case 0xff1d: video_.write<0xff1d>(value); break;
|
||||||
case 0xff1e: video_.write<0xff1e>(*value); break;
|
case 0xff1e: video_.write<0xff1e>(value); break;
|
||||||
case 0xff1f: video_.write<0xff1f>(*value); break;
|
case 0xff1f: video_.write<0xff1f>(value); break;
|
||||||
|
|
||||||
case 0xff3e: page_cpu_rom(); break;
|
case 0xff3e: page_cpu_rom(); break;
|
||||||
case 0xff3f: page_cpu_ram(); break;
|
case 0xff3f: page_cpu_ram(); break;
|
||||||
@@ -626,8 +625,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Processor = CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true>;
|
struct M6502Traits {
|
||||||
Processor m6502_;
|
static constexpr auto uses_ready_line = true;
|
||||||
|
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
|
||||||
|
using BusHandlerT = ConcreteMachine;
|
||||||
|
};
|
||||||
|
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||||
|
|
||||||
Outputs::Speaker::Speaker *get_speaker() override {
|
Outputs::Speaker::Speaker *get_speaker() override {
|
||||||
return &speaker_;
|
return &speaker_;
|
||||||
@@ -637,11 +640,11 @@ private:
|
|||||||
if(c1541_) c1541_->set_activity_observer(observer);
|
if(c1541_) c1541_->set_activity_observer(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_irq_line(bool active) override {
|
void set_irq_line(const bool active) override {
|
||||||
m6502_.set_irq_line(active);
|
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(active);
|
||||||
}
|
}
|
||||||
void set_ready_line(bool active) override {
|
void set_ready_line(const bool active) override {
|
||||||
m6502_.set_ready_line(active);
|
m6502_.template set<CPU::MOS6502Mk2::Line::Ready>(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
void page_video_rom() {
|
void page_video_rom() {
|
||||||
@@ -788,26 +791,19 @@ private:
|
|||||||
// TODO: substantially simplify the below; at the minute it's a
|
// TODO: substantially simplify the below; at the minute it's a
|
||||||
// literal transcription of the original as a simple first step.
|
// literal transcription of the original as a simple first step.
|
||||||
void read_dipole() {
|
void read_dipole() {
|
||||||
using Register = CPU::MOS6502::Register;
|
using Flag = CPU::MOS6502Mk2::Flag;
|
||||||
using Flag = CPU::MOS6502::Flag;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Get registers now and ensure they'll be written back at function exit.
|
// Get registers now and ensure they'll be written back at function exit.
|
||||||
//
|
//
|
||||||
CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags)));
|
auto registers = m6502_.registers();
|
||||||
uint8_t x, y, a;
|
|
||||||
uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer));
|
|
||||||
struct ScopeGuard {
|
struct ScopeGuard {
|
||||||
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
|
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
|
||||||
~ScopeGuard() { at_exit_(); }
|
~ScopeGuard() { at_exit_(); }
|
||||||
private:
|
private:
|
||||||
std::function<void(void)> at_exit_;
|
std::function<void(void)> at_exit_;
|
||||||
} registers([&] {
|
} store_registers([&] {
|
||||||
m6502_.set_value_of(Register::Flags, flags.get());
|
m6502_.set_registers(registers);
|
||||||
m6502_.set_value_of(Register::A, a);
|
|
||||||
m6502_.set_value_of(Register::X, x);
|
|
||||||
m6502_.set_value_of(Register::Y, y);
|
|
||||||
m6502_.set_value_of(Register::StackPointer, s);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -822,38 +818,38 @@ private:
|
|||||||
// 6502 pseudo-ops.
|
// 6502 pseudo-ops.
|
||||||
//
|
//
|
||||||
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
|
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
|
||||||
flags.set_nz(target = map_.read(address));
|
registers.flags.set_per<Flag::NegativeZero>(target = map_.read(address));
|
||||||
};
|
};
|
||||||
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
|
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
|
||||||
flags.set_nz(target = value);
|
registers.flags.set_per<Flag::NegativeZero>(target = value);
|
||||||
};
|
};
|
||||||
const auto pha = [&] () {
|
const auto pha = [&] () {
|
||||||
map_.write(0x100 + s) = a;
|
map_.write(0x100 + registers.s) = registers.a;
|
||||||
--s;
|
--registers.s;
|
||||||
};
|
};
|
||||||
const auto pla = [&] () {
|
const auto pla = [&] () {
|
||||||
++s;
|
++registers.s;
|
||||||
a = map_.read(0x100 + s);
|
registers.a = map_.read(0x100 + registers.s);
|
||||||
};
|
};
|
||||||
const auto bit = [&] (const uint8_t value) {
|
const auto bit = [&] (const uint8_t value) {
|
||||||
flags.zero_result = a & value;
|
registers.flags.set_per<Flag::Zero>(registers.a & value);
|
||||||
flags.negative_result = value;
|
registers.flags.set_per<Flag::Negative>(value);
|
||||||
flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow;
|
registers.flags.set_per<Flag::Overflow>(value);
|
||||||
};
|
};
|
||||||
const auto cmp = [&] (const uint8_t value) {
|
const auto cmp = [&] (const uint8_t value) {
|
||||||
const uint16_t temp16 = a - value;
|
const uint16_t temp16 = registers.a - value;
|
||||||
flags.set_nz(uint8_t(temp16));
|
registers.flags.set_per<Flag::NegativeZero>(uint8_t(temp16));
|
||||||
flags.carry = ((~temp16) >> 8)&1;
|
registers.flags.set_per<Flag::Carry>(((~temp16) >> 8)&1);
|
||||||
};
|
};
|
||||||
const auto andimm = [&] (const uint8_t value) {
|
const auto andimm = [&] (const uint8_t value) {
|
||||||
a &= value;
|
registers.a &= value;
|
||||||
flags.set_nz(a);
|
registers.flags.set_per<Flag::NegativeZero>(registers.a);
|
||||||
};
|
};
|
||||||
const auto ne = [&]() -> bool {
|
const auto ne = [&]() -> bool {
|
||||||
return flags.zero_result;
|
return !registers.flags.get<Flag::Zero>();
|
||||||
};
|
};
|
||||||
const auto eq = [&]() -> bool {
|
const auto eq = [&]() -> bool {
|
||||||
return !flags.zero_result;
|
return registers.flags.get<Flag::Zero>();
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -862,7 +858,7 @@ private:
|
|||||||
const auto dipok = [&] {
|
const auto dipok = [&] {
|
||||||
// clc ; everything's fine
|
// clc ; everything's fine
|
||||||
// rts
|
// rts
|
||||||
flags.carry = 0;
|
registers.flags.set_per<Flag::Carry>(0);
|
||||||
};
|
};
|
||||||
const auto rshort = [&] {
|
const auto rshort = [&] {
|
||||||
// bit tshrtd ; got a short
|
// bit tshrtd ; got a short
|
||||||
@@ -878,7 +874,7 @@ private:
|
|||||||
const auto rderr1 = [&] {
|
const auto rderr1 = [&] {
|
||||||
// sec ; i'm confused
|
// sec ; i'm confused
|
||||||
// rts
|
// rts
|
||||||
flags.carry = Flag::Carry;
|
registers.flags.set_per<Flag::Carry>(Flag::Carry);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -891,8 +887,8 @@ private:
|
|||||||
//rddipl
|
//rddipl
|
||||||
// ldx dsamp1 ; setup x,y with 1st sample point
|
// ldx dsamp1 ; setup x,y with 1st sample point
|
||||||
// ldy dsamp1+1
|
// ldy dsamp1+1
|
||||||
ldabs(x, dsamp1);
|
ldabs(registers.x, dsamp1);
|
||||||
ldabs(y, dsamp1 + 1);
|
ldabs(registers.y, dsamp1 + 1);
|
||||||
advance_cycles(8);
|
advance_cycles(8);
|
||||||
|
|
||||||
//badeg1
|
//badeg1
|
||||||
@@ -901,9 +897,9 @@ private:
|
|||||||
// pha
|
// pha
|
||||||
// lda dsamp2
|
// lda dsamp2
|
||||||
// pha
|
// pha
|
||||||
ldabs(a, dsamp2 + 1);
|
ldabs(registers.a, dsamp2 + 1);
|
||||||
pha();
|
pha();
|
||||||
ldabs(a, dsamp2);
|
ldabs(registers.a, dsamp2);
|
||||||
pha();
|
pha();
|
||||||
advance_cycles(14);
|
advance_cycles(14);
|
||||||
|
|
||||||
@@ -911,7 +907,7 @@ private:
|
|||||||
//rwtl ; wait till rd line is high
|
//rwtl ; wait till rd line is high
|
||||||
// bit port [= $0001]
|
// bit port [= $0001]
|
||||||
// beq rwtl ; !ls!
|
// beq rwtl ; !ls!
|
||||||
ldimm(a, 0x10);
|
ldimm(registers.a, 0x10);
|
||||||
advance_cycles(2);
|
advance_cycles(2);
|
||||||
do {
|
do {
|
||||||
bit(io_input());
|
bit(io_input());
|
||||||
@@ -933,8 +929,8 @@ private:
|
|||||||
|
|
||||||
// stx timr2l
|
// stx timr2l
|
||||||
// sty timr2h
|
// sty timr2h
|
||||||
timers_.write<2>(x);
|
timers_.write<2>(registers.x);
|
||||||
timers_.write<3>(y);
|
timers_.write<3>(registers.y);
|
||||||
advance_cycles(8);
|
advance_cycles(8);
|
||||||
|
|
||||||
|
|
||||||
@@ -945,9 +941,9 @@ private:
|
|||||||
// pla
|
// pla
|
||||||
// sta timr3h ;go! ...tb
|
// sta timr3h ;go! ...tb
|
||||||
pla();
|
pla();
|
||||||
timers_.write<4>(a);
|
timers_.write<4>(registers.a);
|
||||||
pla();
|
pla();
|
||||||
timers_.write<5>(a);
|
timers_.write<5>(registers.a);
|
||||||
advance_cycles(14);
|
advance_cycles(14);
|
||||||
|
|
||||||
|
|
||||||
@@ -955,8 +951,8 @@ private:
|
|||||||
//
|
//
|
||||||
// lda #$50 ; clr ta,tb
|
// lda #$50 ; clr ta,tb
|
||||||
// sta tedirq
|
// sta tedirq
|
||||||
ldimm(a, 0x50);
|
ldimm(registers.a, 0x50);
|
||||||
interrupts_.set_status(a);
|
interrupts_.set_status(registers.a);
|
||||||
advance_cycles(6);
|
advance_cycles(6);
|
||||||
|
|
||||||
|
|
||||||
@@ -969,7 +965,7 @@ private:
|
|||||||
// and #$10 ; a look at that edge again
|
// and #$10 ; a look at that edge again
|
||||||
// bne badeg1 ; woa! got a bad edge trigger !ls!
|
// bne badeg1 ; woa! got a bad edge trigger !ls!
|
||||||
do {
|
do {
|
||||||
ldimm(a, io_input());
|
ldimm(registers.a, io_input());
|
||||||
cmp(io_input());
|
cmp(io_input());
|
||||||
if(advance_cycles(9)) {
|
if(advance_cycles(9)) {
|
||||||
return;
|
return;
|
||||||
@@ -992,7 +988,7 @@ private:
|
|||||||
|
|
||||||
// lda #$10
|
// lda #$10
|
||||||
//wata ; wait for ta to timeout
|
//wata ; wait for ta to timeout
|
||||||
ldimm(a, 0x10);
|
ldimm(registers.a, 0x10);
|
||||||
advance_cycles(3);
|
advance_cycles(3);
|
||||||
do {
|
do {
|
||||||
// bit port ; kuldge, kludge, kludge !!! <<><>>
|
// bit port ; kuldge, kludge, kludge !!! <<><>>
|
||||||
@@ -1020,7 +1016,7 @@ private:
|
|||||||
do {
|
do {
|
||||||
// lda port
|
// lda port
|
||||||
// cmp port
|
// cmp port
|
||||||
ldimm(a, io_input());
|
ldimm(registers.a, io_input());
|
||||||
cmp(io_input());
|
cmp(io_input());
|
||||||
|
|
||||||
if(advance_cycles(9)) {
|
if(advance_cycles(9)) {
|
||||||
@@ -1048,7 +1044,7 @@ private:
|
|||||||
//
|
//
|
||||||
//; wait for tb to timeout
|
//; wait for tb to timeout
|
||||||
//; now do the dipole sample #2
|
//; now do the dipole sample #2
|
||||||
ldimm(a, 0x40);
|
ldimm(registers.a, 0x40);
|
||||||
advance_cycles(3);
|
advance_cycles(3);
|
||||||
do {
|
do {
|
||||||
bit(interrupts_.status());
|
bit(interrupts_.status());
|
||||||
@@ -1063,7 +1059,7 @@ private:
|
|||||||
// cmp port
|
// cmp port
|
||||||
// bne casdb3
|
// bne casdb3
|
||||||
do {
|
do {
|
||||||
ldimm(a, io_input());
|
ldimm(registers.a, io_input());
|
||||||
cmp(io_input());
|
cmp(io_input());
|
||||||
if(advance_cycles(9)) {
|
if(advance_cycles(9)) {
|
||||||
return;
|
return;
|
||||||
@@ -1084,10 +1080,10 @@ private:
|
|||||||
// sta timr2l
|
// sta timr2l
|
||||||
// lda zcell+1
|
// lda zcell+1
|
||||||
// sta timr2h
|
// sta timr2h
|
||||||
ldabs(a, zcell);
|
ldabs(registers.a, zcell);
|
||||||
timers_.write<2>(a);
|
timers_.write<2>(registers.a);
|
||||||
ldabs(a, zcell + 1);
|
ldabs(registers.a, zcell + 1);
|
||||||
timers_.write<3>(y);
|
timers_.write<3>(registers.y);
|
||||||
advance_cycles(16);
|
advance_cycles(16);
|
||||||
|
|
||||||
|
|
||||||
@@ -1096,9 +1092,9 @@ private:
|
|||||||
// lda #$10
|
// lda #$10
|
||||||
// sta tedirq ; verify +180 half of word dipole
|
// sta tedirq ; verify +180 half of word dipole
|
||||||
// lda #$10
|
// lda #$10
|
||||||
ldimm(a, 0x10);
|
ldimm(registers.a, 0x10);
|
||||||
interrupts_.set_status(a);
|
interrupts_.set_status(registers.a);
|
||||||
ldimm(a, 0x10);
|
ldimm(registers.a, 0x10);
|
||||||
advance_cycles(8);
|
advance_cycles(8);
|
||||||
|
|
||||||
//wata2
|
//wata2
|
||||||
@@ -1116,7 +1112,7 @@ private:
|
|||||||
// cmp port
|
// cmp port
|
||||||
// bne casdb4
|
// bne casdb4
|
||||||
do {
|
do {
|
||||||
ldimm(a, io_input());
|
ldimm(registers.a, io_input());
|
||||||
cmp(io_input());
|
cmp(io_input());
|
||||||
if(advance_cycles(9)) {
|
if(advance_cycles(9)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#include "Activity/Source.hpp"
|
#include "Activity/Source.hpp"
|
||||||
#include "Machines/MachineTypes.hpp"
|
#include "Machines/MachineTypes.hpp"
|
||||||
|
|
||||||
#include "Processors/6502/6502.hpp"
|
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||||
#include "Components/6560/6560.hpp"
|
#include "Components/6560/6560.hpp"
|
||||||
#include "Components/6522/6522.hpp"
|
#include "Components/6522/6522.hpp"
|
||||||
|
|
||||||
@@ -72,26 +72,30 @@ public:
|
|||||||
// Port A provides information about the presence or absence of a tape, and parts of
|
// Port A provides information about the presence or absence of a tape, and parts of
|
||||||
// the joystick and serial port state, both of which have been statefully collected
|
// the joystick and serial port state, both of which have been statefully collected
|
||||||
// into port_a_.
|
// into port_a_.
|
||||||
if(!port) {
|
if constexpr (port == MOS::MOS6522::Port::A) {
|
||||||
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
||||||
}
|
}
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives announcements of control line output change from the 6522.
|
/// Receives announcements of control line output change from the 6522.
|
||||||
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line> void set_control_line_output(const bool value) {
|
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
|
||||||
// The CA2 output is used to control the tape motor.
|
void set_control_line_output(const bool value) {
|
||||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
// CA2: control the tape motor.
|
||||||
|
if constexpr (port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||||
tape_->set_motor_control(!value);
|
tape_->set_motor_control(!value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.
|
/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.
|
||||||
void set_serial_line_state(Commodore::Serial::Line line, const bool value) {
|
void set_serial_line_state(const Commodore::Serial::Line line, const bool value) {
|
||||||
|
const auto set = [&](const uint8_t bit) {
|
||||||
|
port_a_ = (port_a_ & ~bit) | (value ? bit : 0x00);
|
||||||
|
};
|
||||||
switch(line) {
|
switch(line) {
|
||||||
default: break;
|
default: break;
|
||||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
case ::Commodore::Serial::Line::Data: set(0x02); break;
|
||||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
case ::Commodore::Serial::Line::Clock: set(0x01); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +150,7 @@ public:
|
|||||||
|
|
||||||
/// Sets all keys as unpressed.
|
/// Sets all keys as unpressed.
|
||||||
void clear_all_keys() {
|
void clear_all_keys() {
|
||||||
memset(columns_, 0xff, sizeof(columns_));
|
std::fill(std::begin(columns_), std::end(columns_), 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
|
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
|
||||||
@@ -230,8 +234,6 @@ struct Vic6560BusHandler {
|
|||||||
// It is assumed that these pointers have been filled in by the machine.
|
// It is assumed that these pointers have been filled in by the machine.
|
||||||
const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions.
|
const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions.
|
||||||
const uint8_t *colour_memory{}; // Colour memory must be contiguous.
|
const uint8_t *colour_memory{}; // Colour memory must be contiguous.
|
||||||
|
|
||||||
// TODO: make the above const.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -280,7 +282,6 @@ class ConcreteMachine:
|
|||||||
public MachineTypes::MappedKeyboardMachine,
|
public MachineTypes::MappedKeyboardMachine,
|
||||||
public MachineTypes::JoystickMachine,
|
public MachineTypes::JoystickMachine,
|
||||||
public Configurable::Device,
|
public Configurable::Device,
|
||||||
public CPU::MOS6502::BusHandler,
|
|
||||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
public Utility::TypeRecipient<CharacterMapper>,
|
public Utility::TypeRecipient<CharacterMapper>,
|
||||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||||
@@ -455,54 +456,50 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
||||||
if(key < 0xfff0) {
|
const auto apply_shifted = [&](const uint16_t key) {
|
||||||
|
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed);
|
||||||
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:
|
|
||||||
user_port_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!is_pressed);
|
|
||||||
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); \
|
|
||||||
break;
|
|
||||||
|
|
||||||
ShiftedMap(KeyUp, KeyDown);
|
switch(key) {
|
||||||
ShiftedMap(KeyLeft, KeyRight);
|
default:
|
||||||
ShiftedMap(KeyF2, KeyF1);
|
keyboard_via_port_handler_.set_key_state(key, is_pressed);
|
||||||
ShiftedMap(KeyF4, KeyF3);
|
break;
|
||||||
ShiftedMap(KeyF6, KeyF5);
|
case KeyRestore:
|
||||||
ShiftedMap(KeyF8, KeyF7);
|
user_port_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!is_pressed);
|
||||||
#undef ShiftedMap
|
break;
|
||||||
}
|
case KeyUp: apply_shifted(KeyDown); break;
|
||||||
|
case KeyLeft: apply_shifted(KeyRight); break;
|
||||||
|
case KeyF2: apply_shifted(KeyF1); break;
|
||||||
|
case KeyF4: apply_shifted(KeyF3); break;
|
||||||
|
case KeyF6: apply_shifted(KeyF5); break;
|
||||||
|
case KeyF8: apply_shifted(KeyF7); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear_all_keys() final {
|
void clear_all_keys() final {
|
||||||
keyboard_via_port_handler_.clear_all_keys();
|
keyboard_via_port_handler_.clear_all_keys();
|
||||||
|
set_key_state(KeyRestore, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||||
return joysticks_;
|
return joysticks_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||||
forceinline Cycles perform_bus_operation(
|
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||||
const CPU::MOS6502::BusOperation operation,
|
|
||||||
const uint16_t address,
|
|
||||||
uint8_t *const value
|
|
||||||
) {
|
|
||||||
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
|
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
|
||||||
cycles_since_mos6560_update_++;
|
cycles_since_mos6560_update_++;
|
||||||
|
|
||||||
// Run the phase-2 part of the cycle, which is whatever the 6502 said it should be.
|
// Run the phase-2 part of the cycle, which is whatever the 6502 said it should be.
|
||||||
const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000;
|
const auto is_from_rom = [&]() {
|
||||||
if(is_read(operation)) {
|
return m6502_.registers().pc.full > 0x8000;
|
||||||
|
};
|
||||||
|
if constexpr (is_read(operation)) {
|
||||||
const auto page = processor_read_memory_map_[address >> 10];
|
const auto page = processor_read_memory_map_[address >> 10];
|
||||||
uint8_t result;
|
uint8_t result;
|
||||||
if(!page) {
|
if(!page) {
|
||||||
if(!is_from_rom) confidence_.add_miss();
|
if(!is_from_rom()) confidence_.add_miss();
|
||||||
result = 0xff;
|
result = 0xff;
|
||||||
} else {
|
} else {
|
||||||
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
|
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
|
||||||
@@ -515,7 +512,7 @@ public:
|
|||||||
if(address & 0x10) result &= user_port_via_.read(address);
|
if(address & 0x10) result &= user_port_via_.read(address);
|
||||||
if(address & 0x20) result &= keyboard_via_.read(address);
|
if(address & 0x20) result &= keyboard_via_.read(address);
|
||||||
|
|
||||||
if(!is_from_rom) {
|
if(!is_from_rom()) {
|
||||||
if((address & 0x100) && !(address & 0x30)) {
|
if((address & 0x100) && !(address & 0x30)) {
|
||||||
confidence_.add_miss();
|
confidence_.add_miss();
|
||||||
} else {
|
} else {
|
||||||
@@ -523,10 +520,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*value = result;
|
value = result;
|
||||||
|
|
||||||
// Consider applying the fast tape hack.
|
// Consider applying the fast tape hack.
|
||||||
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
if(use_fast_tape_hack_ && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
|
||||||
if(address == 0xf7b2) {
|
if(address == 0xf7b2) {
|
||||||
// Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the
|
// Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the
|
||||||
// next header. Skip that via a three-byte NOP and fill in the next header programmatically.
|
// next header. Skip that via a three-byte NOP and fill in the next header programmatically.
|
||||||
@@ -551,10 +548,10 @@ public:
|
|||||||
ram_[0x90] = 0;
|
ram_[0x90] = 0;
|
||||||
ram_[0x93] = 0;
|
ram_[0x93] = 0;
|
||||||
|
|
||||||
*value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
||||||
} else if(address == 0xf90b) {
|
} else if(address == 0xf90b) {
|
||||||
const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
|
auto registers = m6502_.registers();
|
||||||
if(x == 0xe) {
|
if(registers.x == 0xe) {
|
||||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
|
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
|
||||||
const auto tape_position = tape_->serialiser()->offset();
|
const auto tape_position = tape_->serialiser()->offset();
|
||||||
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
|
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
|
||||||
@@ -576,14 +573,13 @@ public:
|
|||||||
|
|
||||||
// set tape status, carry and flag
|
// set tape status, carry and flag
|
||||||
ram_[0x90] |= 0x40;
|
ram_[0x90] |= 0x40;
|
||||||
uint8_t flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags));
|
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Carry>(0);
|
||||||
flags &= ~uint8_t((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
|
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Interrupt>(0);
|
||||||
m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags);
|
|
||||||
|
|
||||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||||
// ensure that the PC leaps to 0xfccf
|
// ensure that the PC leaps to 0xfccf
|
||||||
m6502_.set_value_of(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
|
registers.pc.full = 0xfccf;
|
||||||
*value = 0xea; // i.e. NOP implied
|
value = 0xea; // i.e. NOP implied
|
||||||
hold_tape_ = true;
|
hold_tape_ = true;
|
||||||
Logger::info().append("Found data");
|
Logger::info().append("Found data");
|
||||||
} else {
|
} else {
|
||||||
@@ -592,27 +588,28 @@ public:
|
|||||||
Logger::info().append("Didn't find data");
|
Logger::info().append("Didn't find data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m6502_.set_registers(registers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint8_t *const ram = processor_write_memory_map_[address >> 10];
|
uint8_t *const ram = processor_write_memory_map_[address >> 10];
|
||||||
if(ram) {
|
if(ram) {
|
||||||
update_video();
|
update_video();
|
||||||
ram[address & 0x3ff] = *value;
|
ram[address & 0x3ff] = value;
|
||||||
}
|
}
|
||||||
// Anything between 0x9000 and 0x9400 is the IO area.
|
// Anything between 0x9000 and 0x9400 is the IO area.
|
||||||
if((address&0xfc00) == 0x9000) {
|
if((address&0xfc00) == 0x9000) {
|
||||||
// The VIC is selected by bit 8 = 0
|
// The VIC is selected by bit 8 = 0
|
||||||
if(!(address&0x100)) {
|
if(!(address&0x100)) {
|
||||||
update_video();
|
update_video();
|
||||||
mos6560_.write(address, *value);
|
mos6560_.write(address, value);
|
||||||
}
|
}
|
||||||
// The first VIA is selected by bit 4 = 1.
|
// The first VIA is selected by bit 4 = 1.
|
||||||
if(address & 0x10) user_port_via_.write(address, *value);
|
if(address & 0x10) user_port_via_.write(address, value);
|
||||||
// The second VIA is selected by bit 5 = 1.
|
// The second VIA is selected by bit 5 = 1.
|
||||||
if(address & 0x20) keyboard_via_.write(address, *value);
|
if(address & 0x20) keyboard_via_.write(address, value);
|
||||||
|
|
||||||
if(!is_from_rom) {
|
if(!is_from_rom()) {
|
||||||
if((address & 0x100) && !(address & 0x30)) {
|
if((address & 0x100) && !(address & 0x30)) {
|
||||||
confidence_.add_miss();
|
confidence_.add_miss();
|
||||||
} else {
|
} else {
|
||||||
@@ -620,13 +617,13 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(!ram) {
|
} else if(!ram) {
|
||||||
if(!is_from_rom) confidence_.add_miss();
|
if(!is_from_rom()) confidence_.add_miss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_port_via_.run_for(Cycles(1));
|
user_port_via_.run_for(Cycles(1));
|
||||||
keyboard_via_.run_for(Cycles(1));
|
keyboard_via_.run_for(Cycles(1));
|
||||||
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
|
||||||
if(!typer_->type_next_character()) {
|
if(!typer_->type_next_character()) {
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
typer_.reset();
|
typer_.reset();
|
||||||
@@ -672,8 +669,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mos6522_did_change_interrupt_status(void *) final {
|
void mos6522_did_change_interrupt_status(void *) final {
|
||||||
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
|
m6502_.template set<CPU::MOS6502Mk2::Line::NMI>(user_port_via_.get_interrupt_line());
|
||||||
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
|
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(keyboard_via_.get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
void type_string(const std::string &string) final {
|
void type_string(const std::string &string) final {
|
||||||
@@ -718,10 +715,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct M6502Traits {
|
||||||
|
static constexpr auto uses_ready_line = false;
|
||||||
|
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
|
||||||
|
using BusHandlerT = ConcreteMachine;
|
||||||
|
};
|
||||||
|
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||||
|
|
||||||
void update_video() {
|
void update_video() {
|
||||||
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
|
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
|
||||||
}
|
}
|
||||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
|
||||||
|
|
||||||
std::vector<uint8_t> character_rom_;
|
std::vector<uint8_t> character_rom_;
|
||||||
std::vector<uint8_t> basic_rom_;
|
std::vector<uint8_t> basic_rom_;
|
||||||
@@ -745,12 +748,22 @@ private:
|
|||||||
++address;
|
++address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, size_t length) {
|
void write_to_map(
|
||||||
|
const uint8_t **const map,
|
||||||
|
const uint8_t *const area,
|
||||||
|
const uint16_t address,
|
||||||
|
const size_t length
|
||||||
|
) {
|
||||||
write_to_map([&](const uint16_t address, const size_t offset) {
|
write_to_map([&](const uint16_t address, const size_t offset) {
|
||||||
map[address] = &area[offset];
|
map[address] = &area[offset];
|
||||||
}, address, length);
|
}, address, length);
|
||||||
}
|
}
|
||||||
void write_to_map(uint8_t **const map, uint8_t *area, uint16_t address, size_t length) {
|
void write_to_map(
|
||||||
|
uint8_t **const map,
|
||||||
|
uint8_t *const area,
|
||||||
|
const uint16_t address,
|
||||||
|
const size_t length
|
||||||
|
) {
|
||||||
write_to_map([&](const uint16_t address, const size_t offset) {
|
write_to_map([&](const uint16_t address, const size_t offset) {
|
||||||
map[address] = &area[offset];
|
map[address] = &area[offset];
|
||||||
}, address, length);
|
}, address, length);
|
||||||
|
|||||||
159
Machines/Enterprise/EXOSCodes.hpp
Normal file
159
Machines/Enterprise/EXOSCodes.hpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
//
|
||||||
|
// EXOSCodes.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Various EXOS codes, transcribed from EXOS20_technical_information.pdf via archive.org,
|
||||||
|
// which appears to be a compilation of original documentation so page numbers below
|
||||||
|
// refer to the page within the PDF. Numbers printed on the in-document pages are inconsistent.
|
||||||
|
|
||||||
|
namespace Enterprise::EXOS {
|
||||||
|
|
||||||
|
// Page 67.
|
||||||
|
enum class Function: uint8_t {
|
||||||
|
ResetSystem = 0, // RESET
|
||||||
|
OpenChannel = 1, // OPEN
|
||||||
|
CreateChannel = 2, // CREAT
|
||||||
|
CloseChannel = 3, // CLOSE
|
||||||
|
DestroyChannel = 4, // DEST
|
||||||
|
ReadCharacter = 5, // RDCH
|
||||||
|
ReadBlock = 6, // RDBLK
|
||||||
|
WriteCharacter = 7, // WRCH
|
||||||
|
WriteBlock = 8, // WRBLK
|
||||||
|
ReadChannelStatus = 9, // RSTAT
|
||||||
|
SetChannelStatus = 10, // SSTAT
|
||||||
|
SpecialFunction = 11, // SFUNC
|
||||||
|
|
||||||
|
SetReadToggleEXOSVariable = 16, // EVAR
|
||||||
|
CaptureChannel = 17, // CAPT
|
||||||
|
RedirectChannel = 18, // REDIR
|
||||||
|
SetDefaultDevice = 19, // DDEV
|
||||||
|
ReturnSystemStatus = 20, // SYSS
|
||||||
|
LinkDevices = 21, // LINK
|
||||||
|
ReadEXOSBoundary = 22, // READB
|
||||||
|
SetUSERBoundary = 23, // SETB,
|
||||||
|
AllocateSegment = 24, // ALLOC,
|
||||||
|
FreeSegment = 25, // FREE
|
||||||
|
LocateROMs = 26, // ROMS
|
||||||
|
AllocateChannelBuffer = 27, // BUFF
|
||||||
|
ReturnErrorMessage = 28, // ERRMSG
|
||||||
|
};
|
||||||
|
|
||||||
|
// Page 25.
|
||||||
|
enum class DeviceDescriptorFunction: uint8_t {
|
||||||
|
//
|
||||||
|
// Codes are the same as `Function` in the range 1–11.
|
||||||
|
//
|
||||||
|
Interrupt = 0,
|
||||||
|
Initialise = 12,
|
||||||
|
BufferMoved = 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Error: uint8_t {
|
||||||
|
NoError = 0x00,
|
||||||
|
|
||||||
|
//
|
||||||
|
// General Kernel Errors.
|
||||||
|
//
|
||||||
|
InvalidFunctionCode = 0xff, // IFUNC
|
||||||
|
FunctionCallNotAllowed = 0xfe, // ILLFN
|
||||||
|
InvalidString = 0xfd, // INAME
|
||||||
|
InsufficientStack = 0xfc, // STACK
|
||||||
|
ChannelIllegalOrDoesNotExist = 0xfb, // ICHAN
|
||||||
|
DeviceDoesNotExist = 0xfa, // NODEV
|
||||||
|
ChannelAlreadyExists = 0xf9, // CHANX
|
||||||
|
NoAllocateBufferCallMade = 0xf8, // NOBUF
|
||||||
|
InsufficientRAMForBuffer = 0xf7, // NORAM
|
||||||
|
InsufficientVideoRAM = 0xf6, // NOVID
|
||||||
|
NoFreeSegments = 0xf5, // NOSEG
|
||||||
|
InvalidSegment = 0xf4, // ISEG
|
||||||
|
InvalidUserBoundary = 0xf3, // IBOUND
|
||||||
|
InvalidEXOSVariableNumber = 0xf2, // IVAR
|
||||||
|
InvalidDesviceDescriptorType = 0xf1, // IDESC
|
||||||
|
UnrecognisedCommandString = 0xf0, // NOSTR
|
||||||
|
InvalidFileHeader = 0xef, // ASCII
|
||||||
|
UnknownModuleType = 0xee, // ITYPE
|
||||||
|
InvalidRelocatableModule = 0xed, // IREL
|
||||||
|
NoModule = 0xec, // NOMOD
|
||||||
|
InvalidTimeOrDateValue, // ITIME
|
||||||
|
|
||||||
|
//
|
||||||
|
// General Device Errors.
|
||||||
|
//
|
||||||
|
InvalidSpecialFunctionCode = 0xea, // ISPEC
|
||||||
|
AttemptToOpenSecondChannel = 0xe9, // 2NDCH
|
||||||
|
InvalidUnitNumber = 0xe8, // IUNIT
|
||||||
|
FunctionNotSupported = 0xe7, // NOFN
|
||||||
|
InvalidEscapeSequence = 0xe6, // ESC
|
||||||
|
StopKeyPressed = 0xe5, // STOP
|
||||||
|
EndOfFileMetInRead = 0xe4, // EOF
|
||||||
|
ProtectionViolation = 0xe3, // PROT
|
||||||
|
|
||||||
|
//
|
||||||
|
// Device-Specific Errors.
|
||||||
|
//
|
||||||
|
// FileDoesNotExist = 0xea, // NOFIL
|
||||||
|
// FileAlreadyExists = 0xe9, // EXFIL
|
||||||
|
// FileAlreadyOpen = 0xe8, // FOPEN
|
||||||
|
// FileIsTooBig = 0xe6, // FSIZE
|
||||||
|
// InvalidFilePointerValue = 0xe5, // FPTR
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Keyboard errors.
|
||||||
|
// //
|
||||||
|
// InvalidFunctionKeyNumber = 0xe3, // KFKEY
|
||||||
|
// RunOutOfFunctionKeySpace = 0xe2, // KFSPC
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Sound errors.
|
||||||
|
// //
|
||||||
|
// EnvelopeInvalidOrTooBig = 0xe1, // SENV
|
||||||
|
// NotEnoughRoomToDefineEnvelope = 0xe0, // SENDBF
|
||||||
|
// EnvelopeStorageRequestedTooSmall = 0xdf, // SENFLO
|
||||||
|
// SoundQueueFull = 0xde, // SQFUL
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Video errors.
|
||||||
|
// //
|
||||||
|
// InvalidRowNumberToScroll = 0xdd, // VROW
|
||||||
|
// AttemptToMoveCursorOffPage = 0xdc, // VCURS
|
||||||
|
// InvalidColourPassedToINKOrPAPER = 0xdb, // VCOLR
|
||||||
|
// InvalidXOrYSizeToOPEN = 0xda, // VSIZE
|
||||||
|
// InvalidVideoModeToOPEN = 0xd9, // VMODE
|
||||||
|
// BadParameterToDISPLAY = 0xdb, // VDISP, and officially 'naff' rather than 'bad'
|
||||||
|
// NotEnoughRowsInPageToDISPLAY = 0xd7, // VDSP2
|
||||||
|
// AttemptToMoveBeamOffPage = 0xd6, // VBEAM
|
||||||
|
// LineStyleTooBig = 0xd5, // VLSTY
|
||||||
|
// LineModeTooBig = 0xd4, // VLMOD
|
||||||
|
// CantDisplayCharacterOrGraphic = 0xd3, // VCHAR
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Serial errors.
|
||||||
|
// //
|
||||||
|
// InvalidBaudRate = 0xd2, // BAUD
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Editor errors.
|
||||||
|
// //
|
||||||
|
// InvalidVideoPageForOPEN = 0xd1, // EVID
|
||||||
|
// TroubleInCommunicatingWithKeyboard = 0xd0, // EKEY
|
||||||
|
// InvalidCoordinatesForPosition = 0xcf, // ECURS
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Cassette errors.
|
||||||
|
// //
|
||||||
|
// CRCErrorFromCassetteDriver = 0xce, // CCRC
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // Network errors
|
||||||
|
// //
|
||||||
|
// SerialDeviceOpenCannotUseNetwork = 0xcd, // SEROP
|
||||||
|
// ADDR_NETNotSetUp = 0xcc, // NOADR
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "Dave.hpp"
|
#include "Dave.hpp"
|
||||||
#include "EXDos.hpp"
|
#include "EXDos.hpp"
|
||||||
|
#include "HostFSHandler.hpp"
|
||||||
#include "Keyboard.hpp"
|
#include "Keyboard.hpp"
|
||||||
#include "Nick.hpp"
|
#include "Nick.hpp"
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@
|
|||||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
#include "Processors/Z80/Z80.hpp"
|
#include "Processors/Z80/Z80.hpp"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using Logger = Log::Logger<Log::Source::Enterprise>;
|
using Logger = Log::Logger<Log::Source::Enterprise>;
|
||||||
}
|
}
|
||||||
@@ -72,6 +75,7 @@ template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
|
|||||||
public Activity::Source,
|
public Activity::Source,
|
||||||
public Configurable::Device,
|
public Configurable::Device,
|
||||||
public CPU::Z80::BusHandler,
|
public CPU::Z80::BusHandler,
|
||||||
|
public HostFSHandler::MemoryAccessor,
|
||||||
public Machine,
|
public Machine,
|
||||||
public MachineTypes::AudioProducer,
|
public MachineTypes::AudioProducer,
|
||||||
public MachineTypes::MappedKeyboardMachine,
|
public MachineTypes::MappedKeyboardMachine,
|
||||||
@@ -104,7 +108,8 @@ public:
|
|||||||
z80_(*this),
|
z80_(*this),
|
||||||
nick_(ram_.end() - 65536),
|
nick_(ram_.end() - 65536),
|
||||||
dave_audio_(audio_queue_),
|
dave_audio_(audio_queue_),
|
||||||
speaker_(dave_audio_) {
|
speaker_(dave_audio_),
|
||||||
|
host_fs_(*this) {
|
||||||
|
|
||||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
|
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
|
||||||
set_clock_rate(clock_rate);
|
set_clock_rate(clock_rate);
|
||||||
@@ -228,6 +233,14 @@ public:
|
|||||||
memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size()));
|
memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Possibly install the host FS ROM.
|
||||||
|
host_fs_rom_.fill(0xff);
|
||||||
|
if(!target.media.file_bundles.empty()) {
|
||||||
|
const auto rom = host_fs_.rom();
|
||||||
|
std::copy(rom.begin(), rom.end(), host_fs_rom_.begin());
|
||||||
|
find_host_fs_hooks();
|
||||||
|
}
|
||||||
|
|
||||||
// Seed key state.
|
// Seed key state.
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
|
|
||||||
@@ -257,7 +270,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Z80::BusHandler.
|
// MARK: - Z80::BusHandler.
|
||||||
@@ -539,8 +552,40 @@ public:
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PartialMachineCycle::Read:
|
|
||||||
case PartialMachineCycle::ReadOpcode:
|
case PartialMachineCycle::ReadOpcode:
|
||||||
|
{
|
||||||
|
static bool print_opcode = false;
|
||||||
|
if(print_opcode) {
|
||||||
|
printf("%04x: %02x\n", address, read_pointers_[address >> 14][address]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potential segue for the host FS. I'm relying on branch prediction to
|
||||||
|
// avoid this cost almost always.
|
||||||
|
if(test_host_fs_traps_ && (address >> 14) == 3) [[unlikely]] {
|
||||||
|
const auto is_trap = host_fs_traps_.contains(address);
|
||||||
|
|
||||||
|
if(is_trap) {
|
||||||
|
using Register = CPU::Z80::Register;
|
||||||
|
|
||||||
|
uint8_t a = uint8_t(z80_.value_of(Register::A));
|
||||||
|
uint16_t bc = z80_.value_of(Register::BC);
|
||||||
|
uint16_t de = z80_.value_of(Register::DE);
|
||||||
|
|
||||||
|
// Grab function code from where the PC actually is, and return a NOP
|
||||||
|
host_fs_.perform(read_pointers_[address >> 14][address], a, bc, de);
|
||||||
|
*cycle.value = 0x00; // i.e. NOP.
|
||||||
|
|
||||||
|
z80_.set_value_of(Register::A, a);
|
||||||
|
z80_.set_value_of(Register::BC, bc);
|
||||||
|
z80_.set_value_of(Register::DE, de);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
case PartialMachineCycle::Read:
|
||||||
if(read_pointers_[address >> 14]) {
|
if(read_pointers_[address >> 14]) {
|
||||||
*cycle.value = read_pointers_[address >> 14][address];
|
*cycle.value = read_pointers_[address >> 14][address];
|
||||||
} else {
|
} else {
|
||||||
@@ -570,13 +615,23 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// MARK: - Memory layout
|
// MARK: - Memory layout
|
||||||
|
|
||||||
std::array<uint8_t, 256 * 1024> ram_{};
|
std::array<uint8_t, 256 * 1024> ram_{};
|
||||||
std::array<uint8_t, 64 * 1024> exos_;
|
std::array<uint8_t, 64 * 1024> exos_;
|
||||||
std::array<uint8_t, 16 * 1024> basic_;
|
std::array<uint8_t, 16 * 1024> basic_;
|
||||||
std::array<uint8_t, 16 * 1024> exdos_rom_;
|
std::array<uint8_t, 16 * 1024> exdos_rom_;
|
||||||
std::array<uint8_t, 32 * 1024> epdos_rom_;
|
std::array<uint8_t, 32 * 1024> epdos_rom_;
|
||||||
|
std::array<uint8_t, 16 * 1024> host_fs_rom_;
|
||||||
const uint8_t min_ram_slot_;
|
const uint8_t min_ram_slot_;
|
||||||
|
|
||||||
|
uint8_t *ram_segment(const uint8_t page) {
|
||||||
|
if(page < min_ram_slot_) return nullptr;
|
||||||
|
const auto ram_floor = (0x100 << 14) - ram_.size();
|
||||||
|
// Each segment is 2^14 bytes long and there are 256 of them. So the Enterprise has a 22-bit address space.
|
||||||
|
// RAM is at the end of that range; `ram_floor` is the 22-bit address at which RAM starts.
|
||||||
|
return &ram_[size_t((page << 14)) - ram_floor];
|
||||||
|
}
|
||||||
|
|
||||||
const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};
|
uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};
|
||||||
@@ -595,20 +650,29 @@ private:
|
|||||||
template <size_t slot> void page(const uint8_t offset) {
|
template <size_t slot> void page(const uint8_t offset) {
|
||||||
pages_[slot] = offset;
|
pages_[slot] = offset;
|
||||||
|
|
||||||
|
if constexpr (slot == 3) {
|
||||||
|
test_host_fs_traps_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
if(page_rom<slot>(offset, 0, exos_)) return;
|
if(page_rom<slot>(offset, 0, exos_)) return;
|
||||||
if(page_rom<slot>(offset, 16, basic_)) return;
|
if(page_rom<slot>(offset, 16, basic_)) return;
|
||||||
if(page_rom<slot>(offset, 32, exdos_rom_)) return;
|
if(page_rom<slot>(offset, 32, exdos_rom_)) return;
|
||||||
if(page_rom<slot>(offset, 48, epdos_rom_)) return;
|
if(page_rom<slot>(offset, 48, epdos_rom_)) return;
|
||||||
|
if(page_rom<slot>(offset, 64, host_fs_rom_)) {
|
||||||
|
if constexpr (slot == 3) {
|
||||||
|
test_host_fs_traps_ = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Of whatever size of RAM I've declared above, use only the final portion.
|
// Of whatever size of RAM I've declared above, use only the final portion.
|
||||||
// This correlated with Nick always having been handed the final 64kb and,
|
// This correlated with Nick always having been handed the final 64kb and,
|
||||||
// at least while the RAM is the first thing declared above, does a little
|
// at least while the RAM is the first thing declared above, does a little
|
||||||
// to benefit data locality. Albeit not in a useful sense.
|
// to benefit data locality. Albeit not in a useful sense.
|
||||||
if(offset >= min_ram_slot_) {
|
if(offset >= min_ram_slot_) {
|
||||||
const auto ram_floor = 4194304 - ram_.size();
|
|
||||||
const size_t address = offset * 0x4000 - ram_floor;
|
|
||||||
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
|
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
|
||||||
page<slot>(&ram_[address], &ram_[address]);
|
auto pointer = ram_segment(offset);
|
||||||
|
page<slot>(pointer, pointer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +685,6 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Memory Timing
|
// MARK: - Memory Timing
|
||||||
|
|
||||||
// The wait mode affects all memory accesses _outside of the video area_.
|
// The wait mode affects all memory accesses _outside of the video area_.
|
||||||
enum class WaitMode {
|
enum class WaitMode {
|
||||||
None,
|
None,
|
||||||
@@ -631,6 +694,7 @@ private:
|
|||||||
bool is_video_[4]{};
|
bool is_video_[4]{};
|
||||||
|
|
||||||
// MARK: - ScanProducer
|
// MARK: - ScanProducer
|
||||||
|
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) override {
|
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) override {
|
||||||
nick_.last_valid()->set_scan_target(scan_target);
|
nick_.last_valid()->set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
@@ -706,11 +770,14 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!media.file_bundles.empty()) {
|
||||||
|
host_fs_.set_file_bundle(media.file_bundles.front());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interrupts
|
// MARK: - Interrupts
|
||||||
|
|
||||||
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
|
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
|
||||||
void set_interrupts(const uint8_t mask, const HalfCycles offset = HalfCycles(0)) {
|
void set_interrupts(const uint8_t mask, const HalfCycles offset = HalfCycles(0)) {
|
||||||
interrupt_state_ |= uint8_t(mask);
|
interrupt_state_ |= uint8_t(mask);
|
||||||
@@ -742,9 +809,69 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - EXDos card.
|
// MARK: - EXDos card.
|
||||||
|
|
||||||
EXDos exdos_;
|
EXDos exdos_;
|
||||||
|
|
||||||
|
// MARK: - Host FS.
|
||||||
|
|
||||||
|
HostFSHandler host_fs_;
|
||||||
|
std::unordered_set<uint16_t> host_fs_traps_;
|
||||||
|
bool test_host_fs_traps_ = false;
|
||||||
|
|
||||||
|
uint8_t hostfs_read(const uint16_t address) override {
|
||||||
|
if(read_pointers_[address >> 14]) {
|
||||||
|
return read_pointers_[address >> 14][address];
|
||||||
|
} else {
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t &user_ram(const uint16_t address) {
|
||||||
|
// "User" accesses go to to wherever the user last had paged;
|
||||||
|
// per 5.4 System Segment Usage those pages are stored in memory from
|
||||||
|
// 0xbffc, so grab from there.
|
||||||
|
const auto page_id = address >> 14;
|
||||||
|
const uint8_t page = read_pointers_[0xbffc >> 14] ? read_pointers_[0xbffc >> 14][0xbffc + page_id] : 0xff;
|
||||||
|
const auto offset = address & 0x3fff;
|
||||||
|
return ram_segment(page)[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t hostfs_user_read(const uint16_t address) override {
|
||||||
|
return user_ram(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hostfs_user_write(const uint16_t address, const uint8_t value) override {
|
||||||
|
user_ram(address) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_host_fs_hooks() {
|
||||||
|
static constexpr uint8_t syscall[] = {
|
||||||
|
0xed, 0xfe, 0xfe
|
||||||
|
};
|
||||||
|
|
||||||
|
auto begin = host_fs_rom_.begin();
|
||||||
|
while(true) {
|
||||||
|
begin = std::search(
|
||||||
|
begin, host_fs_rom_.end(),
|
||||||
|
std::begin(syscall), std::end(syscall)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(begin == host_fs_rom_.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto offset = begin - host_fs_rom_.begin() + 0xc000; // ROM will be paged in slot 3, i.e. at $c000.
|
||||||
|
host_fs_traps_.insert(uint16_t(offset));
|
||||||
|
|
||||||
|
// Move function code up to where this trap was, and NOP out the tail.
|
||||||
|
begin[0] = begin[3];
|
||||||
|
begin[1] = begin[2] = begin[3] = 0x00;
|
||||||
|
begin += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Activity Source
|
// MARK: - Activity Source
|
||||||
|
|
||||||
void set_activity_observer([[maybe_unused]] Activity::Observer *const observer) final {
|
void set_activity_observer([[maybe_unused]] Activity::Observer *const observer) final {
|
||||||
if constexpr (has_disk_controller) {
|
if constexpr (has_disk_controller) {
|
||||||
exdos_.set_activity_observer(observer);
|
exdos_.set_activity_observer(observer);
|
||||||
@@ -752,6 +879,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Configuration options.
|
// MARK: - Configuration options.
|
||||||
|
|
||||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||||
options->output = get_video_signal_configurable();
|
options->output = get_video_signal_configurable();
|
||||||
|
|||||||
1
Machines/Enterprise/HostFS/compile.sh
Executable file
1
Machines/Enterprise/HostFS/compile.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
pyz80.py --obj=hostfs.rom hostfs.z80s && xxd -i hostfs.rom
|
||||||
215
Machines/Enterprise/HostFS/hostfs.z80s
Normal file
215
Machines/Enterprise/HostFS/hostfs.z80s
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
;
|
||||||
|
; Designed for assembly with pyz80, https://github.com/simonowen/pyz80/
|
||||||
|
; E.g. pyz80 --obj=hostfs.rom hostfs.z80s
|
||||||
|
;
|
||||||
|
|
||||||
|
;
|
||||||
|
; Sources:
|
||||||
|
;
|
||||||
|
; http://ep.homeserver.hu/Dokumentacio/Konyvek/EXOS_2.1_technikal_information/exos/kernel/Ch9.html
|
||||||
|
; on signature, device chain pointer and ROM entry point
|
||||||
|
;
|
||||||
|
; http://ep.homeserver.hu/Dokumentacio/Konyvek/EXOS_2.1_technikal_information/exos/kernel/Ch6.html
|
||||||
|
; on the device chain
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
; This code adapts the same mechanism for a host call as that used by EP128Emu's FILE IO ROM.
|
||||||
|
; My original thinking was that one could be substituted for the other to permit comparative testing.
|
||||||
|
; EP128 has a couple of emulator-specific call codes that I don't implement though, and otherwise
|
||||||
|
; doesn't seem to work in this emulator. And likely the converse holds.
|
||||||
|
;
|
||||||
|
hostfscall: macro
|
||||||
|
db 0xed, 0xfe, 0xfe
|
||||||
|
db \0
|
||||||
|
endm
|
||||||
|
|
||||||
|
exoscall: macro
|
||||||
|
rst 0x30
|
||||||
|
db \0
|
||||||
|
endm
|
||||||
|
|
||||||
|
org 0xc000
|
||||||
|
|
||||||
|
dm "EXOS_ROM" ; Standard ROM signature.
|
||||||
|
|
||||||
|
; Pointer to the included device chain, which should be valid when this
|
||||||
|
; ROM is paged at $4000, though when executed from it'll be at $c000.
|
||||||
|
dw 0x4000 + (device_chain & 0x3fff)
|
||||||
|
|
||||||
|
; ROM entry point; handle nothing.
|
||||||
|
ret
|
||||||
|
|
||||||
|
dw 0 ; XX_NEXT_LOW/HI: Pointer to start of next device. There is no next device.
|
||||||
|
dw 0xfffe ; XX_RAM_LOW/HI: [(Amount of host RAM used) + 2] negatived.
|
||||||
|
|
||||||
|
device_chain_type:
|
||||||
|
db 0 ; DD_TYPE: Type, which must be 0.
|
||||||
|
db 0 ; DD_IRQFLAG: No interrupts required.
|
||||||
|
db 0 ; DD_FLAGS: Not a video device.
|
||||||
|
|
||||||
|
dw 0x4000 + (dispatch & 0x3fff)
|
||||||
|
db 0 ; DD_TAB_LOW/HI/SEG:
|
||||||
|
|
||||||
|
db 0 ; DD_UNIT_COUNT: ?
|
||||||
|
device_name:
|
||||||
|
db 4
|
||||||
|
dm "FILE" ; DD_NAME
|
||||||
|
|
||||||
|
device_chain:
|
||||||
|
dw device_chain - device_chain_type
|
||||||
|
|
||||||
|
dispatch:
|
||||||
|
@dispatch: EQU FOR 14
|
||||||
|
dw call{@dispatch}
|
||||||
|
NEXT @dispatch
|
||||||
|
|
||||||
|
;
|
||||||
|
; Interrupt.
|
||||||
|
;
|
||||||
|
; The device chain indicates that this ROM doesn't receive interrupts. So no need to escalate.
|
||||||
|
;
|
||||||
|
call0:
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Open channel.
|
||||||
|
;
|
||||||
|
; EXOS requires the programmer manually to call its function 27 to allocate a channel buffer if
|
||||||
|
; it otherwise expects to succeed. So some handling is most easily done within the client machine.
|
||||||
|
;
|
||||||
|
call1:
|
||||||
|
ld b, a ; Backup the channel number
|
||||||
|
hostfscall 1
|
||||||
|
call allocate_exos_buffer
|
||||||
|
ret z ; Exit on success.
|
||||||
|
|
||||||
|
; Otherwise, close the file and return the EXOS error.
|
||||||
|
ld c, a
|
||||||
|
ld a, b
|
||||||
|
hostfscall 3
|
||||||
|
ld a, c
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Create channel.
|
||||||
|
;
|
||||||
|
call2:
|
||||||
|
ld b, a
|
||||||
|
hostfscall 2
|
||||||
|
call allocate_exos_buffer
|
||||||
|
ret z ; Exit on success.
|
||||||
|
|
||||||
|
; Otherwise, erase the newly-created file and return the EXOS error.
|
||||||
|
ld c, a
|
||||||
|
ld a, b
|
||||||
|
hostfscall 4
|
||||||
|
ld a, c
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Close channel.
|
||||||
|
;
|
||||||
|
call3:
|
||||||
|
hostfscall 3
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Destroy channel.
|
||||||
|
;
|
||||||
|
call4:
|
||||||
|
hostfscall 4
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Read character.
|
||||||
|
;
|
||||||
|
call5:
|
||||||
|
hostfscall 5
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Read block.
|
||||||
|
;
|
||||||
|
call6:
|
||||||
|
hostfscall 6
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Write character.
|
||||||
|
;
|
||||||
|
call7:
|
||||||
|
hostfscall 7
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Write block.
|
||||||
|
;
|
||||||
|
call8:
|
||||||
|
hostfscall 8
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Read channel status.
|
||||||
|
;
|
||||||
|
call9:
|
||||||
|
hostfscall 9
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Set channel status.
|
||||||
|
;
|
||||||
|
call10:
|
||||||
|
hostfscall 10
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Special function.
|
||||||
|
;
|
||||||
|
call11:
|
||||||
|
hostfscall 11
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Initialise.
|
||||||
|
;
|
||||||
|
call12:
|
||||||
|
hostfscall 12
|
||||||
|
|
||||||
|
;
|
||||||
|
; Set this as the default filing system.
|
||||||
|
; Disk dives do this, it's not unprecedented.
|
||||||
|
;
|
||||||
|
ld de, device_name
|
||||||
|
ld c, 1
|
||||||
|
exoscall 19
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Buffer moved.
|
||||||
|
;
|
||||||
|
call13:
|
||||||
|
hostfscall 13
|
||||||
|
ret
|
||||||
|
|
||||||
|
;
|
||||||
|
; Attempts to allocate EXOS storage for a channel.
|
||||||
|
; Returns Z set for success, clear for failure.
|
||||||
|
;
|
||||||
|
allocate_exos_buffer:
|
||||||
|
; Exit immediately if that call already failed.
|
||||||
|
and a
|
||||||
|
ret nz
|
||||||
|
|
||||||
|
; Restore the channel number and otherwise configure to allocate a buffer.
|
||||||
|
push bc
|
||||||
|
ld a, b
|
||||||
|
ld bc, 0
|
||||||
|
ld de, 1
|
||||||
|
exoscall 27
|
||||||
|
|
||||||
|
; If there's no error from that, exit.
|
||||||
|
pop bc
|
||||||
|
and a
|
||||||
|
ret
|
||||||
262
Machines/Enterprise/HostFSHandler.cpp
Normal file
262
Machines/Enterprise/HostFSHandler.cpp
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
//
|
||||||
|
// HostFSHandler.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "HostFSHandler.hpp"
|
||||||
|
#include "EXOSCodes.hpp"
|
||||||
|
|
||||||
|
using namespace Enterprise;
|
||||||
|
|
||||||
|
HostFSHandler::HostFSHandler(MemoryAccessor &accessor) : accessor_(accessor) {}
|
||||||
|
|
||||||
|
void HostFSHandler::perform(const uint8_t function, uint8_t &a, uint16_t &bc, uint16_t &de) {
|
||||||
|
const auto set_error = [&](const EXOS::Error error) {
|
||||||
|
a = uint8_t(error);
|
||||||
|
};
|
||||||
|
const auto set_b = [&](const uint8_t ch) {
|
||||||
|
bc = uint16_t((bc & 0xffff) | (ch << 8));
|
||||||
|
};
|
||||||
|
const auto set_c = [&](const uint8_t ch) {
|
||||||
|
bc = (bc & 0xff00) | (ch);
|
||||||
|
};
|
||||||
|
const auto b = [&]() -> uint8_t {
|
||||||
|
return bc >> 8;
|
||||||
|
};
|
||||||
|
const auto read_name = [&]() {
|
||||||
|
// Get name.
|
||||||
|
uint8_t length = accessor_.hostfs_read(de++);
|
||||||
|
std::string name;
|
||||||
|
while(length--) {
|
||||||
|
name.push_back(char(accessor_.hostfs_read(de++)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the key file if no name is specified.
|
||||||
|
if(name.empty()) {
|
||||||
|
if(const auto key_file = bundle_->key_file(); key_file.has_value()) {
|
||||||
|
name = *key_file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions that don't require an existing channel.
|
||||||
|
//
|
||||||
|
switch(function) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
case uint8_t(EXOS::DeviceDescriptorFunction::Initialise):
|
||||||
|
channels_.clear();
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case uint8_t(EXOS::DeviceDescriptorFunction::Interrupt):
|
||||||
|
case uint8_t(EXOS::DeviceDescriptorFunction::BufferMoved):
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Page 54.
|
||||||
|
// Emprically: C contains the unit number.
|
||||||
|
case uint8_t(EXOS::Function::OpenChannel): {
|
||||||
|
if(a == 255) {
|
||||||
|
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto name = read_name();
|
||||||
|
|
||||||
|
try {
|
||||||
|
channels_.emplace(a, bundle_->open(name, Storage::FileMode::ReadWrite));
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} catch(Storage::FileHolder::Error) {
|
||||||
|
try {
|
||||||
|
channels_.emplace(a, bundle_->open(name, Storage::FileMode::Read));
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} catch(Storage::FileHolder::Error) {
|
||||||
|
// set_error(EXOS::Error::FileDoesNotExist);
|
||||||
|
set_error(EXOS::Error::ProtectionViolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Page 54.
|
||||||
|
case uint8_t(EXOS::Function::CreateChannel): {
|
||||||
|
if(a == 255) {
|
||||||
|
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto name = read_name();
|
||||||
|
|
||||||
|
try {
|
||||||
|
channels_.emplace(a, bundle_->open(name, Storage::FileMode::Rewrite));
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} catch(Storage::FileHolder::Error) {
|
||||||
|
// set_error(EXOS::Error::FileAlreadyExists);
|
||||||
|
set_error(EXOS::Error::ProtectionViolation);
|
||||||
|
}
|
||||||
|
} return;
|
||||||
|
|
||||||
|
case uint8_t(EXOS::Function::SpecialFunction):
|
||||||
|
// Not supported;
|
||||||
|
set_error(EXOS::Error::InvalidSpecialFunctionCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions from here require a channel already open.
|
||||||
|
//
|
||||||
|
const auto channel = channels_.find(a);
|
||||||
|
if(channel == channels_.end()) {
|
||||||
|
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &file = channel->second;
|
||||||
|
|
||||||
|
switch(function) {
|
||||||
|
default:
|
||||||
|
printf("UNIMPLEMENTED function %d with A:%02x BC:%04x DE:%04x\n", function, a, bc, de);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Page 54.
|
||||||
|
case uint8_t(EXOS::Function::CloseChannel):
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
channels_.erase(channel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Page 54.
|
||||||
|
case uint8_t(EXOS::Function::DestroyChannel): {
|
||||||
|
const auto name = file.name();
|
||||||
|
channels_.erase(channel);
|
||||||
|
if(bundle_->erase(name)) {
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} else {
|
||||||
|
set_error(EXOS::Error::ProtectionViolation);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Page 55.
|
||||||
|
case uint8_t(EXOS::Function::ReadCharacter): {
|
||||||
|
const auto next = file.get();
|
||||||
|
if(file.eof()) {
|
||||||
|
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||||
|
} else {
|
||||||
|
set_b(next);
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Page 55.
|
||||||
|
case uint8_t(EXOS::Function::WriteCharacter): {
|
||||||
|
if(file.put(b())) {
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} else {
|
||||||
|
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Page 55.
|
||||||
|
case uint8_t(EXOS::Function::ReadBlock): {
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
while(bc) {
|
||||||
|
const auto next = file.get();
|
||||||
|
if(channel->second.eof()) {
|
||||||
|
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor_.hostfs_user_write(de++, next);
|
||||||
|
--bc;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Page 56.
|
||||||
|
case uint8_t(EXOS::Function::WriteBlock): {
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
while(bc) {
|
||||||
|
const auto next = accessor_.hostfs_user_read(de);
|
||||||
|
if(!file.put(next)) {
|
||||||
|
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++de;
|
||||||
|
--bc;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Page 56.
|
||||||
|
case uint8_t(EXOS::Function::ReadChannelStatus):
|
||||||
|
a = file.eof() ? 0xff : 0x00;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Page 56.
|
||||||
|
case uint8_t(EXOS::Function::SetChannelStatus): {
|
||||||
|
if(bc & 4) {
|
||||||
|
// Protection byte is not supported.
|
||||||
|
set_error(EXOS::Error::FunctionNotSupported);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bc & 1) { // User is requesting a seek.
|
||||||
|
auto pointer = de;
|
||||||
|
uint32_t file_pointer;
|
||||||
|
file_pointer = accessor_.hostfs_user_read(pointer++);
|
||||||
|
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 8);
|
||||||
|
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 16);
|
||||||
|
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 24);
|
||||||
|
|
||||||
|
if(!file.seek(file_pointer, Storage::Whence::SET)) {
|
||||||
|
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in both position and length.
|
||||||
|
set_c(3);
|
||||||
|
const uint32_t file_pointer = uint32_t(file.tell());
|
||||||
|
const uint32_t file_length = uint32_t(file.stats().st_size);
|
||||||
|
|
||||||
|
auto pointer = de;
|
||||||
|
const auto write = [&](const uint32_t source) {
|
||||||
|
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 0));
|
||||||
|
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 8));
|
||||||
|
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 16));
|
||||||
|
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 24));
|
||||||
|
};
|
||||||
|
write(file_pointer);
|
||||||
|
write(file_length);
|
||||||
|
|
||||||
|
set_error(EXOS::Error::NoError);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostFSHandler::set_file_bundle(std::shared_ptr<Storage::FileBundle::FileBundle> bundle) {
|
||||||
|
bundle_ = bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> HostFSHandler::rom() {
|
||||||
|
// Assembled and transcribed from hostfs.z80.
|
||||||
|
return std::vector<uint8_t>{
|
||||||
|
0x45, 0x58, 0x4f, 0x53, 0x5f, 0x52, 0x4f, 0x4d, 0x1b, 0x40, 0xc9, 0x00,
|
||||||
|
0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x1d, 0x40, 0x00, 0x00, 0x04, 0x46,
|
||||||
|
0x49, 0x4c, 0x45, 0x0c, 0x00, 0x39, 0xc0, 0x3a, 0xc0, 0x4b, 0xc0, 0x5c,
|
||||||
|
0xc0, 0x61, 0xc0, 0x66, 0xc0, 0x6b, 0xc0, 0x70, 0xc0, 0x75, 0xc0, 0x7a,
|
||||||
|
0xc0, 0x7f, 0xc0, 0x84, 0xc0, 0x89, 0xc0, 0x95, 0xc0, 0xc9, 0x47, 0xed,
|
||||||
|
0xfe, 0xfe, 0x01, 0xcd, 0x9a, 0xc0, 0xc8, 0x4f, 0x78, 0xed, 0xfe, 0xfe,
|
||||||
|
0x03, 0x79, 0xc9, 0x47, 0xed, 0xfe, 0xfe, 0x02, 0xcd, 0x9a, 0xc0, 0xc8,
|
||||||
|
0x4f, 0x78, 0xed, 0xfe, 0xfe, 0x04, 0x79, 0xc9, 0xed, 0xfe, 0xfe, 0x03,
|
||||||
|
0xc9, 0xed, 0xfe, 0xfe, 0x04, 0xc9, 0xed, 0xfe, 0xfe, 0x05, 0xc9, 0xed,
|
||||||
|
0xfe, 0xfe, 0x06, 0xc9, 0xed, 0xfe, 0xfe, 0x07, 0xc9, 0xed, 0xfe, 0xfe,
|
||||||
|
0x08, 0xc9, 0xed, 0xfe, 0xfe, 0x09, 0xc9, 0xed, 0xfe, 0xfe, 0x0a, 0xc9,
|
||||||
|
0xed, 0xfe, 0xfe, 0x0b, 0xc9, 0xed, 0xfe, 0xfe, 0x0c, 0x11, 0x16, 0xc0,
|
||||||
|
0x0e, 0x01, 0xf7, 0x13, 0xc9, 0xed, 0xfe, 0xfe, 0x0d, 0xc9, 0xa7, 0xc0,
|
||||||
|
0xc5, 0x78, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, 0xf7, 0x1b, 0xc1, 0xa7,
|
||||||
|
0xc9
|
||||||
|
};
|
||||||
|
}
|
||||||
52
Machines/Enterprise/HostFSHandler.hpp
Normal file
52
Machines/Enterprise/HostFSHandler.hpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// HostFSHandler.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Storage/FileBundle/FileBundle.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Enterprise {
|
||||||
|
|
||||||
|
struct HostFSHandler {
|
||||||
|
struct MemoryAccessor {
|
||||||
|
// Accessors that read from however the Z80's 64kb is currently laid out.
|
||||||
|
virtual uint8_t hostfs_read(uint16_t) = 0;
|
||||||
|
// virtual void hostfs_write(uint16_t, uint8_t) = 0;
|
||||||
|
|
||||||
|
// Accessors that read from 'user' address space, i.e. the 64kb Z80 address space as currently
|
||||||
|
// mapped according to the user's preference.
|
||||||
|
virtual uint8_t hostfs_user_read(uint16_t) = 0;
|
||||||
|
virtual void hostfs_user_write(uint16_t, uint8_t) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
HostFSHandler(MemoryAccessor &);
|
||||||
|
|
||||||
|
/// Perform the internally-defined @c function given other provided state.
|
||||||
|
/// These function calls mostly align with those in EXOSCodes.hpp
|
||||||
|
void perform(uint8_t function, uint8_t &a, uint16_t &bc, uint16_t &de);
|
||||||
|
|
||||||
|
/// Sets the bundle of files on which this handler should operate.
|
||||||
|
void set_file_bundle(std::shared_ptr<Storage::FileBundle::FileBundle> bundle);
|
||||||
|
|
||||||
|
/// @returns A suitable in-client filing system ROM.
|
||||||
|
std::vector<uint8_t> rom();
|
||||||
|
|
||||||
|
private:
|
||||||
|
MemoryAccessor &accessor_;
|
||||||
|
std::shared_ptr<Storage::FileBundle::FileBundle> bundle_;
|
||||||
|
|
||||||
|
using ChannelHandler = uint8_t;
|
||||||
|
std::unordered_map<ChannelHandler, Storage::FileHolder> channels_;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
@@ -360,7 +360,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
speaker_.audio_queue.flush();
|
speaker_.audio_queue.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeEffect effect_for_file_did_change(const std::string &) const final {
|
ChangeEffect effect_for_file_did_change(const std::string &) const final {
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||||
|
|||||||
@@ -817,7 +817,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
speaker_.queue.flush();
|
speaker_.queue.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TimedMachine.
|
// MARK: - TimedMachine.
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
audio_queue_.flush();
|
audio_queue_.lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr unsigned int clock_rate() {
|
static constexpr unsigned int clock_rate() {
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ struct SizedInt {
|
|||||||
constexpr SizedInt(const IntT start_value) noexcept : counter_(start_value & Mask) {}
|
constexpr SizedInt(const IntT start_value) noexcept : counter_(start_value & Mask) {}
|
||||||
SizedInt() = default;
|
SizedInt() = default;
|
||||||
|
|
||||||
|
template <int begin = 0>
|
||||||
IntT get() const {
|
IntT get() const {
|
||||||
return counter_;
|
return counter_ >> begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
SizedInt operator +(const SizedInt offset) const { return SizedInt<bits>(counter_ + offset.counter_); }
|
SizedInt operator +(const SizedInt offset) const { return SizedInt<bits>(counter_ + offset.counter_); }
|
||||||
|
|||||||
@@ -753,6 +753,10 @@
|
|||||||
4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; };
|
4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; };
|
||||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||||
4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; };
|
4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; };
|
||||||
|
4B96DECC2EBEE7D100505298 /* SID.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96DECA2EBEE7D100505298 /* SID.cpp */; };
|
||||||
|
4B96DECD2EBEE7D100505298 /* SID.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96DECA2EBEE7D100505298 /* SID.cpp */; };
|
||||||
|
4B96DECE2EBEE7D100505298 /* SID.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96DECA2EBEE7D100505298 /* SID.cpp */; };
|
||||||
|
4B96DED32EC3ECDA00505298 /* SIDTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B96DED22EC3ECDA00505298 /* SIDTests.mm */; };
|
||||||
4B96F7CE263E33B10092AEE1 /* DSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7CC263E33B10092AEE1 /* DSK.cpp */; };
|
4B96F7CE263E33B10092AEE1 /* DSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7CC263E33B10092AEE1 /* DSK.cpp */; };
|
||||||
4B96F7CF263E33B10092AEE1 /* DSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7CC263E33B10092AEE1 /* DSK.cpp */; };
|
4B96F7CF263E33B10092AEE1 /* DSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7CC263E33B10092AEE1 /* DSK.cpp */; };
|
||||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; };
|
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; };
|
||||||
@@ -1144,6 +1148,12 @@
|
|||||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||||
4BCE1DF125D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
4BCE1DF125D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
||||||
4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
||||||
|
4BCF1ACF2ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||||
|
4BCF1AD02ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||||
|
4BCF1AD12ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||||
|
4BCF1AD52ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||||
|
4BCF1AD62ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||||
|
4BCF1AD72ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
||||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||||
@@ -1952,6 +1962,10 @@
|
|||||||
4B95FA9C1F11893B0008E395 /* ZX8081Controller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081Controller.swift; sourceTree = "<group>"; };
|
4B95FA9C1F11893B0008E395 /* ZX8081Controller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081Controller.swift; sourceTree = "<group>"; };
|
||||||
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
|
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
|
||||||
4B96DEC32EBEA88C00505298 /* TubeProcessor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TubeProcessor.hpp; sourceTree = "<group>"; };
|
4B96DEC32EBEA88C00505298 /* TubeProcessor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TubeProcessor.hpp; sourceTree = "<group>"; };
|
||||||
|
4B96DEC92EBEE7D100505298 /* SID.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SID.hpp; sourceTree = "<group>"; };
|
||||||
|
4B96DECA2EBEE7D100505298 /* SID.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SID.cpp; sourceTree = "<group>"; };
|
||||||
|
4B96DED22EC3ECDA00505298 /* SIDTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SIDTests.mm; sourceTree = "<group>"; };
|
||||||
|
4B96DED42EC53BC300505298 /* BiquadFilter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BiquadFilter.hpp; sourceTree = "<group>"; };
|
||||||
4B96F7CB263E30B00092AEE1 /* RawSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RawSectorDump.hpp; sourceTree = "<group>"; };
|
4B96F7CB263E30B00092AEE1 /* RawSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RawSectorDump.hpp; sourceTree = "<group>"; };
|
||||||
4B96F7CC263E33B10092AEE1 /* DSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DSK.cpp; sourceTree = "<group>"; };
|
4B96F7CC263E33B10092AEE1 /* DSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DSK.cpp; sourceTree = "<group>"; };
|
||||||
4B96F7CD263E33B10092AEE1 /* DSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DSK.hpp; sourceTree = "<group>"; };
|
4B96F7CD263E33B10092AEE1 /* DSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DSK.hpp; sourceTree = "<group>"; };
|
||||||
@@ -2417,6 +2431,11 @@
|
|||||||
4BCE005F227D39AB000CA200 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
4BCE005F227D39AB000CA200 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||||
4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Bus.cpp; sourceTree = "<group>"; };
|
4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Bus.cpp; sourceTree = "<group>"; };
|
||||||
4BCE1DF025D4C3FA00AE7A2B /* Bus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
|
4BCE1DF025D4C3FA00AE7A2B /* Bus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
|
||||||
|
4BCF1ACC2ECE759000109999 /* FileBundle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FileBundle.hpp; sourceTree = "<group>"; };
|
||||||
|
4BCF1ACD2ECE759000109999 /* FileBundle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FileBundle.cpp; sourceTree = "<group>"; };
|
||||||
|
4BCF1AD22ECF743500109999 /* EXOSCodes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EXOSCodes.hpp; sourceTree = "<group>"; };
|
||||||
|
4BCF1AD32ECF884100109999 /* HostFSHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = HostFSHandler.hpp; sourceTree = "<group>"; };
|
||||||
|
4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = HostFSHandler.cpp; sourceTree = "<group>"; };
|
||||||
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Oric.cpp; sourceTree = "<group>"; };
|
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Oric.cpp; sourceTree = "<group>"; };
|
||||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Oric.hpp; sourceTree = "<group>"; };
|
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Oric.hpp; sourceTree = "<group>"; };
|
||||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||||
@@ -2692,11 +2711,14 @@
|
|||||||
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */,
|
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */,
|
||||||
4B051CA12676F52200CA44E8 /* Enterprise.cpp */,
|
4B051CA12676F52200CA44E8 /* Enterprise.cpp */,
|
||||||
4B051CB42680158600CA44E8 /* EXDos.cpp */,
|
4B051CB42680158600CA44E8 /* EXDos.cpp */,
|
||||||
|
4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */,
|
||||||
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */,
|
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */,
|
||||||
4B051CAA26783E2000CA44E8 /* Nick.cpp */,
|
4B051CAA26783E2000CA44E8 /* Nick.cpp */,
|
||||||
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */,
|
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */,
|
||||||
4B051CA02676F52200CA44E8 /* Enterprise.hpp */,
|
4B051CA02676F52200CA44E8 /* Enterprise.hpp */,
|
||||||
4B051CB52680158600CA44E8 /* EXDos.hpp */,
|
4B051CB52680158600CA44E8 /* EXDos.hpp */,
|
||||||
|
4BCF1AD22ECF743500109999 /* EXOSCodes.hpp */,
|
||||||
|
4BCF1AD32ECF884100109999 /* HostFSHandler.hpp */,
|
||||||
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */,
|
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */,
|
||||||
4B051CAB26783E2000CA44E8 /* Nick.hpp */,
|
4B051CAB26783E2000CA44E8 /* Nick.hpp */,
|
||||||
);
|
);
|
||||||
@@ -3017,6 +3039,7 @@
|
|||||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */,
|
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */,
|
||||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */,
|
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */,
|
||||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */,
|
4B24095A1C45DF85004DA684 /* Stepper.hpp */,
|
||||||
|
4B96DED42EC53BC300505298 /* BiquadFilter.hpp */,
|
||||||
);
|
);
|
||||||
name = SignalProcessing;
|
name = SignalProcessing;
|
||||||
path = ../../SignalProcessing;
|
path = ../../SignalProcessing;
|
||||||
@@ -3578,6 +3601,7 @@
|
|||||||
4BEE0A691D72496600532C7B /* Cartridge */,
|
4BEE0A691D72496600532C7B /* Cartridge */,
|
||||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||||
|
4BCF1ACE2ECE759000109999 /* FileBundle */,
|
||||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
|
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
|
||||||
4B8DD3832634D37E00B3C866 /* State */,
|
4B8DD3832634D37E00B3C866 /* State */,
|
||||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||||
@@ -4354,6 +4378,15 @@
|
|||||||
path = 68901;
|
path = 68901;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4B96DECB2EBEE7D100505298 /* SID */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4B96DEC92EBEE7D100505298 /* SID.hpp */,
|
||||||
|
4B96DECA2EBEE7D100505298 /* SID.cpp */,
|
||||||
|
);
|
||||||
|
path = SID;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4B9F11C72272375400701480 /* QL Startup */ = {
|
4B9F11C72272375400701480 /* QL Startup */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -4872,6 +4905,7 @@
|
|||||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||||
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
|
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
|
||||||
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
|
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
|
||||||
|
4B96DED22EC3ECDA00505298 /* SIDTests.mm */,
|
||||||
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
|
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
|
||||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||||
@@ -5168,6 +5202,7 @@
|
|||||||
4BF0BC6E2973318E00CCA2B5 /* RP5C01 */,
|
4BF0BC6E2973318E00CCA2B5 /* RP5C01 */,
|
||||||
4B8855A42E84D51B00E251DD /* SAA5050 */,
|
4B8855A42E84D51B00E251DD /* SAA5050 */,
|
||||||
4B0ACBFF237756EC008902D0 /* Serial */,
|
4B0ACBFF237756EC008902D0 /* Serial */,
|
||||||
|
4B96DECB2EBEE7D100505298 /* SID */,
|
||||||
4BB0A6582044FD3000FB3688 /* SN76489 */,
|
4BB0A6582044FD3000FB3688 /* SN76489 */,
|
||||||
4B47F3B42E7B9A14005D4DEC /* uPD7002 */,
|
4B47F3B42E7B9A14005D4DEC /* uPD7002 */,
|
||||||
);
|
);
|
||||||
@@ -5269,6 +5304,15 @@
|
|||||||
path = ADB;
|
path = ADB;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4BCF1ACE2ECE759000109999 /* FileBundle */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4BCF1ACC2ECE759000109999 /* FileBundle.hpp */,
|
||||||
|
4BCF1ACD2ECE759000109999 /* FileBundle.cpp */,
|
||||||
|
);
|
||||||
|
path = FileBundle;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -6163,6 +6207,7 @@
|
|||||||
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B47F3B62E7BAB94005D4DEC /* uPD7002.cpp in Sources */,
|
4B47F3B62E7BAB94005D4DEC /* uPD7002.cpp in Sources */,
|
||||||
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
|
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
|
||||||
|
4B96DECD2EBEE7D100505298 /* SID.cpp in Sources */,
|
||||||
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */,
|
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */,
|
||||||
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */,
|
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */,
|
||||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
|
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||||
@@ -6176,6 +6221,7 @@
|
|||||||
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
||||||
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */,
|
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */,
|
||||||
4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */,
|
4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */,
|
||||||
|
4BCF1AD12ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||||
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
|
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
|
||||||
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */,
|
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */,
|
||||||
4B7C681727517A59001671EC /* Sprites.cpp in Sources */,
|
4B7C681727517A59001671EC /* Sprites.cpp in Sources */,
|
||||||
@@ -6318,6 +6364,7 @@
|
|||||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||||
4B8DF506254E3C9D00F3433C /* ADB.cpp in Sources */,
|
4B8DF506254E3C9D00F3433C /* ADB.cpp in Sources */,
|
||||||
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
|
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
|
||||||
|
4BCF1AD72ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||||
4B8318BC22D3E588006DB630 /* DisplayMetrics.cpp in Sources */,
|
4B8318BC22D3E588006DB630 /* DisplayMetrics.cpp in Sources */,
|
||||||
4BEDA40E25B2844B000C2DBD /* Decoder.cpp in Sources */,
|
4BEDA40E25B2844B000C2DBD /* Decoder.cpp in Sources */,
|
||||||
@@ -6411,6 +6458,7 @@
|
|||||||
4B1082C42C1F5E7D00B07C5D /* CSL.cpp in Sources */,
|
4B1082C42C1F5E7D00B07C5D /* CSL.cpp in Sources */,
|
||||||
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
|
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
|
||||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
||||||
|
4BCF1AD62ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
||||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
||||||
@@ -6476,6 +6524,7 @@
|
|||||||
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
|
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||||
4B051C95266EF50200CA44E8 /* AppleIIController.swift in Sources */,
|
4B051C95266EF50200CA44E8 /* AppleIIController.swift in Sources */,
|
||||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||||
|
4BCF1ACF2ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
|
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
|
||||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||||
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
||||||
@@ -6557,6 +6606,7 @@
|
|||||||
4BC080D026A257A200D03FD8 /* StaticAnalyser.cpp in Sources */,
|
4BC080D026A257A200D03FD8 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
|
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
|
||||||
|
4B96DECC2EBEE7D100505298 /* SID.cpp in Sources */,
|
||||||
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
|
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
|
||||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||||
429B13602B1F7BDA006BB4CB /* StaticAnalyser.cpp in Sources */,
|
429B13602B1F7BDA006BB4CB /* StaticAnalyser.cpp in Sources */,
|
||||||
@@ -6683,6 +6733,7 @@
|
|||||||
4B06AAF82C6460760034D014 /* IntelligentKeyboard.cpp in Sources */,
|
4B06AAF82C6460760034D014 /* IntelligentKeyboard.cpp in Sources */,
|
||||||
4B7752B628217EE70073E2C5 /* DSK.cpp in Sources */,
|
4B7752B628217EE70073E2C5 /* DSK.cpp in Sources */,
|
||||||
4B06AAD12C645F130034D014 /* 1770.cpp in Sources */,
|
4B06AAD12C645F130034D014 /* 1770.cpp in Sources */,
|
||||||
|
4B96DECE2EBEE7D100505298 /* SID.cpp in Sources */,
|
||||||
4B778F2523A5EDF40000D260 /* Encoder.cpp in Sources */,
|
4B778F2523A5EDF40000D260 /* Encoder.cpp in Sources */,
|
||||||
4B778F4223A5F1A70000D260 /* MemoryFuzzer.cpp in Sources */,
|
4B778F4223A5F1A70000D260 /* MemoryFuzzer.cpp in Sources */,
|
||||||
4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */,
|
4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */,
|
||||||
@@ -6778,6 +6829,7 @@
|
|||||||
4B778F4623A5F1D80000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F4623A5F1D80000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B778F1323A5EC890000D260 /* Z80Base.cpp in Sources */,
|
4B778F1323A5EC890000D260 /* Z80Base.cpp in Sources */,
|
||||||
4B778F2923A5EF030000D260 /* CommodoreROM.cpp in Sources */,
|
4B778F2923A5EF030000D260 /* CommodoreROM.cpp in Sources */,
|
||||||
|
4B96DED32EC3ECDA00505298 /* SIDTests.mm in Sources */,
|
||||||
4B06AADC2C645F720034D014 /* BD500.cpp in Sources */,
|
4B06AADC2C645F720034D014 /* BD500.cpp in Sources */,
|
||||||
4B778F4823A5F1E70000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F4823A5F1E70000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||||
@@ -6833,8 +6885,10 @@
|
|||||||
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
|
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
|
||||||
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
|
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
|
||||||
4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */,
|
4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */,
|
||||||
|
4BCF1AD02ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||||
4B06AAF72C64606E0034D014 /* DiskII.cpp in Sources */,
|
4B06AAF72C64606E0034D014 /* DiskII.cpp in Sources */,
|
||||||
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,
|
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,
|
||||||
|
4BCF1AD52ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||||
4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */,
|
4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */,
|
||||||
4B06AAE12C645F8B0034D014 /* Video.cpp in Sources */,
|
4B06AAE12C645F8B0034D014 /* Video.cpp in Sources */,
|
||||||
|
|||||||
@@ -807,6 +807,29 @@
|
|||||||
<key>NSDocumentClass</key>
|
<key>NSDocumentClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>com</string>
|
||||||
|
<string>bas</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Enterprise Executable</string>
|
||||||
|
<key>CFBundleTypeOSTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>????</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Owner</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSDocumentClass</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24128" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24412" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24128"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24412"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ SOURCES += \
|
|||||||
$$SRC/Components/KonamiSCC/*.cpp \
|
$$SRC/Components/KonamiSCC/*.cpp \
|
||||||
$$SRC/Components/OPx/*.cpp \
|
$$SRC/Components/OPx/*.cpp \
|
||||||
$$SRC/Components/RP5C01/*.cpp \
|
$$SRC/Components/RP5C01/*.cpp \
|
||||||
|
$$SRC/Components/SID/*.cpp \
|
||||||
$$SRC/Components/SAA5050/*.cpp \
|
$$SRC/Components/SAA5050/*.cpp \
|
||||||
$$SRC/Components/Serial/*.cpp \
|
$$SRC/Components/Serial/*.cpp \
|
||||||
$$SRC/Components/SN76489/*.cpp \
|
$$SRC/Components/SN76489/*.cpp \
|
||||||
@@ -145,6 +146,7 @@ SOURCES += \
|
|||||||
$$SRC/Storage/Disk/Encodings/MFM/*.cpp \
|
$$SRC/Storage/Disk/Encodings/MFM/*.cpp \
|
||||||
$$SRC/Storage/Disk/Parsers/*.cpp \
|
$$SRC/Storage/Disk/Parsers/*.cpp \
|
||||||
$$SRC/Storage/Disk/Track/*.cpp \
|
$$SRC/Storage/Disk/Track/*.cpp \
|
||||||
|
$$SRC/Storage/FileBundle/*.cpp \
|
||||||
$$SRC/Storage/MassStorage/*.cpp \
|
$$SRC/Storage/MassStorage/*.cpp \
|
||||||
$$SRC/Storage/MassStorage/Encodings/*.cpp \
|
$$SRC/Storage/MassStorage/Encodings/*.cpp \
|
||||||
$$SRC/Storage/MassStorage/Formats/*.cpp \
|
$$SRC/Storage/MassStorage/Formats/*.cpp \
|
||||||
@@ -213,6 +215,7 @@ HEADERS += \
|
|||||||
$$SRC/Components/OPx/*.hpp \
|
$$SRC/Components/OPx/*.hpp \
|
||||||
$$SRC/Components/OPx/Implementation/*.hpp \
|
$$SRC/Components/OPx/Implementation/*.hpp \
|
||||||
$$SRC/Components/RP5C01/*.hpp \
|
$$SRC/Components/RP5C01/*.hpp \
|
||||||
|
$$SRC/Components/SID/*.hpp \
|
||||||
$$SRC/Components/SAA5050/*.hpp \
|
$$SRC/Components/SAA5050/*.hpp \
|
||||||
$$SRC/Components/Serial/*.hpp \
|
$$SRC/Components/Serial/*.hpp \
|
||||||
$$SRC/Components/SN76489/*.hpp \
|
$$SRC/Components/SN76489/*.hpp \
|
||||||
@@ -301,6 +304,7 @@ HEADERS += \
|
|||||||
$$SRC/Storage/Disk/Encodings/MFM/*.hpp \
|
$$SRC/Storage/Disk/Encodings/MFM/*.hpp \
|
||||||
$$SRC/Storage/Disk/Parsers/*.hpp \
|
$$SRC/Storage/Disk/Parsers/*.hpp \
|
||||||
$$SRC/Storage/Disk/Track/*.hpp \
|
$$SRC/Storage/Disk/Track/*.hpp \
|
||||||
|
$$SRC/Storage/FileBundle/*.hpp \
|
||||||
$$SRC/Storage/MassStorage/*.hpp \
|
$$SRC/Storage/MassStorage/*.hpp \
|
||||||
$$SRC/Storage/MassStorage/Encodings/*.hpp \
|
$$SRC/Storage/MassStorage/Encodings/*.hpp \
|
||||||
$$SRC/Storage/MassStorage/Formats/*.hpp \
|
$$SRC/Storage/MassStorage/Formats/*.hpp \
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
|
|||||||
SOURCES += glob.glob('../../Components/OPx/*.cpp')
|
SOURCES += glob.glob('../../Components/OPx/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/RP5C01/*.cpp')
|
SOURCES += glob.glob('../../Components/RP5C01/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/SAA5050/*.cpp')
|
SOURCES += glob.glob('../../Components/SAA5050/*.cpp')
|
||||||
|
SOURCES += glob.glob('../../Components/SID/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
|
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/Serial/*.cpp')
|
SOURCES += glob.glob('../../Components/Serial/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/uPD7002/*.cpp')
|
SOURCES += glob.glob('../../Components/uPD7002/*.cpp')
|
||||||
@@ -133,6 +134,7 @@ SOURCES += glob.glob('../../Storage/Disk/Encodings/MFM/*.cpp')
|
|||||||
SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp')
|
SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp')
|
||||||
SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp')
|
SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp')
|
||||||
SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp')
|
SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp')
|
||||||
|
SOURCES += glob.glob('../../Storage/FileBundle/*.cpp')
|
||||||
SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
|
SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
|
||||||
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
|
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
|
||||||
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
|
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ enum class Line {
|
|||||||
PowerOn,
|
PowerOn,
|
||||||
Overflow,
|
Overflow,
|
||||||
NMI,
|
NMI,
|
||||||
|
Ready,
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: - Address bus.
|
// MARK: - Address bus.
|
||||||
@@ -185,14 +186,24 @@ public:
|
|||||||
(inputs_.interrupt_requests & Inputs::InterruptRequest::PowerOn);
|
(inputs_.interrupt_requests & Inputs::InterruptRequest::PowerOn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Level triggered.
|
// Level triggered interrupts.
|
||||||
case Line::Reset: level_sample(Inputs::InterruptRequest::Reset); break;
|
case Line::Reset: level_sample(Inputs::InterruptRequest::Reset); break;
|
||||||
case Line::IRQ: level_sample(Inputs::InterruptRequest::IRQ); break;
|
case Line::IRQ: level_sample(Inputs::InterruptRequest::IRQ); break;
|
||||||
|
|
||||||
// Edge triggered.
|
// Edge triggered interrupts.
|
||||||
case Line::Overflow: edge_sample(Inputs::InterruptRequest::Reset, inputs_.overflow); break;
|
|
||||||
case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break;
|
case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break;
|
||||||
|
|
||||||
|
// Leval-capturing state.
|
||||||
|
case Line::Ready: inputs_.ready = value; break;
|
||||||
|
|
||||||
|
// Edge-triggered state.
|
||||||
|
case Line::Overflow:
|
||||||
|
if(!inputs_.overflow && value) {
|
||||||
|
registers_.flags.set_per<Flag::Overflow>(Flag::Overflow);
|
||||||
|
}
|
||||||
|
inputs_.overflow = value;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
|||||||
214
SignalProcessing/BiquadFilter.hpp
Normal file
214
SignalProcessing/BiquadFilter.hpp
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// BiquadFilter.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 12/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
|
namespace SignalProcessing {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A biquad[ratic] filter approximates the real analogue thing in taking a 1d PCM signal and applying a
|
||||||
|
filter to it as a function of the current input plus the two most-recent inputs plus the two most-recent outputs.
|
||||||
|
|
||||||
|
So both IIR and three-tap FIR filters are degenerate cases of the biquad.
|
||||||
|
|
||||||
|
It is used quite often in real designs, hence an implementation of this filter specifically.
|
||||||
|
|
||||||
|
... and the below is largely textbook; I can't claim any great knowledge. I am especially indebted to
|
||||||
|
the W3C Group's audio EQ cookbook at https://www.w3.org/TR/audio-eq-cookbook/ .
|
||||||
|
*/
|
||||||
|
class BiquadFilter {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
LowPass,
|
||||||
|
HighPass,
|
||||||
|
BandPass,
|
||||||
|
Notch,
|
||||||
|
AllPass,
|
||||||
|
Peaking,
|
||||||
|
LowShelf,
|
||||||
|
HighShelf
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default construction: a filter that produces _nothing_.
|
||||||
|
BiquadFilter() {}
|
||||||
|
|
||||||
|
BiquadFilter(
|
||||||
|
const Type type,
|
||||||
|
const float sample_rate,
|
||||||
|
const float frequency,
|
||||||
|
const float resonance = 0.707f,
|
||||||
|
const float gain = 8,
|
||||||
|
const bool normalise = true
|
||||||
|
) {
|
||||||
|
configure(type, sample_rate, frequency, resonance, gain, normalise);
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure(
|
||||||
|
const Type type,
|
||||||
|
const float sample_rate,
|
||||||
|
const float frequency,
|
||||||
|
const float resonance = 0.707f,
|
||||||
|
const float gain = 8,
|
||||||
|
const bool normalise = true
|
||||||
|
) {
|
||||||
|
const float w0 = 2.0f * std::numbers::pi_v<float> * frequency / sample_rate;
|
||||||
|
const float alpha = std::sin(w0) / (2.0f * resonance);
|
||||||
|
const float cos_w0 = std::cos(w0);
|
||||||
|
|
||||||
|
float coefficients[5];
|
||||||
|
float magnitude = 1.0f;
|
||||||
|
switch(type) {
|
||||||
|
case Type::LowPass:
|
||||||
|
coefficients[0] = (1.0f - cos_w0) / 2.0f;
|
||||||
|
coefficients[1] = 1.0f - cos_w0;
|
||||||
|
coefficients[2] = (1.0f - cos_w0) / 2.0f;
|
||||||
|
magnitude = 1.0f + alpha;
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[4] = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::HighPass:
|
||||||
|
coefficients[0] = (1.0f - cos_w0) / 2.0f;
|
||||||
|
coefficients[1] = -(1.0f + cos_w0);
|
||||||
|
coefficients[2] = (1.0f - cos_w0) / 2.0f;
|
||||||
|
magnitude = 1.0f + alpha;
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[4] = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::BandPass:
|
||||||
|
coefficients[0] = alpha;
|
||||||
|
coefficients[1] = 0.0f;
|
||||||
|
coefficients[2] = -alpha;
|
||||||
|
magnitude = 1.0f + alpha;
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[0] = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::Notch:
|
||||||
|
coefficients[0] = 1.0f;
|
||||||
|
coefficients[1] = -2.0f * cos_w0;
|
||||||
|
coefficients[2] = 1.0f;
|
||||||
|
magnitude = 1.0f + alpha;
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[4] = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::AllPass:
|
||||||
|
coefficients[0] = 1.0f - alpha;
|
||||||
|
coefficients[1] = -2.0f * cos_w0;
|
||||||
|
coefficients[2] = 1.0f + alpha;
|
||||||
|
magnitude = 1.0f + alpha;
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[4] = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::Peaking: {
|
||||||
|
const float a = std::pow(10.0f, gain / 40.0f);
|
||||||
|
|
||||||
|
coefficients[0] = 1.0f + (alpha * a);
|
||||||
|
coefficients[1] = -2.0f * cos_w0;
|
||||||
|
coefficients[2] = 1.0f - (alpha * a);
|
||||||
|
magnitude = 1.0f + (alpha / a);
|
||||||
|
coefficients[3] = -2.0f * cos_w0;
|
||||||
|
coefficients[4] = 1.0f - (alpha / a);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Type::LowShelf: {
|
||||||
|
const float a_ls = std::pow(10.0f, gain / 40.0f);
|
||||||
|
const float sqrt_a = std::sqrt(a_ls);
|
||||||
|
const float alpha_ls =
|
||||||
|
std::sin(w0) / 2.0f * std::sqrt((a_ls + 1.0f / a_ls) * (1.0f / resonance - 1.0f) + 2.0f);
|
||||||
|
|
||||||
|
coefficients[0] = a_ls * ((a_ls + 1.0f) - (a_ls - 1.0f) * cos_w0 + 2.0f * sqrt_a * alpha_ls);
|
||||||
|
coefficients[1] = 2.0f * a_ls * ((a_ls - 1.0f) - (a_ls + 1.0f) * cos_w0);
|
||||||
|
coefficients[2] = a_ls * ((a_ls + 1.0f) - (a_ls - 1.0f) * cos_w0 - 2.0f * sqrt_a * alpha_ls);
|
||||||
|
magnitude = (a_ls + 1.0f) + (a_ls - 1.0f) * cos_w0 + 2.0f * sqrt_a * alpha_ls;
|
||||||
|
coefficients[3] = -2.0f * ((a_ls - 1) + (a_ls + 1) * cos_w0);
|
||||||
|
coefficients[4] = (a_ls + 1.0f) + (a_ls - 1.0f) * cos_w0 - 2.0f * sqrt_a * alpha_ls;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Type::HighShelf: {
|
||||||
|
const float a_hs = std::pow(10.0f, gain / 40.0f);
|
||||||
|
const float sqrt_a_hs = std::sqrt(a_hs);
|
||||||
|
const float alpha_hs =
|
||||||
|
std::sin(w0) / 2.0f * std::sqrt((a_hs + 1.0f / a_hs) * (1.0f / resonance - 1.0f) + 2.0f);
|
||||||
|
|
||||||
|
coefficients[0] = a_hs * ((a_hs + 1.0f) + (a_hs - 1.0f) * cos_w0 + 2.0f * sqrt_a_hs * alpha_hs);
|
||||||
|
coefficients[1] = -2.0f * a_hs * ((a_hs - 1.0f) + (a_hs + 1.0f) * cos_w0);
|
||||||
|
coefficients[2] = a_hs * ((a_hs + 1.0f) + (a_hs - 1.0f) * cos_w0 - 2.0f * sqrt_a_hs * alpha_hs);
|
||||||
|
magnitude = (a_hs + 1.0f) - (a_hs - 1.0f) * cos_w0 + 2.0f * sqrt_a_hs * alpha_hs;
|
||||||
|
coefficients[3] = 2.0f * ((a_hs - 1.0f) - (a_hs + 1.0f) * cos_w0);
|
||||||
|
coefficients[4] = (a_hs + 1.0f) - (a_hs - 1.0f) * cos_w0 - 2.0f * sqrt_a_hs * alpha_hs;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(normalise) {
|
||||||
|
for(int c = 0; c < 5; c++) {
|
||||||
|
coefficients[c] /= magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int c = 0; c < 5; c++) {
|
||||||
|
#ifdef FIXED
|
||||||
|
coefficients_[c] = FixedType(coefficients[c] * FixedMultiplier);
|
||||||
|
#else
|
||||||
|
coefficients_[c] = coefficients[c];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t apply(const int16_t input) {
|
||||||
|
#ifdef FIXED
|
||||||
|
const auto applied =
|
||||||
|
coefficients_[0] * input +
|
||||||
|
coefficients_[1] * inputs_[0] +
|
||||||
|
coefficients_[2] * inputs_[1] -
|
||||||
|
coefficients_[3] * outputs_[0] -
|
||||||
|
coefficients_[4] * outputs_[1];
|
||||||
|
|
||||||
|
const auto output = int16_t(applied >> FixedShift);
|
||||||
|
#else
|
||||||
|
const float output =
|
||||||
|
coefficients_[0] * float(input) +
|
||||||
|
coefficients_[1] * inputs_[0] +
|
||||||
|
coefficients_[2] * inputs_[1] -
|
||||||
|
coefficients_[3] * outputs_[0] -
|
||||||
|
coefficients_[4] * outputs_[1];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inputs_[1] = inputs_[0];
|
||||||
|
inputs_[0] = input;
|
||||||
|
outputs_[1] = outputs_[0];
|
||||||
|
outputs_[0] = output;
|
||||||
|
|
||||||
|
return int16_t(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef FIXED
|
||||||
|
int16_t inputs_[2]{};
|
||||||
|
int16_t outputs_[2]{};
|
||||||
|
|
||||||
|
using FixedType = int64_t;
|
||||||
|
static constexpr int FixedShift = 48;
|
||||||
|
static constexpr auto FixedMultiplier = static_cast<float>(int64_t(1) << FixedShift);
|
||||||
|
FixedType coefficients_[5]{};
|
||||||
|
#else
|
||||||
|
float inputs_[2]{};
|
||||||
|
float outputs_[2]{};
|
||||||
|
float coefficients_[5]{};
|
||||||
|
#endif
|
||||||
|
// Coefficients indices versus common textbook terms:
|
||||||
|
// 0 = b0; 1 = b1; 2 = b2; 3 = a1; 4 = a2
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -71,9 +71,13 @@ void Controller::process_write_completed() {
|
|||||||
// Provided for subclasses to override.
|
// Provided for subclasses to override.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::is_writing_final_bit() {
|
||||||
|
// Provided for subclasses to override.
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - PLL control and delegate
|
// MARK: - PLL control and delegate
|
||||||
|
|
||||||
void Controller::set_expected_bit_length(Time bit_length) {
|
void Controller::set_expected_bit_length(const Time bit_length) {
|
||||||
bit_length_ = bit_length;
|
bit_length_ = bit_length;
|
||||||
bit_length_.simplify();
|
bit_length_.simplify();
|
||||||
|
|
||||||
@@ -86,6 +90,10 @@ void Controller::set_expected_bit_length(Time bit_length) {
|
|||||||
pll_.set_clocks_per_bit(clocks_per_bit);
|
pll_.set_clocks_per_bit(clocks_per_bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Storage::Time Controller::expected_bit_length() {
|
||||||
|
return bit_length_;
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
||||||
if(is_reading_) process_input_bit(value);
|
if(is_reading_) process_input_bit(value);
|
||||||
}
|
}
|
||||||
@@ -124,9 +132,9 @@ void Controller::set_drive(int index_mask) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::begin_writing(bool clamp_to_index_hole) {
|
void Controller::begin_writing(const bool clamp_to_index_hole, const bool synthesise_initial_writing_events) {
|
||||||
is_reading_ = false;
|
is_reading_ = false;
|
||||||
get_drive().begin_writing(bit_length_, clamp_to_index_hole);
|
get_drive().begin_writing(bit_length_, clamp_to_index_hole, synthesise_initial_writing_events);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::end_writing() {
|
void Controller::end_writing() {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ protected:
|
|||||||
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
||||||
*/
|
*/
|
||||||
void set_expected_bit_length(Time bit_length);
|
void set_expected_bit_length(Time bit_length);
|
||||||
|
Time expected_bit_length();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Advances the drive by @c number_of_cycles cycles.
|
Advances the drive by @c number_of_cycles cycles.
|
||||||
@@ -85,10 +86,16 @@ protected:
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
Should be implemented by subclasses if they implement writing; communicates that
|
Should be implemented by subclasses if they implement writing; communicates that
|
||||||
all bits supplied to write_bit have now been written.
|
all bits supplied to `write_bit` have now been written.
|
||||||
*/
|
*/
|
||||||
virtual void process_write_completed() override;
|
virtual void process_write_completed() override;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Can be implemented by subclasses that perform writing; indicates that the final
|
||||||
|
bit previously provided to the drive has started its output.
|
||||||
|
*/
|
||||||
|
virtual void is_writing_final_bit() override;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Puts the controller and the drive returned by get_drive() into write mode, supplying to
|
Puts the controller and the drive returned by get_drive() into write mode, supplying to
|
||||||
the drive the current bit length.
|
the drive the current bit length.
|
||||||
@@ -99,7 +106,7 @@ protected:
|
|||||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||||
the index hole. Writing will continue over the index hole otherwise.
|
the index hole. Writing will continue over the index hole otherwise.
|
||||||
*/
|
*/
|
||||||
void begin_writing(bool clamp_to_index_hole);
|
void begin_writing(bool clamp_to_index_hole, bool synthesise_initial_writing_events);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Puts the drive returned by get_drive() out of write mode, and marks the controller
|
Puts the drive returned by get_drive() out of write mode, and marks the controller
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ Track *DiskImageHolder<T>::track_at_position(Track::Address address) const {
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
DiskImageHolder<T>::~DiskImageHolder() {
|
DiskImageHolder<T>::~DiskImageHolder() {
|
||||||
if(update_queue_) update_queue_->flush();
|
if(update_queue_) update_queue_->lock_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -10,10 +10,13 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <set>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "Storage/Disk/Track/PCMTrack.hpp"
|
#include "Storage/Disk/Track/PCMTrack.hpp"
|
||||||
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
|
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||||
|
#include "Storage/Disk/Track/TrackSerialiser.hpp"
|
||||||
|
#include "Numeric/SizedInt.hpp"
|
||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
@@ -38,23 +41,42 @@ HeadPosition D64::maximum_head_position() const {
|
|||||||
return HeadPosition(number_of_tracks_);
|
return HeadPosition(number_of_tracks_);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Track> D64::track_at_position(const Track::Address address) const {
|
bool D64::is_read_only() const {
|
||||||
// Figure out where this track starts on the disk.
|
return file_.is_known_read_only();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D64::represents(const std::string &name) const {
|
||||||
|
return name == file_.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
D64::TrackExtent D64::track_extent(const Track::Address address) const {
|
||||||
|
static constexpr int tracks_in_zone[] = {17, 7, 6, 10};
|
||||||
|
static constexpr int sectors_by_zone[] = {21, 19, 18, 17};
|
||||||
|
|
||||||
int offset_to_track = 0;
|
int offset_to_track = 0;
|
||||||
int tracks_to_traverse = address.position.as_int();
|
int tracks_to_traverse = address.position.as_int();
|
||||||
|
|
||||||
int zone_sizes[] = {17, 7, 6, 10};
|
|
||||||
int sectors_by_zone[] = {21, 19, 18, 17};
|
|
||||||
int zone = 0;
|
int zone = 0;
|
||||||
for(int current_zone = 0; current_zone < 4; current_zone++) {
|
for(int current_zone = 0; current_zone < 4; current_zone++) {
|
||||||
int tracks_in_this_zone = std::min(tracks_to_traverse, zone_sizes[current_zone]);
|
const int tracks = std::min(tracks_to_traverse, tracks_in_zone[current_zone]);
|
||||||
offset_to_track += tracks_in_this_zone * sectors_by_zone[current_zone];
|
offset_to_track += tracks * sectors_by_zone[current_zone];
|
||||||
tracks_to_traverse -= tracks_in_this_zone;
|
tracks_to_traverse -= tracks;
|
||||||
if(tracks_in_this_zone == zone_sizes[current_zone]) zone++;
|
if(tracks == tracks_in_zone[current_zone]) {
|
||||||
|
++zone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return TrackExtent {
|
||||||
|
.file_offset = offset_to_track * 256,
|
||||||
|
.zone = zone,
|
||||||
|
.number_of_sectors = sectors_by_zone[zone]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Track> D64::track_at_position(const Track::Address address) const {
|
||||||
// Seek to start of data.
|
// Seek to start of data.
|
||||||
file_.seek(offset_to_track * 256, Whence::SET);
|
const auto extent = track_extent(address);
|
||||||
|
std::lock_guard lock_guard(file_.file_access_mutex());
|
||||||
|
file_.seek(extent.file_offset, Whence::SET);
|
||||||
|
|
||||||
// Build up a PCM sampling of the GCR version of this track.
|
// Build up a PCM sampling of the GCR version of this track.
|
||||||
|
|
||||||
@@ -78,17 +100,17 @@ std::unique_ptr<Track> D64::track_at_position(const Track::Address address) cons
|
|||||||
//
|
//
|
||||||
// = 349 GCR bytes per sector
|
// = 349 GCR bytes per sector
|
||||||
|
|
||||||
std::size_t track_bytes = 349 * size_t(sectors_by_zone[zone]);
|
std::size_t track_bytes = 349 * size_t(extent.number_of_sectors);
|
||||||
std::vector<uint8_t> data(track_bytes);
|
std::vector<uint8_t> data(track_bytes);
|
||||||
|
|
||||||
for(int sector = 0; sector < sectors_by_zone[zone]; sector++) {
|
for(int sector = 0; sector < extent.number_of_sectors; sector++) {
|
||||||
uint8_t *sector_data = &data[size_t(sector) * 349];
|
uint8_t *const sector_data = &data[size_t(sector) * 349];
|
||||||
sector_data[0] = sector_data[1] = sector_data[2] = 0xff;
|
sector_data[0] = sector_data[1] = sector_data[2] = 0xff;
|
||||||
|
|
||||||
uint8_t sector_number = uint8_t(sector); // Sectors count from 0.
|
const uint8_t sector_number = uint8_t(sector); // Sectors count from 0.
|
||||||
uint8_t track_number = uint8_t(address.position.as_int() + 1); // Tracks count from 1.
|
const uint8_t track_number = uint8_t(address.position.as_int() + 1); // Tracks count from 1.
|
||||||
uint8_t checksum = uint8_t(sector_number ^ track_number ^ disk_id_ ^ (disk_id_ >> 8));
|
uint8_t checksum = uint8_t(sector_number ^ track_number ^ disk_id_ ^ (disk_id_ >> 8));
|
||||||
uint8_t header_start[4] = {
|
const uint8_t header_start[4] = {
|
||||||
0x08, checksum, sector_number, track_number
|
0x08, checksum, sector_number, track_number
|
||||||
};
|
};
|
||||||
Encodings::CommodoreGCR::encode_block(§or_data[3], header_start);
|
Encodings::CommodoreGCR::encode_block(§or_data[3], header_start);
|
||||||
@@ -99,7 +121,7 @@ std::unique_ptr<Track> D64::track_at_position(const Track::Address address) cons
|
|||||||
Encodings::CommodoreGCR::encode_block(§or_data[8], header_end);
|
Encodings::CommodoreGCR::encode_block(§or_data[8], header_end);
|
||||||
|
|
||||||
// Pad out post-header parts.
|
// Pad out post-header parts.
|
||||||
uint8_t zeros[4] = {0, 0, 0, 0};
|
static constexpr uint8_t zeros[4] = {0, 0, 0, 0};
|
||||||
Encodings::CommodoreGCR::encode_block(§or_data[13], zeros);
|
Encodings::CommodoreGCR::encode_block(§or_data[13], zeros);
|
||||||
sector_data[18] = 0x52;
|
sector_data[18] = 0x52;
|
||||||
sector_data[19] = 0x94;
|
sector_data[19] = 0x94;
|
||||||
@@ -111,14 +133,15 @@ std::unique_ptr<Track> D64::track_at_position(const Track::Address address) cons
|
|||||||
|
|
||||||
// Compute the latest checksum.
|
// Compute the latest checksum.
|
||||||
checksum = 0;
|
checksum = 0;
|
||||||
for(int c = 0; c < 256; c++)
|
for(int c = 0; c < 256; c++) {
|
||||||
checksum ^= source_data[c];
|
checksum ^= source_data[c];
|
||||||
|
}
|
||||||
|
|
||||||
// Put in another sync.
|
// Put in another sync.
|
||||||
sector_data[21] = sector_data[22] = sector_data[23] = 0xff;
|
sector_data[21] = sector_data[22] = sector_data[23] = 0xff;
|
||||||
|
|
||||||
// Now start writing in the actual data.
|
// Now start writing in the actual data.
|
||||||
uint8_t start_of_data[4] = {
|
const uint8_t start_of_data[4] = {
|
||||||
0x07, source_data[0], source_data[1], source_data[2]
|
0x07, source_data[0], source_data[1], source_data[2]
|
||||||
};
|
};
|
||||||
Encodings::CommodoreGCR::encode_block(§or_data[24], start_of_data);
|
Encodings::CommodoreGCR::encode_block(§or_data[24], start_of_data);
|
||||||
@@ -129,7 +152,7 @@ std::unique_ptr<Track> D64::track_at_position(const Track::Address address) cons
|
|||||||
target_data_offset += 5;
|
target_data_offset += 5;
|
||||||
source_data_offset += 4;
|
source_data_offset += 4;
|
||||||
}
|
}
|
||||||
uint8_t end_of_data[4] = {
|
const uint8_t end_of_data[4] = {
|
||||||
source_data[255], checksum, 0, 0
|
source_data[255], checksum, 0, 0
|
||||||
};
|
};
|
||||||
Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], end_of_data);
|
Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], end_of_data);
|
||||||
@@ -138,6 +161,103 @@ std::unique_ptr<Track> D64::track_at_position(const Track::Address address) cons
|
|||||||
return std::make_unique<PCMTrack>(PCMSegment(data));
|
return std::make_unique<PCMTrack>(PCMSegment(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D64::represents(const std::string &name) const {
|
void D64::set_tracks(const std::map<Track::Address, std::unique_ptr<Track>> &tracks) {
|
||||||
return name == file_.name();
|
for(const auto &[address, track]: tracks) {
|
||||||
|
const auto extent = track_extent(address);
|
||||||
|
std::map<int, std::vector<uint8_t>> decoded;
|
||||||
|
|
||||||
|
// Get bit stream.
|
||||||
|
const auto serialisation =
|
||||||
|
Storage::Disk::track_serialisation(
|
||||||
|
*track,
|
||||||
|
Time(1, extent.number_of_sectors * 349 * 8) // This is relative to a normalised world where
|
||||||
|
// 1 unit of time = 1 track. So don't use
|
||||||
|
// length_of_a_bit_in_time_zone, which is relative to
|
||||||
|
// a wall clock.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decode sectors.
|
||||||
|
Numeric::SizedInt<10> shifter = 0;
|
||||||
|
int repeats = 2;
|
||||||
|
auto bit = serialisation.data.begin();
|
||||||
|
bool is_ended = false;
|
||||||
|
const auto shift = [&] {
|
||||||
|
shifter = uint16_t((shifter.get() << 1) | *bit);
|
||||||
|
++bit;
|
||||||
|
|
||||||
|
if(bit == serialisation.data.end()) {
|
||||||
|
bit = serialisation.data.begin();
|
||||||
|
--repeats;
|
||||||
|
is_ended |= !repeats;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto byte = [&] {
|
||||||
|
for(int c = 0; c < 9; c++) {
|
||||||
|
shift();
|
||||||
|
}
|
||||||
|
const auto result = Encodings::CommodoreGCR::decoding_from_dectet(shifter.get());
|
||||||
|
shift();
|
||||||
|
return uint8_t(result);
|
||||||
|
};
|
||||||
|
const auto block_type = [&] {
|
||||||
|
// Find synchronisation, then get first dectet after that.
|
||||||
|
while(!is_ended && shifter.get() != 0b11111'11111) {
|
||||||
|
shift();
|
||||||
|
}
|
||||||
|
while(!is_ended && shifter.get() == 0b11111'11111) {
|
||||||
|
shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type should be 8 for a header, 7 for some data.
|
||||||
|
return byte();
|
||||||
|
};
|
||||||
|
|
||||||
|
while(!is_ended && decoded.size() != size_t(extent.number_of_sectors)) {
|
||||||
|
// Find a header.
|
||||||
|
const auto header_start = block_type();
|
||||||
|
if(header_start != 0x8) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto checksum = byte();
|
||||||
|
const auto sector_id = byte();
|
||||||
|
const auto track_id = byte();
|
||||||
|
const auto disk_id1 = byte();
|
||||||
|
const auto disk_id2 = byte();
|
||||||
|
|
||||||
|
if(checksum != (sector_id ^ track_id ^ disk_id1 ^ disk_id2)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(sector_id >= extent.number_of_sectors) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip to data.
|
||||||
|
const auto data_start = block_type();
|
||||||
|
if(data_start != 0x7) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy into place if not yet present.
|
||||||
|
uint8_t data_checksum = 0;
|
||||||
|
std::vector<uint8_t> sector_contents(256);
|
||||||
|
for(size_t c = 0; c < 256; c++) {
|
||||||
|
const uint8_t next = byte();
|
||||||
|
data_checksum ^= next;
|
||||||
|
sector_contents[c] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(byte() != data_checksum) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
decoded.emplace(sector_id, std::move(sector_contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write.
|
||||||
|
std::lock_guard lock_guard(file_.file_access_mutex());
|
||||||
|
for(auto &[sector, contents]: decoded) {
|
||||||
|
file_.seek(extent.file_offset + sector * 256, Whence::SET);
|
||||||
|
file_.write(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,21 @@ public:
|
|||||||
|
|
||||||
HeadPosition maximum_head_position() const;
|
HeadPosition maximum_head_position() const;
|
||||||
std::unique_ptr<Track> track_at_position(Track::Address) const;
|
std::unique_ptr<Track> track_at_position(Track::Address) const;
|
||||||
|
bool is_read_only() const;
|
||||||
|
void set_tracks(const std::map<Track::Address, std::unique_ptr<Track>> &);
|
||||||
bool represents(const std::string &) const;
|
bool represents(const std::string &) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable Storage::FileHolder file_;
|
mutable Storage::FileHolder file_;
|
||||||
int number_of_tracks_;
|
int number_of_tracks_;
|
||||||
uint16_t disk_id_;
|
uint16_t disk_id_;
|
||||||
|
|
||||||
|
struct TrackExtent {
|
||||||
|
long file_offset;
|
||||||
|
int zone;
|
||||||
|
int number_of_sectors;
|
||||||
|
};
|
||||||
|
TrackExtent track_extent(Track::Address) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,19 @@
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads, ReadyType rdy_type):
|
Drive::Drive(
|
||||||
|
const int input_clock_rate,
|
||||||
|
const int revolutions_per_minute,
|
||||||
|
const int number_of_heads,
|
||||||
|
const ReadyType rdy_type
|
||||||
|
) :
|
||||||
Storage::TimedEventLoop(input_clock_rate),
|
Storage::TimedEventLoop(input_clock_rate),
|
||||||
available_heads_(number_of_heads),
|
available_heads_(number_of_heads),
|
||||||
ready_type_(rdy_type) {
|
ready_type_(rdy_type) {
|
||||||
set_rotation_speed(revolutions_per_minute);
|
set_rotation_speed(revolutions_per_minute);
|
||||||
|
|
||||||
const auto seed = std::default_random_engine::result_type(std::chrono::system_clock::now().time_since_epoch().count());
|
const auto seed =
|
||||||
|
std::default_random_engine::result_type(std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
std::default_random_engine randomiser(seed);
|
std::default_random_engine randomiser(seed);
|
||||||
|
|
||||||
// Get at least 64 bits of random information; rounding is likey to give this a slight bias.
|
// Get at least 64 bits of random information; rounding is likey to give this a slight bias.
|
||||||
@@ -34,16 +40,21 @@ Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_hea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Drive::Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type) : Drive(input_clock_rate, 300, number_of_heads, rdy_type) {}
|
Drive::Drive(
|
||||||
|
const int input_clock_rate,
|
||||||
|
const int number_of_heads,
|
||||||
|
const ReadyType rdy_type
|
||||||
|
) : Drive(input_clock_rate, 300, number_of_heads, rdy_type) {}
|
||||||
|
|
||||||
void Drive::set_rotation_speed(float revolutions_per_minute) {
|
void Drive::set_rotation_speed(const float revolutions_per_minute) {
|
||||||
// Rationalise the supplied speed so that cycles_per_revolution_ is exact.
|
// Rationalise the supplied speed so that cycles_per_revolution_ is exact.
|
||||||
cycles_per_revolution_ = int(0.5f + float(get_input_clock_rate()) * 60.0f / revolutions_per_minute);
|
cycles_per_revolution_ = int(0.5f + float(get_input_clock_rate()) * 60.0f / revolutions_per_minute);
|
||||||
|
|
||||||
// From there derive the appropriate rotational multiplier and possibly update the
|
// From there derive the appropriate rotational multiplier and possibly update the
|
||||||
// count of cycles since the index hole proportionally.
|
// count of cycles since the index hole proportionally.
|
||||||
const float new_rotational_multiplier = float(cycles_per_revolution_) / float(get_input_clock_rate());
|
const float new_rotational_multiplier = float(cycles_per_revolution_) / float(get_input_clock_rate());
|
||||||
cycles_since_index_hole_ = Cycles::IntType(float(cycles_since_index_hole_) * new_rotational_multiplier / rotational_multiplier_);
|
cycles_since_index_hole_ =
|
||||||
|
Cycles::IntType(float(cycles_since_index_hole_) * new_rotational_multiplier / rotational_multiplier_);
|
||||||
rotational_multiplier_ = new_rotational_multiplier;
|
rotational_multiplier_ = new_rotational_multiplier;
|
||||||
cycles_since_index_hole_ %= cycles_per_revolution_;
|
cycles_since_index_hole_ %= cycles_per_revolution_;
|
||||||
}
|
}
|
||||||
@@ -75,14 +86,16 @@ bool Drive::has_disk() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClockingHint::Preference Drive::preferred_clocking() const {
|
ClockingHint::Preference Drive::preferred_clocking() const {
|
||||||
return (!has_disk_ || (time_until_motor_transition == Cycles(0) && !disk_is_rotating_)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
return (
|
||||||
|
!has_disk_ || (time_until_motor_transition == Cycles(0) && !disk_is_rotating_)
|
||||||
|
) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_is_track_zero() const {
|
bool Drive::get_is_track_zero() const {
|
||||||
return head_position_ == HeadPosition(0);
|
return head_position_ == HeadPosition(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::step(HeadPosition offset) {
|
void Drive::step(const HeadPosition offset) {
|
||||||
if(offset == HeadPosition(0)) {
|
if(offset == HeadPosition(0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +122,7 @@ void Drive::step(HeadPosition offset) {
|
|||||||
did_step(head_position_);
|
did_step(head_position_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Track *Drive::step_to(HeadPosition offset) {
|
Track *Drive::step_to(const HeadPosition offset) {
|
||||||
HeadPosition old_head_position = head_position_;
|
HeadPosition old_head_position = head_position_;
|
||||||
head_position_ = std::max(offset, HeadPosition(0));
|
head_position_ = std::max(offset, HeadPosition(0));
|
||||||
|
|
||||||
@@ -160,7 +173,7 @@ bool Drive::get_is_ready() const {
|
|||||||
return is_ready_;
|
return is_ready_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_motor_on(bool motor_is_on) {
|
void Drive::set_motor_on(const bool motor_is_on) {
|
||||||
// Do nothing if the input hasn't changed.
|
// Do nothing if the input hasn't changed.
|
||||||
if(motor_input_is_on_ == motor_is_on) return;
|
if(motor_input_is_on_ == motor_is_on) return;
|
||||||
motor_input_is_on_ = motor_is_on;
|
motor_input_is_on_ = motor_is_on;
|
||||||
@@ -195,7 +208,7 @@ bool Drive::get_index_pulse() const {
|
|||||||
return index_pulse_remaining_ > Cycles(0);
|
return index_pulse_remaining_ > Cycles(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) {
|
void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *const delegate) {
|
||||||
event_delegate_ = delegate;
|
event_delegate_ = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,13 +250,21 @@ void Drive::run_for(const Cycles cycles) {
|
|||||||
if(cycles_until_bits_written_ > zero) {
|
if(cycles_until_bits_written_ > zero) {
|
||||||
Storage::Time cycles_to_run_for_time(static_cast<int>(cycles_to_run_for));
|
Storage::Time cycles_to_run_for_time(static_cast<int>(cycles_to_run_for));
|
||||||
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
|
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
|
||||||
if(event_delegate_) event_delegate_->process_write_completed();
|
cycles_until_bits_written_.set_zero();
|
||||||
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
|
if(event_delegate_) {
|
||||||
cycles_until_bits_written_.set_zero();
|
event_delegate_->process_write_completed();
|
||||||
else
|
}
|
||||||
cycles_until_bits_written_ -= cycles_to_run_for_time;
|
|
||||||
} else {
|
} else {
|
||||||
|
const auto previous_cycles = cycles_until_bits_written_;
|
||||||
cycles_until_bits_written_ -= cycles_to_run_for_time;
|
cycles_until_bits_written_ -= cycles_to_run_for_time;
|
||||||
|
|
||||||
|
if(
|
||||||
|
previous_cycles >= cycles_per_bit_ &&
|
||||||
|
cycles_until_bits_written_ < cycles_per_bit_ &&
|
||||||
|
event_delegate_
|
||||||
|
) {
|
||||||
|
event_delegate_->is_writing_final_bit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +278,7 @@ void Drive::run_for(const Cycles cycles) {
|
|||||||
|
|
||||||
// MARK: - Track timed event loop
|
// MARK: - Track timed event loop
|
||||||
|
|
||||||
void Drive::get_next_event(float duration_already_passed) {
|
void Drive::get_next_event(const float duration_already_passed) {
|
||||||
/*
|
/*
|
||||||
Quick word on random-bit generation logic below; it seeks to obey the following logic:
|
Quick word on random-bit generation logic below; it seeks to obey the following logic:
|
||||||
if there is a gap of 15µs between recorded bits, start generating flux transitions
|
if there is a gap of 15µs between recorded bits, start generating flux transitions
|
||||||
@@ -387,7 +408,11 @@ void Drive::invalidate_track() {
|
|||||||
|
|
||||||
// MARK: - Writing
|
// MARK: - Writing
|
||||||
|
|
||||||
void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) {
|
void Drive::begin_writing(
|
||||||
|
const Time bit_length,
|
||||||
|
const bool clamp_to_index_hole,
|
||||||
|
const bool synthesise_initial_writing_events
|
||||||
|
) {
|
||||||
// Do nothing if already writing.
|
// Do nothing if already writing.
|
||||||
// TODO: cope properly if there's no disk to write to.
|
// TODO: cope properly if there's no disk to write to.
|
||||||
if(!is_reading_ || !disk_) return;
|
if(!is_reading_ || !disk_) return;
|
||||||
@@ -407,10 +432,15 @@ void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) {
|
|||||||
write_segment_.length_of_a_bit = bit_length / Time(rotational_multiplier_);
|
write_segment_.length_of_a_bit = bit_length / Time(rotational_multiplier_);
|
||||||
write_segment_.data.clear();
|
write_segment_.data.clear();
|
||||||
|
|
||||||
|
cycles_until_bits_written_.set_zero();
|
||||||
write_start_time_ = Time(get_time_into_track());
|
write_start_time_ = Time(get_time_into_track());
|
||||||
|
if(synthesise_initial_writing_events) {
|
||||||
|
write_start_time_ += bit_length;
|
||||||
|
cycles_until_bits_written_ += cycles_per_bit_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::write_bit(bool value) {
|
void Drive::write_bit(const bool value) {
|
||||||
write_segment_.data.push_back(value);
|
write_segment_.data.push_back(value);
|
||||||
cycles_until_bits_written_ += cycles_per_bit_;
|
cycles_until_bits_written_ += cycles_per_bit_;
|
||||||
}
|
}
|
||||||
@@ -444,7 +474,7 @@ bool Drive::is_writing() const {
|
|||||||
return !is_reading_;
|
return !is_reading_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_disk_is_rotating(bool is_rotating) {
|
void Drive::set_disk_is_rotating(const bool is_rotating) {
|
||||||
disk_is_rotating_ = is_rotating;
|
disk_is_rotating_ = is_rotating;
|
||||||
|
|
||||||
if(observer_) {
|
if(observer_) {
|
||||||
@@ -464,7 +494,11 @@ void Drive::set_disk_is_rotating(bool is_rotating) {
|
|||||||
update_clocking_observer();
|
update_clocking_observer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) {
|
void Drive::set_activity_observer(
|
||||||
|
Activity::Observer *const observer,
|
||||||
|
const std::string &name,
|
||||||
|
const bool add_motor_led
|
||||||
|
) {
|
||||||
observer_ = observer;
|
observer_ = observer;
|
||||||
announce_motor_led_ = add_motor_led;
|
announce_motor_led_ = add_motor_led;
|
||||||
if(observer) {
|
if(observer) {
|
||||||
|
|||||||
@@ -36,13 +36,7 @@ public:
|
|||||||
Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY);
|
Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY);
|
||||||
virtual ~Drive();
|
virtual ~Drive();
|
||||||
|
|
||||||
// TODO: Disallow copying.
|
Drive(const Drive &) = delete;
|
||||||
//
|
|
||||||
// GCC 10 has an issue with the way the DiskII constructs its drive array if these are both
|
|
||||||
// deleted, despite not using the copy constructor.
|
|
||||||
//
|
|
||||||
// This seems to be fixed in GCC 11, so reenable this delete when possible.
|
|
||||||
// Drive(const Drive &) = delete;
|
|
||||||
void operator=(const Drive &) = delete;
|
void operator=(const Drive &) = delete;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -74,7 +68,7 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
Sets the current read head.
|
Sets the current read head.
|
||||||
*/
|
*/
|
||||||
void set_head(int head);
|
void set_head(int);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Gets the head count for this disk.
|
Gets the head count for this disk.
|
||||||
@@ -114,13 +108,16 @@ public:
|
|||||||
|
|
||||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||||
the index hole. Writing will continue over the index hole otherwise.
|
the index hole. Writing will continue over the index hole otherwise.
|
||||||
|
|
||||||
|
@param synthesise_initial_writing_events if @c true then an @c is_writing_final_bit() /
|
||||||
|
@c process_write_completed() pair will follow without any data having been written.
|
||||||
*/
|
*/
|
||||||
void begin_writing(Time bit_length, bool clamp_to_index_hole);
|
void begin_writing(Time bit_length, bool clamp_to_index_hole, bool synthesise_initial_writing_events);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
||||||
*/
|
*/
|
||||||
void write_bit(bool value);
|
void write_bit(bool);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Ends write mode, switching back to read mode. The drive will stop overwriting events.
|
Ends write mode, switching back to read mode. The drive will stop overwriting events.
|
||||||
@@ -149,7 +146,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
struct EventDelegate {
|
struct EventDelegate {
|
||||||
/// Informs the delegate that @c event has been reached.
|
/// Informs the delegate that @c event has been reached.
|
||||||
virtual void process_event(const Event &event) = 0;
|
virtual void process_event(const Event &) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
If the drive is in write mode, announces that all queued bits have now been written.
|
If the drive is in write mode, announces that all queued bits have now been written.
|
||||||
@@ -157,8 +154,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void process_write_completed() {}
|
virtual void process_write_completed() {}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
When in write mode, indicates that output of the final bit has begun.
|
||||||
|
*/
|
||||||
|
virtual void is_writing_final_bit() {}
|
||||||
|
|
||||||
/// Informs the delegate of the passing of @c cycles.
|
/// Informs the delegate of the passing of @c cycles.
|
||||||
virtual void advance([[maybe_unused]] Cycles cycles) {}
|
virtual void advance(Cycles) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Sets the current event delegate.
|
/// Sets the current event delegate.
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
using namespace Storage;
|
using namespace Storage;
|
||||||
|
|
||||||
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(const unsigned int time_zone) {
|
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(const unsigned int time_zone) {
|
||||||
// the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time)
|
// The speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being
|
||||||
|
// faster (i.e. each bit taking less time).
|
||||||
return Time(16 - time_zone, 4000000u);
|
return Time(16 - time_zone, 4000000u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public:
|
|||||||
HeadPosition position;
|
HeadPosition position;
|
||||||
|
|
||||||
constexpr auto operator <=>(const Address&) const = default;
|
constexpr auto operator <=>(const Address&) const = default;
|
||||||
constexpr Address(int head, HeadPosition position) : head(head), position(position) {}
|
constexpr Address(const int head, const HeadPosition position) noexcept : head(head), position(position) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
// TODO: if this is a PCMTrack with only one segment and that segment's bit rate is within tolerance,
|
// TODO: if this is a PCMTrack with only one segment and that segment's bit rate is within tolerance,
|
||||||
// just return a copy of that segment.
|
// just return a copy of that segment.
|
||||||
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, const Time length_of_a_bit) {
|
||||||
unsigned int history_size = 16;
|
unsigned int history_size = 16;
|
||||||
std::unique_ptr<Track> track_copy(track.clone());
|
std::unique_ptr<Track> track_copy(track.clone());
|
||||||
|
|
||||||
|
|||||||
35
Storage/FileBundle/FileBundle.cpp
Normal file
35
Storage/FileBundle/FileBundle.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// FileBundle.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 19/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "FileBundle.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace Storage::FileBundle;
|
||||||
|
|
||||||
|
LocalFSFileBundle::LocalFSFileBundle(const std::string &to_contain) {
|
||||||
|
const auto last_separator = to_contain.find_last_of("/\\");
|
||||||
|
if(last_separator == std::string::npos) {
|
||||||
|
key_file_ = to_contain;
|
||||||
|
} else {
|
||||||
|
base_path_ = to_contain.substr(0, last_separator + 1);
|
||||||
|
key_file_ = to_contain.substr(last_separator + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> LocalFSFileBundle::key_file() {
|
||||||
|
return key_file_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::FileHolder LocalFSFileBundle::open(const std::string &name, const Storage::FileMode mode) {
|
||||||
|
return Storage::FileHolder(base_path_ + name, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalFSFileBundle::erase(const std::string &name) {
|
||||||
|
return !remove((base_path_ + name).c_str());
|
||||||
|
}
|
||||||
44
Storage/FileBundle/FileBundle.hpp
Normal file
44
Storage/FileBundle/FileBundle.hpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// FileBundle.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 19/11/2025.
|
||||||
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Storage/FileHolder.hpp"
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Storage::FileBundle {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A File Bundle is a collection of individual files, abstracted from whatever media they might
|
||||||
|
be one.
|
||||||
|
|
||||||
|
Initial motivation is allowing some machines direct local filesystem access. An attempt has
|
||||||
|
been made to draft this in such a way as to allow it to do things like expose ZIP files as
|
||||||
|
bundles in the future.
|
||||||
|
*/
|
||||||
|
struct FileBundle {
|
||||||
|
virtual std::optional<std::string> key_file() = 0;
|
||||||
|
virtual FileHolder open(const std::string &, FileMode) = 0;
|
||||||
|
virtual bool erase(const std::string &) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct LocalFSFileBundle: public FileBundle {
|
||||||
|
LocalFSFileBundle(const std::string &to_contain);
|
||||||
|
|
||||||
|
std::optional<std::string> key_file() override;
|
||||||
|
FileHolder open(const std::string &, FileMode) override;
|
||||||
|
bool erase(const std::string &) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string key_file_;
|
||||||
|
std::string base_path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
@@ -13,6 +13,12 @@
|
|||||||
|
|
||||||
using namespace Storage;
|
using namespace Storage;
|
||||||
|
|
||||||
|
FileHolder::FileHolder(FileHolder &&rhs) {
|
||||||
|
file_ = rhs.file_;
|
||||||
|
rhs.file_ = nullptr;
|
||||||
|
// TODO: this leaves the RHS in an invalid state, which isn't appropriate for move semantics.
|
||||||
|
}
|
||||||
|
|
||||||
FileHolder::~FileHolder() {
|
FileHolder::~FileHolder() {
|
||||||
if(file_) std::fclose(file_);
|
if(file_) std::fclose(file_);
|
||||||
}
|
}
|
||||||
@@ -45,8 +51,8 @@ uint8_t FileHolder::get() {
|
|||||||
return uint8_t(std::fgetc(file_));
|
return uint8_t(std::fgetc(file_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileHolder::put(const uint8_t value) {
|
bool FileHolder::put(const uint8_t value) {
|
||||||
std::fputc(value, file_);
|
return std::fputc(value, file_) == value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileHolder::putn(std::size_t repeats, const uint8_t value) {
|
void FileHolder::putn(std::size_t repeats, const uint8_t value) {
|
||||||
@@ -71,9 +77,9 @@ std::size_t FileHolder::write(const uint8_t *buffer, const std::size_t size) {
|
|||||||
return std::fwrite(buffer, 1, size, file_);
|
return std::fwrite(buffer, 1, size, file_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileHolder::seek(const long offset, const Whence whence) {
|
bool FileHolder::seek(const long offset, const Whence whence) {
|
||||||
[[maybe_unused]] const auto result = std::fseek(file_, offset, int(whence));
|
const auto result = std::fseek(file_, offset, int(whence));
|
||||||
assert(!result);
|
return !result;
|
||||||
}
|
}
|
||||||
|
|
||||||
long FileHolder::tell() const {
|
long FileHolder::tell() const {
|
||||||
|
|||||||
@@ -56,9 +56,10 @@ public:
|
|||||||
Rewrite opens the file for rewriting; none of the original content is preserved; whatever
|
Rewrite opens the file for rewriting; none of the original content is preserved; whatever
|
||||||
the caller outputs will replace the existing file.
|
the caller outputs will replace the existing file.
|
||||||
|
|
||||||
@throws ErrorCantOpen if the file cannot be opened.
|
@throws Error::CantOpen if the file cannot be opened.
|
||||||
*/
|
*/
|
||||||
FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);
|
FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);
|
||||||
|
FileHolder(FileHolder &&);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Writes @c value using successive @c puts, in little endian order.
|
Writes @c value using successive @c puts, in little endian order.
|
||||||
@@ -116,8 +117,11 @@ public:
|
|||||||
/*! Reads a single byte from @c file. */
|
/*! Reads a single byte from @c file. */
|
||||||
uint8_t get();
|
uint8_t get();
|
||||||
|
|
||||||
/*! Writes a single byte from @c file. */
|
/*!
|
||||||
void put(uint8_t);
|
Writes a single byte from @c file.
|
||||||
|
@returns @c true on success; @c false on failure.
|
||||||
|
*/
|
||||||
|
bool put(uint8_t);
|
||||||
|
|
||||||
/*! Writes @c value a total of @c repeats times. */
|
/*! Writes @c value a total of @c repeats times. */
|
||||||
void putn(std::size_t repeats, uint8_t value);
|
void putn(std::size_t repeats, uint8_t value);
|
||||||
@@ -140,7 +144,7 @@ public:
|
|||||||
std::size_t write(const uint8_t *, std::size_t);
|
std::size_t write(const uint8_t *, std::size_t);
|
||||||
|
|
||||||
/*! Moves @c bytes from the anchor indicated by @c whence: SEEK_SET, SEEK_CUR or SEEK_END. */
|
/*! Moves @c bytes from the anchor indicated by @c whence: SEEK_SET, SEEK_CUR or SEEK_END. */
|
||||||
void seek(long offset, Whence);
|
bool seek(long offset, Whence);
|
||||||
|
|
||||||
/*! @returns The current cursor position within this file. */
|
/*! @returns The current cursor position within this file. */
|
||||||
long tell() const;
|
long tell() const;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape, TargetPlatf
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TapePlayer::is_at_end() const {
|
bool TapePlayer::is_at_end() const {
|
||||||
return serialiser_->is_at_end();
|
return !serialiser_ || serialiser_->is_at_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
TapeSerialiser *TapePlayer::serialiser() {
|
TapeSerialiser *TapePlayer::serialiser() {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ set(CLK_SOURCES
|
|||||||
Components/OPx/OPLL.cpp
|
Components/OPx/OPLL.cpp
|
||||||
Components/RP5C01/RP5C01.cpp
|
Components/RP5C01/RP5C01.cpp
|
||||||
Components/SAA5050/SAA5050.cpp
|
Components/SAA5050/SAA5050.cpp
|
||||||
|
Components/SID/SID.cpp
|
||||||
Components/SN76489/SN76489.cpp
|
Components/SN76489/SN76489.cpp
|
||||||
Components/Serial/Line.cpp
|
Components/Serial/Line.cpp
|
||||||
Components/uPD7002/uPD7002.cpp
|
Components/uPD7002/uPD7002.cpp
|
||||||
@@ -129,6 +130,7 @@ set(CLK_SOURCES
|
|||||||
Machines/Enterprise/Dave.cpp
|
Machines/Enterprise/Dave.cpp
|
||||||
Machines/Enterprise/EXDos.cpp
|
Machines/Enterprise/EXDos.cpp
|
||||||
Machines/Enterprise/Enterprise.cpp
|
Machines/Enterprise/Enterprise.cpp
|
||||||
|
Machines/Enterprise/HostFSHandler.cpp
|
||||||
Machines/Enterprise/Keyboard.cpp
|
Machines/Enterprise/Keyboard.cpp
|
||||||
Machines/Enterprise/Nick.cpp
|
Machines/Enterprise/Nick.cpp
|
||||||
Machines/KeyboardMachine.cpp
|
Machines/KeyboardMachine.cpp
|
||||||
@@ -223,6 +225,7 @@ set(CLK_SOURCES
|
|||||||
Storage/Disk/Track/PCMTrack.cpp
|
Storage/Disk/Track/PCMTrack.cpp
|
||||||
Storage/Disk/Track/TrackSerialiser.cpp
|
Storage/Disk/Track/TrackSerialiser.cpp
|
||||||
Storage/Disk/Track/UnformattedTrack.cpp
|
Storage/Disk/Track/UnformattedTrack.cpp
|
||||||
|
Storage/FileBundle/FileBundle.cpp
|
||||||
Storage/FileHolder.cpp
|
Storage/FileHolder.cpp
|
||||||
Storage/MassStorage/Encodings/MacintoshVolume.cpp
|
Storage/MassStorage/Encodings/MacintoshVolume.cpp
|
||||||
Storage/MassStorage/Formats/DAT.cpp
|
Storage/MassStorage/Formats/DAT.cpp
|
||||||
|
|||||||
Reference in New Issue
Block a user