1
0
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:
Thomas Harte 2017-06-15 18:32:38 -04:00 committed by GitHub
commit 87496f9978
33 changed files with 1596 additions and 118 deletions

View File

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

View File

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

View 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 */

View File

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

View File

@ -68,7 +68,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

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

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}
}
/*!

View File

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

View File

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

View 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

View File

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

View File

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

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

View 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
View 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
View 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 */

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

View 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 */

View File

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

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

View 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 */

View File

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