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

Merge pull request #930 from TomHarte/SZX

Adds SZX support.
This commit is contained in:
Thomas Harte 2021-04-26 20:57:06 -04:00 committed by GitHub
commit 9319f0525a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 291 additions and 9 deletions

View File

@ -59,6 +59,7 @@
// State Snapshots
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/SZX.hpp"
#include "../../Storage/State/Z80.hpp"
// Tapes
@ -226,6 +227,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
}
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
#undef TryInsert

View File

@ -198,12 +198,14 @@ struct Utility {
struct State: public Reflection::StructImpl<State> {
uint8_t registers[16]{};
uint8_t selected_register = 0;
// TODO: all audio-production thread state.
State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(selected_register);
}
}
@ -213,6 +215,7 @@ struct State: public Reflection::StructImpl<State> {
target.select_register(c);
target.set_register_value(registers[c]);
}
target.select_register(selected_register);
}
};

View File

@ -30,7 +30,6 @@ struct State: public Reflection::StructImpl<State> {
// Meaningful for 128kb machines only.
uint8_t last_7ffd = 0;
uint8_t last_fffd = 0;
GI::AY38910::State ay;
// Meaningful for the +2a and +3 only.
@ -42,7 +41,6 @@ struct State: public Reflection::StructImpl<State> {
DeclareField(video);
DeclareField(ram);
DeclareField(last_7ffd);
DeclareField(last_fffd);
DeclareField(last_1ffd);
DeclareField(ay);
}

View File

@ -262,6 +262,39 @@ template <Timing timing> class Video {
}
}
static constexpr HalfCycles frame_duration() {
const auto timings = get_timings();
return HalfCycles(timings.cycles_per_line * timings.lines_per_frame);
}
HalfCycles time_since_interrupt() {
const auto timings = get_timings();
if(time_into_frame_ >= timings.interrupt_time) {
return HalfCycles(time_into_frame_ - timings.interrupt_time);
} else {
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
}
}
void set_time_since_interrupt(const HalfCycles time) {
// Advance using run_for to ensure that all proper CRT interactions occurred.
const auto timings = get_timings();
const auto target = (time + timings.interrupt_time) % frame_duration();
const auto now = HalfCycles(time_into_frame_);
// Maybe this is easy?
if(target == now) return;
// Is the time within this frame?
if(time > now) {
run_for(target - time);
return;
}
// Then it's necessary to finish this frame and run into the next.
run_for(frame_duration() - now + time);
}
public:
Video() :
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
@ -427,7 +460,7 @@ template <Timing timing> class Video {
struct State: public Reflection::StructImpl<State> {
uint8_t border_colour = 0;
int time_into_frame = 0;
int half_cycles_since_interrupt = 0;
bool flash = 0;
int flash_counter = 0;
bool is_alternate_line = false;
@ -435,7 +468,7 @@ struct State: public Reflection::StructImpl<State> {
State() {
if(needs_declare()) {
DeclareField(border_colour);
DeclareField(time_into_frame);
DeclareField(half_cycles_since_interrupt);
DeclareField(flash);
DeclareField(flash_counter);
DeclareField(is_alternate_line);
@ -444,18 +477,18 @@ struct State: public Reflection::StructImpl<State> {
template <typename Video> State(const Video &source) : State() {
border_colour = source.border_byte_;
time_into_frame = source.time_into_frame_;
flash = source.flash_mask_;
flash_counter = source.flash_counter_;
is_alternate_line = source. is_alternate_line_;
half_cycles_since_interrupt = source.time_since_interrupt().template as<int>();
}
template <typename Video> void apply(Video &target) {
target.set_border_colour(border_colour);
target.time_into_frame_ = time_into_frame;
target.flash_mask_ = flash ? 0xff : 0x00;
target.flash_counter_ = flash_counter;
target.is_alternate_line_ = is_alternate_line;
target.set_time_since_interrupt(HalfCycles(half_cycles_since_interrupt));
}
};

View File

@ -143,8 +143,6 @@ template<Model model> class ConcreteMachine:
port1ffd_ = state->last_1ffd;
port7ffd_ = state->last_7ffd;
update_memory_map();
GI::AY38910::Utility::select_register(ay_, state->last_fffd);
}
}
}

View File

@ -173,6 +173,8 @@
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; };
4B2B946526377C0200E7097C /* SZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B946326377C0200E7097C /* SZX.cpp */; };
4B2B946626377C0200E7097C /* SZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B946326377C0200E7097C /* SZX.cpp */; };
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; };
@ -1142,6 +1144,8 @@
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = "<group>"; };
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B2B946326377C0200E7097C /* SZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SZX.cpp; sourceTree = "<group>"; };
4B2B946426377C0200E7097C /* SZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SZX.hpp; sourceTree = "<group>"; };
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = "<group>"; };
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
@ -3263,6 +3267,8 @@
4B8DD3832634D37E00B3C866 /* State */ = {
isa = PBXGroup;
children = (
4B2B946326377C0200E7097C /* SZX.cpp */,
4B2B946426377C0200E7097C /* SZX.hpp */,
4B8DD3842634D37E00B3C866 /* SNA.cpp */,
4B8DD3852634D37E00B3C866 /* SNA.hpp */,
4B8DD39526360DDF00B3C866 /* Z80.cpp */,
@ -5237,6 +5243,7 @@
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */,
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4B2B946626377C0200E7097C /* SZX.cpp in Sources */,
4BEDA43225B3C700000C2DBD /* Executor.cpp in Sources */,
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
@ -5531,6 +5538,7 @@
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4BB307BB235001C300457D33 /* 6850.cpp in Sources */,
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B2B946526377C0200E7097C /* SZX.cpp in Sources */,
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,

View File

@ -632,6 +632,26 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>szx</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SZX snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

196
Storage/State/SZX.cpp Normal file
View File

@ -0,0 +1,196 @@
//
// SZX.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "SZX.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
#define LOG_PREFIX "[SZX] "
#include "../../Outputs/Log.hpp"
#include <zlib.h>
using namespace Storage::State;
std::unique_ptr<Analyser::Static::Target> SZX::load(const std::string &file_name) {
FileHolder file(file_name);
// Construct a target with a Spectrum state.
using Target = Analyser::Static::ZXSpectrum::Target;
auto result = std::make_unique<Target>();
auto *const state = new Sinclair::ZXSpectrum::State();
result->state = std::unique_ptr<Reflection::Struct>(state);
// Check signature and major version number.
if(!file.check_signature("ZXST")) {
return nullptr;
}
const uint8_t major_version = file.get8();
[[maybe_unused]] const uint8_t minor_version = file.get8();
if(major_version > 1) {
return nullptr;
}
// Check for a supported machine type.
const uint8_t machine_type = file.get8();
switch(machine_type) {
default: return nullptr;
case 0: result->model = Target::Model::SixteenK; break;
case 1: result->model = Target::Model::FortyEightK; break;
case 2: result->model = Target::Model::OneTwoEightK; break;
case 3: result->model = Target::Model::Plus2; break;
case 4: result->model = Target::Model::Plus2a; break;
case 5: result->model = Target::Model::Plus3; break;
}
// Consequential upon selected machine...
switch(result->model) {
case Target::Model::SixteenK: state->ram.resize(16 * 1024); break;
case Target::Model::FortyEightK: state->ram.resize(48 * 1024); break;
default:
state->ram.resize(128 * 1024);
break;
}
const uint8_t file_flags = file.get8();
[[maybe_unused]] const bool uses_late_timings = file_flags & 1;
// Now parse all included blocks.
while(true) {
const uint32_t blockID = file.get32le();
const uint32_t size = file.get32le();
if(file.eof()) break;
const auto location = file.tell();
#define BLOCK(str) str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24)
switch(blockID) {
default:
LOG("Unhandled block " << char(blockID) << char(blockID >> 8) << char(blockID >> 16) << char(blockID >> 24));
break;
// ZXSTZ80REGS
case BLOCK("Z80R"): {
state->z80.registers.flags = file.get8();
state->z80.registers.a = file.get8();
state->z80.registers.bc = file.get16le();
state->z80.registers.de = file.get16le();
state->z80.registers.hl = file.get16le();
state->z80.registers.af_dash = file.get16le();
state->z80.registers.bc_dash = file.get16le();
state->z80.registers.de_dash = file.get16le();
state->z80.registers.hl_dash = file.get16le();
state->z80.registers.ix = file.get16le();
state->z80.registers.iy = file.get16le();
state->z80.registers.stack_pointer = file.get16le();
state->z80.registers.program_counter = file.get16le();
const uint8_t i = file.get8();
const uint8_t r = file.get8();
state->z80.registers.ir = uint16_t((i << 8) | r);
state->z80.registers.iff1 = file.get8();
state->z80.registers.iff2 = file.get8();
state->z80.registers.interrupt_mode = file.get8();
state->video.half_cycles_since_interrupt = int(file.get32le()) * 2;
// SZX includes a count of remaining cycles that interrupt should be asserted for
// because it supports hardware that might cause an interrupt other than the display.
// This emulator doesn't, so this field can be ignored.
[[maybe_unused]] uint8_t remaining_interrupt_cycles = file.get8();
const uint8_t flags = file.get8();
state->z80.execution_state.is_halted = flags & 2;
// TODO: bit 0 indicates that the last instruction was an EI, or an invalid
// DD or FD. I assume I'm supposed to use that to conclude an interrupt
// verdict but I'm unclear what the effect of an invalid DD or FD is so
// have not yet implemented this.
state->z80.registers.memptr = file.get16le();
} break;
// ZXSTAYBLOCK
case BLOCK("AY\0\0"): {
// This applies to 48kb machines with AY boxes only. This emulator
// doesn't currently support those.
[[maybe_unused]] const uint8_t interface_type = file.get8();
state->ay.selected_register = file.get8();
file.read(state->ay.registers, 16);
} break;
// ZXSTRAMPAGE
case BLOCK("RAMP"): {
const uint16_t flags = file.get16le();
const uint8_t page = file.get8();
std::vector<uint8_t> contents;
if(flags & 1) {
// ZLib compression is applied.
contents.resize(16 * 1024);
const std::vector<uint8_t> source = file.read(size - 3);
uLongf output_length;
uncompress(contents.data(), &output_length, source.data(), source.size());
assert(output_length == contents.size());
} else {
// Data is raw.
contents = file.read(16 * 1024);
}
switch(result->model) {
case Target::Model::SixteenK:
case Target::Model::FortyEightK: {
size_t address = 0;
switch(page) {
default: break;
case 5: address = 0x4000; break;
case 2: address = 0x8000; break;
case 0: address = 0xc000; break;
}
if(address > 0 && (address - 0x4000) <= state->ram.size()) {
memcpy(&state->ram[address - 0x4000], contents.data(), 0x4000);
}
} break;
default:
if(page < 8) {
memcpy(&state->ram[page * 0x4000], contents.data(), 0x4000);
}
break;
}
} break;
// ZXSTSPECREGS
case BLOCK("SPCR"): {
state->video.border_colour = file.get8();
state->last_7ffd = file.get8();
state->last_1ffd = file.get8();
// TODO: use last write to FE, at least.
} break;
}
#undef BLOCK
// Advance to the next block.
file.seek(location + size, SEEK_SET);
}
return result;
}

24
Storage/State/SZX.hpp Normal file
View File

@ -0,0 +1,24 @@
//
// SZX.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_SZX_hpp
#define Storage_State_SZX_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct SZX {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_SZX_hpp */

View File

@ -141,7 +141,7 @@ std::unique_ptr<Analyser::Static::Target> Z80::load(const std::string &file_name
}
}
state->last_fffd = file.get8();
state->ay.selected_register = file.get8();
file.read(state->ay.registers, 16);
if(bonus_header_size != 23) {