mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
36 Commits
2021-07-03
...
2021-07-09
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5810a1a98e | ||
|
|
a4c011e3c0 | ||
|
|
337fd15dc0 | ||
|
|
9bc94f4536 | ||
|
|
3f4cf35384 | ||
|
|
4dd7f2cc09 | ||
|
|
1b29cc34c4 | ||
|
|
53c3c1f5ab | ||
|
|
6225abd751 | ||
|
|
c6fcd9a1eb | ||
|
|
30fbb6ea53 | ||
|
|
0e49258546 | ||
|
|
264b8dfb28 | ||
|
|
6a15b8f695 | ||
|
|
5167d256cc | ||
|
|
16bd826491 | ||
|
|
55af8fa5d9 | ||
|
|
1ec8ff20af | ||
|
|
99a65d3297 | ||
|
|
94907b51aa | ||
|
|
0085265d13 | ||
|
|
8e0893bd42 | ||
|
|
704dc9bdcb | ||
|
|
7a673a2448 | ||
|
|
33e2a4b21c | ||
|
|
3e6b804896 | ||
|
|
e98165a657 | ||
|
|
2a7727d12b | ||
|
|
c20e8f4062 | ||
|
|
4ca9db7d49 | ||
|
|
4add48cffb | ||
|
|
adbfb009f8 | ||
|
|
43ceca8711 | ||
|
|
3ef28a4f03 | ||
|
|
adcd580d5b | ||
|
|
5715c9183f |
@@ -67,7 +67,7 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"";
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
|
||||
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
|
||||
ReflectableEnum(DOS, EXDOS, None);
|
||||
ReflectableEnum(Speed, FourMHz, SixMHz);
|
||||
|
||||
Model model = Model::Enterprise128;
|
||||
EXOSVersion exos_version = EXOSVersion::Any;
|
||||
BASICVersion basic_version = BASICVersion::None;
|
||||
DOS dos = DOS::None;
|
||||
Speed speed = Speed::FourMHz;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
@@ -37,11 +39,13 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Audio::write(uint16_t address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
address &= 0x1f;
|
||||
audio_queue_.defer([address, value, this] {
|
||||
switch(address) {
|
||||
case 0: case 2: case 4:
|
||||
@@ -54,6 +54,10 @@ void Audio::write(uint16_t address, uint8_t value) {
|
||||
break;
|
||||
case 11: noise_.amplitude[0] = value & 0x3f; break;
|
||||
case 15: noise_.amplitude[1] = value & 0x3f; break;
|
||||
|
||||
case 31:
|
||||
global_divider_reload_ = 2 + ((value >> 1)&1);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -65,37 +69,74 @@ void Audio::set_sample_volume_range(int16_t range) {
|
||||
}
|
||||
|
||||
void Audio::update_channel(int c) {
|
||||
if(channels_[c].sync) {
|
||||
channels_[c].count = channels_[c].reload;
|
||||
channels_[c].output <<= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto output = channels_[c].output & 1;
|
||||
channels_[c].output <<= 1;
|
||||
if(!channels_[c].count) {
|
||||
if(channels_[c].sync) {
|
||||
channels_[c].count = channels_[c].reload;
|
||||
output = 0;
|
||||
} else {
|
||||
if(!channels_[c].count) {
|
||||
channels_[c].count = channels_[c].reload;
|
||||
|
||||
if(channels_[c].distortion == Channel::Distortion::None)
|
||||
output ^= 1;
|
||||
else
|
||||
output = poly_state_[int(channels_[c].distortion)];
|
||||
if(channels_[c].distortion == Channel::Distortion::None)
|
||||
output ^= 1;
|
||||
else
|
||||
output = poly_state_[int(channels_[c].distortion)];
|
||||
} else {
|
||||
--channels_[c].count;
|
||||
}
|
||||
|
||||
if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) {
|
||||
output = 0;
|
||||
}
|
||||
if(channels_[c].ring_modulate) {
|
||||
output = ~(output ^ channels_[(c+2)%3].output) & 1;
|
||||
}
|
||||
} else {
|
||||
--channels_[c].count;
|
||||
}
|
||||
|
||||
// Ring modulation applies even when sync is enabled, per SIDBasic.
|
||||
if(channels_[c].ring_modulate) {
|
||||
output = ~(output ^ channels_[(c+2)%3].output) & 1;
|
||||
}
|
||||
|
||||
channels_[c].output |= output;
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(size_t c = 0; c < number_of_samples; c++) {
|
||||
int16_t output_level[2];
|
||||
size_t c = 0;
|
||||
while(c < number_of_samples) {
|
||||
// I'm unclear on the details of the time division multiplexing so,
|
||||
// for now, just sum the outputs.
|
||||
output_level[0] =
|
||||
volume_ *
|
||||
(use_direct_output_[0] ?
|
||||
channels_[0].amplitude[0]
|
||||
: (
|
||||
channels_[0].amplitude[0] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[0] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[0] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[0] * noise_.final_output
|
||||
));
|
||||
|
||||
output_level[1] =
|
||||
volume_ *
|
||||
(use_direct_output_[1] ?
|
||||
channels_[0].amplitude[1]
|
||||
: (
|
||||
channels_[0].amplitude[1] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[1] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[1] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[1] * noise_.final_output
|
||||
));
|
||||
|
||||
while(global_divider_ && c < number_of_samples) {
|
||||
--global_divider_;
|
||||
*reinterpret_cast<uint32_t *>(&target[c << 1]) = *reinterpret_cast<uint32_t *>(output_level);
|
||||
++c;
|
||||
}
|
||||
|
||||
global_divider_ = global_divider_reload_;
|
||||
if(!global_divider_) {
|
||||
global_divider_ = global_divider_reload_;
|
||||
}
|
||||
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
|
||||
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
|
||||
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
|
||||
@@ -162,30 +203,6 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
} else {
|
||||
noise_.final_output = noise_.output & 1;
|
||||
}
|
||||
|
||||
// I'm unclear on the details of the time division multiplexing so,
|
||||
// for now, just sum the outputs.
|
||||
target[(c << 1) + 0] =
|
||||
volume_ *
|
||||
(use_direct_output_[0] ?
|
||||
channels_[0].amplitude[0]
|
||||
: (
|
||||
channels_[0].amplitude[0] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[0] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[0] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[0] * noise_.final_output
|
||||
));
|
||||
|
||||
target[(c << 1) + 1] =
|
||||
volume_ *
|
||||
(use_direct_output_[1] ?
|
||||
channels_[0].amplitude[1]
|
||||
: (
|
||||
channels_[0].amplitude[1] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[1] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[1] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[1] * noise_.final_output
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +215,7 @@ uint8_t TimedInterruptSource::get_new_interrupts() {
|
||||
}
|
||||
|
||||
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
|
||||
address &= 15;
|
||||
address &= 0x1f;
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
@@ -209,21 +226,15 @@ void TimedInterruptSource::write(uint16_t address, uint8_t value) {
|
||||
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
|
||||
break;
|
||||
|
||||
case 7: {
|
||||
case 7:
|
||||
channels_[0].sync = value & 0x01;
|
||||
channels_[1].sync = value & 0x02;
|
||||
rate_ = InterruptRate((value >> 5) & 3);
|
||||
break;
|
||||
|
||||
const InterruptRate rate = InterruptRate((value >> 5) & 3);
|
||||
if(rate != rate_) {
|
||||
rate_ = rate;
|
||||
|
||||
if(rate_ >= InterruptRate::ToneGenerator0) {
|
||||
programmable_level_ = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)].level;
|
||||
} else {
|
||||
programmable_offset_ = programmble_reload(rate_);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 31:
|
||||
global_divider_ = Cycles(2 + ((value >> 1)&1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +260,7 @@ void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement)
|
||||
// from high to low is amongst the included flips.
|
||||
if(is_linked && num_flips + channels_[c].level >= 2) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
programmable_level_ ^= true;
|
||||
}
|
||||
channels_[c].level ^= (num_flips & 1);
|
||||
|
||||
@@ -259,68 +271,67 @@ void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement)
|
||||
}
|
||||
}
|
||||
|
||||
void TimedInterruptSource::run_for(Cycles cycles) {
|
||||
// Update the 1Hz interrupt.
|
||||
one_hz_offset_ -= cycles;
|
||||
if(one_hz_offset_ <= Cycles(0)) {
|
||||
void TimedInterruptSource::run_for(Cycles duration) {
|
||||
// Determine total number of ticks.
|
||||
run_length_ += duration;
|
||||
const Cycles cycles = run_length_.divide(global_divider_);
|
||||
if(cycles == Cycles(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the two-second counter, from which the 1Hz, 50Hz and 1000Hz signals
|
||||
// are derived.
|
||||
const int previous_counter = two_second_counter_;
|
||||
two_second_counter_ = (two_second_counter_ + cycles.as<int>()) % 500'000;
|
||||
|
||||
// Check for a 1Hz rollover.
|
||||
if(previous_counter / 250'000 != two_second_counter_ / 250'000) {
|
||||
interrupts_ |= uint8_t(Interrupt::OneHz);
|
||||
one_hz_offset_ += clock_rate;
|
||||
}
|
||||
|
||||
// Check for 1kHz or 50Hz rollover;
|
||||
switch(rate_) {
|
||||
default: break;
|
||||
case InterruptRate::OnekHz:
|
||||
if(previous_counter / 250 != two_second_counter_ / 250) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
programmable_level_ ^= true;
|
||||
}
|
||||
break;
|
||||
case InterruptRate::FiftyHz:
|
||||
if(previous_counter / 5'000 != two_second_counter_ / 5'000) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
programmable_level_ ^= true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the two tone channels.
|
||||
update_channel(0, rate_ == InterruptRate::ToneGenerator0, cycles.as<int>());
|
||||
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
|
||||
|
||||
// Update the programmable-frequency interrupt.
|
||||
if(rate_ < InterruptRate::ToneGenerator0) {
|
||||
programmable_offset_ -= cycles.as<int>();
|
||||
if(programmable_offset_ <= 0) {
|
||||
if(programmable_level_) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
}
|
||||
programmable_level_ ^= true;
|
||||
programmable_offset_ = programmble_reload(rate_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cycles TimedInterruptSource::get_next_sequence_point() const {
|
||||
int result = one_hz_offset_.as<int>();
|
||||
|
||||
// Since both the 1kHz and 50Hz timers are integer dividers of the 1Hz timer, there's no need
|
||||
// to factor that one in when determining the next sequence point for either of those.
|
||||
switch(rate_) {
|
||||
case InterruptRate::OnekHz:
|
||||
case InterruptRate::FiftyHz:
|
||||
result = std::min(result, programmable_offset_ + (!programmable_level_) * programmble_reload(rate_));
|
||||
break;
|
||||
default:
|
||||
case InterruptRate::OnekHz: return Cycles(250 - (two_second_counter_ % 250));
|
||||
case InterruptRate::FiftyHz: return Cycles(5000 - (two_second_counter_ % 5000));
|
||||
|
||||
case InterruptRate::ToneGenerator0:
|
||||
case InterruptRate::ToneGenerator1: {
|
||||
const auto &channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)];
|
||||
const int cycles_until_interrupt = channel.value + 1 + (!channel.level) * (channel.reload + 1);
|
||||
result = std::min(result, cycles_until_interrupt);
|
||||
} break;
|
||||
}
|
||||
|
||||
return Cycles(result);
|
||||
return Cycles(std::min(
|
||||
250'000 - (two_second_counter_ % 250'000),
|
||||
cycles_until_interrupt
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TimedInterruptSource::get_divider_state() {
|
||||
bool programmable_flag = false;
|
||||
switch(rate_) {
|
||||
case InterruptRate::OnekHz:
|
||||
case InterruptRate::FiftyHz:
|
||||
programmable_flag = programmable_level_;
|
||||
break;
|
||||
case InterruptRate::ToneGenerator0:
|
||||
programmable_flag = channels_[0].level;
|
||||
break;
|
||||
case InterruptRate::ToneGenerator1:
|
||||
programmable_flag = channels_[1].level;
|
||||
break;
|
||||
}
|
||||
|
||||
// one_hz_offset_ counts downwards, so when it crosses the halfway mark
|
||||
// it enters the high part of its wave.
|
||||
return
|
||||
(one_hz_offset_ < half_clock_rate ? 0x4 : 0x0) |
|
||||
(programmable_flag ? 0x1 : 0x0);
|
||||
return uint8_t((two_second_counter_ / 250'000) * 4 | programmable_level_);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ class Audio: public Outputs::Speaker::SampleSource {
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
// Global divider (i.e. 8MHz/12Mhz switch).
|
||||
uint8_t global_divider_;
|
||||
uint8_t global_divider_reload_ = 2;
|
||||
|
||||
// Tone channels.
|
||||
struct Channel {
|
||||
// User-set values.
|
||||
@@ -147,12 +151,16 @@ class TimedInterruptSource {
|
||||
static constexpr Cycles clock_rate{250000};
|
||||
static constexpr Cycles half_clock_rate{125000};
|
||||
|
||||
// Global divider (i.e. 8MHz/12Mhz switch).
|
||||
Cycles global_divider_ = Cycles(2);
|
||||
Cycles run_length_;
|
||||
|
||||
// Interrupts that have fired since get_new_interrupts()
|
||||
// was last called.
|
||||
uint8_t interrupts_ = 0;
|
||||
|
||||
// A counter for the 1Hz interrupt.
|
||||
Cycles one_hz_offset_ = clock_rate;
|
||||
int two_second_counter_ = 0;
|
||||
|
||||
// A counter specific to the 1kHz and 50Hz timers, if in use.
|
||||
enum class InterruptRate {
|
||||
@@ -161,7 +169,6 @@ class TimedInterruptSource {
|
||||
ToneGenerator0,
|
||||
ToneGenerator1,
|
||||
} rate_ = InterruptRate::OnekHz;
|
||||
int programmable_offset_ = programmble_reload(InterruptRate::OnekHz);
|
||||
bool programmable_level_ = false;
|
||||
|
||||
// A local duplicate of the counting state of the first two audio
|
||||
@@ -173,13 +180,6 @@ class TimedInterruptSource {
|
||||
bool level = false;
|
||||
} channels_[2];
|
||||
void update_channel(int c, bool is_linked, int decrement);
|
||||
static constexpr int programmble_reload(InterruptRate rate) {
|
||||
switch(rate) {
|
||||
default: return 0;
|
||||
case InterruptRate::OnekHz: return 125;
|
||||
case InterruptRate::FiftyHz: return 2500;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Enterprise {
|
||||
|
||||
*/
|
||||
|
||||
template <bool has_disk_controller> class ConcreteMachine:
|
||||
template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public Configurable::Device,
|
||||
public CPU::Z80::BusHandler,
|
||||
@@ -88,15 +88,23 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
return uint8_t(0x100 - ram_size / 0x4000);
|
||||
}
|
||||
|
||||
static constexpr double clock_rate = is_6mhz ? 6'000'000.0 : 4'000'000.0;
|
||||
using NickType =
|
||||
std::conditional_t<is_6mhz,
|
||||
JustInTimeActor<Nick, HalfCycles, 13478201, 5680000>,
|
||||
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000>>;
|
||||
|
||||
public:
|
||||
ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
ConcreteMachine(const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
min_ram_slot_(min_ram_slot(target)),
|
||||
z80_(*this),
|
||||
nick_(ram_.end() - 65536),
|
||||
dave_audio_(audio_queue_),
|
||||
speaker_(dave_audio_) {
|
||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
|
||||
set_clock_rate(4'000'000);
|
||||
|
||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
|
||||
set_clock_rate(clock_rate);
|
||||
speaker_.set_input_rate(float(clock_rate) / float(dave_divider));
|
||||
|
||||
ROM::Request request;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
@@ -216,9 +224,6 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
page<2>(0x00);
|
||||
page<3>(0x00);
|
||||
|
||||
// Set up audio.
|
||||
speaker_.set_input_rate(250000.0f); // TODO: a bigger number, and respect the programmable divider.
|
||||
|
||||
// Pass on any media.
|
||||
insert_media(target.media);
|
||||
if(!target.loading_command.empty()) {
|
||||
@@ -270,26 +275,26 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
default: break;
|
||||
|
||||
// For non-video pauses, insert during the initial part of the bus cycle.
|
||||
case CPU::Z80::PartialMachineCycle::ReadStart:
|
||||
case CPU::Z80::PartialMachineCycle::WriteStart:
|
||||
case PartialMachineCycle::ReadStart:
|
||||
case PartialMachineCycle::WriteStart:
|
||||
if(!is_video_[address >> 14] && wait_mode_ == WaitMode::OnAllAccesses) {
|
||||
penalty = HalfCycles(2);
|
||||
penalty = dave_delay_;
|
||||
}
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcodeStart:
|
||||
if(!is_video_[address >> 14] && wait_mode_ != WaitMode::None) {
|
||||
penalty = HalfCycles(2);
|
||||
} else {
|
||||
case PartialMachineCycle::ReadOpcodeStart: {
|
||||
if(is_video_[address >> 14]) {
|
||||
// Query Nick for the amount of delay that would occur with one cycle left
|
||||
// in this read opcode.
|
||||
const auto delay_time = nick_.time_since_flush(HalfCycles(2));
|
||||
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
|
||||
penalty = nick_.back_map(delay, delay_time);
|
||||
} else if(wait_mode_ != WaitMode::None) {
|
||||
penalty = dave_delay_;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
// Video pauses: insert right at the end of the bus cycle.
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
case PartialMachineCycle::Write:
|
||||
// Ensure all video that should have been collected prior to
|
||||
// this write has been.
|
||||
if(is_video_[address >> 14]) {
|
||||
@@ -297,7 +302,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
case PartialMachineCycle::Read:
|
||||
if(is_video_[address >> 14]) {
|
||||
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
|
||||
// cycles from now (i.e. with one cycle left to run).
|
||||
@@ -307,8 +312,8 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
case PartialMachineCycle::Input:
|
||||
case PartialMachineCycle::Output: {
|
||||
if((address & 0xf0) == 0x80) {
|
||||
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
|
||||
// cycles from now (i.e. with one cycle left to run).
|
||||
@@ -334,7 +339,11 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
switch(cycle.operation) {
|
||||
default: break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
case PartialMachineCycle::Interrupt:
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Input:
|
||||
switch(address & 0xff) {
|
||||
default:
|
||||
LOG("Unhandled input from " << PADHEX(2) << (address & 0xff));
|
||||
@@ -372,7 +381,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
|
||||
case 0xb4:
|
||||
*cycle.value =
|
||||
(nick_->get_interrupt_line() ? 0x00 : 0x10) |
|
||||
(nick_->get_interrupt_line() ? 0x10 : 0x00) |
|
||||
dave_timer_->get_divider_state() |
|
||||
interrupt_state_;
|
||||
break;
|
||||
@@ -399,7 +408,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
case PartialMachineCycle::Output:
|
||||
switch(address & 0xff) {
|
||||
default:
|
||||
LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff));
|
||||
@@ -430,6 +439,19 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
case 0xb2: page<2>(*cycle.value); break;
|
||||
case 0xb3: page<3>(*cycle.value); break;
|
||||
|
||||
case 0xbf:
|
||||
switch((*cycle.value >> 2)&3) {
|
||||
default: wait_mode_ = WaitMode::None; break;
|
||||
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
|
||||
case 1: wait_mode_ = WaitMode::OnM1; break;
|
||||
}
|
||||
|
||||
// Dave delays (i.e. those affecting memory areas not associated with Nick)
|
||||
// are one cycle in 8Mhz mode, two cycles in 12Mhz mode.
|
||||
dave_delay_ = HalfCycles(2 + (*cycle.value)&2);
|
||||
|
||||
[[fallthrough]];
|
||||
|
||||
case 0xa0: case 0xa1: case 0xa2: case 0xa3:
|
||||
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
|
||||
case 0xa8: case 0xa9: case 0xaa: case 0xab:
|
||||
@@ -495,19 +517,11 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
// b1 = serial status out
|
||||
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
|
||||
break;
|
||||
case 0xbf:
|
||||
// TODO: onboard RAM, Dave 8/12Mhz select.
|
||||
switch((*cycle.value >> 2)&3) {
|
||||
default: wait_mode_ = WaitMode::None; break;
|
||||
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
|
||||
case 1: wait_mode_ = WaitMode::OnM1; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
case PartialMachineCycle::Read:
|
||||
case PartialMachineCycle::ReadOpcode:
|
||||
if(read_pointers_[address >> 14]) {
|
||||
*cycle.value = read_pointers_[address >> 14][address];
|
||||
} else {
|
||||
@@ -515,7 +529,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
case PartialMachineCycle::Write:
|
||||
if(write_pointers_[address >> 14]) {
|
||||
write_pointers_[address >> 14][address] = *cycle.value;
|
||||
}
|
||||
@@ -588,7 +602,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
None,
|
||||
OnM1,
|
||||
OnAllAccesses
|
||||
} wait_mode_ = WaitMode::None;
|
||||
} wait_mode_ = WaitMode::OnAllAccesses;
|
||||
bool is_video_[4]{};
|
||||
|
||||
// MARK: - ScanProducer
|
||||
@@ -683,7 +697,7 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
|
||||
// MARK: - Chips.
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000> nick_;
|
||||
NickType nick_;
|
||||
bool previous_nick_interrupt_line_ = false;
|
||||
// Cf. timing guesses above.
|
||||
|
||||
@@ -692,12 +706,14 @@ template <bool has_disk_controller> class ConcreteMachine:
|
||||
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
|
||||
HalfCycles time_since_audio_update_;
|
||||
|
||||
// The following two should both use the same divider.
|
||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
|
||||
HalfCycles dave_delay_ = HalfCycles(2);
|
||||
|
||||
// The divider supplied to the JustInTimeActor and the manual divider used in
|
||||
// update_audio() should match.
|
||||
static constexpr int dave_divider = 8;
|
||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
|
||||
inline void update_audio() {
|
||||
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
|
||||
// as per its own register.
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
|
||||
}
|
||||
|
||||
// MARK: - EXDos card.
|
||||
@@ -731,10 +747,19 @@ Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMa
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
const Target *const enterprise_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
if(enterprise_target->dos == Target::DOS::None)
|
||||
return new Enterprise::ConcreteMachine<false>(*enterprise_target, rom_fetcher);
|
||||
else
|
||||
return new Enterprise::ConcreteMachine<true>(*enterprise_target, rom_fetcher);
|
||||
#define BuildMachine(exdos, sixmhz) \
|
||||
if((enterprise_target->dos == Target::DOS::None) != exdos && (enterprise_target->speed == Target::Speed::SixMHz) == sixmhz) { \
|
||||
return new Enterprise::ConcreteMachine<exdos, sixmhz>(*enterprise_target, rom_fetcher); \
|
||||
}
|
||||
|
||||
BuildMachine(false, false);
|
||||
BuildMachine(false, true);
|
||||
BuildMachine(true, false);
|
||||
BuildMachine(true, true);
|
||||
|
||||
#undef BuildMachine
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -94,9 +94,6 @@ class Nick {
|
||||
bool vres_ = false;
|
||||
bool reload_line_parameter_pointer_ = false;
|
||||
|
||||
// An accumulator for border output regions.
|
||||
int border_duration_ = 0;
|
||||
|
||||
// The destination for new pixels.
|
||||
static constexpr int allocation_size = 336;
|
||||
static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16");
|
||||
|
||||
@@ -47,6 +47,11 @@ typedef NS_ENUM(NSInteger, CSMachineEnterpriseModel) {
|
||||
CSMachineEnterpriseModel256,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseSpeed) {
|
||||
CSMachineEnterpriseSpeed4MHz,
|
||||
CSMachineEnterpriseSpeed6MHz
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseEXOS) {
|
||||
CSMachineEnterpriseEXOSVersion21,
|
||||
CSMachineEnterpriseEXOSVersion20,
|
||||
@@ -120,7 +125,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
@@ -144,6 +144,12 @@
|
||||
case CSMachineEnterpriseModel256: target->model = Target::Model::Enterprise256; break;
|
||||
}
|
||||
|
||||
switch(speed) {
|
||||
case CSMachineEnterpriseSpeed6MHz: target->speed = Target::Speed::SixMHz; break;
|
||||
default:
|
||||
case CSMachineEnterpriseSpeed4MHz: target->speed = Target::Speed::FourMHz; break;
|
||||
}
|
||||
|
||||
switch(exosVersion) {
|
||||
case CSMachineEnterpriseEXOSVersion21: target->exos_version = Target::EXOSVersion::v21; break;
|
||||
default:
|
||||
|
||||
@@ -322,8 +322,21 @@ Gw
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lfl-5c-b8j">
|
||||
<rect key="frame" x="67" y="149" width="77" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="4 Mhz" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="axesIndependently" inset="2" selectedItem="5N6-tD-uN7" id="BU2-NJ-Kii">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="ANK-p0-M77">
|
||||
<items>
|
||||
<menuItem title="4 Mhz" state="on" tag="4" id="5N6-tD-uN7"/>
|
||||
<menuItem title="6 Mhz" tag="6" id="bVT-qO-U7n"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
|
||||
<rect key="frame" x="64" y="149" width="107" height="25"/>
|
||||
<rect key="frame" x="64" y="119" width="107" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -337,7 +350,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
|
||||
<rect key="frame" x="67" y="119" width="105" height="25"/>
|
||||
<rect key="frame" x="67" y="89" width="105" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -352,20 +365,20 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
|
||||
<rect key="frame" x="57" y="89" width="83" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
|
||||
<rect key="frame" x="57" y="59" width="83" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" title="DOS" id="sdr-al-7mi">
|
||||
<items>
|
||||
<menuItem title="EXDOS" tag="1" id="8rP-2w-PdU"/>
|
||||
<menuItem title="None" state="on" id="qoS-KO-iEZ"/>
|
||||
<menuItem title="EXDOS" state="on" tag="1" id="8rP-2w-PdU"/>
|
||||
<menuItem title="None" id="qoS-KO-iEZ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
|
||||
<rect key="frame" x="18" y="155" width="43" height="16"/>
|
||||
<rect key="frame" x="18" y="125" width="43" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -381,7 +394,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
|
||||
<rect key="frame" x="18" y="125" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="95" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -389,13 +402,21 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
|
||||
<rect key="frame" x="18" y="95" width="36" height="16"/>
|
||||
<rect key="frame" x="18" y="65" width="36" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
|
||||
<rect key="frame" x="17" y="155" width="47" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="sAw-C9-Sf7">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
|
||||
@@ -403,11 +424,13 @@ Gw
|
||||
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
|
||||
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
|
||||
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="Lfl-5c-b8j" secondAttribute="bottom" constant="10" symbolic="YES" id="Df8-qv-3dY"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="ENF-TY-TQ7"/>
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="leading" secondItem="ykc-W1-YaS" secondAttribute="trailing" constant="8" symbolic="YES" id="GWR-VI-9PG"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="20" symbolic="YES" id="K3s-FA-zMB"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="PhH-bu-pb5" secondAttribute="trailing" constant="20" symbolic="YES" id="LwB-ef-uF4"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="top" secondItem="1cs-PX-RAH" secondAttribute="top" constant="20" symbolic="YES" id="Myt-i4-jVq"/>
|
||||
<constraint firstItem="Lfl-5c-b8j" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Nl3-hL-jwg"/>
|
||||
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="T3e-u7-fiQ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="trailing" constant="20" symbolic="YES" id="TEX-Nw-y2K"/>
|
||||
@@ -415,11 +438,13 @@ Gw
|
||||
<constraint firstItem="pxr-Bq-yh0" firstAttribute="centerY" secondItem="syE-e7-TjU" secondAttribute="centerY" id="UYw-uz-Am0"/>
|
||||
<constraint firstItem="hIr-GH-7xi" firstAttribute="top" secondItem="nen-Za-7zH" secondAttribute="bottom" constant="10" symbolic="YES" id="VOc-2v-s3u"/>
|
||||
<constraint firstItem="syE-e7-TjU" firstAttribute="top" secondItem="hIr-GH-7xi" secondAttribute="bottom" constant="10" symbolic="YES" id="W9S-on-Heq"/>
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Yes-o3-V0m"/>
|
||||
<constraint firstItem="rHr-bh-QMV" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="19" id="XZf-lB-NnA"/>
|
||||
<constraint firstItem="ykc-W1-YaS" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="bAP-lx-pdi"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nen-Za-7zH" secondAttribute="trailing" constant="20" symbolic="YES" id="eEI-5Q-TnO"/>
|
||||
<constraint firstItem="syE-e7-TjU" firstAttribute="leading" secondItem="pxr-Bq-yh0" secondAttribute="trailing" constant="8" symbolic="YES" id="fmM-Ma-Jyu"/>
|
||||
<constraint firstItem="hIr-GH-7xi" firstAttribute="leading" secondItem="dzd-tH-BjX" secondAttribute="trailing" constant="8" symbolic="YES" id="jDQ-TF-Ogf"/>
|
||||
<constraint firstItem="rHr-bh-QMV" firstAttribute="centerY" secondItem="Lfl-5c-b8j" secondAttribute="centerY" id="mnp-JF-dVQ"/>
|
||||
<constraint firstItem="Lfl-5c-b8j" firstAttribute="leading" secondItem="rHr-bh-QMV" secondAttribute="trailing" constant="8" symbolic="YES" id="yfb-SL-v1H"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
@@ -874,6 +899,7 @@ Gw
|
||||
<outlet property="enterpriseDOSButton" destination="syE-e7-TjU" id="sCW-Bj-ZTW"/>
|
||||
<outlet property="enterpriseEXOSButton" destination="nen-Za-7zH" id="NwS-ua-FdA"/>
|
||||
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
|
||||
<outlet property="enterpriseSpeedButton" destination="Lfl-5c-b8j" id="ClN-yN-Nbx"/>
|
||||
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
|
||||
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
|
||||
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>
|
||||
|
||||
@@ -38,6 +38,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
// MARK: - Enterprise properties
|
||||
@IBOutlet var enterpriseModelButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseSpeedButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseEXOSButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
|
||||
@@ -110,6 +111,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
// Enterprise settings
|
||||
enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel"))
|
||||
enterpriseSpeedButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseSpeed"))
|
||||
enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion"))
|
||||
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
|
||||
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
|
||||
@@ -166,6 +168,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
// Enterprise settings
|
||||
standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel")
|
||||
standardUserDefaults.set(enterpriseSpeedButton.selectedTag(), forKey: "new.enterpriseSpeed")
|
||||
standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion")
|
||||
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
|
||||
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
|
||||
@@ -253,14 +256,14 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
default: model = .ROM00
|
||||
}
|
||||
|
||||
let memorySize = Kilobytes(appleIIgsMemorySizeButton.selectedItem!.tag)
|
||||
let memorySize = Kilobytes(appleIIgsMemorySizeButton.selectedTag())
|
||||
return CSStaticAnalyser(appleIIgsModel: model, memorySize: memorySize)
|
||||
|
||||
case "atarist":
|
||||
return CSStaticAnalyser(atariSTModel: .model512k)
|
||||
|
||||
case "cpc":
|
||||
switch cpcModelTypeButton.selectedItem!.tag {
|
||||
switch cpcModelTypeButton.selectedTag() {
|
||||
case 464: return CSStaticAnalyser(amstradCPCModel: .model464)
|
||||
case 664: return CSStaticAnalyser(amstradCPCModel: .model664)
|
||||
case 6128: fallthrough
|
||||
@@ -276,15 +279,22 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
case "enterprise":
|
||||
var model: CSMachineEnterpriseModel = .model128
|
||||
switch enterpriseModelButton.selectedItem!.tag {
|
||||
switch enterpriseModelButton.selectedTag() {
|
||||
case 64: model = .model64
|
||||
case 256: model = .model256
|
||||
case 128: fallthrough
|
||||
default: model = .model128
|
||||
}
|
||||
|
||||
var speed: CSMachineEnterpriseSpeed = .speed4MHz
|
||||
switch enterpriseSpeedButton.selectedTag() {
|
||||
case 6: speed = .speed6MHz
|
||||
case 4: fallthrough
|
||||
default: speed = .speed4MHz
|
||||
}
|
||||
|
||||
var exos: CSMachineEnterpriseEXOS = .version21
|
||||
switch enterpriseEXOSButton.selectedItem!.tag {
|
||||
switch enterpriseEXOSButton.selectedTag() {
|
||||
case 10: exos = .version10
|
||||
case 20: exos = .version20
|
||||
case 21: fallthrough
|
||||
@@ -307,10 +317,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
default: dos = .dosNone
|
||||
}
|
||||
|
||||
return CSStaticAnalyser(enterpriseModel: model, exosVersion: exos, basicVersion: basic, dos: dos)
|
||||
return CSStaticAnalyser(enterpriseModel: model, speed: speed, exosVersion: exos, basicVersion: basic, dos: dos)
|
||||
|
||||
case "mac":
|
||||
switch macintoshModelTypeButton.selectedItem!.tag {
|
||||
switch macintoshModelTypeButton.selectedTag() {
|
||||
case 0: return CSStaticAnalyser(macintoshModel: .model128k)
|
||||
case 1: return CSStaticAnalyser(macintoshModel: .model512k)
|
||||
case 2: return CSStaticAnalyser(macintoshModel: .model512ke)
|
||||
@@ -320,7 +330,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
case "msx":
|
||||
let hasDiskDrive = msxHasDiskDriveButton.state == .on
|
||||
switch msxRegionButton.selectedItem!.tag {
|
||||
switch msxRegionButton.selectedTag() {
|
||||
case 2:
|
||||
return CSStaticAnalyser(msxRegion: .japanese, hasDiskDrive: hasDiskDrive)
|
||||
case 1:
|
||||
@@ -341,7 +351,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
}
|
||||
var model: CSMachineOricModel = .oric1
|
||||
switch oricModelTypeButton.selectedItem!.tag {
|
||||
switch oricModelTypeButton.selectedTag() {
|
||||
case 1: model = .oricAtmos
|
||||
case 2: model = .pravetz
|
||||
default: break
|
||||
@@ -351,7 +361,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
case "spectrum":
|
||||
var model: CSMachineSpectrumModel = .plus2a
|
||||
switch spectrumModelTypeButton.selectedItem!.tag {
|
||||
switch spectrumModelTypeButton.selectedTag() {
|
||||
case 16: model = .sixteenK
|
||||
case 48: model = .fortyEightK
|
||||
case 128: model = .oneTwoEightK
|
||||
@@ -364,9 +374,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
return CSStaticAnalyser(spectrumModel: model)
|
||||
|
||||
case "vic20":
|
||||
let memorySize = Kilobytes(vic20MemorySizeButton.selectedItem!.tag)
|
||||
let memorySize = Kilobytes(vic20MemorySizeButton.selectedTag())
|
||||
let hasC1540 = vic20HasC1540Button.state == .on
|
||||
switch vic20RegionButton.selectedItem!.tag {
|
||||
switch vic20RegionButton.selectedTag() {
|
||||
case 1:
|
||||
return CSStaticAnalyser(vic20Region: .american, memorySize: memorySize, hasC1540: hasC1540)
|
||||
case 2:
|
||||
@@ -381,10 +391,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
}
|
||||
|
||||
case "zx80":
|
||||
return CSStaticAnalyser(zx80MemorySize: Kilobytes(zx80MemorySizeButton.selectedItem!.tag), useZX81ROM: zx80UsesZX81ROMButton.state == .on)
|
||||
return CSStaticAnalyser(zx80MemorySize: Kilobytes(zx80MemorySizeButton.selectedTag()), useZX81ROM: zx80UsesZX81ROMButton.state == .on)
|
||||
|
||||
case "zx81":
|
||||
return CSStaticAnalyser(zx81MemorySize: Kilobytes(zx81MemorySizeButton.selectedItem!.tag))
|
||||
return CSStaticAnalyser(zx81MemorySize: Kilobytes(zx81MemorySizeButton.selectedTag()))
|
||||
|
||||
default: return CSStaticAnalyser()
|
||||
}
|
||||
|
||||
@@ -23,78 +23,80 @@
|
||||
_interruptSource = std::make_unique<Enterprise::Dave::TimedInterruptSource>();
|
||||
}
|
||||
|
||||
- (void)performTestExpectedToggles:(int)expectedToggles expectedInterrupts:(int)expectedInterrupts {
|
||||
// Check that the programmable timer flag toggles at a rate
|
||||
// of 2kHz, causing 1000 interrupts, and that sequence points
|
||||
// are properly predicted.
|
||||
/// Tests that the programmable timer flag toggles and produces interrupts
|
||||
/// at the rate specified, and that the flag toggles when interrupts are signalled.
|
||||
- (void)performTestExpectedInterrupts:(double)expectedInterruptsPerSecond mode:(int)mode {
|
||||
// If a programmable timer mode is requested, synchronise both channels.
|
||||
if(mode >= 2) {
|
||||
_interruptSource->write(0xa7, 3);
|
||||
_interruptSource->run_for(Cycles(2));
|
||||
}
|
||||
|
||||
// Set mode (and disable sync, if it was applied).
|
||||
_interruptSource->write(0xa7, mode << 5);
|
||||
|
||||
int toggles = 0;
|
||||
int interrupts = 0;
|
||||
uint8_t dividerState = _interruptSource->get_divider_state();
|
||||
uint8_t dividerState = _interruptSource->get_divider_state() & 1;
|
||||
int nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
||||
for(int c = 0; c < 250000; c++) {
|
||||
// Advance one cycle. Clock is 250,000 Hz.
|
||||
_interruptSource->run_for(Cycles(1));
|
||||
|
||||
const uint8_t newDividerState = _interruptSource->get_divider_state();
|
||||
if((dividerState^newDividerState)&0x1) {
|
||||
++toggles;
|
||||
}
|
||||
dividerState = newDividerState;
|
||||
|
||||
for(int c = 0; c < 250000 * 5; c++) {
|
||||
// Advance one cycle. Clock is 500,000 Hz.
|
||||
_interruptSource->run_for(Cycles(2));
|
||||
--nextSequencePoint;
|
||||
|
||||
// Check for a status bit change.
|
||||
const uint8_t newDividerState = _interruptSource->get_divider_state();
|
||||
const bool didToggle = (dividerState^newDividerState)&0x1;
|
||||
dividerState = newDividerState;
|
||||
toggles += didToggle;
|
||||
|
||||
// Check for the relevant interrupt.
|
||||
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
|
||||
if(newInterrupts & 0x02) {
|
||||
++interrupts;
|
||||
if(newInterrupts) {
|
||||
XCTAssertEqual(nextSequencePoint, 0);
|
||||
XCTAssertEqual(dividerState&0x1, 0);
|
||||
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
||||
}
|
||||
|
||||
// Failing that, confirm that the other interrupt happend.
|
||||
if(!nextSequencePoint) {
|
||||
XCTAssertTrue(newInterrupts & 0x08);
|
||||
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
||||
if(newInterrupts & 0x02) {
|
||||
++interrupts;
|
||||
XCTAssertTrue(didToggle);
|
||||
} else {
|
||||
// Failing that, confirm that the other interrupt happend.
|
||||
XCTAssertTrue(newInterrupts & 0x08);
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as<int>(), @"At cycle %d", c);
|
||||
}
|
||||
|
||||
XCTAssertEqual(toggles, expectedToggles);
|
||||
XCTAssertEqual(interrupts, expectedInterrupts);
|
||||
XCTAssertEqual(toggles, int(expectedInterruptsPerSecond * 5.0));
|
||||
XCTAssertEqual(interrupts, int(expectedInterruptsPerSecond * 5.0));
|
||||
}
|
||||
|
||||
- (void)test1kHzTimer {
|
||||
// Set 1kHz timer.
|
||||
_interruptSource->write(7, 0 << 5);
|
||||
[self performTestExpectedToggles:2000 expectedInterrupts:1000];
|
||||
[self performTestExpectedInterrupts:1000.0 mode:0];
|
||||
}
|
||||
|
||||
- (void)test50HzTimer {
|
||||
// Set 50Hz timer.
|
||||
_interruptSource->write(7, 1 << 5);
|
||||
[self performTestExpectedToggles:100 expectedInterrupts:50];
|
||||
[self performTestExpectedInterrupts:50.0 mode:1];
|
||||
}
|
||||
|
||||
- (void)testTone0Timer {
|
||||
// Set tone generator 0 as the interrupt source, with a divider of 137;
|
||||
// apply sync momentarily.
|
||||
_interruptSource->write(7, 2 << 5);
|
||||
_interruptSource->write(0, 137);
|
||||
_interruptSource->write(1, 0);
|
||||
|
||||
[self performTestExpectedToggles:250000/138 expectedInterrupts:250000/(138*2)];
|
||||
[self performTestExpectedInterrupts:250000.0/(138.0 * 2.0) mode:2];
|
||||
}
|
||||
|
||||
- (void)testTone1Timer {
|
||||
// Set tone generator 1 as the interrupt source, with a divider of 961;
|
||||
// apply sync momentarily.
|
||||
_interruptSource->write(7, 3 << 5);
|
||||
_interruptSource->write(2, 961 & 0xff);
|
||||
_interruptSource->write(3, (961 >> 8) & 0xff);
|
||||
|
||||
[self performTestExpectedToggles:250000/961 expectedInterrupts:250000/(961*2)];
|
||||
[self performTestExpectedInterrupts:250000.0/(962.0 * 2.0) mode:3];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1073,6 +1073,11 @@ void MainWindow::start_enterprise() {
|
||||
case 2: target->model = Target::Model::Enterprise256; break;
|
||||
}
|
||||
|
||||
switch(ui->enterpriseSpeedComboBox->currentIndex()) {
|
||||
default: target->speed = Target::Speed::FourMHz; break;
|
||||
case 1: target->speed = Target::Speed::SixMHz; break;
|
||||
}
|
||||
|
||||
switch(ui->enterpriseEXOSComboBox->currentIndex()) {
|
||||
default: target->exos_version = Target::EXOSVersion::v10; break;
|
||||
case 1: target->exos_version = Target::EXOSVersion::v20; break;
|
||||
@@ -1252,6 +1257,7 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
|
||||
\
|
||||
/* Enterprise. */ \
|
||||
ComboBox(enterpriseModelComboBox, "enterprise.model"); \
|
||||
ComboBox(enterpriseSpeedComboBox, "enterprise.speed"); \
|
||||
ComboBox(enterpriseEXOSComboBox, "enterprise.exos"); \
|
||||
ComboBox(enterpriseBASICComboBox, "enterprise.basic"); \
|
||||
ComboBox(enterpriseDOSComboBox, "enterprise.dos"); \
|
||||
@@ -1309,6 +1315,7 @@ void MainWindow::restoreSelections() {
|
||||
// MARK: - Activity observation
|
||||
|
||||
void MainWindow::addActivityObserver() {
|
||||
ledStatuses.clear();
|
||||
auto activitySource = machine->activity_source();
|
||||
if(!activitySource) return;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<item>
|
||||
<widget class="QTabWidget" name="machineSelectionTabs">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="appleIITab">
|
||||
<attribute name="title">
|
||||
@@ -346,13 +346,34 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="enterpriseSpeedLabel">
|
||||
<property name="text">
|
||||
<string>Speed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="enterpriseSpeedComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4 MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>6 MHz</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="enterpriseEXOSLabel">
|
||||
<property name="text">
|
||||
<string>EXOS:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="enterpriseEXOSComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -371,14 +392,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="enterpriseBASICLabel">
|
||||
<property name="text">
|
||||
<string>BASIC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="enterpriseBASICComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -402,14 +423,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="enterpriseDOSLabel">
|
||||
<property name="text">
|
||||
<string>DOS:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="enterpriseDOSComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
||||
Reference in New Issue
Block a user