From 283092cfbc2e97710ad8a485b21ddb1b4934e707 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 2 Jul 2021 23:41:19 -0400 Subject: [PATCH] With a unit test in aid, corrects some lingering `TimedInterruptSource` issues. --- Machines/Enterprise/Dave.cpp | 27 ++++-- .../Clock Signal.xcodeproj/project.pbxproj | 4 + .../Clock SignalTests/EnterpriseDaveTests.mm | 89 +++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 OSBindings/Mac/Clock SignalTests/EnterpriseDaveTests.mm diff --git a/Machines/Enterprise/Dave.cpp b/Machines/Enterprise/Dave.cpp index 474ee2940..511275436 100644 --- a/Machines/Enterprise/Dave.cpp +++ b/Machines/Enterprise/Dave.cpp @@ -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; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f2b162d23..d7524a1a8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = ""; }; 4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = ""; }; + 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseDaveTests.mm; sourceTree = ""; }; 4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = ""; }; 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 = ""; }; @@ -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 */, diff --git a/OSBindings/Mac/Clock SignalTests/EnterpriseDaveTests.mm b/OSBindings/Mac/Clock SignalTests/EnterpriseDaveTests.mm new file mode 100644 index 000000000..7fdeb13cb --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/EnterpriseDaveTests.mm @@ -0,0 +1,89 @@ +// +// EnterpriseDaveTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 02/07/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#import + +#include "../../../Machines/Enterprise/Dave.hpp" +#include + +@interface EnterpriseDaveTests : XCTestCase +@end + +@implementation EnterpriseDaveTests { + std::unique_ptr _interruptSource; +} + +- (void)setUp { + [super setUp]; + _interruptSource = std::make_unique(); +} + +- (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(); + 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(); + } + + // Failing that, confirm that the other interrupt happend. + if(!nextSequencePoint) { + XCTAssertTrue(newInterrupts & 0x08); + nextSequencePoint = _interruptSource->get_next_sequence_point().as(); + } + + XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as(), @"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