1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Switches to linked 1/50/1000 Hz timers, and per-interrupt state toggling.

This commit is contained in:
Thomas Harte 2021-07-06 20:12:44 -04:00
parent e98165a657
commit 3e6b804896
3 changed files with 50 additions and 75 deletions

View File

@ -225,21 +225,11 @@ 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;
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;
rate_ = InterruptRate((value >> 5) & 3);
break;
case 31:
global_divider_ = Cycles(2 + ((value >> 1)&1));
@ -269,6 +259,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);
@ -287,67 +278,59 @@ void TimedInterruptSource::run_for(Cycles duration) {
return;
}
// Update the 1Hz interrupt.
one_hz_offset_ -= cycles;
if(one_hz_offset_ <= Cycles(0)) {
// 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>();
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);
int result = 250'000 - (two_second_counter_ % 250'000);
return Cycles(std::min(result, 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);
(two_second_counter_ < 250'000 ? 0x4 : 0x0) |
(programmable_level_ ? 0x1 : 0x0);
}

View File

@ -152,7 +152,7 @@ class TimedInterruptSource {
static constexpr Cycles half_clock_rate{125000};
// Global divider (i.e. 8MHz/12Mhz switch).
Cycles global_divider_;
Cycles global_divider_ = Cycles(2);
Cycles run_length_;
// Interrupts that have fired since get_new_interrupts()
@ -160,7 +160,7 @@ class TimedInterruptSource {
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 {
@ -169,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
@ -181,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

@ -23,17 +23,18 @@
_interruptSource = std::make_unique<Enterprise::Dave::TimedInterruptSource>();
}
- (void)performTestExpectedToggles:(int)expectedToggles expectedInterrupts:(int)expectedInterrupts {
- (void)performTestExpectedToggles:(int)expectedToggles {
// Check that the programmable timer flag toggles at a rate
// of 2kHz, causing 1000 interrupts, and that sequence points
// are properly predicted.
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));
// Advance one cycle. Clock is 500,000 Hz.
_interruptSource->run_for(Cycles(2));
const uint8_t newDividerState = _interruptSource->get_divider_state();
if((dividerState^newDividerState)&0x1) {
@ -48,7 +49,6 @@
if(newInterrupts & 0x02) {
++interrupts;
XCTAssertEqual(nextSequencePoint, 0);
XCTAssertEqual(dividerState&0x1, 0);
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
}
@ -62,19 +62,19 @@
}
XCTAssertEqual(toggles, expectedToggles);
XCTAssertEqual(interrupts, expectedInterrupts);
XCTAssertEqual(interrupts, expectedToggles);
}
- (void)test1kHzTimer {
// Set 1kHz timer.
_interruptSource->write(7, 0 << 5);
[self performTestExpectedToggles:2000 expectedInterrupts:1000];
[self performTestExpectedToggles:1000];
}
- (void)test50HzTimer {
// Set 50Hz timer.
_interruptSource->write(7, 1 << 5);
[self performTestExpectedToggles:100 expectedInterrupts:50];
[self performTestExpectedToggles:50];
}
- (void)testTone0Timer {
@ -84,7 +84,7 @@
_interruptSource->write(0, 137);
_interruptSource->write(1, 0);
[self performTestExpectedToggles:250000/138 expectedInterrupts:250000/(138*2)];
[self performTestExpectedToggles:250000/(138 * 2)];
}
- (void)testTone1Timer {
@ -94,7 +94,7 @@
_interruptSource->write(2, 961 & 0xff);
_interruptSource->write(3, (961 >> 8) & 0xff);
[self performTestExpectedToggles:250000/961 expectedInterrupts:250000/(961*2)];
[self performTestExpectedToggles:250000/(961 * 2)];
}
@end