mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Merge pull request #131 from TomHarte/ZX80FileFormats
Adds very preliminary emulation of the ZX80.
This commit is contained in:
commit
87496f9978
@ -22,3 +22,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
|
||||
Fuzz(buffer.data(), buffer.size());
|
||||
}
|
||||
|
@ -11,10 +11,12 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
void Fuzz(uint8_t *buffer, size_t size);
|
||||
void Fuzz(std::vector<uint8_t> &buffer);
|
||||
|
||||
}
|
||||
|
||||
|
106
Machines/ZX8081/Video.cpp
Normal file
106
Machines/ZX8081/Video.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
Video::Video() :
|
||||
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)),
|
||||
line_data_(nullptr),
|
||||
line_data_pointer_(nullptr),
|
||||
cycles_since_update_(0),
|
||||
sync_(false) {
|
||||
|
||||
// Set a composite sampling function that assumes 8bpp input grayscale.
|
||||
// TODO: lessen this to 1bpp.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"return float(texture(texID, coordinate).r) / 255.0;"
|
||||
"}");
|
||||
|
||||
// Show only the centre 80% of the TV frame.
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
}
|
||||
|
||||
void Video::run_for_cycles(int number_of_cycles) {
|
||||
// Just keep a running total of the amount of time that remains owed to the CRT.
|
||||
cycles_since_update_ += (unsigned int)number_of_cycles << 1;
|
||||
}
|
||||
|
||||
void Video::flush() {
|
||||
flush(sync_);
|
||||
}
|
||||
|
||||
void Video::flush(bool next_sync) {
|
||||
if(sync_) {
|
||||
// If in sync, that takes priority. Output the proper amount of sync.
|
||||
crt_->output_sync(cycles_since_update_);
|
||||
} else {
|
||||
// If not presently in sync, then...
|
||||
|
||||
if(line_data_) {
|
||||
// If there is output data queued, output it either if it's being interrupted by
|
||||
// sync, or if we're past its end anyway. Otherwise let it be.
|
||||
unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_);
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
crt_->output_data(data_length, 1);
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= data_length;
|
||||
} else return;
|
||||
}
|
||||
|
||||
// Any pending pixels being dealt with, pad with the white level.
|
||||
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = 0xff;
|
||||
crt_->output_level(cycles_since_update_);
|
||||
}
|
||||
|
||||
cycles_since_update_ = 0;
|
||||
}
|
||||
|
||||
void Video::set_sync(bool sync) {
|
||||
// Do nothing if sync hasn't changed.
|
||||
if(sync_ == sync) return;
|
||||
|
||||
// Complete whatever was being drawn, and update sync.
|
||||
flush(sync);
|
||||
sync_ = sync;
|
||||
}
|
||||
|
||||
void Video::output_byte(uint8_t byte) {
|
||||
// Complete whatever was going on.
|
||||
flush();
|
||||
|
||||
// Grab a buffer if one isn't already available.
|
||||
if(!line_data_) {
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(320);
|
||||
}
|
||||
|
||||
// If a buffer was obtained, serialise the new pixels.
|
||||
if(line_data_) {
|
||||
uint8_t mask = 0x80;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
line_data_pointer_[c] = (byte & mask) ? 0xff : 0x00;
|
||||
mask >>= 1;
|
||||
}
|
||||
line_data_pointer_ += 8;
|
||||
|
||||
// If that fills the buffer, output it now.
|
||||
if(line_data_pointer_ - line_data_ == 320) {
|
||||
crt_->output_data(320, 1);
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= 160;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Video::get_crt() {
|
||||
return crt_;
|
||||
}
|
54
Machines/ZX8081/Video.hpp
Normal file
54
Machines/ZX8081/Video.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_ZX8081_Video_hpp
|
||||
#define Machines_ZX8081_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
/*!
|
||||
Packages a ZX80/81-style video feed into a CRT-compatible waveform.
|
||||
|
||||
While sync is active, this feed will output the sync level.
|
||||
|
||||
While sync is inactive, this feed will output the white level unless it is supplied
|
||||
with a byte to output. When a byte is supplied for output, it will be interpreted as
|
||||
a 1-bit graphic and output over the next 4 cycles, picking between the white level
|
||||
and the black level.
|
||||
*/
|
||||
class Video {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video();
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Advances time by @c number_of_cycles cycles.
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
/// Forces output to catch up to the current output position.
|
||||
void flush();
|
||||
|
||||
/// Sets the current sync output.
|
||||
void set_sync(bool sync);
|
||||
/// Causes @c byte to be serialised into pixels and output over the next four cycles.
|
||||
void output_byte(uint8_t byte);
|
||||
|
||||
private:
|
||||
bool sync_;
|
||||
uint8_t *line_data_, *line_data_pointer_;
|
||||
unsigned int cycles_since_update_;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
void flush(bool next_sync);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
252
Machines/ZX8081/ZX8081.cpp
Normal file
252
Machines/ZX8081/ZX8081.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
//
|
||||
// ZX8081.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
namespace {
|
||||
// The clock rate is 3.25Mhz.
|
||||
const unsigned int ZX8081ClockRate = 3250000;
|
||||
}
|
||||
|
||||
Machine::Machine() :
|
||||
vsync_(false),
|
||||
hsync_(false),
|
||||
nmi_is_enabled_(false),
|
||||
tape_player_(ZX8081ClockRate) {
|
||||
set_clock_rate(ZX8081ClockRate);
|
||||
tape_player_.set_motor_control(true);
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
int Machine::perform_machine_cycle(const CPU::Z80::MachineCycle &cycle) {
|
||||
int wait_cycles = 0;
|
||||
|
||||
int previous_counter = horizontal_counter_;
|
||||
horizontal_counter_ += cycle.length;
|
||||
|
||||
if(previous_counter < vsync_start_cycle_ && horizontal_counter_ >= vsync_start_cycle_) {
|
||||
video_->run_for_cycles(vsync_start_cycle_ - previous_counter);
|
||||
set_hsync(true);
|
||||
if(nmi_is_enabled_) {
|
||||
set_non_maskable_interrupt_line(true);
|
||||
if(!get_halt_line()) {
|
||||
wait_cycles = vsync_end_cycle_ - horizontal_counter_;
|
||||
}
|
||||
}
|
||||
video_->run_for_cycles(horizontal_counter_ - vsync_start_cycle_ + wait_cycles);
|
||||
} else if(previous_counter <= vsync_end_cycle_ && horizontal_counter_ > vsync_end_cycle_) {
|
||||
video_->run_for_cycles(vsync_end_cycle_ - previous_counter);
|
||||
set_hsync(false);
|
||||
if(nmi_is_enabled_) set_non_maskable_interrupt_line(false);
|
||||
video_->run_for_cycles(horizontal_counter_ - vsync_end_cycle_);
|
||||
} else {
|
||||
video_->run_for_cycles(cycle.length);
|
||||
}
|
||||
|
||||
horizontal_counter_ += wait_cycles;
|
||||
if(is_zx81_) horizontal_counter_ %= 207;
|
||||
|
||||
// tape_player_.run_for_cycles(cycle.length + wait_cycles);
|
||||
|
||||
uint16_t refresh = 0;
|
||||
uint16_t address = cycle.address ? *cycle.address : 0;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::BusOperation::Output:
|
||||
set_vsync(false);
|
||||
line_counter_ = 0;
|
||||
|
||||
switch(address & 7) {
|
||||
default: break;
|
||||
case 0x5: nmi_is_enabled_ = false; break;
|
||||
case 0x6: nmi_is_enabled_ = is_zx81_; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::BusOperation::Input: {
|
||||
uint8_t value = 0xff;
|
||||
if(!(address&1)) {
|
||||
set_vsync(true);
|
||||
|
||||
uint16_t mask = 0x100;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
if(!(address & mask)) value &= key_states_[c];
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
|
||||
}
|
||||
*cycle.value = value;
|
||||
} break;
|
||||
|
||||
case CPU::Z80::BusOperation::Interrupt:
|
||||
line_counter_ = (line_counter_ + 1) & 7;
|
||||
*cycle.value = 0xff;
|
||||
horizontal_counter_ = 0;
|
||||
break;
|
||||
|
||||
case CPU::Z80::BusOperation::ReadOpcode:
|
||||
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
|
||||
// address is low. The Z80 signals a refresh, providing the refresh address during the
|
||||
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
|
||||
// of the IRQ line if necessary.
|
||||
refresh = get_value_of_register(CPU::Z80::Register::Refresh);
|
||||
set_interrupt_line(!(refresh & 0x40), -2);
|
||||
set_interrupt_line(false);
|
||||
|
||||
// Check for use of the fast tape hack.
|
||||
if(address == tape_trap_address_) { // TODO: && fast_tape_hack_enabled_
|
||||
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
if(next_byte != -1) {
|
||||
uint16_t hl = get_value_of_register(CPU::Z80::Register::HL);
|
||||
ram_[hl & ram_mask_] = (uint8_t)next_byte;
|
||||
*cycle.value = 0x00;
|
||||
set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
case CPU::Z80::BusOperation::Read:
|
||||
if(address < ram_base_) {
|
||||
*cycle.value = rom_[address & rom_mask_];
|
||||
} else {
|
||||
uint8_t value = ram_[address & ram_mask_];
|
||||
|
||||
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
|
||||
// currently active, perform a video output and return a NOP. Otherwise,
|
||||
// just return the value as read.
|
||||
if(cycle.operation == CPU::Z80::BusOperation::ReadOpcode && address&0x8000 && !(value & 0x40) && !get_halt_line()) {
|
||||
size_t char_address = (size_t)((refresh & 0xff00) | ((value & 0x3f) << 3) | line_counter_);
|
||||
if(char_address < ram_base_) {
|
||||
uint8_t mask = (value & 0x80) ? 0x00 : 0xff;
|
||||
value = rom_[char_address & rom_mask_] ^ mask;
|
||||
}
|
||||
|
||||
video_->output_byte(value);
|
||||
*cycle.value = 0;
|
||||
} else *cycle.value = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::BusOperation::Write:
|
||||
if(address >= ram_base_) {
|
||||
ram_[address & ram_mask_] = *cycle.value;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
return wait_cycles;
|
||||
}
|
||||
|
||||
void Machine::flush() {
|
||||
video_->flush();
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
video_.reset(new Video);
|
||||
}
|
||||
|
||||
void Machine::close_output() {
|
||||
video_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
||||
return video_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU::Z80::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
is_zx81_ = target.zx8081.isZX81;
|
||||
if(is_zx81_) {
|
||||
rom_ = zx81_rom_;
|
||||
tape_trap_address_ = 0x37c;
|
||||
tape_return_address_ = 0x380;
|
||||
vsync_start_cycle_ = 13;
|
||||
vsync_end_cycle_ = 33;
|
||||
vsync_start_cycle_ = 16;
|
||||
vsync_end_cycle_ = 32;
|
||||
} else {
|
||||
rom_ = zx80_rom_;
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_cycle_ = 13;
|
||||
vsync_end_cycle_ = 33;
|
||||
}
|
||||
rom_mask_ = (uint16_t)(rom_.size() - 1);
|
||||
|
||||
switch(target.zx8081.memory_model) {
|
||||
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
|
||||
ram_.resize(1024);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 1023;
|
||||
break;
|
||||
case StaticAnalyser::ZX8081MemoryModel::SixteenKB:
|
||||
ram_.resize(16384);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 16383;
|
||||
break;
|
||||
case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB:
|
||||
ram_.resize(65536);
|
||||
ram_base_ = 8192;
|
||||
ram_mask_ = 65535;
|
||||
break;
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(target.tapes.size()) {
|
||||
tape_player_.set_tape(target.tapes.front());
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMType type, std::vector<uint8_t> data) {
|
||||
switch(type) {
|
||||
case ZX80: zx80_rom_ = data; break;
|
||||
case ZX81: zx81_rom_ = data; break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Video
|
||||
|
||||
void Machine::set_vsync(bool sync) {
|
||||
vsync_ = sync;
|
||||
update_sync();
|
||||
}
|
||||
|
||||
void Machine::set_hsync(bool sync) {
|
||||
hsync_ = sync;
|
||||
update_sync();
|
||||
}
|
||||
|
||||
void Machine::update_sync() {
|
||||
video_->set_sync(vsync_ || hsync_);
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(isPressed)
|
||||
key_states_[key >> 8] &= (uint8_t)(~key);
|
||||
else
|
||||
key_states_[key >> 8] |= (uint8_t)key;
|
||||
}
|
||||
|
||||
void Machine::clear_all_keys() {
|
||||
memset(key_states_, 0xff, 8);
|
||||
}
|
96
Machines/ZX8081/ZX8081.hpp
Normal file
96
Machines/ZX8081/ZX8081.hpp
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// ZX8081.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ZX8081_hpp
|
||||
#define ZX8081_hpp
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
enum ROMType: uint8_t {
|
||||
ZX80, ZX81
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
|
||||
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
|
||||
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
|
||||
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
|
||||
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
|
||||
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
|
||||
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
|
||||
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
|
||||
};
|
||||
|
||||
class Machine:
|
||||
public CPU::Z80::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine {
|
||||
public:
|
||||
Machine();
|
||||
|
||||
int perform_machine_cycle(const CPU::Z80::MachineCycle &cycle);
|
||||
void flush();
|
||||
|
||||
void setup_output(float aspect_ratio);
|
||||
void close_output();
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker();
|
||||
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
|
||||
void set_rom(ROMType type, std::vector<uint8_t> data);
|
||||
void set_key_state(uint16_t key, bool isPressed);
|
||||
void clear_all_keys();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Video> video_;
|
||||
std::vector<uint8_t> zx81_rom_, zx80_rom_;
|
||||
uint16_t tape_trap_address_, tape_return_address_;
|
||||
|
||||
std::vector<uint8_t> ram_;
|
||||
uint16_t ram_mask_, ram_base_;
|
||||
|
||||
std::vector<uint8_t> rom_;
|
||||
uint16_t rom_mask_;
|
||||
|
||||
bool vsync_, hsync_;
|
||||
int line_counter_;
|
||||
|
||||
uint8_t key_states_[8];
|
||||
|
||||
void set_vsync(bool sync);
|
||||
void set_hsync(bool sync);
|
||||
void update_sync();
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
Storage::Tape::ZX8081::Parser parser_;
|
||||
|
||||
int horizontal_counter_;
|
||||
bool is_zx81_;
|
||||
bool nmi_is_enabled_;
|
||||
int vsync_start_cycle_, vsync_end_cycle_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ZX8081_hpp */
|
@ -19,6 +19,11 @@
|
||||
4B14145E1B5887AA00E04248 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* 6502AllRAM.cpp */; };
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
|
||||
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */; };
|
||||
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497891EE4AC5E00CE2596 /* StaticAnalyser.cpp */; };
|
||||
4B14978F1EE4B4D200CE2596 /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; };
|
||||
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */; };
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
||||
@ -93,6 +98,7 @@
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
|
||||
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.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 */; };
|
||||
@ -379,6 +385,7 @@
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */; };
|
||||
@ -398,6 +405,7 @@
|
||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */; };
|
||||
4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; };
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
|
||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; };
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */; };
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
||||
@ -464,6 +472,15 @@
|
||||
4B14145A1B58879D00E04248 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
|
||||
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
|
||||
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
|
||||
4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX80O81P.cpp; sourceTree = "<group>"; };
|
||||
4B1497871EE4A1DA00CE2596 /* ZX80O81P.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX80O81P.hpp; sourceTree = "<group>"; };
|
||||
4B1497891EE4AC5E00CE2596 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/ZX8081/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B14978A1EE4AC5E00CE2596 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/ZX8081/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSZX8081.h; sourceTree = "<group>"; };
|
||||
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSZX8081.mm; sourceTree = "<group>"; };
|
||||
4B1497901EE4B5A800CE2596 /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = ZX8081/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4B1497911EE4B5A800CE2596 /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = ZX8081/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = "<group>"; };
|
||||
4B1E85731D170228001EF87D /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
|
||||
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
|
||||
@ -595,6 +612,8 @@
|
||||
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
|
||||
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20OptionsPanel.swift; sourceTree = "<group>"; };
|
||||
4B9CCDA21DA27C3F0098B625 /* CSJoystickMachine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickMachine.h; sourceTree = "<group>"; };
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.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>"; };
|
||||
@ -907,6 +926,8 @@
|
||||
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = "<group>"; };
|
||||
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = "<group>"; };
|
||||
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
|
||||
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
|
||||
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
|
||||
@ -939,6 +960,8 @@
|
||||
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
4BD3A3091EE755C800B5B501 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = ZX8081/Video.cpp; sourceTree = "<group>"; };
|
||||
4BD3A30A1EE755C800B5B501 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = ZX8081/Video.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>"; };
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
@ -1064,6 +1087,26 @@
|
||||
name = "Test Binaries";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B14978C1EE4AC6200CE2596 /* ZX80/81 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B1497891EE4AC5E00CE2596 /* StaticAnalyser.cpp */,
|
||||
4B14978A1EE4AC5E00CE2596 /* StaticAnalyser.hpp */,
|
||||
);
|
||||
name = ZX80/81;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1497931EE4B5AC00CE2596 /* ZX8081 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B1497901EE4B5A800CE2596 /* ZX8081.cpp */,
|
||||
4B1497911EE4B5A800CE2596 /* ZX8081.hpp */,
|
||||
4BD3A3091EE755C800B5B501 /* Video.cpp */,
|
||||
4BD3A30A1EE755C800B5B501 /* Video.hpp */,
|
||||
);
|
||||
name = ZX8081;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1E85791D174DEC001EF87D /* 6532 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1129,13 +1172,15 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
|
||||
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
|
||||
4B2A539B1D117D36003C6002 /* CSElectron.h */,
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */,
|
||||
4BCF1FA61DADC5250039D2E7 /* CSOric.h */,
|
||||
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */,
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */,
|
||||
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
|
||||
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */,
|
||||
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */,
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
|
||||
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
|
||||
);
|
||||
path = Wrappers;
|
||||
sourceTree = "<group>";
|
||||
@ -1270,6 +1315,7 @@
|
||||
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
|
||||
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */,
|
||||
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */,
|
||||
);
|
||||
path = Documents;
|
||||
sourceTree = "<group>";
|
||||
@ -1312,15 +1358,15 @@
|
||||
4B69FB391C4D908A00B5F0AA /* Storage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */,
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */,
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */,
|
||||
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */,
|
||||
4BEE0A691D72496600532C7B /* Cartridge */,
|
||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */,
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */,
|
||||
);
|
||||
name = Storage;
|
||||
path = ../../Storage;
|
||||
@ -1349,6 +1395,8 @@
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */,
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */,
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */,
|
||||
4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */,
|
||||
4B1497871EE4A1DA00CE2596 /* ZX80O81P.hpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@ -1373,6 +1421,8 @@
|
||||
4B8805F31DCFD22A003085B1 /* Commodore.hpp */,
|
||||
4B8805F91DCFF807003085B1 /* Oric.cpp */,
|
||||
4B8805FA1DCFF807003085B1 /* Oric.hpp */,
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */,
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */,
|
||||
);
|
||||
name = Parsers;
|
||||
sourceTree = "<group>";
|
||||
@ -1381,7 +1431,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8805F51DCFF6C9003085B1 /* Commodore.cpp */,
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */,
|
||||
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */,
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */,
|
||||
);
|
||||
name = Data;
|
||||
sourceTree = "<group>";
|
||||
@ -1834,6 +1886,7 @@
|
||||
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
|
||||
4B2E2D9E1C3A070900138695 /* Electron */,
|
||||
4BCF1FA51DADC3E10039D2E7 /* Oric */,
|
||||
4B1497931EE4B5AC00CE2596 /* ZX8081 */,
|
||||
);
|
||||
name = Machines;
|
||||
path = ../../Machines;
|
||||
@ -2072,8 +2125,9 @@
|
||||
4BD14B121D7462810088EAD6 /* Acorn */,
|
||||
4BA799961D8B65730045123D /* Atari */,
|
||||
4BC830D21D6E7C6D0000A26F /* Commodore */,
|
||||
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
|
||||
4B5A12581DD55873007A2231 /* Disassembler */,
|
||||
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
|
||||
4B14978C1EE4AC6200CE2596 /* ZX80/81 */,
|
||||
);
|
||||
name = StaticAnalyser;
|
||||
sourceTree = "<group>";
|
||||
@ -2203,6 +2257,7 @@
|
||||
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -2512,6 +2567,7 @@
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
|
||||
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
@ -2542,14 +2598,18 @@
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */,
|
||||
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */,
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||
4B77069D1EC904570053B588 /* Z80.cpp in Sources */,
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
|
||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
|
||||
@ -2578,6 +2638,7 @@
|
||||
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
|
||||
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,
|
||||
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
|
||||
@ -2588,6 +2649,7 @@
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
|
||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
||||
4B14978F1EE4B4D200CE2596 /* CSZX8081.mm in Sources */,
|
||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
|
||||
4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */,
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||
@ -2660,6 +2722,14 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B1497971EE4B97F00CE2596 /* Base */,
|
||||
);
|
||||
name = ZX8081Options.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -68,7 +68,7 @@
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
48
OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
Normal file
48
OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="7Pv-WL-2Rq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
|
||||
<constraint firstAttribute="bottom" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="20" id="eZe-Eu-INg"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-2" y="-8"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
@ -104,7 +104,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
@ -120,7 +120,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
@ -140,7 +140,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
@ -154,6 +154,36 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>p</string>
|
||||
<string>81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
|
@ -18,6 +18,7 @@
|
||||
#import "CSElectron.h"
|
||||
#import "CSOric.h"
|
||||
#import "CSVic20.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
#import "Clock_Signal-Swift.h"
|
||||
|
||||
@ -49,6 +50,7 @@
|
||||
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
|
||||
case StaticAnalyser::Target::Oric: return @"OricOptions";
|
||||
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
|
||||
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
@ -61,6 +63,7 @@
|
||||
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
|
||||
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
|
||||
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
|
||||
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] init];
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
17
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.h
Normal file
17
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.h
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// CSZX8081.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
@interface CSZX8081 : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
|
||||
@end
|
104
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm
Normal file
104
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSZX8081.mm
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// CSZX8081.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSZX8081.h"
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
#import "CSMachine+Subclassing.h"
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
@implementation CSZX8081 {
|
||||
ZX8081::Machine _zx8081;
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine {
|
||||
return &_zx8081;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_zx8081.set_rom(ZX8081::ROMType::ZX80, [self rom:@"zx80"].stdVector8);
|
||||
_zx8081.set_rom(ZX8081::ROMType::ZX81, [self rom:@"zx81"].stdVector8);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/ZX8081"];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard Mapping
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_zx8081.clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key)
|
||||
{
|
||||
case VK_ANSI_0: _zx8081.set_key_state(ZX8081::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _zx8081.set_key_state(ZX8081::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _zx8081.set_key_state(ZX8081::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _zx8081.set_key_state(ZX8081::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _zx8081.set_key_state(ZX8081::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _zx8081.set_key_state(ZX8081::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _zx8081.set_key_state(ZX8081::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _zx8081.set_key_state(ZX8081::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _zx8081.set_key_state(ZX8081::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _zx8081.set_key_state(ZX8081::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _zx8081.set_key_state(ZX8081::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _zx8081.set_key_state(ZX8081::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _zx8081.set_key_state(ZX8081::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _zx8081.set_key_state(ZX8081::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _zx8081.set_key_state(ZX8081::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _zx8081.set_key_state(ZX8081::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _zx8081.set_key_state(ZX8081::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _zx8081.set_key_state(ZX8081::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _zx8081.set_key_state(ZX8081::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _zx8081.set_key_state(ZX8081::Key::KeyP, isPressed); break;
|
||||
|
||||
case VK_ANSI_A: _zx8081.set_key_state(ZX8081::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _zx8081.set_key_state(ZX8081::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _zx8081.set_key_state(ZX8081::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _zx8081.set_key_state(ZX8081::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _zx8081.set_key_state(ZX8081::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _zx8081.set_key_state(ZX8081::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _zx8081.set_key_state(ZX8081::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _zx8081.set_key_state(ZX8081::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _zx8081.set_key_state(ZX8081::Key::KeyL, isPressed); break;
|
||||
|
||||
case VK_ANSI_Z: _zx8081.set_key_state(ZX8081::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _zx8081.set_key_state(ZX8081::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _zx8081.set_key_state(ZX8081::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _zx8081.set_key_state(ZX8081::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _zx8081.set_key_state(ZX8081::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _zx8081.set_key_state(ZX8081::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _zx8081.set_key_state(ZX8081::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Shift:
|
||||
case VK_RightShift:
|
||||
_zx8081.set_key_state(ZX8081::Key::KeyShift, isPressed); break;
|
||||
break;
|
||||
|
||||
case VK_ANSI_Period:_zx8081.set_key_state(ZX8081::Key::KeyDot, isPressed); break;
|
||||
case VK_Return: _zx8081.set_key_state(ZX8081::Key::KeyEnter, isPressed); break;
|
||||
case VK_Space: _zx8081.set_key_state(ZX8081::Key::KeySpace, isPressed); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"zx8081"; }
|
||||
|
||||
@end
|
@ -59,6 +59,7 @@ typedef NS_ENUM(NSInteger, CSTestMachineZ80Register) {
|
||||
@property(nonatomic, readonly, nonnull) NSArray<CSTestMachineZ80BusOperationCapture *> *busOperationCaptures;
|
||||
|
||||
@property(nonatomic, readonly) BOOL isHalted;
|
||||
@property(nonatomic, readonly) int completedCycles;
|
||||
|
||||
@property(nonatomic) BOOL nmiLine;
|
||||
@property(nonatomic) BOOL irqLine;
|
||||
|
@ -149,6 +149,10 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
return _processor->get_halt_line() ? YES : NO;
|
||||
}
|
||||
|
||||
- (int)completedCycles {
|
||||
return _processor->get_length_of_completed_machine_cycles();
|
||||
}
|
||||
|
||||
- (void)setNmiLine:(BOOL)nmiLine {
|
||||
_nmiLine = nmiLine;
|
||||
_processor->set_non_maskable_interrupt_line(nmiLine ? true : false);
|
||||
|
@ -194,6 +194,10 @@ class FUSETests: XCTestCase {
|
||||
|
||||
machine.runForNumber(ofCycles: Int32(targetState.tStates))
|
||||
|
||||
// Verify that exactly the right number of cycles was hit; this is a primitive cycle length tester.
|
||||
let cyclesRun = machine.completedCycles
|
||||
XCTAssert(cyclesRun == Int32(targetState.tStates), "Instruction length off; was \(machine.completedCycles) but should be \(targetState.tStates): \(name)")
|
||||
|
||||
let finalState = RegisterState(machine: machine)
|
||||
|
||||
// Compare processor state.
|
||||
|
@ -135,8 +135,13 @@ class Z80InterruptTests: XCTestCase {
|
||||
machine.runForNumber(ofCycles: 4)
|
||||
XCTAssertEqual(machine.value(for: .programCounter), 0x0102)
|
||||
|
||||
// run for eleven more cycles to allow the IRQ to begin
|
||||
machine.runForNumber(ofCycles: 13)
|
||||
// run for twelve cycles and confirm that the IRQ has yet to begin
|
||||
machine.runForNumber(ofCycles: 12)
|
||||
|
||||
XCTAssertNotEqual(machine.value(for: .programCounter), 0x38)
|
||||
|
||||
// run for one more cycles to allow the IRQ to begin
|
||||
machine.runForNumber(ofCycles: 1)
|
||||
|
||||
// confirm that the PC is now at 0x38, that the old is on the stack and
|
||||
// that interrupts are now disabled
|
||||
|
@ -38,7 +38,7 @@ enum Register {
|
||||
|
||||
IXh, IXl, IX,
|
||||
IYh, IYl, IY,
|
||||
R, I,
|
||||
R, I, Refresh,
|
||||
|
||||
IFF1, IFF2, IM
|
||||
};
|
||||
@ -285,6 +285,13 @@ template <class T> class Processor {
|
||||
Program(INDEX(), FETCHL(temp8_, INDEX_ADDR()), {MicroOp::op, &temp8_}), \
|
||||
Program({MicroOp::op, &a_})
|
||||
|
||||
#define READ_OP_GROUP_D(op) \
|
||||
Program({MicroOp::op, &bc_.bytes.high}), Program({MicroOp::op, &bc_.bytes.low}), \
|
||||
Program({MicroOp::op, &de_.bytes.high}), Program({MicroOp::op, &de_.bytes.low}), \
|
||||
Program({MicroOp::op, &index.bytes.high}), Program({MicroOp::op, &index.bytes.low}), \
|
||||
Program(INDEX(), FETCHL(temp8_, INDEX_ADDR()), WAIT(1), {MicroOp::op, &temp8_}), \
|
||||
Program({MicroOp::op, &a_})
|
||||
|
||||
#define RMW(x, op, ...) Program(INDEX(), FETCHL(x, INDEX_ADDR()), {MicroOp::op, &x}, WAIT(1), STOREL(x, INDEX_ADDR()))
|
||||
#define RMWI(x, op, ...) Program(WAIT(2), FETCHL(x, INDEX_ADDR()), {MicroOp::op, &x}, WAIT(1), STOREL(x, INDEX_ADDR()))
|
||||
|
||||
@ -345,7 +352,7 @@ template <class T> class Processor {
|
||||
|
||||
// Copy in all programs and set pointers.
|
||||
size_t destination = 0;
|
||||
for(int c = 0; c < 256; c++) {
|
||||
for(size_t c = 0; c < 256; c++) {
|
||||
target.instructions[c] = &target.all_operations[destination];
|
||||
for(int t = 0; t < lengths[c];) {
|
||||
// Skip zero-length bus cycles.
|
||||
@ -385,19 +392,19 @@ template <class T> class Processor {
|
||||
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.bytes.high),
|
||||
/* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ Program(FETCH16(temp16_, pc_), STORE16L(bc_, temp16_)),
|
||||
/* 0x44 NEG */ Program({MicroOp::NEG}), /* 0x45 RETN */ Program(POP(pc_), {MicroOp::RETN}),
|
||||
/* 0x46 IM 0 */ Program({MicroOp::IM}), /* 0x47 LD I, A */ LD(i_, a_),
|
||||
/* 0x46 IM 0 */ Program({MicroOp::IM}), /* 0x47 LD I, A */ Program(WAIT(1), {MicroOp::Move8, &a_, &i_}),
|
||||
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.bytes.low),
|
||||
/* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ Program(FETCH16(temp16_, pc_), FETCH16L(bc_, temp16_)),
|
||||
/* 0x4c NEG */ Program({MicroOp::NEG}), /* 0x4d RETI */ Program(POP(pc_), {MicroOp::RETN}),
|
||||
/* 0x4e IM 0/1 */ Program({MicroOp::IM}), /* 0x4f LD R, A */ LD(r_, a_),
|
||||
/* 0x4e IM 0/1 */ Program({MicroOp::IM}), /* 0x4f LD R, A */ Program(WAIT(1), {MicroOp::Move8, &a_, &r_}),
|
||||
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(de_.bytes.high),
|
||||
/* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ Program(FETCH16(temp16_, pc_), STORE16L(de_, temp16_)),
|
||||
/* 0x54 NEG */ Program({MicroOp::NEG}), /* 0x55 RETN */ Program(POP(pc_), {MicroOp::RETN}),
|
||||
/* 0x56 IM 1 */ Program({MicroOp::IM}), /* 0x57 LD A, I */ Program({MicroOp::Move8, &i_, &a_}, {MicroOp::SetAFlags}),
|
||||
/* 0x56 IM 1 */ Program({MicroOp::IM}), /* 0x57 LD A, I */ Program(WAIT(1), {MicroOp::Move8, &i_, &a_}, {MicroOp::SetAFlags}),
|
||||
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(de_.bytes.low),
|
||||
/* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ Program(FETCH16(temp16_, pc_), FETCH16L(de_, temp16_)),
|
||||
/* 0x5c NEG */ Program({MicroOp::NEG}), /* 0x5d RETN */ Program(POP(pc_), {MicroOp::RETN}),
|
||||
/* 0x5e IM 2 */ Program({MicroOp::IM}), /* 0x5f LD A, R */ Program({MicroOp::Move8, &r_, &a_}, {MicroOp::SetAFlags}),
|
||||
/* 0x5e IM 2 */ Program({MicroOp::IM}), /* 0x5f LD A, R */ Program(WAIT(1), {MicroOp::Move8, &r_, &a_}, {MicroOp::SetAFlags}),
|
||||
/* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(hl_.bytes.high),
|
||||
/* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ Program(FETCH16(temp16_, pc_), STORE16L(hl_, temp16_)),
|
||||
/* 0x64 NEG */ Program({MicroOp::NEG}), /* 0x65 RETN */ Program(POP(pc_), {MicroOp::RETN}),
|
||||
@ -461,7 +468,7 @@ template <class T> class Processor {
|
||||
/* 0x40 – 0x7f: BIT */
|
||||
/* 0x80 – 0xcf: RES */
|
||||
/* 0xd0 – 0xdf: SET */
|
||||
CB_PAGE(MODIFY_OP_GROUP, READ_OP_GROUP)
|
||||
CB_PAGE(MODIFY_OP_GROUP, READ_OP_GROUP_D)
|
||||
};
|
||||
InstructionTable offsets_cb_program_table = {
|
||||
CB_PAGE(IX_MODIFY_OP_GROUP, IX_READ_OP_GROUP)
|
||||
@ -775,7 +782,10 @@ template <class T> class Processor {
|
||||
while(bus_request_line_) {
|
||||
static MachineCycle bus_acknowledge_cycle = {BusOperation::BusAcknowledge, 1};
|
||||
number_of_cycles_ -= static_cast<T *>(this)->perform_machine_cycle(bus_acknowledge_cycle) + 1;
|
||||
if(!number_of_cycles_) return;
|
||||
if(!number_of_cycles_) {
|
||||
static_cast<T *>(this)->flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while(!bus_request_line_) {
|
||||
@ -783,14 +793,18 @@ template <class T> class Processor {
|
||||
scheduled_program_counter_++;
|
||||
|
||||
#define set_parity(v) \
|
||||
parity_overflow_result_ = v^1;\
|
||||
parity_overflow_result_ = (uint8_t)(v^1);\
|
||||
parity_overflow_result_ ^= parity_overflow_result_ >> 4;\
|
||||
parity_overflow_result_ ^= parity_overflow_result_ << 2;\
|
||||
parity_overflow_result_ ^= parity_overflow_result_ >> 1;
|
||||
|
||||
switch(operation->type) {
|
||||
case MicroOp::BusOperation:
|
||||
if(number_of_cycles_ < operation->machine_cycle.length) { scheduled_program_counter_--; return; }
|
||||
if(number_of_cycles_ < operation->machine_cycle.length) {
|
||||
scheduled_program_counter_--;
|
||||
static_cast<T *>(this)->flush();
|
||||
return;
|
||||
}
|
||||
number_of_cycles_ -= operation->machine_cycle.length;
|
||||
last_request_status_ = request_status_;
|
||||
number_of_cycles_ -= static_cast<T *>(this)->perform_machine_cycle(operation->machine_cycle);
|
||||
@ -800,7 +814,7 @@ template <class T> class Processor {
|
||||
break;
|
||||
case MicroOp::DecodeOperation:
|
||||
r_ = (r_ & 0x80) | ((r_ + current_instruction_page_->r_step) & 0x7f);
|
||||
pc_.full += pc_increment_;
|
||||
pc_.full += pc_increment_ & (uint16_t)halt_mask_;
|
||||
case MicroOp::DecodeOperationNoRChange:
|
||||
scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_];
|
||||
break;
|
||||
@ -854,7 +868,7 @@ template <class T> class Processor {
|
||||
break;
|
||||
|
||||
case MicroOp::CCF:
|
||||
half_carry_result_ = carry_result_ << 4;
|
||||
half_carry_result_ = (uint8_t)(carry_result_ << 4);
|
||||
carry_result_ ^= Flag::Carry;
|
||||
subtract_flag_ = 0;
|
||||
bit53_result_ = a_;
|
||||
@ -884,9 +898,9 @@ template <class T> class Processor {
|
||||
|
||||
#define set_arithmetic_flags(sub, b53) \
|
||||
sign_result_ = zero_result_ = (uint8_t)result; \
|
||||
carry_result_ = result >> 8; \
|
||||
half_carry_result_ = half_result; \
|
||||
parity_overflow_result_ = overflow >> 5; \
|
||||
carry_result_ = (uint8_t)(result >> 8); \
|
||||
half_carry_result_ = (uint8_t)half_result; \
|
||||
parity_overflow_result_ = (uint8_t)(overflow >> 5); \
|
||||
subtract_flag_ = sub; \
|
||||
bit53_result_ = (uint8_t)b53;
|
||||
|
||||
@ -966,8 +980,8 @@ template <class T> class Processor {
|
||||
bit53_result_ = sign_result_ = zero_result_ = a_;
|
||||
parity_overflow_result_ = overflow ? Flag::Overflow : 0;
|
||||
subtract_flag_ = Flag::Subtract;
|
||||
carry_result_ = result >> 8;
|
||||
half_carry_result_ = halfResult;
|
||||
carry_result_ = (uint8_t)(result >> 8);
|
||||
half_carry_result_ = (uint8_t)halfResult;
|
||||
} break;
|
||||
|
||||
case MicroOp::Increment8: {
|
||||
@ -983,8 +997,8 @@ template <class T> class Processor {
|
||||
|
||||
// sign, zero and 5 & 3 are set directly from the result
|
||||
bit53_result_ = sign_result_ = zero_result_ = (uint8_t)result;
|
||||
half_carry_result_ = half_result;
|
||||
parity_overflow_result_ = overflow >> 5;
|
||||
half_carry_result_ = (uint8_t)half_result;
|
||||
parity_overflow_result_ = (uint8_t)(overflow >> 5);
|
||||
subtract_flag_ = 0;
|
||||
} break;
|
||||
|
||||
@ -1001,8 +1015,8 @@ template <class T> class Processor {
|
||||
|
||||
// sign, zero and 5 & 3 are set directly from the result
|
||||
bit53_result_ = sign_result_ = zero_result_ = (uint8_t)result;
|
||||
half_carry_result_ = half_result;
|
||||
parity_overflow_result_ = overflow >> 5;
|
||||
half_carry_result_ = (uint8_t)half_result;
|
||||
parity_overflow_result_ = (uint8_t)(overflow >> 5);
|
||||
subtract_flag_ = Flag::Subtract;
|
||||
} break;
|
||||
|
||||
@ -1071,8 +1085,8 @@ template <class T> class Processor {
|
||||
int halfResult = (sourceValue&0xfff) + (destinationValue&0xfff);
|
||||
|
||||
bit53_result_ = (uint8_t)(result >> 8);
|
||||
carry_result_ = result >> 16;
|
||||
half_carry_result_ = (halfResult >> 8);
|
||||
carry_result_ = (uint8_t)(result >> 16);
|
||||
half_carry_result_ = (uint8_t)(halfResult >> 8);
|
||||
subtract_flag_ = 0;
|
||||
|
||||
*(uint16_t *)operation->destination = (uint16_t)result;
|
||||
@ -1091,9 +1105,9 @@ template <class T> class Processor {
|
||||
sign_result_ = (uint8_t)(result >> 8);
|
||||
zero_result_ = (uint8_t)(result | sign_result_);
|
||||
subtract_flag_ = 0;
|
||||
carry_result_ = result >> 16;
|
||||
half_carry_result_ = halfResult >> 8;
|
||||
parity_overflow_result_ = overflow >> 13;
|
||||
carry_result_ = (uint8_t)(result >> 16);
|
||||
half_carry_result_ = (uint8_t)(halfResult >> 8);
|
||||
parity_overflow_result_ = (uint8_t)(overflow >> 13);
|
||||
|
||||
*(uint16_t *)operation->destination = (uint16_t)result;
|
||||
} break;
|
||||
@ -1114,9 +1128,9 @@ template <class T> class Processor {
|
||||
sign_result_ = (uint8_t)(result >> 8);
|
||||
zero_result_ = (uint8_t)(result | sign_result_);
|
||||
subtract_flag_ = Flag::Subtract;
|
||||
carry_result_ = result >> 16;
|
||||
half_carry_result_ = halfResult >> 8;
|
||||
parity_overflow_result_ = overflow >> 13;
|
||||
carry_result_ = (uint8_t)(result >> 16);
|
||||
half_carry_result_ = (uint8_t)(halfResult >> 8);
|
||||
parity_overflow_result_ = (uint8_t)(overflow >> 13);
|
||||
|
||||
*(uint16_t *)operation->destination = (uint16_t)result;
|
||||
} break;
|
||||
@ -1173,7 +1187,7 @@ template <class T> class Processor {
|
||||
de_.full += dir; \
|
||||
hl_.full += dir; \
|
||||
uint8_t sum = a_ + temp8_; \
|
||||
bit53_result_ = (sum&0x8) | ((sum & 0x02) << 4); \
|
||||
bit53_result_ = (uint8_t)((sum&0x8) | ((sum & 0x02) << 4)); \
|
||||
subtract_flag_ = 0; \
|
||||
half_carry_result_ = 0; \
|
||||
parity_overflow_result_ = bc_.full ? Flag::Parity : 0;
|
||||
@ -1438,7 +1452,7 @@ template <class T> class Processor {
|
||||
memptr_.full = hl_.full + 1;
|
||||
uint8_t low_nibble = a_ & 0xf;
|
||||
a_ = (a_ & 0xf0) | (temp8_ & 0xf);
|
||||
temp8_ = (temp8_ >> 4) | (low_nibble << 4);
|
||||
temp8_ = (uint8_t)((temp8_ >> 4) | (low_nibble << 4));
|
||||
set_decimal_rotate_flags();
|
||||
} break;
|
||||
|
||||
@ -1446,7 +1460,7 @@ template <class T> class Processor {
|
||||
memptr_.full = hl_.full + 1;
|
||||
uint8_t low_nibble = a_ & 0xf;
|
||||
a_ = (a_ & 0xf0) | (temp8_ >> 4);
|
||||
temp8_ = (temp8_ << 4) | low_nibble;
|
||||
temp8_ = (uint8_t)((temp8_ << 4) | low_nibble);
|
||||
set_decimal_rotate_flags();
|
||||
} break;
|
||||
|
||||
@ -1541,7 +1555,7 @@ template <class T> class Processor {
|
||||
break;
|
||||
|
||||
case MicroOp::CalculateIndexAddress:
|
||||
memptr_.full = *(uint16_t *)operation->source + (int8_t)temp8_;
|
||||
memptr_.full = (uint16_t)(*(uint16_t *)operation->source + (int8_t)temp8_);
|
||||
break;
|
||||
|
||||
case MicroOp::IndexedPlaceHolder:
|
||||
@ -1649,10 +1663,11 @@ template <class T> class Processor {
|
||||
|
||||
case Register::R: return r_;
|
||||
case Register::I: return i_;
|
||||
case Register::Refresh: return (uint16_t)(r_ | (i_ << 8));
|
||||
|
||||
case Register::IFF1: return iff1_ ? 1 : 0;
|
||||
case Register::IFF2: return iff2_ ? 1 : 0;
|
||||
case Register::IM: return interrupt_mode_;
|
||||
case Register::IM: return (uint16_t)interrupt_mode_;
|
||||
|
||||
default: return 0;
|
||||
}
|
||||
@ -1707,6 +1722,7 @@ template <class T> class Processor {
|
||||
|
||||
case Register::R: r_ = (uint8_t)value; break;
|
||||
case Register::I: i_ = (uint8_t)value; break;
|
||||
case Register::Refresh: r_ = (uint8_t)value; i_ = (uint8_t)(value >> 8); break;
|
||||
|
||||
case Register::IFF1: iff1_ = !!value; break;
|
||||
case Register::IFF2: iff2_ = !!value; break;
|
||||
@ -1725,8 +1741,14 @@ template <class T> class Processor {
|
||||
|
||||
/*!
|
||||
Sets the logical value of the interrupt line.
|
||||
|
||||
@param offset If called while within perform_machine_cycle this may be a value indicating
|
||||
how many cycles before now the line changed state. The value may not be longer than the
|
||||
current machine cycle. If called at any other time, this must be zero.
|
||||
*/
|
||||
void set_interrupt_line(bool value) {
|
||||
void set_interrupt_line(bool value, int offset = 0) {
|
||||
if(irq_line_ == value) return;
|
||||
|
||||
// IRQ requests are level triggered and masked.
|
||||
irq_line_ = value;
|
||||
if(irq_line_ && iff1_) {
|
||||
@ -1734,14 +1756,28 @@ template <class T> class Processor {
|
||||
} else {
|
||||
request_status_ &= ~Interrupt::IRQ;
|
||||
}
|
||||
|
||||
// If this change happened at least one cycle ago then: (i) we're promised that this is a machine
|
||||
// cycle per the contract on supplying an offset; and (ii) that means it happened before the lines
|
||||
// were sampled. So adjust the most recent sample.
|
||||
if(offset < 0) {
|
||||
last_request_status_ = (last_request_status_ & ~Interrupt::IRQ) | (request_status_ & Interrupt::IRQ);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the logical value of the non-maskable interrupt line.
|
||||
|
||||
@param offset See discussion in set_interrupt_line.
|
||||
*/
|
||||
void set_non_maskable_interrupt_line(bool value) {
|
||||
void set_non_maskable_interrupt_line(bool value, int offset = 0) {
|
||||
// NMIs are edge triggered and cannot be masked.
|
||||
if(value) request_status_ |= Interrupt::NMI;
|
||||
if(value) {
|
||||
request_status_ |= Interrupt::NMI;
|
||||
if(offset < 0) {
|
||||
last_request_status_ |= Interrupt::NMI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -14,9 +14,10 @@ namespace {
|
||||
|
||||
class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<ConcreteAllRAMProcessor> {
|
||||
public:
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor() {}
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor(), completed_cycles(0) {}
|
||||
|
||||
inline int perform_machine_cycle(const MachineCycle &cycle) {
|
||||
completed_cycles += cycle.length;
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case BusOperation::ReadOpcode:
|
||||
@ -88,6 +89,14 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
void set_non_maskable_interrupt_line(bool value) {
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor>::set_non_maskable_interrupt_line(value);
|
||||
}
|
||||
|
||||
int get_length_of_completed_machine_cycles() {
|
||||
return completed_cycles;
|
||||
}
|
||||
|
||||
private:
|
||||
int completed_cycles;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ class AllRAMProcessor:
|
||||
virtual void set_interrupt_line(bool value) = 0;
|
||||
virtual void set_non_maskable_interrupt_line(bool value) = 0;
|
||||
|
||||
virtual int get_length_of_completed_machine_cycles() = 0;
|
||||
|
||||
protected:
|
||||
MemoryAccessDelegate *delegate_;
|
||||
AllRAMProcessor() : ::CPU::AllRAMProcessor(65536), delegate_(nullptr) {}
|
||||
|
6
ROMImages/ZX8081/readme.txt
Normal file
6
ROMImages/ZX8081/readme.txt
Normal file
@ -0,0 +1,6 @@
|
||||
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
|
||||
|
||||
Expected files:
|
||||
|
||||
zx80.rom
|
||||
zx81.rom
|
@ -15,6 +15,7 @@
|
||||
#include "Atari/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
|
||||
// Cartridges
|
||||
#include "../Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||
@ -32,13 +33,15 @@
|
||||
#include "../Storage/Tape/Formats/OricTAP.hpp"
|
||||
#include "../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
|
||||
typedef int TargetPlatformType;
|
||||
enum class TargetPlatform: TargetPlatformType {
|
||||
Acorn = 1 << 0,
|
||||
Atari2600 = 1 << 1,
|
||||
Commodore = 1 << 2,
|
||||
Oric = 1 << 3
|
||||
Oric = 1 << 3,
|
||||
ZX8081 = 1 << 4,
|
||||
};
|
||||
|
||||
using namespace StaticAnalyser;
|
||||
@ -86,6 +89,8 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
||||
|
||||
if(lowercase_extension)
|
||||
{
|
||||
Format("80", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
|
||||
@ -93,6 +98,8 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
||||
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK
|
||||
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
|
||||
Format("o", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
|
||||
// PRG
|
||||
if(!strcmp(lowercase_extension, "prg"))
|
||||
@ -125,6 +132,7 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
||||
if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets);
|
||||
if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets);
|
||||
if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets);
|
||||
if(potential_platforms & (TargetPlatformType)TargetPlatform::ZX8081) ZX8081::AddTargets(disks, tapes, cartridges, targets);
|
||||
|
||||
free(lowercase_extension);
|
||||
}
|
||||
|
@ -40,6 +40,12 @@ enum class Atari2600PagingModel {
|
||||
Pitfall2
|
||||
};
|
||||
|
||||
enum class ZX8081MemoryModel {
|
||||
Unexpanded,
|
||||
SixteenKB,
|
||||
SixtyFourKB
|
||||
};
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
@ -49,7 +55,8 @@ struct Target {
|
||||
Atari2600,
|
||||
Electron,
|
||||
Vic20,
|
||||
Oric
|
||||
Oric,
|
||||
ZX8081
|
||||
} machine;
|
||||
float probability;
|
||||
|
||||
@ -74,6 +81,11 @@ struct Target {
|
||||
Vic20MemoryModel memory_model;
|
||||
bool has_c1540;
|
||||
} vic20;
|
||||
|
||||
struct {
|
||||
ZX8081MemoryModel memory_model;
|
||||
bool isZX81;
|
||||
} zx8081;
|
||||
};
|
||||
|
||||
std::string loadingCommand;
|
||||
|
53
StaticAnalyser/ZX8081/StaticAnalyser.cpp
Normal file
53
StaticAnalyser/ZX8081/StaticAnalyser.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<Storage::Data::ZX8081::File> files;
|
||||
Storage::Tape::ZX8081::Parser parser;
|
||||
|
||||
while(!tape->is_at_end()) {
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
|
||||
if(next_file != nullptr) {
|
||||
files.push_back(*next_file);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void StaticAnalyser::ZX8081::AddTargets(
|
||||
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
|
||||
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
|
||||
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
|
||||
std::list<StaticAnalyser::Target> &destination) {
|
||||
|
||||
if(!tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(tapes.front());
|
||||
if(!files.empty()) {
|
||||
StaticAnalyser::Target target;
|
||||
target.machine = Target::ZX8081;
|
||||
target.zx8081.isZX81 = files.front().isZX81;
|
||||
if(files.front().data.size() > 16384) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||
} else if(files.front().data.size() > 1024) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
|
||||
} else {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
|
||||
}
|
||||
target.tapes = tapes;
|
||||
destination.push_back(target);
|
||||
}
|
||||
}
|
||||
}
|
27
StaticAnalyser/ZX8081/StaticAnalyser.hpp
Normal file
27
StaticAnalyser/ZX8081/StaticAnalyser.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_ZX8081_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace ZX8081 {
|
||||
|
||||
void AddTargets(
|
||||
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
|
||||
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
|
||||
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
|
||||
std::list<Target> &destination
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
127
Storage/Data/ZX8081.cpp
Normal file
127
Storage/Data/ZX8081.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
//
|
||||
// ZX8081.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
using namespace Storage::Data::ZX8081;
|
||||
|
||||
static uint16_t short_at(size_t address, const std::vector<uint8_t> &data) {
|
||||
return (uint16_t)(data[address] | (data[address + 1] << 8));
|
||||
}
|
||||
|
||||
static std::shared_ptr<File> ZX80FileFromData(const std::vector<uint8_t> &data) {
|
||||
// Does this look like a ZX80 file?
|
||||
|
||||
if(data.size() < 0x28) return nullptr;
|
||||
|
||||
// uint16_t next_line_number = short_at(0x2, data);
|
||||
// uint16_t first_visible_line = short_at(0x13, data);
|
||||
|
||||
uint16_t vars = short_at(0x8, data);
|
||||
uint16_t end_of_file = short_at(0xa, data);
|
||||
uint16_t display_address = short_at(0xc, data);
|
||||
|
||||
// check that the end of file is contained within the supplied data
|
||||
if(end_of_file - 0x4000 > data.size()) return nullptr;
|
||||
|
||||
// check for the proper ordering of buffers
|
||||
if(vars > end_of_file) return nullptr;
|
||||
if(end_of_file > display_address) return nullptr;
|
||||
|
||||
// TODO: does it make sense to inspect the tokenised BASIC?
|
||||
// It starts at 0x4028 and proceeds as [16-bit line number] [tokens] [0x76],
|
||||
// but I'm as yet unable to find documentation of the tokens.
|
||||
|
||||
// TODO: check that the line numbers declared above exist (?)
|
||||
|
||||
std::shared_ptr<File> file(new File);
|
||||
file->data = data;
|
||||
file->isZX81 = false;
|
||||
return file;
|
||||
}
|
||||
|
||||
static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data) {
|
||||
// Does this look like a ZX81 file?
|
||||
|
||||
// Look for a file name.
|
||||
size_t data_pointer = 0;
|
||||
int c = 11;
|
||||
while(c--) {
|
||||
if(data[data_pointer] & 0x80) break;
|
||||
data_pointer++;
|
||||
}
|
||||
if(!c) return nullptr;
|
||||
data_pointer++;
|
||||
|
||||
if(data.size() < data_pointer + 0x405e - 0x4009) return nullptr;
|
||||
|
||||
if(data[data_pointer]) return nullptr;
|
||||
|
||||
uint16_t vars = short_at(data_pointer + 0x4010 - 0x4009, data);
|
||||
uint16_t end_of_file = short_at(data_pointer + 0x4014 - 0x4009, data);
|
||||
// uint16_t display_address = short_at(0x400c - 0x4009, data);
|
||||
|
||||
// check that the end of file is contained within the supplied data
|
||||
if(data_pointer + end_of_file - 0x4009 > data.size()) return nullptr;
|
||||
|
||||
// check for the proper ordering of buffers
|
||||
if(vars > end_of_file) return nullptr;
|
||||
// if(end_of_file > display_address) return nullptr;
|
||||
|
||||
// TODO: does it make sense to inspect the tokenised BASIC?
|
||||
// It starts at 0x4028 and proceeds as [16-bit line number] [tokens] [0x76],
|
||||
// but I'm as yet unable to find documentation of the tokens.
|
||||
|
||||
// TODO: check that the line numbers declared above exist (?)
|
||||
|
||||
std::shared_ptr<File> file(new File);
|
||||
file->data = data;
|
||||
file->isZX81 = true;
|
||||
return file;
|
||||
}
|
||||
|
||||
std::shared_ptr<File> Storage::Data::ZX8081::FileFromData(const std::vector<uint8_t> &data) {
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> result = ZX80FileFromData(data);
|
||||
if(result) return result;
|
||||
return ZX81FileFromData(data);
|
||||
}
|
||||
|
||||
#pragma mark - String conversion
|
||||
|
||||
std::wstring Storage::Data::ZX8081::StringFromData(const std::vector<uint8_t> &data, bool is_zx81) {
|
||||
std::wstring string;
|
||||
|
||||
wchar_t zx80_map[64] = {
|
||||
' ', u'\u2598', u'\u259d', u'\u2580', u'\u2596', u'\u258c', u'\u259e', u'\u259b', u'\u2588', u'\u2584', u'\u2580', '"', u'£', '$', ':', '?',
|
||||
'(', ')', '>', '<', '=', '+', '-', '*', '/', ';', ',', '.', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
};
|
||||
// TODO: the block character conversions shown here are in the wrong order
|
||||
wchar_t zx81_map[64] = {
|
||||
' ', u'\u2598', u'\u259d', u'\u2580', u'\u2596', u'\u258c', u'\u259e', u'\u259b', u'\u2588', u'\u2584', u'\u2580', '"', u'£', '$', ':', '?',
|
||||
'(', ')', '-', '+', '*', '/', '=', '>', '<', ';', ',', '.', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
};
|
||||
wchar_t *map = is_zx81 ? zx81_map : zx80_map;
|
||||
|
||||
for(uint8_t byte : data) {
|
||||
string.push_back(map[byte & 0x3f]);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DataFromString(const std::wstring &string, bool is_zx81) {
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// TODO
|
||||
|
||||
return data;
|
||||
}
|
36
Storage/Data/ZX8081.hpp
Normal file
36
Storage/Data/ZX8081.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// ZX8081.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Data_ZX8081_hpp
|
||||
#define Storage_Data_ZX8081_hpp
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Storage {
|
||||
namespace Data {
|
||||
namespace ZX8081 {
|
||||
|
||||
struct File {
|
||||
std::vector<uint8_t> data;
|
||||
std::string name;
|
||||
bool isZX81;
|
||||
};
|
||||
|
||||
std::shared_ptr<File> FileFromData(const std::vector<uint8_t> &data);
|
||||
|
||||
std::wstring StringFromData(const std::vector<uint8_t> &data, bool is_zx81);
|
||||
std::vector<uint8_t> DataFromString(const std::wstring &string, bool is_zx81);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ZX8081_hpp */
|
102
Storage/Tape/Formats/ZX80O81P.cpp
Normal file
102
Storage/Tape/Formats/ZX80O81P.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// ZX80O.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ZX80O81P.hpp"
|
||||
#include "../../Data/ZX8081.hpp"
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
ZX80O81P::ZX80O81P(const char *file_name) :
|
||||
Storage::FileHolder(file_name) {
|
||||
|
||||
// Grab the actual file contents
|
||||
data_.resize((size_t)file_stats_.st_size);
|
||||
fread(data_.data(), 1, (size_t)file_stats_.st_size, file_);
|
||||
|
||||
// If it's a ZX81 file, prepend a file name.
|
||||
char type = (char)tolower(file_name[strlen(file_name) - 1]);
|
||||
if(type == 'p' || type == '1') {
|
||||
// TODO, maybe: prefix a proper file name; this is leaving the file nameless.
|
||||
data_.insert(data_.begin(), 0x80);
|
||||
}
|
||||
|
||||
std::shared_ptr<::Storage::Data::ZX8081::File> file = Storage::Data::ZX8081::FileFromData(data_);
|
||||
if(!file) throw ErrorNotZX80O81P;
|
||||
|
||||
// then rewind and start again
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
void ZX80O81P::virtual_reset() {
|
||||
data_pointer_ = 0;
|
||||
is_past_silence_ = false;
|
||||
has_ended_final_byte_ = false;
|
||||
is_high_ = true;
|
||||
bit_pointer_ = wave_pointer_ = 0;
|
||||
}
|
||||
|
||||
bool ZX80O81P::has_finished_data() {
|
||||
return (data_pointer_ == data_.size()) && !wave_pointer_ && !bit_pointer_;
|
||||
}
|
||||
|
||||
bool ZX80O81P::is_at_end() {
|
||||
return has_finished_data() && has_ended_final_byte_;
|
||||
}
|
||||
|
||||
Tape::Pulse ZX80O81P::virtual_get_next_pulse() {
|
||||
Tape::Pulse pulse;
|
||||
|
||||
// Start with 1 second of silence.
|
||||
if(!is_past_silence_ || has_finished_data()) {
|
||||
pulse.type = Pulse::Type::Low;
|
||||
pulse.length.length = 5;
|
||||
pulse.length.clock_rate = 1;
|
||||
is_past_silence_ = true;
|
||||
has_ended_final_byte_ = has_finished_data();
|
||||
return pulse;
|
||||
}
|
||||
|
||||
// For each byte, output 8 bits and then silence.
|
||||
if(!bit_pointer_ && !wave_pointer_) {
|
||||
byte_ = data_[data_pointer_];
|
||||
data_pointer_++;
|
||||
bit_pointer_ = 0;
|
||||
wave_pointer_ = 0;
|
||||
}
|
||||
|
||||
if(!wave_pointer_) {
|
||||
// post-waves silence (here actually a pre-waves silence) is 1300µs
|
||||
pulse.length.length = 13;
|
||||
pulse.length.clock_rate = 10000;
|
||||
pulse.type = Pulse::Type::Low;
|
||||
|
||||
wave_pointer_ ++;
|
||||
} else {
|
||||
// pulses are of length 150µs
|
||||
pulse.length.length = 3;
|
||||
pulse.length.clock_rate = 20000;
|
||||
|
||||
if(is_high_) {
|
||||
pulse.type = Pulse::Type::High;
|
||||
is_high_ = false;
|
||||
} else {
|
||||
pulse.type = Pulse::Type::Low;
|
||||
is_high_ = true;
|
||||
|
||||
// Bytes are stored MSB first.
|
||||
int wave_count = (byte_ & (0x80 >> bit_pointer_)) ? 9 : 4;
|
||||
wave_pointer_++;
|
||||
if(wave_pointer_ == wave_count + 1) {
|
||||
bit_pointer_ = (bit_pointer_ + 1)&7;
|
||||
wave_pointer_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pulse;
|
||||
}
|
58
Storage/Tape/Formats/ZX80O81P.hpp
Normal file
58
Storage/Tape/Formats/ZX80O81P.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// ZX80O.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ZX80O81P_hpp
|
||||
#define ZX80O81P_hpp
|
||||
|
||||
#include "../Tape.hpp"
|
||||
#include "../../FileHolder.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
|
||||
/*!
|
||||
Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture.
|
||||
*/
|
||||
class ZX80O81P: public Tape, public Storage::FileHolder {
|
||||
public:
|
||||
/*!
|
||||
Constructs an @c ZX80O containing content from the file with name @c file_name.
|
||||
|
||||
@throws ErrorNotZX80O if this file could not be opened and recognised as a valid ZX80-format .O.
|
||||
*/
|
||||
ZX80O81P(const char *file_name);
|
||||
|
||||
enum {
|
||||
ErrorNotZX80O81P
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
bool has_finished_data();
|
||||
|
||||
uint8_t byte_;
|
||||
int bit_pointer_;
|
||||
int wave_pointer_;
|
||||
bool is_past_silence_, has_ended_final_byte_;
|
||||
bool is_high_;
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
size_t data_pointer_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ZX80O_hpp */
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
@ -27,33 +28,50 @@ namespace Tape {
|
||||
template <typename WaveType, typename SymbolType> class Parser {
|
||||
public:
|
||||
/// Instantiates a new parser with the supplied @c tape.
|
||||
Parser() : _has_next_symbol(false), _error_flag(false) {}
|
||||
Parser() : has_next_symbol_(false), error_flag_(false) {}
|
||||
|
||||
/// Resets the error flag.
|
||||
void reset_error_flag() { _error_flag = false; }
|
||||
void reset_error_flag() { error_flag_ = false; }
|
||||
/// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise.
|
||||
bool get_error_flag() { return _error_flag; }
|
||||
bool get_error_flag() { return error_flag_; }
|
||||
|
||||
/*!
|
||||
Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol
|
||||
or the tape runs out, returning the most-recently declared symbol.
|
||||
*/
|
||||
SymbolType get_next_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
while(!_has_next_symbol && !tape->is_at_end())
|
||||
{
|
||||
SymbolType get_next_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
while(!has_next_symbol_ && !tape->is_at_end()) {
|
||||
process_pulse(tape->get_next_pulse());
|
||||
}
|
||||
_has_next_symbol = false;
|
||||
return _next_symbol;
|
||||
if(tape->is_at_end()) mark_end();
|
||||
has_next_symbol_ = false;
|
||||
return next_symbol_;
|
||||
}
|
||||
|
||||
/*!
|
||||
This class provides a single token of lookahead; return_symbol allows the single previous
|
||||
token supplied by get_next_symbol to be returned, in which case it will be the thing returned
|
||||
by the next call to get_next_symbol.
|
||||
*/
|
||||
void return_symbol(SymbolType symbol) {
|
||||
assert(!has_next_symbol_);
|
||||
has_next_symbol_ = true;
|
||||
next_symbol_ = symbol;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave
|
||||
or to take no action.
|
||||
*/
|
||||
virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0;
|
||||
|
||||
/*!
|
||||
An optional implementation for subclasses; called to announce that the tape has ended: that
|
||||
no more process_pulse calls will occur.
|
||||
*/
|
||||
virtual void mark_end() {}
|
||||
|
||||
protected:
|
||||
|
||||
/*!
|
||||
@ -61,10 +79,9 @@ template <typename WaveType, typename SymbolType> class Parser {
|
||||
|
||||
Expected to be called by subclasses from @c process_pulse as and when recognised waves arise.
|
||||
*/
|
||||
void push_wave(WaveType wave)
|
||||
{
|
||||
_wave_queue.push_back(wave);
|
||||
inspect_waves(_wave_queue);
|
||||
void push_wave(WaveType wave) {
|
||||
wave_queue_.push_back(wave);
|
||||
inspect_waves(wave_queue_);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -73,9 +90,8 @@ template <typename WaveType, typename SymbolType> class Parser {
|
||||
Expected to be called by subclasses from @c process_pulse if it is recognised that the first set of waves
|
||||
do not form a valid symbol.
|
||||
*/
|
||||
void remove_waves(int number_of_waves)
|
||||
{
|
||||
_wave_queue.erase(_wave_queue.begin(), _wave_queue.begin()+number_of_waves);
|
||||
void remove_waves(int number_of_waves) {
|
||||
wave_queue_.erase(wave_queue_.begin(), wave_queue_.begin()+number_of_waves);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -84,20 +100,25 @@ template <typename WaveType, typename SymbolType> class Parser {
|
||||
Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves
|
||||
waves together represent @c symbol.
|
||||
*/
|
||||
void push_symbol(SymbolType symbol, int number_of_waves)
|
||||
{
|
||||
_has_next_symbol = true;
|
||||
_next_symbol = symbol;
|
||||
void push_symbol(SymbolType symbol, int number_of_waves) {
|
||||
has_next_symbol_ = true;
|
||||
next_symbol_ = symbol;
|
||||
remove_waves(number_of_waves);
|
||||
}
|
||||
|
||||
void set_error_flag()
|
||||
{
|
||||
_error_flag = true;
|
||||
void set_error_flag() {
|
||||
error_flag_ = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise.
|
||||
*/
|
||||
bool is_at_end(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
return tape->is_at_end() && wave_queue_.empty() && !has_next_symbol_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _error_flag;
|
||||
bool error_flag_;
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is
|
||||
@ -107,9 +128,9 @@ template <typename WaveType, typename SymbolType> class Parser {
|
||||
*/
|
||||
virtual void inspect_waves(const std::vector<WaveType> &waves) = 0;
|
||||
|
||||
std::vector<WaveType> _wave_queue;
|
||||
SymbolType _next_symbol;
|
||||
bool _has_next_symbol;
|
||||
std::vector<WaveType> wave_queue_;
|
||||
SymbolType next_symbol_;
|
||||
bool has_next_symbol_;
|
||||
};
|
||||
|
||||
}
|
||||
|
136
Storage/Tape/Parsers/ZX8081.cpp
Normal file
136
Storage/Tape/Parsers/ZX8081.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
//
|
||||
// ZX8081.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
using namespace Storage::Tape::ZX8081;
|
||||
|
||||
Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {}
|
||||
|
||||
void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
pulse_time_ += pulse.length;
|
||||
bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
|
||||
if(pulse_is_high == pulse_was_high_) return;
|
||||
pulse_was_high_ = pulse_is_high;
|
||||
post_pulse();
|
||||
}
|
||||
|
||||
void Parser::post_pulse() {
|
||||
const float expected_pulse_length = 150.0f / 1000000.0f;
|
||||
const float expected_gap_length = 1300.0f / 1000000.0f;
|
||||
float pulse_time = pulse_time_.get_float();
|
||||
pulse_time_.set_zero();
|
||||
|
||||
if(pulse_time > expected_gap_length * 1.25f) {
|
||||
push_wave(WaveType::LongGap);
|
||||
}
|
||||
else if(pulse_time > expected_pulse_length * 1.25f) {
|
||||
push_wave(WaveType::Gap);
|
||||
}
|
||||
else if(pulse_time >= expected_pulse_length * 0.75f && pulse_time <= expected_pulse_length * 1.25f) {
|
||||
push_wave(WaveType::Pulse);
|
||||
}
|
||||
else {
|
||||
push_wave(WaveType::Unrecognised);
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::mark_end() {
|
||||
// Push the last thing detected, and post an 'unrecognised' to ensure
|
||||
// the queue empties out.
|
||||
post_pulse();
|
||||
push_wave(WaveType::Unrecognised);
|
||||
}
|
||||
|
||||
void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
||||
// A long gap is a file gap.
|
||||
if(waves[0] == WaveType::LongGap) {
|
||||
push_symbol(SymbolType::FileGap, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if(waves.size() >= 9) {
|
||||
size_t wave_offset = 0;
|
||||
// If the very first thing is a gap, swallow it.
|
||||
if(waves[0] == WaveType::Gap) {
|
||||
wave_offset = 1;
|
||||
}
|
||||
|
||||
// Count the number of pulses at the start of this vector
|
||||
size_t number_of_pulses = 0;
|
||||
while(waves[number_of_pulses + wave_offset] == WaveType::Pulse && number_of_pulses < waves.size()) {
|
||||
number_of_pulses++;
|
||||
}
|
||||
|
||||
// If those pulses were followed by a gap then they might be
|
||||
// a recognised symbol.
|
||||
if(number_of_pulses > 17 || number_of_pulses < 7) {
|
||||
push_symbol(SymbolType::Unrecognised, 1);
|
||||
}
|
||||
else if(number_of_pulses + wave_offset < waves.size() &&
|
||||
(waves[number_of_pulses + wave_offset] == WaveType::LongGap || waves[number_of_pulses + wave_offset] == WaveType::Gap)) {
|
||||
// A 1 is 18 up/down waves, a 0 is 8. But the final down will be indistinguishable from
|
||||
// the gap that follows the bit due to the simplified "high is high, everything else is low"
|
||||
// logic applied to pulse detection. So those two things will merge. Meaning we're looking for
|
||||
// 17 and/or 7 pulses.
|
||||
int gaps_to_swallow = (int)wave_offset + ((waves[number_of_pulses + wave_offset] == WaveType::Gap) ? 1 : 0);
|
||||
switch(number_of_pulses) {
|
||||
case 17: push_symbol(SymbolType::One, 17 + gaps_to_swallow); break;
|
||||
case 7: push_symbol(SymbolType::Zero, 7 + gaps_to_swallow); break;
|
||||
default: push_symbol(SymbolType::Unrecognised, 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
int c = 8;
|
||||
int result = 0;
|
||||
while(c--) {
|
||||
if(is_at_end(tape)) return -1;
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
if(symbol == SymbolType::FileGap) {
|
||||
return_symbol(symbol);
|
||||
return -1;
|
||||
}
|
||||
if(symbol != SymbolType::One && symbol != SymbolType::Zero) {
|
||||
return -1;
|
||||
}
|
||||
result = (result << 1) | (symbol == SymbolType::One ? 1 : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> Parser::get_next_file_data(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
if(is_at_end(tape)) return nullptr;
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
if(symbol != SymbolType::FileGap) {
|
||||
return nullptr;
|
||||
}
|
||||
while(symbol == SymbolType::FileGap && !is_at_end(tape)) {
|
||||
symbol = get_next_symbol(tape);
|
||||
}
|
||||
if(is_at_end(tape)) return nullptr;
|
||||
return_symbol(symbol);
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> result(new std::vector<uint8_t>);
|
||||
int byte;
|
||||
while(!is_at_end(tape)) {
|
||||
byte = get_next_byte(tape);
|
||||
if(byte == -1) return result;
|
||||
result->push_back((uint8_t)byte);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> Parser::get_next_file(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::shared_ptr<std::vector<uint8_t>> file_data = get_next_file_data(tape);
|
||||
if(!file_data) return nullptr;
|
||||
return Storage::Data::ZX8081::FileFromData(*file_data);
|
||||
}
|
65
Storage/Tape/Parsers/ZX8081.hpp
Normal file
65
Storage/Tape/Parsers/ZX8081.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// ZX8081.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Tape_Parsers_ZX8081_hpp
|
||||
#define Storage_Tape_Parsers_ZX8081_hpp
|
||||
|
||||
#include "TapeParser.hpp"
|
||||
|
||||
#include "../../Data/ZX8081.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
namespace ZX8081 {
|
||||
|
||||
enum class WaveType {
|
||||
Pulse, Gap, LongGap, Unrecognised
|
||||
};
|
||||
|
||||
enum class SymbolType {
|
||||
One, Zero, FileGap, Unrecognised
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
/*!
|
||||
Reads and combines the next eight bits. Returns -1 if any errors are encountered.
|
||||
*/
|
||||
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
/*!
|
||||
Waits for a long gap, reads all the bytes between that and the next long gap, then
|
||||
attempts to parse those as a valid ZX80 or ZX81 file. If no file is found,
|
||||
returns nullptr.
|
||||
*/
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> get_next_file(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
private:
|
||||
bool pulse_was_high_;
|
||||
Time pulse_time_;
|
||||
void post_pulse();
|
||||
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void mark_end();
|
||||
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> get_next_file_data(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ZX8081_hpp */
|
@ -19,22 +19,19 @@ TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
|
||||
#pragma mark - Seeking
|
||||
|
||||
void Storage::Tape::Tape::seek(Time &seek_time)
|
||||
{
|
||||
void Storage::Tape::Tape::seek(Time &seek_time) {
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
while(next_time_ < seek_time) get_next_pulse();
|
||||
}
|
||||
|
||||
void Storage::Tape::Tape::reset()
|
||||
{
|
||||
void Storage::Tape::Tape::reset() {
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
Tape::Pulse Tape::get_next_pulse()
|
||||
{
|
||||
Tape::Pulse Tape::get_next_pulse() {
|
||||
Tape::Pulse pulse = virtual_get_next_pulse();
|
||||
current_time_ = next_time_;
|
||||
next_time_ += pulse.length;
|
||||
@ -43,30 +40,25 @@ Tape::Pulse Tape::get_next_pulse()
|
||||
|
||||
#pragma mark - Player
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape)
|
||||
{
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape()
|
||||
{
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
|
||||
return tape_;
|
||||
}
|
||||
|
||||
bool TapePlayer::has_tape()
|
||||
{
|
||||
bool TapePlayer::has_tape() {
|
||||
return (bool)tape_;
|
||||
}
|
||||
|
||||
void TapePlayer::get_next_pulse()
|
||||
{
|
||||
void TapePlayer::get_next_pulse() {
|
||||
// get the new pulse
|
||||
if(tape_)
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
else
|
||||
{
|
||||
else {
|
||||
current_pulse_.length.length = 1;
|
||||
current_pulse_.length.clock_rate = 1;
|
||||
current_pulse_.type = Tape::Pulse::Zero;
|
||||
@ -75,21 +67,17 @@ void TapePlayer::get_next_pulse()
|
||||
set_next_event_time_interval(current_pulse_.length);
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
if(has_tape())
|
||||
{
|
||||
void TapePlayer::run_for_cycles(int number_of_cycles) {
|
||||
if(has_tape()) {
|
||||
TimedEventLoop::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_input_pulse()
|
||||
{
|
||||
void TapePlayer::run_for_input_pulse() {
|
||||
jump_to_next_event();
|
||||
}
|
||||
|
||||
void TapePlayer::process_next_event()
|
||||
{
|
||||
void TapePlayer::process_next_event() {
|
||||
process_input_pulse(current_pulse_);
|
||||
get_next_pulse();
|
||||
}
|
||||
@ -100,38 +88,30 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate), motor_is_running_(false)
|
||||
{}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled)
|
||||
{
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_tape_output(bool set)
|
||||
{
|
||||
void BinaryTapePlayer::set_tape_output(bool set) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool BinaryTapePlayer::get_input()
|
||||
{
|
||||
bool BinaryTapePlayer::get_input() {
|
||||
return input_level_;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void BinaryTapePlayer::run_for_cycles(int number_of_cycles) {
|
||||
if(motor_is_running_) TapePlayer::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_delegate(Delegate *delegate)
|
||||
{
|
||||
void BinaryTapePlayer::set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
void BinaryTapePlayer::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
bool new_input_level = pulse.type == Tape::Pulse::Low;
|
||||
if(input_level_ != new_input_level)
|
||||
{
|
||||
if(input_level_ != new_input_level) {
|
||||
input_level_ = new_input_level;
|
||||
if(delegate_) delegate_->tape_did_change_input(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user