mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Introduces an initial shift unit test, and makes it pass.
This commit is contained in:
parent
d7329c1bdd
commit
210bcaa56d
@ -391,9 +391,11 @@ template <typename T> void MOS6522<T>::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 <typename T> void MOS6522<T>::shift_in() {
|
||||
template <typename T> void MOS6522<T>::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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -8,6 +8,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user