1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-23 03:32:32 +00:00

Merge pull request #837 from TomHarte/Vic20Tests

Further improves 6522 emulation.
This commit is contained in:
Thomas Harte 2020-09-22 22:07:34 -04:00 committed by GitHub
commit 94dba70bbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 152 additions and 22 deletions

View File

@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage {
void access(int address); 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(); inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line; /// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler. /// 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 set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output(); void evaluate_cb2_output();
void evaluate_port_b_output();
}; };
} }

View File

@ -43,7 +43,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
registers_.output[1] = value; registers_.output[1] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
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)); registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts(); reevaluate_interrupts();
@ -85,6 +85,12 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
registers_.next_timer[0] = registers_.timer_latch[0]; registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true; 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. // Clear existing interrupt flag.
registers_.interrupt_flags &= ~InterruptFlag::Timer1; registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts(); reevaluate_interrupts();
@ -113,6 +119,13 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
case 0xb: // Auxiliary control ('ACR'). case 0xb: // Auxiliary control ('ACR').
registers_.auxiliary_control = value; registers_.auxiliary_control = value;
evaluate_cb2_output(); 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; break;
case 0xc: { // Peripheral control ('PCR'). case 0xc: { // Peripheral control ('PCR').
// const auto old_peripheral_control = registers_.peripheral_control; // const auto old_peripheral_control = registers_.peripheral_control;
@ -176,12 +189,12 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
case 0x0: // Read Port B ('IRB'). case 0x0: // Read Port B ('IRB').
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts(); 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 0xf:
case 0x1: // Read Port A ('IRA'). case 0x1: // Read Port A ('IRA').
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts(); 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 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB').
case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA'). case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA').
@ -218,9 +231,10 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
return 0xff; return 0xff;
} }
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) { template <typename T> uint8_t MOS6522<T>::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<HalfCycles>()); bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port); 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); return (input & ~output_mask) | (output & output_mask);
} }
@ -354,13 +368,22 @@ template <typename T> void MOS6522<T>::do_phase1() {
// Determine whether to toggle PB7. // Determine whether to toggle PB7.
if(registers_.auxiliary_control&0x80) { 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<HalfCycles>()); bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); evaluate_port_b_output();
} }
} }
} }
template <typename T> void MOS6522<T>::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. */ /*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
auto number_of_half_cycles = half_cycles.as_integral(); auto number_of_half_cycles = half_cycles.as_integral();

View File

@ -34,7 +34,9 @@ class MOS6522Storage {
uint8_t peripheral_control = 0; uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0; uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0; uint8_t interrupt_enable = 0;
bool timer_needs_reload = false; bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_; } registers_;
// Control state. // Control state.

View File

@ -860,6 +860,7 @@
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
4BF437EF209D0F7E008CBD6B /* 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 */; }; 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; };
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; };
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; 4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; };
@ -1830,6 +1831,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, 4B9F11CA2272433900701480 /* libz.tbd in Frameworks */,
4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -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 // MARK: Data direction tests
func testDataDirection() { func testDataDirection() {
// set low four bits of register B as output, the top four as input // set low four bits of register B as output, the top four as input

View File

@ -70,12 +70,9 @@
- (void)testSeekToSecondBit { - (void)testSeekToSecondBit {
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
Storage::Time target_time(1, 10);
Storage::Time found_time = segmentSource.seek_to(target_time); const float found_time = segmentSource.seek_to(1.0f / 10.0f);
found_time.simplify(); XCTAssertTrue(fabsf(found_time - 1.0f / 20.0f) < 0.01f, @"A request to seek to 1/10th should have seeked to 1/20th");
XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th");
Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
next_event.length.simplify(); next_event.length.simplify();
@ -85,12 +82,9 @@
- (void)testSeekBeyondFinalBit { - (void)testSeekBeyondFinalBit {
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; 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); XCTAssertTrue(fabsf(found_time - 47.0f / 20.0f) < 0.01f, @"A request to seek to 24/10ths should have seeked to 47/20ths");
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");
Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
next_event.length.simplify(); next_event.length.simplify();

View File

@ -73,16 +73,14 @@
} }
Storage::Disk::PCMTrack track(segments); 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); const auto offset = track.seek_to(late_time);
XCTAssert(offset <= late_time, "Found location should be at or before sought time"); XCTAssert(offset <= late_time, "Found location should be at or before sought time");
const auto difference = late_time - offset; const auto difference = late_time - offset;
const double difference_duration = difference.get<double>(); XCTAssert(difference >= 0.0 && difference < 0.005, "Next event should occur soon");
XCTAssert(difference_duration >= 0.0 && difference_duration < 0.005, "Next event should occur soon");
const double offset_duration = offset.get<double>(); XCTAssert(offset >= 0.0 && offset < 0.5, "Next event should occur soon");
XCTAssert(offset_duration >= 0.0 && offset_duration < 0.5, "Next event should occur soon");
auto next_event = track.get_next_event(); auto next_event = track.get_next_event();
double next_event_duration = next_event.length.get<double>(); double next_event_duration = next_event.length.get<double>();