1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #51 from TomHarte/WD1770

Adds FM and MFM encoding logic, SSD, DSD, ADF file support, and an emulated WD1770
This commit is contained in:
Thomas Harte 2016-09-28 21:32:08 -04:00 committed by GitHub
commit 6c449e7936
48 changed files with 2336 additions and 450 deletions

460
Components/1770/1770.cpp Normal file
View File

@ -0,0 +1,460 @@
//
// 1770.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
using namespace WD;
WD1770::WD1770() :
Storage::Disk::Controller(8000000, 16, 300),
status_(0),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
is_awaiting_marker_value_(false),
is_reading_data_(false)
{
set_is_double_density(false);
posit_event(Event::Command);
}
void WD1770::set_is_double_density(bool is_double_density)
{
is_double_density_ = is_double_density;
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_double_density ? 500000 : 250000;
set_expected_bit_length(bit_length);
if(!is_double_density) is_awaiting_marker_value_ = false;
}
void WD1770::set_register(int address, uint8_t value)
{
switch(address&3)
{
case 0:
command_ = value;
posit_event(Event::Command);
// TODO: is this force interrupt?
break;
case 1: track_ = value; break;
case 2: sector_ = value; break;
case 3: data_ = value; break;
}
}
uint8_t WD1770::get_register(int address)
{
switch(address&3)
{
default: return status_;
case 1: return track_;
case 2: return sector_;
case 3: status_ &= ~Flag::DataRequest; return data_;
}
}
void WD1770::run_for_cycles(unsigned int number_of_cycles)
{
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
if(delay_time_)
{
if(delay_time_ <= number_of_cycles)
{
delay_time_ = 0;
posit_event(Event::Timer);
}
else
{
delay_time_ -= number_of_cycles;
}
}
}
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
Token::Type token_type = Token::Byte;
if(!is_reading_data_)
{
if(!is_double_density_)
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
break;
default:
break;
}
}
else
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::MFMIndexAddressMark:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
return;
case Storage::Encodings::MFM::MFMAddressMark:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
return;
default:
break;
}
}
if(token_type != Token::Byte)
{
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event(Event::Token);
return;
}
}
if(bits_since_token_ == 16)
{
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_)
{
is_awaiting_marker_value_ = false;
switch(latest_token_.byte_value)
{
case Storage::Encodings::MFM::MFMIndexAddressByte:
latest_token_.type = Token::Index;
break;
case Storage::Encodings::MFM::MFMIDAddressByte:
latest_token_.type = Token::ID;
break;
case Storage::Encodings::MFM::MFMDataAddressByte:
latest_token_.type = Token::Data;
break;
case Storage::Encodings::MFM::MFMDeletedDataAddressByte:
latest_token_.type = Token::DeletedData;
break;
default: break;
}
}
posit_event(Event::Token);
return;
}
}
void WD1770::process_index_hole()
{
index_hole_count_++;
posit_event(Event::IndexHole);
if(index_hole_count_target_ == index_hole_count_)
{
posit_event(Event::IndexHoleTarget);
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !(status_&Flag::Busy))
{
status_ &= ~Flag::MotorOn;
set_motor_on(false);
}
}
// +------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +------+----------+-------------------------+
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
#define READ_ID() \
if(new_event_type == Event::Token) \
{ \
if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
{ \
header[distance_into_section_ - 1] = latest_token_.byte_value; \
distance_into_section_++; \
} \
}
#define CONCATENATE(x, y) x ## y
#define INDIRECT_CONCATENATE(x, y) TOKENPASTE(x, y)
#define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__)
#define SPIN_UP() \
status_ |= Flag::MotorOn; \
set_motor_on(true); \
index_hole_count_ = 0; \
index_hole_count_target_ = 6; \
WAIT_FOR_EVENT(Event::IndexHoleTarget);
void WD1770::posit_event(Event new_event_type)
{
if(!(interesting_event_mask_ & (int)new_event_type)) return;
interesting_event_mask_ &= ~new_event_type;
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
wait_for_command:
printf("Idle...\n");
is_reading_data_ = false;
status_ &= ~Flag::Busy;
index_hole_count_ = 0;
WAIT_FOR_EVENT(Event::Command);
printf("Starting %02x\n", command_);
status_ |= Flag::Busy;
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
goto begin_type_3;
/*
Type 1 entry point.
*/
begin_type_1:
// Set initial flags, skip spin-up if possible.
status_ &= ~(Flag::DataRequest | Flag::DataRequest | Flag::SeekError);
set_interrupt_request(false);
if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type1_type;
// Perform spin up.
SPIN_UP();
status_ |= Flag::SpinUp;
test_type1_type:
// Set step direction if this is a step in or out.
if((command_ >> 5) == 2) step_direction_ = 1;
if((command_ >> 5) == 3) step_direction_ = 0;
if((command_ >> 5) != 0) goto perform_step_command;
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
if(!(command_ & 0x10))
{
track_ = 0xff;
data_ = 0;
}
perform_seek_or_restore_command:
if(track_ == data_) goto verify;
step_direction_ = (data_ > track_);
adjust_track:
if(step_direction_) track_++; else track_--;
perform_step:
if(!step_direction_ && get_is_track_zero())
{
track_ = 0;
goto verify;
}
step(step_direction_ ? 1 : -1);
int time_to_wait;
switch(command_ & 3)
{
default:
case 0: time_to_wait = 6; break; // 2 on a 1772
case 1: time_to_wait = 12; break; // 3 on a 1772
case 2: time_to_wait = 20; break; // 5 on a 1772
case 3: time_to_wait = 30; break; // 6 on a 1772
}
WAIT_FOR_TIME(time_to_wait);
if(command_ >> 5) goto verify;
goto perform_seek_or_restore_command;
perform_step_command:
if(command_ & 0x10) goto adjust_track;
goto perform_step;
verify:
if(!(command_ & 0x04))
{
set_interrupt_request(true);
goto wait_for_command;
}
index_hole_count_ = 0;
distance_into_section_ = 0;
verify_read_data:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
READ_ID();
if(index_hole_count_ == 6)
{
set_interrupt_request(true);
status_ |= Flag::SeekError;
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
is_reading_data_ = false;
// TODO: CRC check
if(header[0] == track_)
{
printf("Reached track %d\n", track_);
status_ &= ~Flag::CRCError;
set_interrupt_request(true);
goto wait_for_command;
}
distance_into_section_ = 0;
}
goto verify_read_data;
/*
Type 2 entry point.
*/
begin_type_2:
status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | Flag::WriteProtect | Flag::RecordType);
set_interrupt_request(false);
distance_into_section_ = 0;
if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type2_delay;
// Perform spin up.
SPIN_UP();
test_type2_delay:
index_hole_count_ = 0;
if(!(command_ & 0x04)) goto test_type2_write_protection;
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20) // TODO:: && is_write_protected
{
set_interrupt_request(true);
status_ |= Flag::WriteProtect;
goto wait_for_command;
}
type2_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
READ_ID();
if(index_hole_count_ == 5)
{
set_interrupt_request(true);
status_ |= Flag::RecordNotFound;
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
is_reading_data_ = false;
if(header[0] == track_ && header[2] == sector_)
{
// TODO: test CRC
goto type2_read_or_write_data;
}
distance_into_section_ = 0;
}
goto type2_get_header;
type2_read_or_write_data:
if(command_&0x20) goto type2_write_data;
goto type2_read_data;
type2_read_data:
WAIT_FOR_EVENT(Event::Token);
// TODO: timeout
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
{
status_ |= (latest_token_.type == Token::DeletedData) ? Flag::RecordType : 0;
distance_into_section_ = 0;
is_reading_data_ = true;
goto type2_read_byte;
}
goto type2_read_data;
type2_read_byte:
WAIT_FOR_EVENT(Event::Token);
if(latest_token_.type != Token::Byte) goto type2_read_byte;
if(status_ & Flag::DataRequest) status_ |= Flag::LostData;
data_ = latest_token_.byte_value;
status_ |= Flag::DataRequest;
distance_into_section_++;
if(distance_into_section_ == 128 << header[3])
{
distance_into_section_ = 0;
goto type2_check_crc;
}
goto type2_read_byte;
type2_check_crc:
WAIT_FOR_EVENT(Event::Token);
if(latest_token_.type != Token::Byte) goto type2_read_byte;
header[distance_into_section_] = latest_token_.byte_value;
distance_into_section_++;
if(distance_into_section_ == 2)
{
// TODO: check CRC
if(command_ & 0x10)
{
sector_++;
goto test_type2_write_protection;
}
set_interrupt_request(true);
printf("Read sector %d\n", sector_);
goto wait_for_command;
}
goto type2_check_crc;
type2_write_data:
printf("!!!TODO: data portion of sector!!!\n");
begin_type_3:
printf("!!!TODO: type 3 commands!!!\n");
END_SECTION()
}

92
Components/1770/1770.hpp Normal file
View File

@ -0,0 +1,92 @@
//
// 1770.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef _770_hpp
#define _770_hpp
#include "../../Storage/Disk/DiskController.hpp"
namespace WD {
class WD1770: public Storage::Disk::Controller {
public:
WD1770();
void set_is_double_density(bool is_double_density);
void set_register(int address, uint8_t value);
uint8_t get_register(int address);
void run_for_cycles(unsigned int number_of_cycles);
enum Flag: uint8_t {
MotorOn = 0x80,
WriteProtect = 0x40,
RecordType = 0x20,
SpinUp = 0x20,
RecordNotFound = 0x10,
SeekError = 0x10,
CRCError = 0x08,
LostData = 0x04,
TrackZero = 0x04,
DataRequest = 0x02,
Index = 0x02,
Busy = 0x01
};
private:
uint8_t status_;
uint8_t track_;
uint8_t sector_;
uint8_t data_;
uint8_t command_;
int index_hole_count_;
int index_hole_count_target_;
int bits_since_token_;
int distance_into_section_;
bool is_awaiting_marker_value_;
int step_direction_;
void set_interrupt_request(bool interrupt_request) {}
// Tokeniser
bool is_reading_data_;
bool is_double_density_;
int shift_register_;
struct Token {
enum Type {
Index, ID, Data, DeletedData, Byte
} type;
uint8_t byte_value;
} latest_token_;
// Events
enum Event: int {
Command = (1 << 0), // Indicates receipt of a new command.
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
Timer = (1 << 3), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 4) // Indicates that index_hole_count_ has reached index_hole_count_target_.
};
void posit_event(Event type);
int interesting_event_mask_;
int resume_point_;
int delay_time_;
// ID buffer
uint8_t header[6];
//
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
};
}
#endif /* _770_hpp */

View File

@ -14,7 +14,7 @@ using namespace Commodore::C1540;
Machine::Machine() :
_shift_register(0),
Storage::Disk::Drive(1000000, 4, 300)
Storage::Disk::Controller(1000000, 4, 300)
{
// create a serial port and a VIA to run it
_serialPortVIA.reset(new SerialPortVIA);
@ -102,11 +102,19 @@ void Machine::set_rom(const uint8_t *rom)
memcpy(_rom, rom, sizeof(_rom));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
{
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
}
void Machine::run_for_cycles(int number_of_cycles)
{
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
set_motor_on(_driveVIA.get_motor_enabled());
if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down
Storage::Disk::Drive::run_for_cycles(number_of_cycles);
Storage::Disk::Controller::run_for_cycles(number_of_cycles);
}
#pragma mark - 6522 delegate

View File

@ -15,7 +15,7 @@
#include "../SerialBus.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/DiskDrive.hpp"
#include "../../../Storage/Disk/DiskController.hpp"
namespace Commodore {
namespace C1540 {
@ -216,7 +216,7 @@ class Machine:
public CPU6502::Processor<Machine>,
public MOS::MOS6522IRQDelegate::Delegate,
public DriveVIA::Delegate,
public Storage::Disk::Drive {
public Storage::Disk::Controller {
public:
Machine();
@ -232,6 +232,7 @@ class Machine:
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
void run_for_cycles(int number_of_cycles);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);

View File

@ -9,7 +9,7 @@
#ifndef SerialBus_hpp
#define SerialBus_hpp
#import <vector>
#include <vector>
namespace Commodore {
namespace Serial {

View File

@ -120,276 +120,303 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
else
{
if(address >= 0xc000)
// if((address >> 8) == 0xfc)
// {
// printf("d");
// }
switch(address & 0xff0f)
{
if((address & 0xff00) == 0xfe00)
{
switch(address&0xf)
{
case 0x0:
if(isReadOperation(operation))
{
*value = _interrupt_status;
_interrupt_status &= ~PowerOnReset;
}
else
{
_interrupt_control = (*value) & ~1;
evaluate_interrupts();
}
break;
case 0x1:
break;
case 0x2:
if(!isReadOperation(operation))
{
_startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1);
if(!_startScreenAddress) _startScreenAddress |= 0x8000;
}
break;
case 0x3:
if(!isReadOperation(operation))
{
_startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9);
if(!_startScreenAddress) _startScreenAddress |= 0x8000;
}
break;
case 0x4:
if(isReadOperation(operation))
{
*value = _tape.get_data_register();
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
}
else
{
_tape.set_data_register(*value);
_tape.clear_interrupts(Interrupt::TransmitDataEmpty);
}
break;
case 0x5:
if(!isReadOperation(operation))
{
const uint8_t interruptDisable = (*value)&0xf0;
if( interruptDisable )
{
if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd;
if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock;
if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect;
evaluate_interrupts();
// TODO: NMI
}
// latch the paged ROM in case external hardware is being emulated
_active_rom = (Electron::ROMSlot)(*value & 0xf);
// apply the ULA's test
if(*value & 0x08)
{
if(*value & 0x04)
{
_keyboard_is_active = false;
_basic_is_active = false;
}
else
{
_keyboard_is_active = !(*value & 0x02);
_basic_is_active = !_keyboard_is_active;
}
}
}
break;
case 0x6:
if(!isReadOperation(operation))
{
update_audio();
_speaker->set_divider(*value);
_tape.set_counter(*value);
}
break;
case 0x7:
if(!isReadOperation(operation))
{
// update screen mode
uint8_t new_screen_mode = ((*value) >> 3)&7;
if(new_screen_mode == 7) new_screen_mode = 4;
if(new_screen_mode != _screen_mode)
{
// printf("To mode %d, at %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7);
update_display();
_screen_mode = new_screen_mode;
switch(_screen_mode)
{
case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break;
case 3: _screenModeBaseAddress = 0x4000; break;
case 4: case 5: _screenModeBaseAddress = 0x5800; break;
case 6: _screenModeBaseAddress = 0x6000; break;
}
}
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != _speaker->get_is_enabled())
{
update_audio();
_speaker->set_is_enabled(new_speaker_is_enabled);
_tape.set_is_enabled(!new_speaker_is_enabled);
}
_tape.set_is_running(((*value)&0x40) ? true : false);
_tape.set_is_in_input_mode(((*value)&0x04) ? false : true);
// TODO: caps lock LED
}
break;
default:
{
if(!isReadOperation(operation))
{
update_display();
static const int registers[4][4] = {
{10, 8, 2, 0},
{14, 12, 6, 4},
{15, 13, 7, 5},
{11, 9, 3, 1},
};
const int index = (address >> 1)&3;
const uint8_t colour = ~(*value);
if(address&1)
{
_palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4);
_palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4);
_palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4);
_palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4);
_palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2);
_palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2);
}
else
{
_palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1);
_palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1);
_palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1);
_palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1);
_palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2);
_palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2);
}
// regenerate all palette tables for now
#define pack(a, b) (uint8_t)((a << 4) | (b))
for(int byte = 0; byte < 256; byte++)
{
uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte];
target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]);
target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]);
target = (uint8_t *)&_paletteTables.eighty2bpp[byte];
target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
target = (uint8_t *)&_paletteTables.eighty1bpp[byte];
target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]);
target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]);
target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]);
target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]);
_paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
_paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)],
_palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]);
}
#undef pack
}
}
break;
}
}
else
{
case 0xfe00:
if(isReadOperation(operation))
{
if(
_use_fast_tape_hack &&
_tape.has_tape() &&
(operation == CPU6502::BusOperation::ReadOpcode) &&
(
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
(address == 0xfa51) || (address == 0xfa52) || // pathway.
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
// dispatched; we can check whether it would be call 14
// (i.e. read byte) and, if so, whether the OS was about to
// issue a read byte call to a ROM despite being the tape
// FS being selected. If so then this is a get byte that
// we should service synthetically. Put the byte into Y
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
)
*value = _interrupt_status;
_interrupt_status &= ~PowerOnReset;
}
else
{
_interrupt_control = (*value) & ~1;
evaluate_interrupts();
}
break;
case 0xfe02:
if(!isReadOperation(operation))
{
_startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1);
if(!_startScreenAddress) _startScreenAddress |= 0x8000;
}
break;
case 0xfe03:
if(!isReadOperation(operation))
{
_startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9);
if(!_startScreenAddress) _startScreenAddress |= 0x8000;
}
break;
case 0xfe04:
if(isReadOperation(operation))
{
*value = _tape.get_data_register();
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
}
else
{
_tape.set_data_register(*value);
_tape.clear_interrupts(Interrupt::TransmitDataEmpty);
}
break;
case 0xfe05:
if(!isReadOperation(operation))
{
const uint8_t interruptDisable = (*value)&0xf0;
if( interruptDisable )
{
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
if(address == 0xf0a8)
if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd;
if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock;
if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect;
evaluate_interrupts();
// TODO: NMI
}
// latch the paged ROM in case external hardware is being emulated
_active_rom = (Electron::ROMSlot)(*value & 0xf);
// apply the ULA's test
if(*value & 0x08)
{
if(*value & 0x04)
{
if(!_ram[0x247] && service_call == 14)
{
_tape.set_delegate(nullptr);
// TODO: handle tape wrap around.
int cycles_left_while_plausibly_in_data = 50;
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
while(1)
{
_tape.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false;
if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
(_fast_load_is_in_data || _tape.get_data_register() == 0x2a)
) break;
}
_tape.set_delegate(this);
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
_interrupt_status |= _tape.get_interrupt_status();
_fast_load_is_in_data = true;
set_value_of_register(CPU6502::Register::A, 0);
set_value_of_register(CPU6502::Register::Y, _tape.get_data_register());
*value = 0x60; // 0x60 is RTS
}
else
*value = _os[address & 16383];
_keyboard_is_active = false;
_basic_is_active = false;
}
else
*value = 0xea;
{
_keyboard_is_active = !(*value & 0x02);
_basic_is_active = !_keyboard_is_active;
}
}
}
break;
case 0xfe06:
if(!isReadOperation(operation))
{
update_audio();
_speaker->set_divider(*value);
_tape.set_counter(*value);
}
break;
case 0xfe07:
if(!isReadOperation(operation))
{
// update screen mode
uint8_t new_screen_mode = ((*value) >> 3)&7;
if(new_screen_mode == 7) new_screen_mode = 4;
if(new_screen_mode != _screen_mode)
{
update_display();
_screen_mode = new_screen_mode;
switch(_screen_mode)
{
case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break;
case 3: _screenModeBaseAddress = 0x4000; break;
case 4: case 5: _screenModeBaseAddress = 0x5800; break;
case 6: _screenModeBaseAddress = 0x6000; break;
}
}
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != _speaker->get_is_enabled())
{
update_audio();
_speaker->set_is_enabled(new_speaker_is_enabled);
_tape.set_is_enabled(!new_speaker_is_enabled);
}
_tape.set_is_running(((*value)&0x40) ? true : false);
_tape.set_is_in_input_mode(((*value)&0x04) ? false : true);
// TODO: caps lock LED
}
break;
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
{
if(!isReadOperation(operation))
{
update_display();
static const int registers[4][4] = {
{10, 8, 2, 0},
{14, 12, 6, 4},
{15, 13, 7, 5},
{11, 9, 3, 1},
};
const int index = (address >> 1)&3;
const uint8_t colour = ~(*value);
if(address&1)
{
_palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4);
_palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4);
_palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4);
_palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4);
_palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2);
_palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2);
}
else
{
*value = _os[address & 16383];
_palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1);
_palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1);
_palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1);
_palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1);
_palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2);
_palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2);
}
}
}
}
else
{
if(isReadOperation(operation))
{
*value = _roms[_active_rom][address & 16383];
if(_keyboard_is_active)
{
*value &= 0xf0;
for(int address_line = 0; address_line < 14; address_line++)
// regenerate all palette tables for now
#define pack(a, b) (uint8_t)((a << 4) | (b))
for(int byte = 0; byte < 256; byte++)
{
if(!(address&(1 << address_line))) *value |= _key_states[address_line];
uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte];
target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]);
target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]);
target = (uint8_t *)&_paletteTables.eighty2bpp[byte];
target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
target = (uint8_t *)&_paletteTables.eighty1bpp[byte];
target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]);
target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]);
target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]);
target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]);
_paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
_paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)],
_palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]);
}
}
if(_basic_is_active)
{
*value &= _roms[ROMSlotBASIC][address & 16383];
#undef pack
}
}
break;
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
if(_plus3 && (address&0x00f0) == 0x00c0)
{
if(is_holding_shift_ && address == 0xfcc4)
{
is_holding_shift_ = false;
set_key_state(KeyShift, false);
}
if(isReadOperation(operation))
*value = _plus3->get_register(address);
else
_plus3->set_register(address, *value);
}
break;
case 0xfc00:
if(_plus3 && (address&0x00f0) == 0x00c0)
{
if(!isReadOperation(operation))
{
_plus3->set_control_register(*value);
}
else
*value = 1;
}
break;
default:
if(address >= 0xc000)
{
if(isReadOperation(operation))
{
if(
_use_fast_tape_hack &&
_tape.has_tape() &&
(operation == CPU6502::BusOperation::ReadOpcode) &&
(
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
(address == 0xfa51) || (address == 0xfa52) || // pathway.
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
// dispatched; we can check whether it would be call 14
// (i.e. read byte) and, if so, whether the OS was about to
// issue a read byte call to a ROM despite being the tape
// FS being selected. If so then this is a get byte that
// we should service synthetically. Put the byte into Y
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
)
{
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
if(address == 0xf0a8)
{
if(!_ram[0x247] && service_call == 14)
{
_tape.set_delegate(nullptr);
// TODO: handle tape wrap around.
int cycles_left_while_plausibly_in_data = 50;
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
while(1)
{
_tape.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false;
if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
(_fast_load_is_in_data || _tape.get_data_register() == 0x2a)
) break;
}
_tape.set_delegate(this);
_tape.clear_interrupts(Interrupt::ReceiveDataFull);
_interrupt_status |= _tape.get_interrupt_status();
_fast_load_is_in_data = true;
set_value_of_register(CPU6502::Register::A, 0);
set_value_of_register(CPU6502::Register::Y, _tape.get_data_register());
*value = 0x60; // 0x60 is RTS
}
else
*value = _os[address & 16383];
}
else
*value = 0xea;
}
else
{
*value = _os[address & 16383];
}
}
}
else
{
if(isReadOperation(operation))
{
*value = _roms[_active_rom][address & 16383];
if(_keyboard_is_active)
{
*value &= 0xf0;
for(int address_line = 0; address_line < 14; address_line++)
{
if(!(address&(1 << address_line))) *value |= _key_states[address_line];
}
}
if(_basic_is_active)
{
*value &= _roms[ROMSlotBASIC][address & 16383];
}
} else if(_rom_write_masks[_active_rom])
{
_roms[_active_rom][address & 16383] = *value;
}
}
break;
}
}
@ -448,6 +475,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
_tape.run_for_cycles(cycles);
if(_typer) _typer->update((int)cycles);
if(_plus3) _plus3->run_for_cycles(4*cycles);
return cycles;
}
@ -465,22 +493,57 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
_tape.set_tape(target.tapes.front());
}
if(target.disks.size())
{
_plus3.reset(new Plus3);
if(target.acorn.has_dfs)
{
set_rom(ROMSlot0, _dfs, true);
}
if(target.acorn.has_adfs)
{
set_rom(ROMSlot4, _adfs, true);
set_rom(ROMSlot5, std::vector<uint8_t>(_adfs.begin() + 16384, _adfs.end()), true);
}
_plus3->set_disk(target.disks.front(), 0);
}
ROMSlot slot = ROMSlot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges)
{
set_rom(slot, cartridge->get_segments().front().data, false);
slot = (ROMSlot)(((int)slot + 1)&15);
}
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.acorn.should_hold_shift)
{
set_key_state(KeyShift, true);
is_holding_shift_ = true;
}
}
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable)
{
uint8_t *target = nullptr;
switch(slot)
{
case ROMSlotDFS: _dfs = data; return;
case ROMSlotADFS: _adfs = data; return;
case ROMSlotOS: target = _os; break;
default: target = _roms[slot]; break;
default:
target = _roms[slot];
_rom_write_masks[slot] = is_writeable;
break;
}
memcpy(target, data, std::min((size_t)16384, length));
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
}
inline void Machine::signal_interrupt(Electron::Interrupt interrupt)

View File

@ -15,8 +15,10 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "Plus3.hpp"
#include <cstdint>
#include <vector>
namespace Electron {
@ -30,7 +32,7 @@ enum ROMSlot: uint8_t {
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
ROMSlotOS
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
};
enum Interrupt: uint8_t {
@ -146,7 +148,7 @@ class Machine:
public:
Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable);
void configure_as_target(const StaticAnalyser::Target &target);
void set_key_state(Key key, bool isPressed);
@ -187,7 +189,9 @@ class Machine:
// Things that directly constitute the memory map.
uint8_t _roms[16][16384];
bool _rom_write_masks[16];
uint8_t _os[16384], _ram[32768];
std::vector<uint8_t> _dfs, _adfs;
// Things affected by registers, explicitly or otherwise.
uint8_t _interrupt_status, _interrupt_control;
@ -227,6 +231,10 @@ class Machine:
bool _use_fast_tape_hack;
bool _fast_load_is_in_data;
// Disk
std::unique_ptr<Plus3> _plus3;
bool is_holding_shift_;
// Outputs
std::shared_ptr<Outputs::CRT::CRT> _crt;
std::shared_ptr<Speaker> _speaker;

View File

@ -0,0 +1,35 @@
//
// Plus3.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Plus3.hpp"
using namespace Electron;
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
if(!_drives[drive]) _drives[drive].reset(new Storage::Disk::Drive);
_drives[drive]->set_disk(disk);
}
void Plus3::set_control_register(uint8_t control)
{
// TODO:
// bit 0 => enable or disable drive 1
// bit 1 => enable or disable drive 2
// bit 2 => side select
// bit 3 => single density select
switch(control&3)
{
case 0: set_drive(nullptr); break;
default: set_drive(_drives[0]); break;
case 2: set_drive(_drives[1]); break;
}
if(_drives[0]) _drives[0]->set_head((control & 0x04) ? 1 : 0);
if(_drives[1]) _drives[1]->set_head((control & 0x04) ? 1 : 0);
set_is_double_density(!(control & 0x08));
}

View File

@ -0,0 +1,28 @@
//
// Plus3.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Plus3_hpp
#define Plus3_hpp
#include "../../Components/1770/1770.hpp"
namespace Electron {
class Plus3 : public WD::WD1770 {
public:
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_control_register(uint8_t control);
private:
std::shared_ptr<Storage::Disk::Drive> _drives[2];
};
}
#endif /* Plus3_hpp */

11
NumberTheory/CRC.cpp Normal file
View File

@ -0,0 +1,11 @@
//
// CRC.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRC.hpp"
using namespace NumberTheory;

40
NumberTheory/CRC.hpp Normal file
View File

@ -0,0 +1,40 @@
//
// CRC.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CRC_hpp
#define CRC_hpp
#include <cstdint>
namespace NumberTheory {
class CRC16 {
public:
CRC16(uint16_t polynomial, uint16_t reset_value) :
reset_value_(reset_value), value_(reset_value), polynomial_(polynomial) {}
inline void reset() { value_ = reset_value_; }
inline void add(uint8_t value) {
// TODO: go table based
value_ ^= (uint16_t)value << 8;
for(int c = 0; c < 8; c++)
{
uint16_t exclusive_or = (value_&0x8000) ? polynomial_ : 0x0000;
value_ = (uint16_t)(value_ << 1) ^ exclusive_or;
}
}
inline uint16_t get_value() { return value_; }
private:
uint16_t reset_value_, polynomial_;
uint16_t value_;
};
}
#endif /* CRC_hpp */

View File

@ -26,6 +26,8 @@
4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; };
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
@ -45,12 +47,13 @@
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */; };
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; };
4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; };
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA22B051D8817CE0008C640 /* Disk.cpp */; };
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; };
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; };
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; };
@ -347,13 +350,20 @@
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; };
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -415,6 +425,10 @@
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = "<group>"; };
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
@ -449,8 +463,8 @@
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskDrive.cpp; sourceTree = "<group>"; };
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = "<group>"; };
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = "<group>"; };
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = "<group>"; };
4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; };
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
@ -458,6 +472,8 @@
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
4BA22B051D8817CE0008C640 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Commodore/Disk.cpp; sourceTree = "<group>"; };
4BA22B061D8817CE0008C640 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Commodore/Disk.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
@ -789,8 +805,12 @@
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = "<group>"; };
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
@ -802,6 +822,16 @@
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -904,6 +934,8 @@
4B2A53961D117D36003C6002 /* CSMachine.mm */,
4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */,
4B2A53981D117D36003C6002 /* Wrappers */,
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */,
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */,
);
path = Machine;
sourceTree = "<group>";
@ -936,6 +968,8 @@
children = (
4B2E2D9B1C3A070400138695 /* Electron.cpp */,
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */,
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
);
name = Electron;
sourceTree = "<group>";
@ -1089,11 +1123,13 @@
children = (
4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */,
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */,
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */,
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */,
@ -1108,6 +1144,10 @@
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
4B4C836E1D4F623200CD541F /* D64.cpp */,
4B4C836F1D4F623200CD541F /* D64.hpp */,
4BF829611D8F536B001BAE39 /* SSD.cpp */,
4BF829621D8F536B001BAE39 /* SSD.hpp */,
4BD69F921D98760000243FE1 /* AcornADF.cpp */,
4BD69F931D98760000243FE1 /* AcornADF.hpp */,
);
path = Formats;
sourceTree = "<group>";
@ -1388,6 +1428,8 @@
isa = PBXGroup;
children = (
4BB697C61D4B558F00248BDF /* Factors.hpp */,
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
);
name = NumberTheory;
sourceTree = "<group>";
@ -1397,6 +1439,8 @@
children = (
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
4BF8295B1D8F048B001BAE39 /* MFM.cpp */,
4BF8295C1D8F048B001BAE39 /* MFM.hpp */,
);
name = Encodings;
sourceTree = "<group>";
@ -1538,16 +1582,16 @@
4BC830D21D6E7C6D0000A26F /* Commodore */ = {
isa = PBXGroup;
children = (
4BA22B051D8817CE0008C640 /* Disk.cpp */,
4BA22B061D8817CE0008C640 /* Disk.hpp */,
4BE77A2C1D84ADFB00BC3827 /* File.cpp */,
4BE77A2D1D84ADFB00BC3827 /* File.hpp */,
4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */,
4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */,
4BC830CF1D6E7C690000A26F /* Tape.cpp */,
4BC830D01D6E7C690000A26F /* Tape.hpp */,
4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */,
4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */,
4BE77A2C1D84ADFB00BC3827 /* File.cpp */,
4BE77A2D1D84ADFB00BC3827 /* File.hpp */,
4BA22B051D8817CE0008C640 /* Disk.cpp */,
4BA22B061D8817CE0008C640 /* Disk.hpp */,
);
name = Commodore;
sourceTree = "<group>";
@ -1558,6 +1602,7 @@
4BC9DF4B1D04691600F44158 /* 6522 */,
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
4BD468F81D8DF4290084958B /* 1770 */,
);
name = Components;
path = ../../Components;
@ -1583,6 +1628,10 @@
4BD14B121D7462810088EAD6 /* Acorn */ = {
isa = PBXGroup;
children = (
4BF829641D8F732B001BAE39 /* Disk.cpp */,
4BF829651D8F732B001BAE39 /* Disk.hpp */,
4BF829671D8F7361001BAE39 /* File.cpp */,
4BF829681D8F7361001BAE39 /* File.hpp */,
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
4B96F7201D75119A0058BB2D /* Tape.cpp */,
@ -1591,6 +1640,15 @@
name = Acorn;
sourceTree = "<group>";
};
4BD468F81D8DF4290084958B /* 1770 */ = {
isa = PBXGroup;
children = (
4BD468F51D8DF41D0084958B /* 1770.cpp */,
4BD468F61D8DF41D0084958B /* 1770.hpp */,
);
name = 1770;
sourceTree = "<group>";
};
4BD5F1961D1352A000631CD1 /* Updater */ = {
isa = PBXGroup;
children = (
@ -2061,6 +2119,7 @@
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
@ -2069,23 +2128,30 @@
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */,
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */,
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
@ -2095,6 +2161,8 @@
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */,
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,

View File

@ -21,6 +21,6 @@ class DocumentController: NSDocumentController {
}
}
return try! super.makeDocument(withContentsOf: url, ofType: typeName)
return try super.makeDocument(withContentsOf: url, ofType: typeName)
}
}

View File

@ -31,15 +31,27 @@ class ElectronDocument: MachineDocument {
return dataForResource(name, ofType: "rom", inDirectory: "ROMImages/Electron")
}
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
override init() {
super.init();
if let os = rom("os"), let basic = rom("basic") {
self.electron.setOSROM(os)
self.electron.setBASICROM(basic)
}
if let dfs = rom("DFS-1770-2.20") {
self.electron.setDFSROM(dfs)
}
if let adfs1 = rom("ADFS-E00_1"), let adfs2 = rom("ADFS-E00_2") {
var fullADFS = adfs1
fullADFS.append(adfs2)
self.electron.setADFSROM(fullADFS as Data)
}
}
// override func windowControllerDidLoadNib(_ aController: NSWindowController) {
// super.windowControllerDidLoadNib(aController)
// }
override var windowNibName: String? {
return "ElectronDocument"
}

View File

@ -130,6 +130,22 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>ssd</string>
<string>dsd</string>
<string>adf</string>
<string>adl</string>
<string>adm</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).ElectronDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -0,0 +1,18 @@
//
// NSData+StdVector.h
// Clock Signal
//
// Created by Thomas Harte on 20/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <cstdint>
#include <vector>
@interface NSData (StdVector)
- (std::vector<uint8_t>)stdVector8;
@end

View File

@ -0,0 +1,19 @@
//
// NSData+StdVector.m
// Clock Signal
//
// Created by Thomas Harte on 20/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "NSData+StdVector.h"
@implementation NSData (StdVector)
- (std::vector<uint8_t>)stdVector8
{
uint8_t *bytes8 = (uint8_t *)self.bytes;
return std::vector<uint8_t>(bytes8, bytes8 + self.length);
}
@end

View File

@ -16,7 +16,8 @@
- (void)setOSROM:(nonnull NSData *)rom;
- (void)setBASICROM:(nonnull NSData *)rom;
- (void)setROM:(nonnull NSData *)rom slot:(int)slot;
- (void)setDFSROM:(nonnull NSData *)rom;
- (void)setADFSROM:(nonnull NSData *)rom;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useTelevisionOutput;

View File

@ -10,6 +10,7 @@
#include "Electron.hpp"
#import "CSMachine+Subclassing.h"
#import "NSData+StdVector.h"
#include "StaticAnalyser.hpp"
#include "TapeUEF.hpp"
@ -25,21 +26,14 @@
StaticAnalyser::GetTargets([url fileSystemRepresentation]);
}
- (void)setOSROM:(nonnull NSData *)rom {
@synchronized(self) {
_electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes);
}
}
- (void)setBASICROM:(nonnull NSData *)rom {
@synchronized(self) {
_electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes);
}
}
- (void)setOSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotOS]; }
- (void)setBASICROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotBASIC]; }
- (void)setADFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotADFS]; }
- (void)setDFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotDFS]; }
- (void)setROM:(nonnull NSData *)rom slot:(int)slot {
@synchronized(self) {
_electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes);
_electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false);
}
}

View File

@ -5,12 +5,12 @@ Expected files:
basic.rom
os.rom
plus1.rom
DFS-1770-2.20.rom
ADFS-E00_1.rom
ADFS-E00_2.rom
Likely to be desired in the future:
adfs.rom
ADFS-E00_1.rom
ADFS-E00_2.rom
DFSE00r3.rom
ElectronExpansionRomPresAP2-v1.23.rom
os300.rom

View File

@ -0,0 +1,280 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Disk.hpp"
#include "../../Storage/Disk/DiskController.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../NumberTheory/CRC.hpp"
#include <algorithm>
using namespace StaticAnalyser::Acorn;
class FMParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
FMParser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm)
{
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
set_expected_bit_length(bit_length);
drive.reset(new Storage::Disk::Drive);
set_drive(drive);
set_motor_on(true);
}
/*!
Attempts to read the sector located at @c track and @c sector.
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t track, uint8_t sector)
{
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) step(direction);
}
return get_sector(sector);
}
private:
unsigned int shift_register_;
int index_count_;
uint8_t track_;
int bit_count_;
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_cache_[65536];
NumberTheory::CRC16 crc_generator_;
bool is_mfm_;
void process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
void process_index_hole()
{
index_count_++;
}
uint8_t get_next_byte()
{
bit_count_ = 0;
while(bit_count_ < 16) run_for_cycles(1);
uint8_t byte = (uint8_t)(
((shift_register_&0x0001) >> 0) |
((shift_register_&0x0004) >> 1) |
((shift_register_&0x0010) >> 2) |
((shift_register_&0x0040) >> 3) |
((shift_register_&0x0100) >> 4) |
((shift_register_&0x0400) >> 5) |
((shift_register_&0x1000) >> 6) |
((shift_register_&0x4000) >> 7));
crc_generator_.add(byte);
return byte;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector()
{
std::shared_ptr<Storage::Encodings::MFM::Sector> sector(new Storage::Encodings::MFM::Sector);
index_count_ = 0;
while(index_count_ < 2)
{
// look for an ID address mark
while(1)
{
run_for_cycles(1);
if(is_mfm_)
{
if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMIDAddressByte) break;
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break;
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.reset();
sector->track = get_next_byte();
sector->side = get_next_byte();
sector->sector = get_next_byte();
uint8_t size = get_next_byte();
uint16_t header_crc = crc_generator_.get_value();
if((header_crc >> 8) != get_next_byte()) continue;
if((header_crc & 0xff) != get_next_byte()) continue;
// look for data mark
while(1)
{
run_for_cycles(1);
if(is_mfm_)
{
if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark)
{
uint8_t mark = get_next_byte();
if(mark == Storage::Encodings::MFM::MFMDataAddressByte) break;
if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr;
}
}
else
{
if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break;
if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr;
}
if(index_count_ >= 2) return nullptr;
}
size_t data_size = (size_t)(128 << size);
sector->data.reserve(data_size);
crc_generator_.reset();
for(size_t c = 0; c < data_size; c++)
{
sector->data.push_back(get_next_byte());
}
uint16_t data_crc = crc_generator_.get_value();
if((data_crc >> 8) != get_next_byte()) continue;
if((data_crc & 0xff) != get_next_byte()) continue;
return sector;
}
return nullptr;
}
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector)
{
// uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
// if(sector_cache_[sector_address]) return sector_cache_[sector_address];
std::shared_ptr<Storage::Encodings::MFM::Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
};
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
std::unique_ptr<Catalogue> catalogue(new Catalogue);
FMParser parser(false);
parser.drive->set_disk(disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0);
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 1);
if(!names || !details) return nullptr;
if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
uint8_t final_file_offset = details->data[5];
if(final_file_offset&7) return nullptr;
char disk_name[13];
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
catalogue->name = disk_name;
switch((details->data[6] >> 4)&3)
{
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
// So iterating backwards implies the least amount of seeking.
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8)
{
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
new_file.name = name;
new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->data[file_offset + 7] & 0x80);
long data_length = (long)(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12));
int start_sector = details->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8);
new_file.data.reserve((size_t)data_length);
if(start_sector < 2) continue;
while(data_length > 0)
{
uint8_t sector = (uint8_t)(start_sector % 10);
uint8_t track = (uint8_t)(start_sector / 10);
start_sector++;
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(track, sector);
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_front(new_file);
}
return catalogue;
}
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::unique_ptr<Catalogue> catalogue(new Catalogue);
FMParser parser(true);
parser.drive->set_disk(disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1);
if(!free_space_map_second_half) return nullptr;
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++)
{
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
}
// Quick sanity checks.
if(root_directory[0x4cb]) return nullptr;
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
switch(free_space_map_second_half->data[0xfd])
{
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
return catalogue;
}

View File

@ -0,0 +1,35 @@
//
// Disk.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Acorn_Disk_hpp
#define StaticAnalyser_Acorn_Disk_hpp
#include "File.hpp"
#include "../../Storage/Disk/Disk.hpp"
namespace StaticAnalyser {
namespace Acorn {
struct Catalogue {
std::string name;
std::list<File> files;
enum class BootOption {
None,
LoadBOOT,
RunBOOT,
ExecBOOT
} bootOption;
};
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
#endif /* Disk_hpp */

View File

@ -0,0 +1,9 @@
//
// File.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "File.hpp"

View File

@ -0,0 +1,47 @@
//
// File.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef File_hpp
#define File_hpp
#include <list>
#include <memory>
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
std::vector<uint8_t> data;
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
bool header_crc_matched;
bool data_crc_matched;
std::vector<uint8_t> data;
};
std::list<Chunk> chunks;
};
}
}
#endif /* File_hpp */

View File

@ -8,6 +8,7 @@
#include "StaticAnalyser.hpp"
#include "Disk.hpp"
#include "Tape.hpp"
using namespace StaticAnalyser::Acorn;
@ -66,6 +67,9 @@ void StaticAnalyser::Acorn::AddTargets(
Target target;
target.machine = Target::Electron;
target.probability = 1.0; // TODO: a proper estimation
target.acorn.has_dfs = false;
target.acorn.has_adfs = false;
target.acorn.should_hold_shift = false;
// strip out inappropriate cartridges
target.cartridges = AcornCartridgesFrom(cartridges);
@ -110,8 +114,35 @@ void StaticAnalyser::Acorn::AddTargets(
}
}
// TODO: disks
if(disks.size() > 0)
{
std::shared_ptr<Storage::Disk::Disk> disk = disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue)
{
target.disks = disks;
target.acorn.has_dfs = !!dfs_catalogue;
target.acorn.has_adfs = !!adfs_catalogue;
if(target.tapes.size() || target.cartridges.size())
std::string adfs_command;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
switch(bootOption)
{
case Catalogue::BootOption::None: adfs_command = "*CAT\n"; break;
case Catalogue::BootOption::LoadBOOT: adfs_command = "*MOUNT\n*LOAD !BOOT\n"; break;
case Catalogue::BootOption::RunBOOT: adfs_command = "*MOUNT\n*RUN !BOOT\n"; break;
case Catalogue::BootOption::ExecBOOT: adfs_command = "*MOUNT\n*EXEC !BOOT\n"; break;
}
if(target.acorn.has_dfs && bootOption != Catalogue::BootOption::None)
target.acorn.should_hold_shift = true;
else
target.loadingCommand = adfs_command;
}
}
if(target.tapes.size() || target.disks.size() || target.cartridges.size())
destination.push_back(target);
}

View File

@ -10,6 +10,7 @@
#include <deque>
#include "../TapeParser.hpp"
#include "../../NumberTheory/CRC.hpp"
using namespace StaticAnalyser::Acorn;
@ -23,7 +24,9 @@ enum class SymbolType {
class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, SymbolType> {
public:
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) : TapeParser(tape) {}
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) :
TapeParser(tape),
_crc(0x1021, 0x0000) {}
int get_next_bit()
{
@ -49,7 +52,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
set_error_flag();
return -1;
}
add_to_crc((uint8_t)value);
_crc.add((uint8_t)value);
return value;
}
@ -67,8 +70,8 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
return result;
}
void reset_crc() { _crc = 0; }
uint16_t get_crc() { return _crc; }
void reset_crc() { _crc.reset(); }
uint16_t get_crc() { return _crc.get_value(); }
private:
void process_pulse(Storage::Tape::Tape::Pulse pulse)
@ -111,17 +114,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, Symbol
remove_waves(1);
}
void add_to_crc(uint8_t value)
{
_crc ^= (uint16_t)value << 8;
for(int c = 0; c < 8; c++)
{
uint16_t exclusive_or = (_crc&0x8000) ? 0x1021 : 0x0000;
_crc = (uint16_t)(_crc << 1) ^ exclusive_or;
}
}
uint16_t _crc;
NumberTheory::CRC16 _crc;
};
static std::unique_ptr<File::Chunk> GetNextChunk(Acorn1200BaudTapeParser &parser)

View File

@ -9,40 +9,14 @@
#ifndef StaticAnalyser_Acorn_Tape_hpp
#define StaticAnalyser_Acorn_Tape_hpp
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "File.hpp"
#include "../../Storage/Tape/Tape.hpp"
namespace StaticAnalyser {
namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
std::vector<uint8_t> data;
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
bool header_crc_matched;
bool data_crc_matched;
std::vector<uint8_t> data;
};
std::list<Chunk> chunks;
};
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}

View File

@ -7,7 +7,7 @@
//
#include "Disk.hpp"
#include "../../Storage/Disk/DiskDrive.hpp"
#include "../../Storage/Disk/DiskController.hpp"
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "Utilities.hpp"
@ -17,12 +17,14 @@
using namespace StaticAnalyser::Commodore;
class CommodoreGCRParser: public Storage::Disk::Drive {
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
CommodoreGCRParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(1)
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1)
{
// Make sure this drive really is at track '1'.
while(!get_is_track_zero()) step(-1);
drive.reset(new Storage::Disk::Drive);
set_drive(drive);
}
struct Sector
@ -186,7 +188,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
{
std::list<File> files;
CommodoreGCRParser parser;
parser.set_disk(disk);
parser.drive->set_disk(disk);
// find any sector whatsoever to establish the current track
std::shared_ptr<CommodoreGCRParser::Sector> sector;

View File

@ -20,8 +20,10 @@
#include "../Storage/Cartridge/Formats/PRG.hpp"
// Disks
#include "../Storage/Disk/Formats/AcornADF.hpp"
#include "../Storage/Disk/Formats/D64.hpp"
#include "../Storage/Disk/Formats/G64.hpp"
#include "../Storage/Disk/Formats/SSD.hpp"
// Tapes
#include "../Storage/Tape/Formats/CommodoreTAP.hpp"
@ -79,8 +81,10 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
}
Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
// PRG
@ -98,8 +102,8 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
}
}
// ROM
Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM
Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD
Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP
Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)

View File

@ -46,6 +46,7 @@ struct Target {
struct {
bool has_adfs;
bool has_dfs;
bool should_hold_shift;
} acorn;
};

View File

@ -73,10 +73,15 @@ class Disk {
*/
virtual unsigned int get_head_position_count() = 0;
/*!
Returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
*/
virtual unsigned int get_head_count() { return 1; }
/*!
Returns the @c Track at @c position if there are any detectable events there; returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(unsigned int position) = 0;
virtual std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position) = 0;
};
}

View File

@ -1,68 +1,37 @@
//
// DiskDrive.cpp
// DiskController.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "DiskDrive.hpp"
#include "DiskController.hpp"
using namespace Storage::Disk;
Drive::Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
_clock_rate(clock_rate * clock_rate_multiplier),
_clock_rate_multiplier(clock_rate_multiplier),
_head_position(0),
TimedEventLoop(clock_rate * clock_rate_multiplier)
{
_rotational_multiplier.length = 60;
_rotational_multiplier.clock_rate = revolutions_per_minute;
_rotational_multiplier.simplify();
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
Time one;
set_expected_bit_length(one);
}
void Drive::set_expected_bit_length(Time bit_length)
void Controller::setup_track() // Time initial_offset
{
_bit_length = bit_length;
_track = _drive->get_track();
// _track = _disk->get_track_at_position(0, (unsigned int)_head_position);
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate);
_pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
_pll->set_delegate(this);
}
void Drive::set_disk(std::shared_ptr<Disk> disk)
{
_disk = disk;
set_track(Time());
}
bool Drive::has_disk()
{
return (bool)_disk;
}
bool Drive::get_is_track_zero()
{
return _head_position == 0;
}
void Drive::step(int direction)
{
_head_position = std::max(_head_position + direction, 0);
Time extra_time = get_time_into_next_event() / _rotational_multiplier;
extra_time.simplify();
_time_into_track += extra_time;
set_track(_time_into_track);
}
void Drive::set_track(Time initial_offset)
{
_track = _disk->get_track_at_position((unsigned int)_head_position);
// TODO: probably a better implementation of the empty track?
Time offset;
/* Time offset;
if(_track && _time_into_track.length > 0)
{
Time time_found = _track->seek_to(_time_into_track).simplify();
@ -73,17 +42,18 @@ void Drive::set_track(Time initial_offset)
{
offset = _time_into_track;
_time_into_track.set_zero();
}
}*/
reset_timer();
get_next_event();
reset_timer_to_offset(offset * _rotational_multiplier);
// reset_timer_to_offset(offset * _rotational_multiplier);
}
void Drive::run_for_cycles(int number_of_cycles)
void Controller::run_for_cycles(int number_of_cycles)
{
if(has_disk())
if(_drive && _drive->has_disk() && _motor_is_on)
{
if(!_track) setup_track();
number_of_cycles *= _clock_rate_multiplier;
while(number_of_cycles)
{
@ -99,7 +69,7 @@ void Drive::run_for_cycles(int number_of_cycles)
#pragma mark - Track timed event loop
void Drive::get_next_event()
void Controller::get_next_event()
{
if(_track)
_current_event = _track->get_next_event();
@ -115,7 +85,7 @@ void Drive::get_next_event()
set_next_event_time_interval(_current_event.length * _rotational_multiplier);
}
void Drive::process_next_event()
void Controller::process_next_event()
{
switch(_current_event.type)
{
@ -132,9 +102,50 @@ void Drive::process_next_event()
get_next_event();
}
#pragma mark - PLL delegate
#pragma mark - PLL control and delegate
void Drive::digital_phase_locked_loop_output_bit(int value)
void Controller::set_expected_bit_length(Time bit_length)
{
_bit_length = bit_length;
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate);
_pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
_pll->set_delegate(this);
}
void Controller::digital_phase_locked_loop_output_bit(int value)
{
process_input_bit(value, _cycles_since_index_hole);
}
#pragma mark - Drive actions
bool Controller::get_is_track_zero()
{
if(!_drive) return false;
return _drive->get_is_track_zero();
}
void Controller::step(int direction)
{
if(_drive) _drive->step(direction);
invalidate_track();
}
void Controller::set_motor_on(bool motor_on)
{
_motor_is_on = motor_on;
}
void Controller::set_drive(std::shared_ptr<Drive> drive)
{
_drive = drive;
invalidate_track();
}
void Controller::invalidate_track()
{
_track = nullptr;
}

View File

@ -1,15 +1,15 @@
//
// DiskDrive.hpp
// DiskController.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef DiskDrive_hpp
#define DiskDrive_hpp
#ifndef Storage_Disk_Controller_hpp
#define Storage_Disk_Controller_hpp
#include "Disk.hpp"
#include "Drive.hpp"
#include "DigitalPhaseLockedLoop.hpp"
#include "../TimedEventLoop.hpp"
@ -17,64 +17,43 @@ namespace Storage {
namespace Disk {
/*!
Provides the shell for emulating a disk drive something that takes a disk and has a drive head
that steps between tracks, using a phase locked loop ('PLL') to decode a bit stream from the surface of
the disk.
Provides the shell for emulating a disk controller something that is connected to a disk drive and uses a
phase locked loop ('PLL') to decode a bit stream from the surface of the disk.
Partly abstract; it is expected that subclasses will provide methods to deal with receiving a newly-recognised
bit from the PLL and with crossing the index hole.
TODO: double sided disks, communication of head size and permissible stepping extents, appropriate
simulation of gain.
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
*/
class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
public:
class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
protected:
/*!
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
spinning inserted disks at @c revolutions_per_minute.
*/
Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
/*!
Communicates to the PLL the expected length of a bit.
Communicates to the PLL the expected length of a bit as a fraction of a second.
*/
void set_expected_bit_length(Time bit_length);
/*!
Inserts @c disk into the drive.
*/
void set_disk(std::shared_ptr<Disk> disk);
/*!
@returns @c true if a disk is currently inserted; @c false otherwise.
*/
bool has_disk();
/*!
Advances the drive by @c number_of_cycles cycles.
*/
void run_for_cycles(int number_of_cycles);
/*!
@returns @c true if the drive head is currently at track zero; @c false otherwise.
Sets the current drive.
*/
bool get_is_track_zero();
/*!
Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers
step outwards.
*/
void step(int direction);
void set_drive(std::shared_ptr<Drive> drive);
void invalidate_track();
/*!
Enables or disables the disk motor.
*/
void set_motor_on(bool motor_on);
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
protected:
/*!
Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying
the amount of time since the index hole was last seen.
@ -89,6 +68,12 @@ class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
// for TimedEventLoop
virtual void process_next_event();
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
bool get_is_track_zero();
void step(int direction);
private:
Time _bit_length;
unsigned int _clock_rate;
@ -96,15 +81,16 @@ class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
Time _rotational_multiplier;
std::shared_ptr<DigitalPhaseLockedLoop> _pll;
std::shared_ptr<Disk> _disk;
std::shared_ptr<Drive> _drive;
std::shared_ptr<Track> _track;
int _head_position;
unsigned int _cycles_since_index_hole;
void set_track(Time initial_offset);
inline void get_next_event();
Track::Event _current_event;
Time _time_into_track;
bool _motor_is_on;
void setup_track();
};
}

46
Storage/Disk/Drive.cpp Normal file
View File

@ -0,0 +1,46 @@
//
// Drive.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Drive.hpp"
#include <algorithm>
using namespace Storage::Disk;
Drive::Drive()
: _head_position(0), _head(0) {}
void Drive::set_disk(std::shared_ptr<Disk> disk)
{
_disk = disk;
}
bool Drive::has_disk()
{
return (bool)_disk;
}
bool Drive::get_is_track_zero()
{
return _head_position == 0;
}
void Drive::step(int direction)
{
_head_position = std::max(_head_position + direction, 0);
}
void Drive::set_head(unsigned int head)
{
_head = head;
}
std::shared_ptr<Track> Drive::get_track()
{
if(_disk) return _disk->get_track_at_position(_head, (unsigned int)_head_position);
return nullptr;
}

59
Storage/Disk/Drive.hpp Normal file
View File

@ -0,0 +1,59 @@
//
// Drive.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Drive_hpp
#define Drive_hpp
#include <memory>
#include "Disk.hpp"
namespace Storage {
namespace Disk {
class Drive {
public:
Drive();
/*!
Inserts @c disk into the drive.
*/
void set_disk(std::shared_ptr<Disk> disk);
/*!
@returns @c true if a disk is currently inserted; @c false otherwise.
*/
bool has_disk();
/*!
@returns @c true if the drive head is currently at track zero; @c false otherwise.
*/
bool get_is_track_zero();
/*!
Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers
step outwards.
*/
void step(int direction);
/*!
*/
void set_head(unsigned int head);
std::shared_ptr<Track> get_track();
private:
std::shared_ptr<Disk> _disk;
int _head_position;
unsigned int _head;
};
}
}
#endif /* Drive_hpp */

View File

@ -6,8 +6,8 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CommodoreGCR_hpp
#define CommodoreGCR_hpp
#ifndef Storage_Disk_Encodings_CommodoreGCR_hpp
#define Storage_Disk_Encodings_CommodoreGCR_hpp
#include "../../Storage.hpp"
#include <cstdint>

View File

@ -0,0 +1,217 @@
//
// MFM.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "MFM.hpp"
#include "../PCMTrack.hpp"
#include "../../../NumberTheory/CRC.hpp"
using namespace Storage::Encodings::MFM;
template <class T> class Shifter {
public:
virtual void add_byte(uint8_t input) = 0;
virtual void add_index_address_mark() = 0;
virtual void add_ID_address_mark() = 0;
virtual void add_data_address_mark() = 0;
virtual void add_deleted_data_address_mark() = 0;
protected:
/*!
Intended to be overridden by subclasses; should write value out as PCM data,
MSB first.
*/
void output_short(uint16_t value);
};
template <class T> class MFMShifter: public Shifter<T> {
public:
void add_byte(uint8_t input) {
uint16_t spread_value =
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7)
);
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15));
output_ = spread_value | ((~or_bits) & 0xaaaa);
static_cast<T *>(this)->output_short(output_);
}
void add_index_address_mark() {
static_cast<T *>(this)->output_short(output_ = MFMIndexAddressMark);
add_byte(MFMIndexAddressByte);
}
void add_ID_address_mark() {
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
add_byte(MFMIDAddressByte);
}
void add_data_address_mark() {
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
add_byte(MFMDataAddressByte);
}
void add_deleted_data_address_mark() {
static_cast<T *>(this)->output_short(output_ = MFMAddressMark);
add_byte(MFMDeletedDataAddressByte);
}
private:
uint16_t output_;
};
template <class T> class FMShifter: public Shifter<T> {
// encodes each 16-bit part as clock, data, clock, data [...]
public:
void add_byte(uint8_t input) {
static_cast<T *>(this)->output_short(
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7) |
0xaaaa
));
}
void add_index_address_mark() { static_cast<T *>(this)->output_short(FMIndexAddressMark); }
void add_ID_address_mark() { static_cast<T *>(this)->output_short(FMIDAddressMark); }
void add_data_address_mark() { static_cast<T *>(this)->output_short(FMDataAddressMark); }
void add_deleted_data_address_mark() { static_cast<T *>(this)->output_short(FMDeletedDataAddressMark); }
};
static uint8_t logarithmic_size_for_size(size_t size)
{
switch(size)
{
default: return 0;
case 256: return 1;
case 512: return 2;
case 1024: return 3;
case 2048: return 4;
case 4196: return 5;
}
}
template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors(
const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes, size_t post_address_mark_bytes,
size_t pre_data_mark_bytes, size_t post_data_bytes,
size_t inter_sector_gap,
size_t expected_track_bytes)
{
T shifter;
NumberTheory::CRC16 crc_generator(0x1021, 0xffff);
// output the index mark
shifter.add_index_address_mark();
// add the post-index mark
for(int c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
// add sectors
for(const Sector &sector : sectors)
{
// gap
for(int c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
// sector header
shifter.add_ID_address_mark();
shifter.add_byte(sector.track);
shifter.add_byte(sector.side);
shifter.add_byte(sector.sector);
uint8_t size = logarithmic_size_for_size(sector.data.size());
shifter.add_byte(size);
// header CRC
crc_generator.reset();
crc_generator.add(sector.track);
crc_generator.add(sector.side);
crc_generator.add(sector.sector);
crc_generator.add(size);
uint16_t crc_value = crc_generator.get_value();
shifter.add_byte(crc_value >> 8);
shifter.add_byte(crc_value & 0xff);
// gap
for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e);
for(int c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data
shifter.add_data_address_mark();
crc_generator.reset();
for(size_t c = 0; c < sector.data.size(); c++)
{
shifter.add_byte(sector.data[c]);
crc_generator.add(sector.data[c]);
}
// data CRC
crc_value = crc_generator.get_value();
shifter.add_byte(crc_value >> 8);
shifter.add_byte(crc_value & 0xff);
// gap
for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00);
for(int c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e);
}
while(shifter.segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
shifter.segment.number_of_bits = (unsigned int)(shifter.segment.data.size() * 8);
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(shifter.segment)));
}
struct VectorReceiver {
void output_short(uint16_t value) {
segment.data.push_back(value >> 8);
segment.data.push_back(value & 0xff);
}
Storage::Disk::PCMSegment segment;
};
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors)
{
struct VectorShifter: public FMShifter<VectorShifter>, VectorReceiver {
using VectorReceiver::output_short;
};
return GetTrackWithSectors<VectorShifter>(
sectors,
16, 0x00,
6, 0,
17, 14,
0,
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors)
{
struct VectorShifter: public MFMShifter<VectorShifter>, VectorReceiver {
using VectorReceiver::output_short;
};
return GetTrackWithSectors<VectorShifter>(
sectors,
50, 0x4e,
12, 22,
12, 18,
32,
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
}

View File

@ -0,0 +1,45 @@
//
// MFM.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Disk_Encodings_MFM_hpp
#define Storage_Disk_Encodings_MFM_hpp
#include <cstdint>
#include <vector>
#include "../Disk.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010
const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
const uint16_t MFMIndexAddressMark = 0x5224;
const uint16_t MFMAddressMark = 0x4489;
const uint8_t MFMIndexAddressByte = 0xfc;
const uint8_t MFMIDAddressByte = 0xfe;
const uint8_t MFMDataAddressByte = 0xfb;
const uint8_t MFMDeletedDataAddressByte = 0xf8;
struct Sector {
uint8_t track, side, sector;
std::vector<uint8_t> data;
};
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors);
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors);
}
}
}
#endif /* MFM_hpp */

View File

@ -0,0 +1,87 @@
//
// AcornADF.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "AcornADF.hpp"
#include <sys/stat.h>
#include "../Encodings/MFM.hpp"
namespace {
static const unsigned int sectors_per_track = 16;
static const unsigned int bytes_per_sector = 256;
}
using namespace Storage::Disk;
AcornADF::AcornADF(const char *file_name) : _file(nullptr)
{
struct stat file_stats;
stat(file_name, &file_stats);
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats.st_size % bytes_per_sector) throw ErrorNotAcornADF;
if(file_stats.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorCantOpen;
// check that the initial directory's 'Hugo's are present
fseek(_file, 513, SEEK_SET);
uint8_t bytes[4];
fread(bytes, 1, 4, _file);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
fseek(_file, 0x6fb, SEEK_SET);
fread(bytes, 1, 4, _file);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
}
AcornADF::~AcornADF()
{
if(_file) fclose(_file);
}
unsigned int AcornADF::get_head_position_count()
{
return 80;
}
unsigned int AcornADF::get_head_count()
{
return 1;
}
std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> track;
if(head >= 2) return track;
long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track;
fseek(_file, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < sectors_per_track; sector++)
{
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = (uint8_t)head;
new_sector.sector = (uint8_t)sector;
new_sector.data.resize(bytes_per_sector);
fread(&new_sector.data[0], 1, bytes_per_sector, _file);
if(feof(_file))
break;
sectors.push_back(std::move(new_sector));
}
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
return track;
}

View File

@ -0,0 +1,48 @@
//
// AcornADF.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef AcornADF_hpp
#define AcornADF_hpp
#include "../Disk.hpp"
namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing an ADF disk image a decoded sector dump of an Acorn ADFS disk.
*/
class AcornADF: public Disk {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const char *file_name);
~AcornADF();
enum {
ErrorCantOpen,
ErrorNotAcornADF,
};
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
};
}
}
#endif /* AcornADF_hpp */

View File

@ -53,10 +53,10 @@ unsigned int D64::get_head_position_count()
return _number_of_tracks*2;
}
std::shared_ptr<Track> D64::get_track_at_position(unsigned int position)
std::shared_ptr<Track> D64::get_track_at_position(unsigned int head, unsigned int position)
{
// every other track is missing
if(position&1)
// every other track is missing, as is any head above 0
if(position&1 || head)
return std::shared_ptr<Track>();
// figure out where this track starts on the disk
@ -102,8 +102,8 @@ std::shared_ptr<Track> D64::get_track_at_position(unsigned int position)
PCMSegment track;
size_t track_bytes = 349 * (size_t)sectors_by_zone[zone];
track.number_of_bits = (unsigned int)track_bytes * 8;
uint8_t *data = new uint8_t[track_bytes];
track.data.reset(data);
track.data.resize(track_bytes);
uint8_t *data = &track.data[0];
memset(data, 0, track_bytes);

View File

@ -35,7 +35,7 @@ class D64: public Disk {
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
std::shared_ptr<Track> get_track_at_position(unsigned int position);
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;

View File

@ -54,13 +54,14 @@ unsigned int G64::get_head_position_count()
return _number_of_tracks > 84 ? _number_of_tracks : 84;
}
std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
std::shared_ptr<Track> G64::get_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> resulting_track;
// if there's definitely no track here, return the empty track
// (TODO: should be supplying one with an index hole?)
if(position >= _number_of_tracks) return resulting_track;
if(head >= 1) return resulting_track;
// seek to this track's entry in the track table
fseek(_file, (long)((position * 4) + 0xc), SEEK_SET);
@ -84,8 +85,8 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
track_length |= (uint16_t)fgetc(_file) << 8;
// grab the byte contents of this track
std::unique_ptr<uint8_t> track_contents(new uint8_t[track_length]);
fread(track_contents.get(), 1, track_length, _file);
std::vector<uint8_t> track_contents(track_length);
fread(&track_contents[0], 1, track_length, _file);
// seek to this track's entry in the speed zone table
fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET);
@ -123,8 +124,8 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
PCMSegment segment;
segment.number_of_bits = number_of_bytes * 8;
segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed);
segment.data.reset(new uint8_t[number_of_bytes]);
memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes);
segment.data.resize(number_of_bytes);
memcpy(&segment.data[0], &track_contents[start_byte_in_current_speed], number_of_bytes);
segments.push_back(std::move(segment));
current_speed = byte_speed;

View File

@ -37,7 +37,7 @@ class G64: public Disk {
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
std::shared_ptr<Track> get_track_at_position(unsigned int position);
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;

View File

@ -0,0 +1,81 @@
//
// SSD.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "SSD.hpp"
#include <sys/stat.h>
#include "../Encodings/MFM.hpp"
using namespace Storage::Disk;
SSD::SSD(const char *file_name) : _file(nullptr)
{
struct stat file_stats;
stat(file_name, &file_stats);
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats.st_size & 255) throw ErrorNotSSD;
if(file_stats.st_size < 512) throw ErrorNotSSD;
if(file_stats.st_size > 800*256) throw ErrorNotSSD;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorCantOpen;
// this has two heads if the suffix is .dsd, one if it's .ssd
_head_count = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
_track_count = (unsigned int)(file_stats.st_size / (256 * 10));
if(_track_count < 40) _track_count = 40;
else if(_track_count < 80) _track_count = 80;
}
SSD::~SSD()
{
if(_file) fclose(_file);
}
unsigned int SSD::get_head_position_count()
{
return _track_count;
}
unsigned int SSD::get_head_count()
{
return _head_count;
}
std::shared_ptr<Track> SSD::get_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> track;
if(head >= _head_count) return track;
long file_offset = (position * _head_count + head) * 256 * 10;
fseek(_file, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < 10; sector++)
{
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = 0;
new_sector.sector = (uint8_t)sector;
new_sector.data.resize(256);
fread(&new_sector.data[0], 1, 256, _file);
if(feof(_file))
break;
sectors.push_back(std::move(new_sector));
}
if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
return track;
}

View File

@ -0,0 +1,50 @@
//
// SSD.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef SSD_hpp
#define SSD_hpp
#include "../Disk.hpp"
namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a DSD or SSD disk image a decoded sector dump of an Acorn DFS disk.
*/
class SSD: public Disk {
public:
/*!
Construct an @c SSD containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
*/
SSD(const char *file_name);
~SSD();
enum {
ErrorCantOpen,
ErrorNotSSD,
};
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
unsigned int _head_count;
unsigned int _track_count;
};
}
}
#endif /* SSD_hpp */

View File

@ -36,7 +36,7 @@ PCMTrack::Event PCMTrack::get_next_event()
unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate;
unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length;
const uint8_t *segment_data = _segments[_segment_pointer].data.get();
const uint8_t *segment_data = &_segments[_segment_pointer].data[0];
while(_bit_pointer < _segments[_segment_pointer].number_of_bits)
{
// for timing simplicity, bits are modelled as happening at the end of their window

View File

@ -23,7 +23,7 @@ namespace Disk {
struct PCMSegment {
Time length_of_a_bit;
unsigned int number_of_bits;
std::unique_ptr<uint8_t> data;
std::vector<uint8_t> data;
};
/*!