mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Starts working in the 48kb and 128kb Spectrums.
This commit is contained in:
parent
cf481effa6
commit
0af405aa46
@ -19,6 +19,10 @@ namespace ZXSpectrum {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
SixteenK,
|
||||
FortyEightK,
|
||||
OneTwoEightK,
|
||||
Plus2,
|
||||
Plus2a,
|
||||
Plus3,
|
||||
);
|
||||
|
@ -18,7 +18,9 @@ namespace Sinclair {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
enum class VideoTiming {
|
||||
Plus3
|
||||
FortyEightK,
|
||||
OneTwoEightK,
|
||||
Plus3,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -81,8 +81,29 @@ template<Model model> class ConcreteMachine:
|
||||
|
||||
// With only the +2a and +3 currently supported, the +3 ROM is always
|
||||
// the one required.
|
||||
const auto roms =
|
||||
rom_fetcher({ {"ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, 0x96e3c17a} });
|
||||
std::vector<ROMMachine::ROM> rom_names;
|
||||
const std::string machine = "ZXSpectrum";
|
||||
switch(model) {
|
||||
case Model::SixteenK:
|
||||
case Model::FortyEightK:
|
||||
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
|
||||
break;
|
||||
|
||||
case Model::OneTwoEightK:
|
||||
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
|
||||
break;
|
||||
|
||||
case Model::Plus2:
|
||||
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
|
||||
break;
|
||||
|
||||
case Model::Plus2a:
|
||||
case Model::Plus3: {
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
|
||||
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
|
||||
} break;
|
||||
}
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
|
||||
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
|
||||
|
||||
@ -110,7 +131,7 @@ template<Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
static constexpr unsigned int clock_rate() {
|
||||
// constexpr unsigned int ClockRate = 3'500'000;
|
||||
constexpr unsigned int OriginalClockRate = 3'500'000;
|
||||
constexpr unsigned int Plus3ClockRate = 3'546'875; // See notes below; this is a guess.
|
||||
|
||||
// Notes on timing for the +2a and +3:
|
||||
@ -137,7 +158,7 @@ template<Model model> class ConcreteMachine:
|
||||
// the Spectrum is a PAL machine with a fixed colour phase relationship. For
|
||||
// this emulator's world, that's a first!
|
||||
|
||||
return Plus3ClockRate;
|
||||
return model < Model::OneTwoEightK ? OriginalClockRate : Plus3ClockRate;
|
||||
}
|
||||
|
||||
// MARK: - TimedMachine.
|
||||
@ -189,18 +210,22 @@ template<Model model> class ConcreteMachine:
|
||||
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
|
||||
// Apply contention if necessary.
|
||||
if(
|
||||
is_contended_[address >> 14] &&
|
||||
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
|
||||
cycle.operation <= PartialMachineCycle::WriteStart) {
|
||||
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
|
||||
// of MREQ, which is always half a cycle into a read or write.
|
||||
//
|
||||
// TODO: somehow provide that information in the PartialMachineCycle?
|
||||
if constexpr (model >= Model::Plus2a) {
|
||||
if(
|
||||
is_contended_[address >> 14] &&
|
||||
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
|
||||
cycle.operation <= PartialMachineCycle::WriteStart) {
|
||||
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
|
||||
// of MREQ, which is always half a cycle into a read or write.
|
||||
//
|
||||
// TODO: somehow provide that information in the PartialMachineCycle?
|
||||
|
||||
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
}
|
||||
} else {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
// For all other machine cycles, model the action as happening at the end of the machine cycle;
|
||||
@ -263,39 +288,45 @@ template<Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// Test for classic 128kb paging register (i.e. port 7ffd).
|
||||
if((address & 0xc002) == 0x4000) {
|
||||
port7ffd_ = *cycle.value;
|
||||
update_memory_map();
|
||||
if constexpr (model >= Model::OneTwoEightK) {
|
||||
if((address & 0xc002) == 0x4000) {
|
||||
port7ffd_ = *cycle.value;
|
||||
update_memory_map();
|
||||
|
||||
// Set the proper video base pointer.
|
||||
set_video_address();
|
||||
// Set the proper video base pointer.
|
||||
set_video_address();
|
||||
|
||||
// Potentially lock paging, _after_ the current
|
||||
// port values have taken effect.
|
||||
disable_paging_ |= *cycle.value & 0x20;
|
||||
}
|
||||
|
||||
// Test for +2a/+3 paging (i.e. port 1ffd).
|
||||
if((address & 0xf002) == 0x1000) {
|
||||
port1ffd_ = *cycle.value;
|
||||
update_memory_map();
|
||||
update_video_base();
|
||||
|
||||
if constexpr (model == Model::Plus3) {
|
||||
fdc_->set_motor_on(*cycle.value & 0x08);
|
||||
// Potentially lock paging, _after_ the current
|
||||
// port values have taken effect.
|
||||
disable_paging_ |= *cycle.value & 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
if((address & 0xc002) == 0xc000) {
|
||||
// Select AY register.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::select_register(ay_, *cycle.value);
|
||||
// Test for +2a/+3 paging (i.e. port 1ffd).
|
||||
if constexpr (model >= Model::Plus2a) {
|
||||
if((address & 0xf002) == 0x1000) {
|
||||
port1ffd_ = *cycle.value;
|
||||
update_memory_map();
|
||||
update_video_base();
|
||||
|
||||
if constexpr (model == Model::Plus3) {
|
||||
fdc_->set_motor_on(*cycle.value & 0x08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((address & 0xc002) == 0x8000) {
|
||||
// Write to AY register.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::write_data(ay_, *cycle.value);
|
||||
if constexpr (model >= Model::OneTwoEightK) {
|
||||
if((address & 0xc002) == 0xc000) {
|
||||
// Select AY register.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::select_register(ay_, *cycle.value);
|
||||
}
|
||||
|
||||
if((address & 0xc002) == 0x8000) {
|
||||
// Write to AY register.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::write_data(ay_, *cycle.value);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (model == Model::Plus3) {
|
||||
@ -574,7 +605,7 @@ template<Model model> class ConcreteMachine:
|
||||
is_contended_[bank] = (source >= 4 && source < 8);
|
||||
pages_[bank] = source;
|
||||
|
||||
uint8_t *read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
|
||||
uint8_t *const read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
|
||||
const auto offset = bank*16384;
|
||||
|
||||
read_pointers_[bank] = read - offset;
|
||||
@ -712,8 +743,12 @@ Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMa
|
||||
const auto zx_target = dynamic_cast<const Analyser::Static::ZXSpectrum::Target *>(target);
|
||||
|
||||
switch(zx_target->model) {
|
||||
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
|
||||
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
|
||||
case Model::SixteenK: return new ConcreteMachine<Model::SixteenK>(*zx_target, rom_fetcher);
|
||||
case Model::FortyEightK: return new ConcreteMachine<Model::FortyEightK>(*zx_target, rom_fetcher);
|
||||
case Model::OneTwoEightK: return new ConcreteMachine<Model::OneTwoEightK>(*zx_target, rom_fetcher);
|
||||
case Model::Plus2: return new ConcreteMachine<Model::Plus2>(*zx_target, rom_fetcher);
|
||||
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
|
||||
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -63,6 +63,10 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineSpectrumModel) {
|
||||
CSMachineSpectrumModelSixteenK,
|
||||
CSMachineSpectrumModelFortyEightK,
|
||||
CSMachineSpectrumModelOneTwoEightK,
|
||||
CSMachineSpectrumModelPlus2,
|
||||
CSMachineSpectrumModelPlus2a,
|
||||
CSMachineSpectrumModelPlus3,
|
||||
};
|
||||
|
@ -194,8 +194,12 @@
|
||||
using Target = Analyser::Static::ZXSpectrum::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
switch(model) {
|
||||
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
|
||||
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
|
||||
case CSMachineSpectrumModelSixteenK: target->model = Target::Model::SixteenK; break;
|
||||
case CSMachineSpectrumModelFortyEightK: target->model = Target::Model::FortyEightK; break;
|
||||
case CSMachineSpectrumModelOneTwoEightK: target->model = Target::Model::OneTwoEightK; break;
|
||||
case CSMachineSpectrumModelPlus2: target->model = Target::Model::Plus2; break;
|
||||
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
|
||||
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
|
||||
}
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -584,11 +584,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
|
||||
<view key="view" id="bmd-gL-gzT">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="186"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
|
||||
<rect key="frame" x="108" y="47" width="116" height="25"/>
|
||||
<rect key="frame" x="108" y="157" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@ -602,7 +602,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
|
||||
<rect key="frame" x="18" y="53" width="87" height="16"/>
|
||||
<rect key="frame" x="18" y="163" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -622,24 +622,28 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
|
||||
<view key="view" id="bMx-F6-JUb">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="186"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
|
||||
<rect key="frame" x="67" y="47" width="62" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="+2a" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
|
||||
<rect key="frame" x="67" y="157" width="76" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="8lt-dk-zPr">
|
||||
<items>
|
||||
<menuItem title="+2a" state="on" tag="21" id="Fo7-NL-Kv5"/>
|
||||
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
|
||||
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
|
||||
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
|
||||
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
|
||||
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
|
||||
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
|
||||
<rect key="frame" x="18" y="53" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="163" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -298,6 +298,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
case "spectrum":
|
||||
var model: CSMachineSpectrumModel = .plus2a
|
||||
switch spectrumModelTypeButton.selectedItem!.tag {
|
||||
case 16: model = .sixteenK
|
||||
case 48: model = .fortyEightK
|
||||
case 128: model = .oneTwoEightK
|
||||
case 2: model = .plus2
|
||||
case 21: model = .plus2a
|
||||
case 3: model = .plus3
|
||||
default: break
|
||||
|
BIN
ROMImages/ZXSpectrum/128.rom
Normal file
BIN
ROMImages/ZXSpectrum/128.rom
Normal file
Binary file not shown.
BIN
ROMImages/ZXSpectrum/48.rom
Normal file
BIN
ROMImages/ZXSpectrum/48.rom
Normal file
Binary file not shown.
BIN
ROMImages/ZXSpectrum/plus2.rom
Normal file
BIN
ROMImages/ZXSpectrum/plus2.rom
Normal file
Binary file not shown.
@ -9,3 +9,6 @@ material but retain that copyright"."
|
||||
With that in mind, Amstrad have kindly given their permission for the redistribution of their copyrighted material but retain that copyright. Material expected here, copyright Amstrad:
|
||||
|
||||
plus3.rom — the +2a/+3 ROM file, 64kb in size.
|
||||
plus2.rom – the +2 ROM file, 32kb in size.
|
||||
128.rom – the 128kb ROM file, 32kb in size.
|
||||
48.rom – the 16/48kb ROM file, 16kb in size.
|
Loading…
x
Reference in New Issue
Block a user