1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

36 Commits

Author SHA1 Message Date
Thomas Harte
5810a1a98e Merge pull request #971 from TomHarte/ChaseHQ
Flip meaning of INT1 input read.
2021-07-09 22:48:01 -04:00
Thomas Harte
a4c011e3c0 Flip meaning of INT1 input read. 2021-07-09 22:39:51 -04:00
Thomas Harte
337fd15dc0 Merge pull request #970 from TomHarte/SwiftUniformity
Swift: be consisted on `.selectedTag()`.
2021-07-08 22:43:57 -04:00
Thomas Harte
9bc94f4536 Be consisted on .selectedTag(). 2021-07-08 22:38:54 -04:00
Thomas Harte
3f4cf35384 Merge pull request #969 from TomHarte/SixMHz
Adds the option of running an Enterprise at 6MHz.
2021-07-08 22:36:22 -04:00
Thomas Harte
4dd7f2cc09 Add 6Mhz option to Qt UI. 2021-07-08 22:30:35 -04:00
Thomas Harte
1b29cc34c4 Correct input list. 2021-07-08 22:22:48 -04:00
Thomas Harte
53c3c1f5ab Allows macOS users to select the 6MHz Enterprise. 2021-07-08 18:50:37 -04:00
Thomas Harte
6225abd751 Adds 6MHz Enterprise option. 2021-07-07 20:57:04 -04:00
Thomas Harte
c6fcd9a1eb Merge pull request #968 from TomHarte/DaveAudio
Dave: apply ring modulation during sync, too.
2021-07-06 23:41:37 -04:00
Thomas Harte
30fbb6ea53 Ensure run command is issued. 2021-07-06 23:16:16 -04:00
Thomas Harte
0e49258546 Remove caveman debugging. 2021-07-06 23:15:53 -04:00
Thomas Harte
264b8dfb28 Dave: apply ring modulation even in sync mode. 2021-07-06 23:11:30 -04:00
Thomas Harte
6a15b8f695 Merge pull request #967 from TomHarte/EnterpriseTiming
Correct Enterprise timing error.
2021-07-06 22:48:58 -04:00
Thomas Harte
5167d256cc Remove detritus. 2021-07-06 22:43:17 -04:00
Thomas Harte
16bd826491 Reduce nesting. 2021-07-06 22:32:59 -04:00
Thomas Harte
55af8fa5d9 Avoid erroneous Nick delays. 2021-07-06 22:28:44 -04:00
Thomas Harte
1ec8ff20af Ensure data bus is 0xff during interrupts. 2021-07-06 21:58:17 -04:00
Thomas Harte
99a65d3297 Merge pull request #966 from TomHarte/DaveUnifiedTimer
Switches to a unified counter for 1/50/1000Hz Dave interrupts.
2021-07-06 21:50:32 -04:00
Thomas Harte
94907b51aa Remove redundant parameter. 2021-07-06 20:47:49 -04:00
Thomas Harte
0085265d13 Test for a longer period; fix expected tone 1 count. 2021-07-06 20:46:22 -04:00
Thomas Harte
8e0893bd42 Clarifies control flow. 2021-07-06 20:28:32 -04:00
Thomas Harte
704dc9bdcb Improves test, to assert that state toggles happen at interrupts. 2021-07-06 20:25:32 -04:00
Thomas Harte
7a673a2448 Avoid confusing temporary storage. 2021-07-06 20:23:09 -04:00
Thomas Harte
33e2a4b21c Minor cleanups. 2021-07-06 20:20:13 -04:00
Thomas Harte
3e6b804896 Switches to linked 1/50/1000 Hz timers, and per-interrupt state toggling. 2021-07-06 20:12:44 -04:00
Thomas Harte
e98165a657 Merge pull request #965 from TomHarte/DaveDivider
Ensure two-cycle pauses in 12MHz mode.
2021-07-04 21:13:05 -04:00
Thomas Harte
2a7727d12b Merge branch 'master' into DaveDivider 2021-07-04 21:02:09 -04:00
Thomas Harte
c20e8f4062 Honours 8/12Mhz selection in non-video delays. 2021-07-03 23:05:09 -04:00
Thomas Harte
4ca9db7d49 Merge pull request #963 from TomHarte/DaveDivider
Obey Dave's 8/12MHz programmable divider.
2021-07-03 23:00:11 -04:00
Thomas Harte
4add48cffb Obey Dave's 8/12MHz programmable divider. 2021-07-03 22:43:20 -04:00
Thomas Harte
adbfb009f8 Merge pull request #960 from TomHarte/QtLEDs
Ensure LEDs are cleared between machines in Qt.
2021-07-03 19:17:51 -04:00
Thomas Harte
43ceca8711 Use type alias. 2021-07-03 19:10:39 -04:00
Thomas Harte
3ef28a4f03 Remove unused instance variable. 2021-07-03 19:10:29 -04:00
Thomas Harte
adcd580d5b Ensure LEDs are cleared upon a new machine. 2021-07-03 19:06:15 -04:00
Thomas Harte
5715c9183f The target is now definitely used. 2021-07-03 15:20:37 -04:00
13 changed files with 337 additions and 223 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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