1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

With a unit test in aid, corrects some lingering TimedInterruptSource issues.

This commit is contained in:
Thomas Harte 2021-07-02 23:41:19 -04:00
parent 614953a222
commit 283092cfbc
3 changed files with 114 additions and 6 deletions

View File

@ -215,7 +215,7 @@ void TimedInterruptSource::write(uint16_t address, uint8_t value) {
const InterruptRate rate = InterruptRate((value >> 5) & 3);
if(rate != rate_) {
rate_ = InterruptRate((value >> 5) & 3);
rate_ = rate;
if(rate_ >= InterruptRate::ToneGenerator0) {
programmable_level_ = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)].level;
@ -234,12 +234,27 @@ void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement)
if(decrement <= channels_[c].value) {
channels_[c].value -= decrement;
} else {
const int num_flips = (decrement - channels_[c].value) / (channels_[c].reload + 1);
// The decrement is greater than the current value, therefore
// there'll be at least one flip.
//
// After decreasing the decrement by the current value + 1,
// it'll be clear how many decrements are left after reload.
//
// Dividing that by the number of decrements necessary for a
// flip will provide the total number of flips.
const int decrements_after_flip = decrement - (channels_[c].value + 1);
const int num_flips = 1 + decrements_after_flip / (channels_[c].reload + 1);
// If this is a linked channel, set the interrupt mask if a transition
// from high to low is amongst the included flips.
if(is_linked && num_flips + channels_[c].level >= 2) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
}
channels_[c].level ^= (num_flips & 1);
channels_[c].value = (decrement - channels_[c].value) % (channels_[c].reload + 1);
// Apply the modulo number of decrements to the reload value to
// figure out where things stand now.
channels_[c].value = channels_[c].reload - decrements_after_flip % (channels_[c].reload + 1);
}
}
}
@ -275,12 +290,12 @@ Cycles TimedInterruptSource::get_next_sequence_point() const {
switch(rate_) {
case InterruptRate::OnekHz:
case InterruptRate::FiftyHz:
result = std::min(result, programmable_offset_);
result = std::min(result, programmable_offset_ + (!programmable_level_)*programmble_reload(rate_));
break;
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 ? 0 : channel.reload + 1);
const auto &channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)];
const int cycles_until_interrupt = channel.value + 1 + (channel.level ? 0 : channel.reload + 1);
result = std::min(result, cycles_until_interrupt);
} break;
}

View File

@ -242,6 +242,7 @@
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */; };
4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; };
@ -1279,6 +1280,7 @@
4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; };
4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = "<group>"; };
4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = "<group>"; };
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseDaveTests.mm; sourceTree = "<group>"; };
4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = "<group>"; };
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; };
@ -4066,6 +4068,7 @@
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */,
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */,
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */,
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */,
@ -5774,6 +5777,7 @@
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
4B778F5623A5F2AF0000D260 /* CPM.cpp in Sources */,
4B778F1C23A5ED3F0000D260 /* TimedEventLoop.cpp in Sources */,
4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */,
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */,
4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */,

View File

@ -0,0 +1,89 @@
//
// EnterpriseDaveTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 02/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../Machines/Enterprise/Dave.hpp"
#include <memory>
@interface EnterpriseDaveTests : XCTestCase
@end
@implementation EnterpriseDaveTests {
std::unique_ptr<Enterprise::Dave::TimedInterruptSource> _interruptSource;
}
- (void)setUp {
[super setUp];
_interruptSource = std::make_unique<Enterprise::Dave::TimedInterruptSource>();
}
- (void)testExpectedToggles:(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.
int toggles = 0;
int interrupts = 0;
uint8_t dividerState = _interruptSource->get_divider_state();
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;
--nextSequencePoint;
// Check for the relevant interrupt.
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
if(newInterrupts & 0x02) {
++interrupts;
XCTAssertEqual(nextSequencePoint, 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>();
}
XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as<int>(), @"At cycle %d", c);
}
XCTAssertEqual(toggles, expectedToggles);
XCTAssertEqual(interrupts, expectedInterrupts);
}
- (void)test1kHzTimer {
// Set 1kHz timer.
_interruptSource->write(7, 0 << 5);
[self testExpectedToggles:2000 expectedInterrupts:1000];
}
- (void)test50HzTimer {
// Set 50Hz timer.
_interruptSource->write(7, 1 << 5);
[self testExpectedToggles:100 expectedInterrupts:50];
}
- (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(2, 0);
[self testExpectedToggles:250000/138 expectedInterrupts:250000/(138*2)];
}
@end