1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Merge branch 'master' into AppleIIgs

This commit is contained in:
Thomas Harte 2021-02-02 21:29:38 -05:00
commit 6526c645a5
20 changed files with 347 additions and 44 deletions

View File

@ -61,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@ -111,9 +107,10 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target->media.disks = media.disks;
target->has_dfs = bool(dfs_catalogue);
target->has_adfs = bool(adfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
@ -144,6 +141,28 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target->should_shift_restart = true;
} else {
target->loading_command = "*CAT\n";
}
}
TargetList targets;
if(!target->media.empty()) {
targets.push_back(std::move(target));

View File

@ -18,7 +18,8 @@ namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false;
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
bool has_ap6_rom = false;
bool has_sideways_ram = false;
@ -27,7 +28,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_adfs);
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
DeclareField(has_dfs);
DeclareField(has_ap6_rom);
DeclareField(has_sideways_ram);

View File

@ -53,6 +53,7 @@
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
@ -118,6 +119,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD

View File

@ -1089,7 +1089,7 @@ template <bool has_fdc> class ConcreteMachine:
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
HalfCycles get_typer_delay() const final {
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0);
}

View File

@ -17,6 +17,9 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp"
@ -31,7 +34,7 @@
namespace Electron {
class ConcreteMachine:
template <bool has_scsi_bus> class ConcreteMachine:
public Machine,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
@ -42,10 +45,15 @@ class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
public Utility::TypeRecipient<CharacterMapper>,
public Activity::Source {
public Activity::Source,
public SCSI::Bus::Observer,
public ClockingHint::Observer {
public:
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
scsi_bus_(4'000'000),
hard_drive_(scsi_bus_, 0),
scsi_device_(scsi_bus_.add_device()),
video_output_(ram_),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
@ -64,10 +72,15 @@ class ConcreteMachine:
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
};
if(target.has_adfs) {
const size_t pres_adfs_rom_position = required_roms.size();
if(target.has_pres_adfs) {
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
}
const size_t acorn_adfs_rom_position = required_roms.size();
if(target.has_acorn_adfs) {
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
}
const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) {
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
@ -91,19 +104,23 @@ class ConcreteMachine:
* the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11;
* the DFS, if in use, occupies slot 1;
* the ADFS, if in use, occupies slots 4 and 5;
* the Pres ADFS, if in use, occupies slots 4 and 5;
* the Acorn ADFS, if in use, occupies slot 6;
* the AP6, if in use, occupies slot 15; and
* if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM.
*/
if(target.has_dfs || target.has_adfs) {
if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) {
plus3_ = std::make_unique<Plus3>();
if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
}
if(target.has_adfs) {
set_rom(ROM::Slot4, *roms[2], true);
set_rom(ROM::Slot5, *roms[3], true);
if(target.has_pres_adfs) {
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
}
if(target.has_acorn_adfs) {
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
}
}
@ -128,6 +145,11 @@ class ConcreteMachine:
if(target.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
if(has_scsi_bus) {
scsi_bus_.add_observer(this);
scsi_bus_.set_clocking_hint_observer(this);
}
}
~ConcreteMachine() {
@ -193,7 +215,12 @@ class ConcreteMachine:
set_rom(slot, cartridge->get_segments().front().data, false);
}
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
// TODO: allow this only at machine startup?
if(!media.mass_storage_devices.empty()) {
hard_drive_->set_storage(media.mass_storage_devices.front());
}
return !media.empty();
}
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@ -315,7 +342,73 @@ class ConcreteMachine:
plus3_->set_control_register(*value);
} else *value = 1;
}
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_acknowledge_ = true;
if(!isReadOperation(operation)) {
scsi_data_ = *value;
push_scsi_output();
} else {
*value = SCSI::data_lines(scsi_bus_.get_state());
push_scsi_output();
}
}
break;
case 0xfc03:
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_interrupt_state_ = false;
scsi_interrupt_mask_ = *value & 1;
evaluate_interrupts();
}
break;
case 0xfc01:
if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) {
// Status byte is:
//
// b7: SCSI C/D
// b6: SCSI I/O
// b5: SCSI REQ
// b4: interrupt flag
// b3: 0
// b2: 0
// b1: SCSI BSY
// b0: SCSI MSG
const auto state = scsi_bus_.get_state();
*value =
(state & SCSI::Line::Control ? 0x80 : 0x00) |
(state & SCSI::Line::Input ? 0x40 : 0x00) |
(state & SCSI::Line::Request ? 0x20 : 0x00) |
((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) |
(state & SCSI::Line::Busy ? 0x02 : 0x00) |
(state & SCSI::Line::Message ? 0x01 : 0x00);
// Empirical guess: this is also the trigger to affect busy/request/acknowledge
// signalling. Maybe?
if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) {
scsi_select_ = false;
push_scsi_output();
}
}
break;
case 0xfc02:
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_select_ = true;
push_scsi_output();
}
break;
// SCSI locations:
//
// fc40: data, read and write
// fc41: status read
// fc42: select write
// fc43: interrupt latch
//
//
// Interrupt latch is:
//
// b0: enable or disable IRQ on REQ
// (and, possibly, writing to the latch acknowledges?)
default:
if(address >= 0xc000) {
@ -414,6 +507,12 @@ class ConcreteMachine:
}
}
if constexpr (has_scsi_bus) {
if(scsi_is_clocked_) {
scsi_bus_.run_for(Cycles(int(cycles)));
}
}
return Cycles(int(cycles));
}
@ -447,13 +546,44 @@ class ConcreteMachine:
m6502_.run_for(cycles);
}
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final {
// Release acknowledge when request is released.
if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) {
scsi_acknowledge_ = false;
push_scsi_output();
}
// Output occurs only while SCSI::Line::Input is inactive; therefore a change
// in that line affects what's on the bus.
if(((new_state^previous_bus_state_)&SCSI::Line::Input)) {
push_scsi_output();
}
scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request;
previous_bus_state_ = new_state;
evaluate_interrupts();
}
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final {
scsi_is_clocked_ = preference != ClockingHint::Preference::None;
}
void tape_did_change_interrupt_status(Tape *) final {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}
HalfCycles get_typer_delay() const final {
return m6502_.get_is_resetting() ? Cycles(750'000) : Cycles(0);
HalfCycles get_typer_delay(const std::string &text) const final {
if(!m6502_.get_is_resetting()) {
return Cycles(0);
}
// Add a longer delay for a command at reset that involves pressing a modifier;
// empirically this seems to be a requirement, in order to avoid a collision with
// the system's built-in modifier-at-startup test (e.g. to perform shift+break).
CharacterMapper test_mapper;
const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]);
return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000);
}
HalfCycles get_typer_frequency() const final {
@ -494,10 +624,14 @@ class ConcreteMachine:
if(activity_observer_) {
activity_observer_->register_led(caps_led);
activity_observer_->set_led_status(caps_led, caps_led_state_);
}
if(plus3_) {
plus3_->set_activity_observer(observer);
}
if(plus3_) {
plus3_->set_activity_observer(observer);
}
if(has_scsi_bus) {
scsi_bus_.set_activity_observer(observer);
}
}
@ -591,7 +725,12 @@ class ConcreteMachine:
} else {
interrupt_status_ &= ~1;
}
m6502_.set_irq_line(interrupt_status_ & 1);
if constexpr (has_scsi_bus) {
m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1));
} else {
m6502_.set_irq_line(interrupt_status_ & 1);
}
}
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
@ -635,6 +774,25 @@ class ConcreteMachine:
bool is_holding_shift_ = false;
int shift_restart_counter_ = 0;
// Hard drive.
SCSI::Bus scsi_bus_;
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState;
const size_t scsi_device_ = 0;
uint8_t scsi_data_ = 0;
bool scsi_select_ = false;
bool scsi_acknowledge_ = false;
bool scsi_is_clocked_ = false;
bool scsi_interrupt_state_ = false;
bool scsi_interrupt_mask_ = false;
void push_scsi_output() {
scsi_bus_.set_device_output(scsi_device_,
(scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) |
(scsi_select_ ? SCSI::Line::SelectTarget : 0) |
(scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0)
);
}
// Outputs
VideoOutput video_output_;
@ -657,7 +815,12 @@ using namespace Electron;
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Acorn::Target;
const Target *const acorn_target = dynamic_cast<const Target *>(target);
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
if(acorn_target->media.mass_storage_devices.empty()) {
return new Electron::ConcreteMachine<false>(*acorn_target, rom_fetcher);
} else {
return new Electron::ConcreteMachine<true>(*acorn_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@ -16,6 +16,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
default: break;
BIND(BackTick, KeyCopy);
BIND(Backslash, KeyCopy);
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);

View File

@ -31,11 +31,15 @@ enum Key: uint16_t {
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
// Virtual keys.
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyBreak = 0xfffd,
};
constexpr bool is_modifier(Key key) {
return (key == KeyShift) || (key == KeyControl) || (key == KeyFunc);
}
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};

View File

@ -413,7 +413,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
// to satisfy CPU::MOS6502::BusHandler
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
if(!isWriteOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
@ -432,39 +432,39 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
} else {
if((address & 0xff00) == 0x0300) {
if(address < 0x0310 || (disk_interface == DiskInterface::None)) {
if(isReadOperation(operation)) *value = via_.read(address);
if(!isWriteOperation(operation)) *value = via_.read(address);
else via_.write(address, *value);
} else {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
if(isReadOperation(operation)) *value = bd500_.read(address);
if(!isWriteOperation(operation)) *value = bd500_.read(address);
else bd500_.write(address, *value);
break;
case DiskInterface::Jasmin:
if(address >= 0x3f4) {
if(isReadOperation(operation)) *value = jasmin_.read(address);
if(!isWriteOperation(operation)) *value = jasmin_.read(address);
else jasmin_.write(address, *value);
}
break;
case DiskInterface::Microdisc:
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.read(address);
if(!isWriteOperation(operation)) *value = microdisc_.read(address);
else microdisc_.write(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
if(!isWriteOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
if(!isWriteOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
break;
case DiskInterface::Pravetz:
if(address >= 0x0320) {
if(isReadOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
if(!isWriteOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
else {
switch(address) {
case 0x380: case 0x381: case 0x382: case 0x383:
@ -476,13 +476,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
} else {
flush_diskii();
const int disk_value = diskii_.read_address(address);
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
if(!isWriteOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
}
break;
}
}
} else {
if(isReadOperation(operation))
if(!isWriteOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) update_video();

View File

@ -109,7 +109,7 @@ class TypeRecipient: public Typer::Delegate {
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
void add_typer(const std::string &string) {
if(!typer_) {
typer_ = std::make_unique<Typer>(string, get_typer_delay(), get_typer_frequency(), character_mapper, this);
typer_ = std::make_unique<Typer>(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this);
} else {
typer_->append(string);
}
@ -137,7 +137,7 @@ class TypeRecipient: public Typer::Delegate {
typer_ = nullptr;
}
virtual HalfCycles get_typer_delay() const { return HalfCycles(0); }
virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;

View File

@ -387,7 +387,7 @@ template<bool is_zx81> class ConcreteMachine:
}
// MARK: - Typer timing
HalfCycles get_typer_delay() const final {
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
}

View File

@ -912,6 +912,7 @@
4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; };
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; };
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
@ -1916,6 +1917,8 @@
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = "<group>"; };
4BE8EB5525C0EA490040BC40 /* Sizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = "<group>"; };
4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = "<group>"; };
4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = "<group>"; };
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
@ -2870,6 +2873,8 @@
children = (
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
4B74CF802312FA9C00500CE8 /* HFV.cpp */,
4BE8EB6425C750B50040BC40 /* DAT.cpp */,
4BE8EB6525C750B50040BC40 /* DAT.hpp */,
);
path = Formats;
sourceTree = "<group>";
@ -5239,6 +5244,7 @@
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,

View File

@ -567,6 +567,24 @@
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dat</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Hard Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>

View File

@ -121,7 +121,7 @@
using Target = Analyser::Static::Acorn::Target;
auto target = std::make_unique<Target>();
target->has_dfs = dfs;
target->has_adfs = adfs;
target->has_pres_adfs = adfs;
target->has_ap6_rom = ap6;
target->has_sideways_ram = sidewaysRAM;
_targets.push_back(std::move(target));

View File

@ -1184,7 +1184,7 @@ void MainWindow::start_electron() {
auto target = std::make_unique<Target>();
target->has_dfs = ui->electronDFSCheckBox->isChecked();
target->has_adfs = ui->electronADFSCheckBox->isChecked();
target->has_pres_adfs = ui->electronADFSCheckBox->isChecked();
target->has_ap6_rom = ui->electronAP6CheckBox->isChecked();
target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked();

View File

@ -8,10 +8,9 @@ DFS-1770-2.20.rom — used only if the user opens a DFS disk image
ADFS-E00_1.rom — used only if the user opens an ADFS disk image
ADFS-E00_2.rom
AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below.
adfs.rom - used only if the user opens a hard disk image
Possibly to be desired in the future:
* adfs.rom
* ElectronExpansionRomPresAP2-v1.23.rom
* os300.rom
Commands that trigger a request for the AP6v133 ROM:

View File

@ -0,0 +1,41 @@
//
// DAT.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "DAT.hpp"
using namespace Storage::MassStorage;
DAT::DAT(const std::string &file_name) : file_(file_name) {
// Is the file a multiple of 256 bytes in size?
const auto file_size = file_.stats().st_size;
if(file_size & 255) throw std::exception();
// Does it contain the 'Hugo' signature?
file_.seek(0x201, SEEK_SET);
if(!file_.check_signature("Hugo")) {
throw std::exception();
}
}
size_t DAT::get_block_size() {
return 256;
}
size_t DAT::get_number_of_blocks() {
return size_t(file_.stats().st_size) / 256;
}
std::vector<uint8_t> DAT::get_block(size_t address) {
file_.seek(long(address * 256), SEEK_SET);
return file_.read(256);
}
void DAT::set_block(size_t address, const std::vector<uint8_t> &contents) {
file_.seek(long(address * 256), SEEK_SET);
file_.write(contents);
}

View File

@ -0,0 +1,40 @@
//
// DAT.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MassStorage_DAT_hpp
#define MassStorage_DAT_hpp
#include "../MassStorageDevice.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace MassStorage {
/*!
Provides a @c MassStorageDevice containing an Acorn ADFS image, which is just a
sector dump of an ADFS volume. It will be validated for an ADFS catalogue and communicate
in 256-byte blocks.
*/
class DAT: public MassStorageDevice {
public:
DAT(const std::string &file_name);
private:
FileHolder file_;
/* MassStorageDevices overrides. */
size_t get_block_size() final;
size_t get_number_of_blocks() final;
std::vector<uint8_t> get_block(size_t address) final;
void set_block(size_t address, const std::vector<uint8_t> &) final;
};
}
}
#endif /* MassStorage_DAT_hpp */

View File

@ -38,6 +38,7 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon
if(!device_) return false;
const auto specs = state.read_write_specs();
LOG("Write: " << specs.number_of_blocks << " to " << specs.address);
responder.receive_data(device_->get_block_size() * specs.number_of_blocks, [this, specs] (const Target::CommandState &state, Target::Responder &responder) {
const auto received_data = state.received_data();

View File

@ -90,6 +90,13 @@ constexpr double DeskewDelay = ns(45.0);
/// any two devices.
constexpr double CableSkew = ns(10.0);
/*!
@returns The value of the data lines per @c state.
*/
constexpr uint8_t data_lines(BusState state) {
return uint8_t(state & 0xff);
}
#undef ns
#undef us

View File

@ -17,8 +17,8 @@ template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, B
/*
"The target determines that it is selected when the SEL# signal
and its SCSI ID bit are active and the BSY# and I#/O signals
are false. It then asserts the signal within a selection abort
time."
are false. It then asserts the signal within a selection
abort time."
*/
// Wait for deskew, at the very least.