diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 8e0811e66..71506b8a4 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -128,13 +128,14 @@ template class MOS6522: public MOS6522Storage { void access(int address); - uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); + uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask); inline void reevaluate_interrupts(); /// Sets the current intended output value for the port and line; /// if this affects the visible output, it will be passed to the handler. void set_control_line_output(Port port, Line line, LineState value); void evaluate_cb2_output(); + void evaluate_port_b_output(); }; } diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index 533b8d1be..c7c5f6ef4 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -43,7 +43,7 @@ template void MOS6522::write(int address, uint8_t value) { registers_.output[1] = value; bus_handler_.run_for(time_since_bus_handler_call_.flush()); - bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); + evaluate_port_b_output(); registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); reevaluate_interrupts(); @@ -85,6 +85,12 @@ template void MOS6522::write(int address, uint8_t value) { registers_.next_timer[0] = registers_.timer_latch[0]; timer_is_running_[0] = true; + // If PB7 output mode is active, set it low. + if(registers_.auxiliary_control & 0x80) { + registers_.timer_port_b_output &= 0x7f; + evaluate_port_b_output(); + } + // Clear existing interrupt flag. registers_.interrupt_flags &= ~InterruptFlag::Timer1; reevaluate_interrupts(); @@ -113,6 +119,13 @@ template void MOS6522::write(int address, uint8_t value) { case 0xb: // Auxiliary control ('ACR'). registers_.auxiliary_control = value; evaluate_cb2_output(); + + // This is a bit of a guess: reset the timer-based PB7 output to its default high level + // any timer that timer-linked PB7 output is disabled. + if(!(registers_.auxiliary_control & 0x80)) { + registers_.timer_port_b_output |= 0x80; + } + evaluate_port_b_output(); break; case 0xc: { // Peripheral control ('PCR'). // const auto old_peripheral_control = registers_.peripheral_control; @@ -176,12 +189,12 @@ template uint8_t MOS6522::read(int address) { case 0x0: // Read Port B ('IRB'). registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); reevaluate_interrupts(); - return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); + return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80); case 0xf: case 0x1: // Read Port A ('IRA'). registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); reevaluate_interrupts(); - return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); + return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0); case 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB'). case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA'). @@ -218,9 +231,10 @@ template uint8_t MOS6522::read(int address) { return 0xff; } -template uint8_t MOS6522::get_port_input(Port port, uint8_t output_mask, uint8_t output) { +template uint8_t MOS6522::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) { bus_handler_.run_for(time_since_bus_handler_call_.flush()); const uint8_t input = bus_handler_.get_port_input(port); + output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask); return (input & ~output_mask) | (output & output_mask); } @@ -354,13 +368,22 @@ template void MOS6522::do_phase1() { // Determine whether to toggle PB7. if(registers_.auxiliary_control&0x80) { - registers_.output[1] ^= 0x80; + registers_.timer_port_b_output ^= 0x80; bus_handler_.run_for(time_since_bus_handler_call_.flush()); - bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); + evaluate_port_b_output(); } } } +template void MOS6522::evaluate_port_b_output() { + // Apply current timer-linked PB7 output if any atop the stated output. + const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80; + bus_handler_.set_port_output( + Port::B, + (registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit, + registers_.data_direction[1] | timer_control_bit); +} + /*! Runs for a specified number of half cycles. */ template void MOS6522::run_for(const HalfCycles half_cycles) { auto number_of_half_cycles = half_cycles.as_integral(); diff --git a/Components/6522/Implementation/6522Storage.hpp b/Components/6522/Implementation/6522Storage.hpp index da9e30981..ceff38a8f 100644 --- a/Components/6522/Implementation/6522Storage.hpp +++ b/Components/6522/Implementation/6522Storage.hpp @@ -34,7 +34,9 @@ class MOS6522Storage { uint8_t peripheral_control = 0; uint8_t interrupt_flags = 0; uint8_t interrupt_enable = 0; + bool timer_needs_reload = false; + uint8_t timer_port_b_output = 0xff; } registers_; // Control state. diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c4c11eb2c..b86a62c7d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -860,6 +860,7 @@ 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; + 4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; }; 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; 4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; @@ -1830,6 +1831,7 @@ buildActionMask = 2147483647; files = ( 4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, + 4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OSBindings/Mac/Clock SignalTests/6522Tests.swift b/OSBindings/Mac/Clock SignalTests/6522Tests.swift index d56ce5903..7126a09c5 100644 --- a/OSBindings/Mac/Clock SignalTests/6522Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/6522Tests.swift @@ -101,6 +101,116 @@ class MOS6522Tests: XCTestCase { } + // MARK: PB7 timer 1 tests + // These follow the same logic and check for the same results as the VICE VIC-20 via_pb7 tests. + + // Perfoms: + // + // (1) establish initial ACR and port B output value, and grab port B input value. + // (2) start timer 1, grab port B input value. + // (3) set final ACR, grab port B input value. + // (4) allow timer 1 to expire, grab port B input value. + private func runTest(startACR: UInt8, endACR: UInt8, portBOutput: UInt8) -> [UInt8] { + var result: [UInt8] = [] + + // Clear all register values. + for n: UInt in 0...15 { + m6522.setValue(0, forRegister: n) + } + m6522.run(forHalfCycles: 2) + + // Set data direction and current port B value. + m6522.setValue(0xff, forRegister: 2) + m6522.run(forHalfCycles: 2) + m6522.setValue(portBOutput, forRegister: 0) + m6522.run(forHalfCycles: 2) + + // Set initial ACR and grab the current port B value. + m6522.setValue(startACR, forRegister: 0xb) + m6522.run(forHalfCycles: 2) + result.append(m6522.value(forRegister: 0)) + m6522.run(forHalfCycles: 2) + + // Start timer 1 and grab the value. + m6522.setValue(1, forRegister: 0x5) + m6522.run(forHalfCycles: 2) + result.append(m6522.value(forRegister: 0)) + m6522.run(forHalfCycles: 2) + + // Set the final ACR value and grab value. + m6522.setValue(endACR, forRegister: 0xb) + m6522.run(forHalfCycles: 2) + result.append(m6522.value(forRegister: 0)) + m6522.run(forHalfCycles: 2) + + // Make sure timer 1 has expired. + m6522.run(forHalfCycles: 512) + + // Grab the final value. + result.append(m6522.value(forRegister: 0)) + + return result + } + + func testTimer1PB7() { + // Original top row. [original Vic-20 screen output in comments on the right] + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00]) // @@@@ (i.e. 0, 0, 0, 0) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00]) // @@@@ (i.e. 0, 0, 0, 0) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00]) // @@b@ (i.e. 0, 0, 1, 0) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00]) // @@b@ (i.e. 0, 0, 1, 0) + + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff]) // cccc (i.e. 1, 1, 1, 1) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff]) // cccc (i.e. 1, 1, 1, 1) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f]) // ccca (i.e. 1, 1, 1, 0) + XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f]) // ccca (i.e. 1, 1, 1, 0) + + // Second row. [same output as first row] + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00]) // @@@@ + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00]) // @@@@ + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00]) // @@b@ + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00]) // @@b@ + + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff]) // cccc + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff]) // cccc + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f]) // ccca + XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f]) // ccca + + // Third row. + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00]) // b@@@ (i.e. 1, 0, 0, 0) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00]) // b@@@ (i.e. 1, 0, 0, 0) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80]) // b@@b (i.e. 1, 0, 0, 1) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80]) // b@@b (i.e. 1, 0, 0, 1) + + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff]) // cacc (i.e. 1, 0, 1, 1) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff]) // cacc (i.e. 1, 0, 1, 1) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff]) // caac (i.e. 1, 0, 0, 1) + XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff]) // caac (i.e. 1, 0, 0, 1) + + // Final row. [same output as third row] + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00]) // b@@@ + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00]) // b@@@ + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80]) // b@@b + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80]) // b@@b + + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff]) // cacc + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff]) // cacc + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff]) // caac + XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff]) // caac + + // Conclusions: + // + // after inital ACR and port B value: [original data if not in PB7 output mode, otherwise 1] + // after starting timer 1: [original data if not in PB7 output mode, otherwise 0] + // after final ACR value: [original data if not in PB7 output mode, 1 if has transitioned to PB7 mode, 0 if was already in PB7 mode] + // after timer 1 expiry: [original data if not in PB7 mode, 1 if timer has expired while in PB7 mode] + // + // i.e. + // (1) there is separate storage for the programmer-set PB7 and the timer output; + // (2) the timer output is reset upon a timer write only if PB7 output is enabled; + // (3) expiry toggles the output. + } + + // MARK: Data direction tests func testDataDirection() { // set low four bits of register B as output, the top four as input diff --git a/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm b/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm index 6eca9d231..38d9ac14d 100644 --- a/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm +++ b/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm @@ -70,12 +70,9 @@ - (void)testSeekToSecondBit { Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; - Storage::Time target_time(1, 10); - Storage::Time found_time = segmentSource.seek_to(target_time); - found_time.simplify(); - - XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th"); + const float found_time = segmentSource.seek_to(1.0f / 10.0f); + XCTAssertTrue(fabsf(found_time - 1.0f / 20.0f) < 0.01f, @"A request to seek to 1/10th should have seeked to 1/20th"); Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); next_event.length.simplify(); @@ -85,12 +82,9 @@ - (void)testSeekBeyondFinalBit { Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; - Storage::Time target_time(24, 10); + const float found_time = segmentSource.seek_to(2.4f); - Storage::Time found_time = segmentSource.seek_to(target_time); - found_time.simplify(); - - XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths"); + XCTAssertTrue(fabsf(found_time - 47.0f / 20.0f) < 0.01f, @"A request to seek to 24/10ths should have seeked to 47/20ths"); Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); next_event.length.simplify(); diff --git a/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm b/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm index fd3c5bfee..d20e87477 100644 --- a/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm +++ b/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm @@ -73,16 +73,14 @@ } Storage::Disk::PCMTrack track(segments); - Storage::Time late_time(967445, 2045454); + const float late_time = 967445.0f / 2045454.0f; const auto offset = track.seek_to(late_time); XCTAssert(offset <= late_time, "Found location should be at or before sought time"); const auto difference = late_time - offset; - const double difference_duration = difference.get(); - XCTAssert(difference_duration >= 0.0 && difference_duration < 0.005, "Next event should occur soon"); + XCTAssert(difference >= 0.0 && difference < 0.005, "Next event should occur soon"); - const double offset_duration = offset.get(); - XCTAssert(offset_duration >= 0.0 && offset_duration < 0.5, "Next event should occur soon"); + XCTAssert(offset >= 0.0 && offset < 0.5, "Next event should occur soon"); auto next_event = track.get_next_event(); double next_event_duration = next_event.length.get();