From 210bcaa56d58fb6662383eb7af21e0de9d106d67 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Jul 2019 22:13:36 -0400 Subject: [PATCH] Introduces an initial shift unit test, and makes it pass. --- .../Implementation/6522Implementation.hpp | 18 +- .../6522/Implementation/6522Storage.hpp | 17 ++ .../Mac/Clock SignalTests/6522Tests.swift | 177 ++++++++++-------- .../Clock SignalTests/Bridges/MOS6522Bridge.h | 9 + .../Bridges/MOS6522Bridge.mm | 13 ++ 5 files changed, 150 insertions(+), 84 deletions(-) diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index 839e78933..71df5fe59 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -391,9 +391,11 @@ template void MOS6522::evaluate_cb2_output() { // My guess: other CB2 functions work only if the shift register is disabled (?). if((registers_.auxiliary_control >> 2)&7) { // Shift register is enabled, one way or the other; but announce only output. - if(registers_.auxiliary_control & 0x10) { + if(is_shifting_out()) { + // Output mode; set the level according to the current top of the shift register. bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80)); } else { + // Input mode. bus_handler_.set_control_line_output(Port::B, Line::Two, true); } } else { @@ -433,13 +435,15 @@ template void MOS6522::shift_in() { template void MOS6522::shift_out() { // When shifting out, the shift register rotates rather than strictly shifts. // TODO: is that true for all modes? - registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); - evaluate_cb2_output(); + if(shift_mode() == ShiftMode::ShiftOutUnderT2FreeRunning || shift_bits_remaining_) { + registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); + evaluate_cb2_output(); - --shift_bits_remaining_; - if(!shift_bits_remaining_) { - registers_.interrupt_flags |= InterruptFlag::ShiftRegister; - reevaluate_interrupts(); + --shift_bits_remaining_; + if(!shift_bits_remaining_) { + registers_.interrupt_flags |= InterruptFlag::ShiftRegister; + reevaluate_interrupts(); + } } } diff --git a/Components/6522/Implementation/6522Storage.hpp b/Components/6522/Implementation/6522Storage.hpp index bb3eb596c..818710e9f 100644 --- a/Components/6522/Implementation/6522Storage.hpp +++ b/Components/6522/Implementation/6522Storage.hpp @@ -68,6 +68,23 @@ class MOS6522Storage { Timer2 = 1 << 5, Timer1 = 1 << 6, }; + + enum class ShiftMode { + Disabled = 0, + ShiftInUnderT2 = 1, + ShiftInUnderPhase2 = 2, + ShiftInUnderCB1 = 3, + ShiftOutUnderT2FreeRunning = 4, + ShiftOutUnderT2 = 5, + ShiftOutUnderPhase2 = 6, + ShiftOutUnderCB1 = 7 + }; + ShiftMode shift_mode() const { + return ShiftMode((registers_.auxiliary_control >> 2) & 7); + } + bool is_shifting_out() const { + return registers_.auxiliary_control & 0x10; + } }; } diff --git a/OSBindings/Mac/Clock SignalTests/6522Tests.swift b/OSBindings/Mac/Clock SignalTests/6522Tests.swift index 83f2851ad..d56ce5903 100644 --- a/OSBindings/Mac/Clock SignalTests/6522Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/6522Tests.swift @@ -11,119 +11,112 @@ import Foundation class MOS6522Tests: XCTestCase { - fileprivate func with6522(_ action: (MOS6522Bridge) -> ()) { - let bridge = MOS6522Bridge() - action(bridge) + private var m6522: MOS6522Bridge! + + override func setUp() { + m6522 = MOS6522Bridge() } // MARK: Timer tests func testTimerCount() { - with6522 { - // set timer 1 to a value of $000a - $0.setValue(10, forRegister: 4) - $0.setValue(0, forRegister: 5) + // set timer 1 to a value of m652200a + m6522.setValue(10, forRegister: 4) + m6522.setValue(0, forRegister: 5) - // complete the setting cycle - $0.run(forHalfCycles: 2) + // complete the setting cycle + m6522.run(forHalfCycles: 2) - // run for 5 cycles - $0.run(forHalfCycles: 10) + // run for 5 cycles + m6522.run(forHalfCycles: 10) - // check that the timer has gone down by 5 - XCTAssert($0.value(forRegister: 4) == 5, "Low order byte should be 5; was \($0.value(forRegister: 4))") - XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))") - } + // check that the timer has gone down by 5 + XCTAssert(m6522.value(forRegister: 4) == 5, "Low order byte should be 5; was \(m6522.value(forRegister: 4))") + XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))") } func testTimerLatches() { - with6522 { - // set timer 2 to $1020 - $0.setValue(0x10, forRegister: 8) - $0.setValue(0x20, forRegister: 9) + // set timer 2 to $1020 + m6522.setValue(0x10, forRegister: 8) + m6522.setValue(0x20, forRegister: 9) - // change the low-byte latch - $0.setValue(0x40, forRegister: 8) + // change the low-byte latch + m6522.setValue(0x40, forRegister: 8) - // complete the cycle - $0.run(forHalfCycles: 2) + // complete the cycle + m6522.run(forHalfCycles: 2) - // chek that the new latched value hasn't been copied - XCTAssert($0.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 8))") - XCTAssert($0.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \($0.value(forRegister: 9))") + // chek that the new latched value hasn't been copied + XCTAssert(m6522.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 8))") + XCTAssert(m6522.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \(m6522.value(forRegister: 9))") - // write the low-byte latch - $0.setValue(0x50, forRegister: 9) + // write the low-byte latch + m6522.setValue(0x50, forRegister: 9) - // complete the cycle - $0.run(forHalfCycles: 2) + // complete the cycle + m6522.run(forHalfCycles: 2) - // chek that the latched value has been copied - XCTAssert($0.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \($0.value(forRegister: 8))") - XCTAssert($0.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \($0.value(forRegister: 9))") - } + // chek that the latched value has been copied + XCTAssert(m6522.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \(m6522.value(forRegister: 8))") + XCTAssert(m6522.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \(m6522.value(forRegister: 9))") } func testTimerReload() { - with6522 { - // set timer 1 to a value of $0010, enable repeating mode - $0.setValue(16, forRegister: 4) - $0.setValue(0, forRegister: 5) - $0.setValue(0x40, forRegister: 11) - $0.setValue(0x40 | 0x80, forRegister: 14) + // set timer 1 to a value of m6522010, enable repeating mode + m6522.setValue(16, forRegister: 4) + m6522.setValue(0, forRegister: 5) + m6522.setValue(0x40, forRegister: 11) + m6522.setValue(0x40 | 0x80, forRegister: 14) - // complete the cycle to set initial values - $0.run(forHalfCycles: 2) + // complete the cycle to set initial values + m6522.run(forHalfCycles: 2) - // run for 16 cycles - $0.run(forHalfCycles: 32) + // run for 16 cycles + m6522.run(forHalfCycles: 32) - // check that the timer has gone down to 0 but not yet triggered an interrupt - XCTAssert($0.value(forRegister: 4) == 0, "Low order byte should be 0; was \($0.value(forRegister: 4))") - XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))") - XCTAssert(!$0.irqLine, "IRQ should not yet be active") + // check that the timer has gone down to 0 but not yet triggered an interrupt + XCTAssert(m6522.value(forRegister: 4) == 0, "Low order byte should be 0; was \(m6522.value(forRegister: 4))") + XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))") + XCTAssert(!m6522.irqLine, "IRQ should not yet be active") - // check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered - $0.run(forHalfCycles: 2) - XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))") - XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))") - XCTAssert(!$0.irqLine, "IRQ should not yet be active") + // check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered + m6522.run(forHalfCycles: 2) + XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))") + XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))") + XCTAssert(!m6522.irqLine, "IRQ should not yet be active") - // check that one half-cycle later the timer is still $ffff and IRQ has triggered... - $0.run(forHalfCycles: 1) - XCTAssert($0.irqLine, "IRQ should be active") - XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))") - XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))") + // check that one half-cycle later the timer is still $ffff and IRQ has triggered... + m6522.run(forHalfCycles: 1) + XCTAssert(m6522.irqLine, "IRQ should be active") + XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))") + XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))") - // ... but that reading the timer cleared the interrupt - XCTAssert(!$0.irqLine, "IRQ should be active") + // ... but that reading the timer cleared the interrupt + XCTAssert(!m6522.irqLine, "IRQ should be active") - // check that one half-cycles later the timer has reloaded - $0.run(forHalfCycles: 1) - XCTAssert($0.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 4))") - XCTAssert($0.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \($0.value(forRegister: 5))") - } + // check that one half-cycles later the timer has reloaded + m6522.run(forHalfCycles: 1) + XCTAssert(m6522.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 4))") + XCTAssert(m6522.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \(m6522.value(forRegister: 5))") } // MARK: Data direction tests func testDataDirection() { - with6522 { - // set low four bits of register B as output, the top four as input - $0.setValue(0xf0, forRegister: 2) + // set low four bits of register B as output, the top four as input + m6522.setValue(0xf0, forRegister: 2) - // ask to output 0x8c - $0.setValue(0x8c, forRegister: 0) + // ask to output 0x8c + m6522.setValue(0x8c, forRegister: 0) - // complete the cycle - $0.run(forHalfCycles: 2) + // complete the cycle + m6522.run(forHalfCycles: 2) - // set current input as 0xda - $0.portBInput = 0xda + // set current input as 0xda + m6522.portBInput = 0xda - // test that the result of reading register B is therefore 0x8a - XCTAssert($0.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \($0.value(forRegister: 0))") - } + // test that the result of reading register B is therefore 0x8a + XCTAssert(m6522.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \(m6522.value(forRegister: 0))") } func testShiftDisabled() { @@ -213,7 +206,37 @@ class MOS6522Tests: XCTestCase { func testShiftOutUnderPhase2() { /* In mode 6, the shift rate is controlled by the 02 system clock (Figure 27). + + (... and I'm assuming the same behaviour as shift out under control of T2 + otherwise, based on original context) */ + // Set the shift register to a non-zero something. + m6522.setValue(0xaa, forRegister: 10) + + // Set shift register mode 6. + m6522.setValue(6 << 2, forRegister: 11) + + // Make sure the shift register's interrupt bit is set. + m6522.run(forHalfCycles: 16) + XCTAssertEqual(m6522.value(forRegister: 13) & 0x04, 0x04) + + // Test that output is now inhibited: CB2 should remain unchanged. + let initialOutput = m6522.value(forControlLine: .two, port: .B) + for _ in 1...8 { + m6522.run(forHalfCycles: 2) + XCTAssertEqual(m6522.value(forControlLine: .two, port: .B), initialOutput) + } + + // Set a new value to the shift register. + m6522.setValue(0x16, forRegister: 10) + + // Test that the new value is shifted out. + var output = 0 + for _ in 1..<8 { + m6522.run(forHalfCycles: 2) + output = (output << 1) | (m6522.value(forControlLine: .two, port: .B) ? 1 : 0) + } + XCTAssertEqual(output, 0x16) } func testShiftOutUnderCB1() { diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.h b/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.h index c1e62f653..aedcac367 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.h +++ b/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.h @@ -8,6 +8,14 @@ #import +typedef NS_ENUM(NSInteger, MOS6522BridgePort) { + MOS6522BridgePortA = 0, MOS6522BridgePortB = 1 +}; + +typedef NS_ENUM(NSInteger, MOS6522BridgeLine) { + MOS6522BridgeLineOne = 0, MOS6522BridgeLineTwo = 1 +}; + @interface MOS6522Bridge : NSObject @property (nonatomic, readonly) BOOL irqLine; @@ -16,6 +24,7 @@ - (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber; - (uint8_t)valueForRegister:(NSUInteger)registerNumber; +- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port; - (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles; diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.mm b/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.mm index f14941e28..c442e3be8 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/MOS6522Bridge.mm @@ -19,7 +19,12 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { bool irq_line; uint8_t port_a_value; uint8_t port_b_value; + bool control_line_values[2][2]; + /* + All methods below here are to replace those defined by + MOS::MOS6522::PortHandler. + */ void set_interrupt_status(bool new_status) { irq_line = new_status; } @@ -27,6 +32,10 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { uint8_t get_port_input(MOS::MOS6522::Port port) { return port ? port_b_value : port_a_value; } + + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { + control_line_values[int(port)][int(line)] = value; + } }; @implementation MOS6522Bridge { @@ -75,4 +84,8 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { return _viaPortHandler.port_b_value; } +- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port { + return _viaPortHandler.control_line_values[port][line]; +} + @end