From 88e2b382e52f8999e58d2301e5a41a08f8c4beb2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Jun 2016 18:57:35 -0400 Subject: [PATCH 1/3] Made an attempt at a full and thorough 6532 implementation (and got a bit more explicit about flag size in the 6502). --- Components/6532/6532.hpp | 122 ++++++++++++++++++++++--------- Machines/Atari2600/Atari2600.hpp | 1 + Processors/6502/CPU6502.hpp | 2 +- 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/Components/6532/6532.hpp b/Components/6532/6532.hpp index d47970cf1..d2f68da2d 100644 --- a/Components/6532/6532.hpp +++ b/Components/6532/6532.hpp @@ -10,6 +10,7 @@ #define _532_hpp #include +#include namespace MOS { @@ -33,24 +34,31 @@ template class MOS6532 { { const uint8_t decodedAddress = address & 0x07; switch(decodedAddress) { - case 0x00: - case 0x02: - static_cast(this)->set_port_output(decodedAddress / 2, value, _port[decodedAddress / 2].direction); - _port[decodedAddress/2].output = value; + // Port output + case 0x00: case 0x02: + _port[decodedAddress / 2].output = value; + static_cast(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask); + break; + case 0x01: case 0x03: + _port[decodedAddress / 2].output_mask = value; + static_cast(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask); break; - case 0x01: - case 0x03: - _port[decodedAddress / 2].direction = value; - break; - - case 0x04: - case 0x05: - case 0x06: - case 0x07: - _timer.writtenShift = _timer.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10 - _timer.value = ((unsigned int)(value) << _timer.activeShift) | ((1 << _timer.activeShift)-1); - _timer.status &= ~0x80; + // The timer and edge detect control + case 0x04: case 0x05: case 0x06: case 0x07: + if(address & 0x10) + { + _timer.writtenShift = _timer.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10 + _timer.value = ((unsigned int)(value) << _timer.activeShift) | ((1 << _timer.activeShift)-1); + _timer.interrupt_enabled = !!(address&0x08); + _interrupt_status &= ~InterruptFlag::Timer; + evaluate_interrupts(); + } + else + { + _a7_interrupt.enabled = !!(address&0x2); + _a7_interrupt.active_on_positive = !!(address & 0x01); + } break; } } @@ -59,22 +67,25 @@ template class MOS6532 { { const uint8_t decodedAddress = address & 0x7; switch(decodedAddress) { - case 0x00: - case 0x02: + // Port input + case 0x00: case 0x02: { const int port = decodedAddress / 2; uint8_t input = static_cast(this)->get_port_input(port); - return (input & ~_port[port].direction) | (_port[port].output & _port[port].direction); + return (input & ~_port[port].output_mask) | (_port[port].output & _port[port].output_mask); } break; - case 0x01: - case 0x03: - return _port[decodedAddress / 2].direction; + case 0x01: case 0x03: + return _port[decodedAddress / 2].output_mask; break; - case 0x04: - case 0x06: + + // Timer and interrupt control + case 0x04: case 0x06: { uint8_t value = (uint8_t)(_timer.value >> _timer.activeShift); + _timer.interrupt_enabled = !!(address&0x08); + _interrupt_status &= ~InterruptFlag::Timer; + evaluate_interrupts(); if(_timer.activeShift != _timer.writtenShift) { unsigned int shift = _timer.writtenShift - _timer.activeShift; @@ -85,11 +96,12 @@ template class MOS6532 { return value; } break; - case 0x05: - case 0x07: + + case 0x05: case 0x07: { - uint8_t value = _timer.status; - _timer.status &= ~0x40; + uint8_t value = _interrupt_status; + _interrupt_status &= ~InterruptFlag::PA7; + evaluate_interrupts(); return value; } break; @@ -100,36 +112,80 @@ template class MOS6532 { inline void run_for_cycles(unsigned int number_of_cycles) { + // permit counting _to_ zero; counting _through_ zero initiates the other behaviour if(_timer.value >= number_of_cycles) { _timer.value -= number_of_cycles; } else { number_of_cycles -= _timer.value; _timer.value = 0x100 - number_of_cycles; _timer.activeShift = 0; - _timer.status |= 0xc0; + _interrupt_status |= InterruptFlag::Timer; + evaluate_interrupts(); } } MOS6532() : - _timer({.status = 0}) + _interrupt_status(0), _port{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, _a7_interrupt({.last_port_value = 0, .enabled = false}) {} + inline void set_port_did_change(int port) + { + if(!port) + { + uint8_t new_port_a_value = (get_port_input(0) & ~_port[0].output_mask) | (_port[0].output & _port[0].output_mask); + uint8_t difference = new_port_a_value ^ _a7_interrupt.last_port_value; + _a7_interrupt.last_port_value = new_port_a_value; + if(difference&0x80) + { + if( + ((new_port_a_value&0x80) && _a7_interrupt.active_on_positive) || + (!(new_port_a_value&0x80) && !_a7_interrupt.active_on_positive) + ) + { + _interrupt_status |= InterruptFlag::PA7; + evaluate_interrupts(); + } + } + } + } + private: uint8_t _ram[128]; struct { unsigned int value; unsigned int activeShift, writtenShift; - uint8_t status; + bool interrupt_enabled; } _timer; struct { - uint8_t direction, output; + bool enabled; + bool active_on_positive; + uint8_t last_port_value; + } _a7_interrupt; + + struct { + uint8_t output_mask, output; } _port[2]; + uint8_t _interrupt_status; + enum InterruptFlag: uint8_t { + Timer = 0x80, + PA7 = 0x40 + }; + // expected to be overridden uint8_t get_port_input(int port) { return 0xff; } - void set_port_output(int port, uint8_t value, uint8_t direction_mask) {} + void set_port_output(int port, uint8_t value, uint8_t output_mask) {} + void set_irq_line(bool new_value) {} + + inline void evaluate_interrupts() + { + set_irq_line( + ((_interrupt_status&InterruptFlag::Timer) && _timer.interrupt_enabled) || + ((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled) + ); + } }; } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index baa8dd0b0..08b3a3fef 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -60,6 +60,7 @@ class PIA: public MOS::MOS6532 { inline void update_port_input(int port, uint8_t mask, bool set) { if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask; + set_port_did_change(port); } PIA() : diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 5bd2c5653..7005dfe29 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -31,7 +31,7 @@ enum Register { /* Flags as defined on the 6502; can be used to decode the result of @c get_flags or to form a value for @c set_flags. */ -enum Flag { +enum Flag: uint8_t { Sign = 0x80, Overflow = 0x40, Always = 0x20, From fe17d1778c03896fdd924e57bc9465920c40962a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Jun 2016 21:02:42 -0400 Subject: [PATCH 2/3] Expanded 6532 tests substantially, beefing up implementation to match. --- Components/6532/6532.hpp | 19 +++- .../Mac/Clock SignalTests/6532Tests.swift | 98 ++++++++++++++++++- .../Mac/Clock SignalTests/MOS6532Bridge.mm | 5 + 3 files changed, 115 insertions(+), 7 deletions(-) diff --git a/Components/6532/6532.hpp b/Components/6532/6532.hpp index d2f68da2d..0b42515bf 100644 --- a/Components/6532/6532.hpp +++ b/Components/6532/6532.hpp @@ -38,10 +38,12 @@ template class MOS6532 { case 0x00: case 0x02: _port[decodedAddress / 2].output = value; static_cast(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask); + set_port_did_change(decodedAddress / 2); break; case 0x01: case 0x03: _port[decodedAddress / 2].output_mask = value; static_cast(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask); + set_port_did_change(decodedAddress / 2); break; // The timer and edge detect control @@ -125,7 +127,10 @@ template class MOS6532 { } MOS6532() : - _interrupt_status(0), _port{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, _a7_interrupt({.last_port_value = 0, .enabled = false}) + _interrupt_status(0), + _port{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, + _a7_interrupt({.last_port_value = 0, .enabled = false}), + _interrupt_line(false) {} inline void set_port_did_change(int port) @@ -149,6 +154,11 @@ template class MOS6532 { } } + inline bool get_inerrupt_line() + { + return _interrupt_line; + } + private: uint8_t _ram[128]; @@ -173,6 +183,7 @@ template class MOS6532 { Timer = 0x80, PA7 = 0x40 }; + bool _interrupt_line; // expected to be overridden uint8_t get_port_input(int port) { return 0xff; } @@ -181,10 +192,10 @@ template class MOS6532 { inline void evaluate_interrupts() { - set_irq_line( + _interrupt_line = ((_interrupt_status&InterruptFlag::Timer) && _timer.interrupt_enabled) || - ((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled) - ); + ((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled); + set_irq_line(_interrupt_line); } }; diff --git a/OSBindings/Mac/Clock SignalTests/6532Tests.swift b/OSBindings/Mac/Clock SignalTests/6532Tests.swift index 77bf396c9..8432f572a 100644 --- a/OSBindings/Mac/Clock SignalTests/6532Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/6532Tests.swift @@ -20,7 +20,7 @@ class MOS6532Tests: XCTestCase { func testOneTickTimer() { with6532 { // set a count of 128 at single-clock intervals - $0.setValue(128, forRegister:4) + $0.setValue(128, forRegister:0x14) // run for one clock and the count should now be 127 $0.runForCycles(1) @@ -32,11 +32,11 @@ class MOS6532Tests: XCTestCase { } } - // TODO: the test below makes the assumption that divider phase is flexible; verify + // TODO: the tests below makes the assumption that divider phase is flexible; verify func testEightTickTimer() { with6532 { // set a count of 28 at eight-clock intervals - $0.setValue(28, forRegister:5) + $0.setValue(28, forRegister:0x15) // run for seven clock and the count should still be 28 $0.runForCycles(7) @@ -59,4 +59,96 @@ class MOS6532Tests: XCTestCase { XCTAssert($0.valueForRegister(4) == 0xfa, "Timer should decrement after eighth cycle") } } + + func testTimerInterrupt() { + with6532 { + // set a count of 1 at single-clock intervals + $0.setValue(1, forRegister:0x1c) + + // run for one clock and the count should now be zero + $0.runForCycles(1) + + // interrupt shouldn't be signalled yet, bit should not be set + XCTAssert(!$0.irqLine, "IRQ line should not be set") + XCTAssert($0.valueForRegister(5) == 0x00, "Counter interrupt should not be set") + + // run for one more clock + $0.runForCycles(1) + + // the interrupt line and bit should now be set + XCTAssert($0.irqLine, "IRQ line should be set") + XCTAssert($0.valueForRegister(5) == 0x80, "Counter interrupt should be set") + + // writing again to the timer should clear both + $0.setValue(1, forRegister:0x1c) + XCTAssert(!$0.irqLine, "IRQ line should be clear") + XCTAssert($0.valueForRegister(5) == 0x00, "Counter interrupt should not be set") + } + } + + + // MARK: PA7 interrupt tests + func testPA7InterruptDisabled() { + with6532 { + // disable edge detection + $0.setValue(0, forRegister:4) + + // set output mode for port a + $0.setValue(0xff, forRegister:1) + + // toggle bit 7 of port a in both directions + $0.setValue(0x80, forRegister:0) + $0.setValue(0x00, forRegister:0) + $0.setValue(0x80, forRegister:0) + + // confirm that the interrupt flag is set but the line is not + XCTAssert(!$0.irqLine, "IRQ line should not be set") + XCTAssert($0.valueForRegister(5) == 0x40, "Timer interrupt bit should be set") + + // reading the status register should have reset the interrupt flag + XCTAssert($0.valueForRegister(5) == 0x00, "Timer interrupt bit should be reset") + } + } + + func testPA7LeadingEdge() { + with6532 { + // seed port a is high; ensure interrupt bit is clear + $0.setValue(0x00, forRegister:0) + $0.valueForRegister(5) + + // enable leading edge detection + $0.setValue(0, forRegister:7) + + // set output mode for port a + $0.setValue(0xff, forRegister:1) + + // toggle bit 7 of port a in a leading direction + $0.setValue(0x80, forRegister:0) + + // confirm that both the interrupt flag are the line are set + XCTAssert($0.irqLine, "IRQ line should be set") + XCTAssert($0.valueForRegister(5) == 0x40, "Timer interrupt bit should be set") + } + } + + func testPA7TrailingEdge() { + with6532 { + // seed port a is high; ensure interrupt bit is clear + $0.setValue(0x80, forRegister:0) + $0.valueForRegister(5) + + // enable trailing edge detection + $0.setValue(0, forRegister:6) + + // set output mode for port a + $0.setValue(0xff, forRegister:1) + + // toggle bit 7 of port a in a rising direction + $0.setValue(0x00, forRegister:0) + + // confirm that both the interrupt flag are the line are set + XCTAssert($0.irqLine, "IRQ line should be set") + XCTAssert($0.valueForRegister(5) == 0x40, "Timer interrupt bit should be set") + } + } } diff --git a/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm b/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm index c7db4c24f..884f199f2 100644 --- a/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm +++ b/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm @@ -32,4 +32,9 @@ class VanillaRIOT: public MOS::MOS6532 { _riot.run_for_cycles((int)numberOfCycles); } +- (BOOL)irqLine +{ + return _riot.get_inerrupt_line(); +} + @end From 25a5455d33f28e6582695d5aa270ab7f08fc6e54 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Jun 2016 21:07:01 -0400 Subject: [PATCH 3/3] Completed bridge interface. --- .../Mac/Clock SignalTests/MOS6532Bridge.mm | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm b/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm index 884f199f2..12ced175b 100644 --- a/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm +++ b/OSBindings/Mac/Clock SignalTests/MOS6532Bridge.mm @@ -10,6 +10,12 @@ #include "6532.hpp" class VanillaRIOT: public MOS::MOS6532 { + public: + uint8_t get_port_input(int port) + { + return input[port]; + } + uint8_t input[2]; }; @implementation MOS6532Bridge @@ -37,4 +43,14 @@ class VanillaRIOT: public MOS::MOS6532 { return _riot.get_inerrupt_line(); } +- (void)setPortAInput:(uint8_t)portAInput +{ + _riot.input[0] = _portAInput = portAInput; +} + +- (void)setPortBInput:(uint8_t)portBInput +{ + _riot.input[1] = _portBInput = portBInput; +} + @end