mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-28 22:29:36 +00:00
Merge pull request #27 from TomHarte/Further6532Tests
Increases unit testing of the 6532
This commit is contained in:
commit
2f00b841d8
@ -10,6 +10,7 @@
|
|||||||
#define _532_hpp
|
#define _532_hpp
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace MOS {
|
namespace MOS {
|
||||||
|
|
||||||
@ -33,24 +34,33 @@ template <class T> class MOS6532 {
|
|||||||
{
|
{
|
||||||
const uint8_t decodedAddress = address & 0x07;
|
const uint8_t decodedAddress = address & 0x07;
|
||||||
switch(decodedAddress) {
|
switch(decodedAddress) {
|
||||||
case 0x00:
|
// Port output
|
||||||
case 0x02:
|
case 0x00: case 0x02:
|
||||||
static_cast<T *>(this)->set_port_output(decodedAddress / 2, value, _port[decodedAddress / 2].direction);
|
_port[decodedAddress / 2].output = value;
|
||||||
_port[decodedAddress/2].output = value;
|
static_cast<T *>(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<T *>(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask);
|
||||||
|
set_port_did_change(decodedAddress / 2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x01:
|
// The timer and edge detect control
|
||||||
case 0x03:
|
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||||
_port[decodedAddress / 2].direction = value;
|
if(address & 0x10)
|
||||||
break;
|
{
|
||||||
|
_timer.writtenShift = _timer.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||||
case 0x04:
|
_timer.value = ((unsigned int)(value) << _timer.activeShift) | ((1 << _timer.activeShift)-1);
|
||||||
case 0x05:
|
_timer.interrupt_enabled = !!(address&0x08);
|
||||||
case 0x06:
|
_interrupt_status &= ~InterruptFlag::Timer;
|
||||||
case 0x07:
|
evaluate_interrupts();
|
||||||
_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);
|
else
|
||||||
_timer.status &= ~0x80;
|
{
|
||||||
|
_a7_interrupt.enabled = !!(address&0x2);
|
||||||
|
_a7_interrupt.active_on_positive = !!(address & 0x01);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,22 +69,25 @@ template <class T> class MOS6532 {
|
|||||||
{
|
{
|
||||||
const uint8_t decodedAddress = address & 0x7;
|
const uint8_t decodedAddress = address & 0x7;
|
||||||
switch(decodedAddress) {
|
switch(decodedAddress) {
|
||||||
case 0x00:
|
// Port input
|
||||||
case 0x02:
|
case 0x00: case 0x02:
|
||||||
{
|
{
|
||||||
const int port = decodedAddress / 2;
|
const int port = decodedAddress / 2;
|
||||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
uint8_t input = static_cast<T *>(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;
|
break;
|
||||||
case 0x01:
|
case 0x01: case 0x03:
|
||||||
case 0x03:
|
return _port[decodedAddress / 2].output_mask;
|
||||||
return _port[decodedAddress / 2].direction;
|
|
||||||
break;
|
break;
|
||||||
case 0x04:
|
|
||||||
case 0x06:
|
// Timer and interrupt control
|
||||||
|
case 0x04: case 0x06:
|
||||||
{
|
{
|
||||||
uint8_t value = (uint8_t)(_timer.value >> _timer.activeShift);
|
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) {
|
if(_timer.activeShift != _timer.writtenShift) {
|
||||||
unsigned int shift = _timer.writtenShift - _timer.activeShift;
|
unsigned int shift = _timer.writtenShift - _timer.activeShift;
|
||||||
@ -85,11 +98,12 @@ template <class T> class MOS6532 {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x05:
|
|
||||||
case 0x07:
|
case 0x05: case 0x07:
|
||||||
{
|
{
|
||||||
uint8_t value = _timer.status;
|
uint8_t value = _interrupt_status;
|
||||||
_timer.status &= ~0x40;
|
_interrupt_status &= ~InterruptFlag::PA7;
|
||||||
|
evaluate_interrupts();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -100,36 +114,89 @@ template <class T> class MOS6532 {
|
|||||||
|
|
||||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
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) {
|
if(_timer.value >= number_of_cycles) {
|
||||||
_timer.value -= number_of_cycles;
|
_timer.value -= number_of_cycles;
|
||||||
} else {
|
} else {
|
||||||
number_of_cycles -= _timer.value;
|
number_of_cycles -= _timer.value;
|
||||||
_timer.value = 0x100 - number_of_cycles;
|
_timer.value = 0x100 - number_of_cycles;
|
||||||
_timer.activeShift = 0;
|
_timer.activeShift = 0;
|
||||||
_timer.status |= 0xc0;
|
_interrupt_status |= InterruptFlag::Timer;
|
||||||
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MOS6532() :
|
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}),
|
||||||
|
_interrupt_line(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool get_inerrupt_line()
|
||||||
|
{
|
||||||
|
return _interrupt_line;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t _ram[128];
|
uint8_t _ram[128];
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
unsigned int value;
|
unsigned int value;
|
||||||
unsigned int activeShift, writtenShift;
|
unsigned int activeShift, writtenShift;
|
||||||
uint8_t status;
|
bool interrupt_enabled;
|
||||||
} _timer;
|
} _timer;
|
||||||
|
|
||||||
struct {
|
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];
|
} _port[2];
|
||||||
|
|
||||||
|
uint8_t _interrupt_status;
|
||||||
|
enum InterruptFlag: uint8_t {
|
||||||
|
Timer = 0x80,
|
||||||
|
PA7 = 0x40
|
||||||
|
};
|
||||||
|
bool _interrupt_line;
|
||||||
|
|
||||||
// expected to be overridden
|
// expected to be overridden
|
||||||
uint8_t get_port_input(int port) { return 0xff; }
|
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()
|
||||||
|
{
|
||||||
|
_interrupt_line =
|
||||||
|
((_interrupt_status&InterruptFlag::Timer) && _timer.interrupt_enabled) ||
|
||||||
|
((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled);
|
||||||
|
set_irq_line(_interrupt_line);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ class PIA: public MOS::MOS6532<PIA> {
|
|||||||
inline void update_port_input(int port, uint8_t mask, bool set)
|
inline void update_port_input(int port, uint8_t mask, bool set)
|
||||||
{
|
{
|
||||||
if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask;
|
if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask;
|
||||||
|
set_port_did_change(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
PIA() :
|
PIA() :
|
||||||
|
@ -20,7 +20,7 @@ class MOS6532Tests: XCTestCase {
|
|||||||
func testOneTickTimer() {
|
func testOneTickTimer() {
|
||||||
with6532 {
|
with6532 {
|
||||||
// set a count of 128 at single-clock intervals
|
// 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
|
// run for one clock and the count should now be 127
|
||||||
$0.runForCycles(1)
|
$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() {
|
func testEightTickTimer() {
|
||||||
with6532 {
|
with6532 {
|
||||||
// set a count of 28 at eight-clock intervals
|
// 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
|
// run for seven clock and the count should still be 28
|
||||||
$0.runForCycles(7)
|
$0.runForCycles(7)
|
||||||
@ -59,4 +59,96 @@ class MOS6532Tests: XCTestCase {
|
|||||||
XCTAssert($0.valueForRegister(4) == 0xfa, "Timer should decrement after eighth cycle")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
#include "6532.hpp"
|
#include "6532.hpp"
|
||||||
|
|
||||||
class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
|
class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
|
||||||
|
public:
|
||||||
|
uint8_t get_port_input(int port)
|
||||||
|
{
|
||||||
|
return input[port];
|
||||||
|
}
|
||||||
|
uint8_t input[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
@implementation MOS6532Bridge
|
@implementation MOS6532Bridge
|
||||||
@ -32,4 +38,19 @@ class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
|
|||||||
_riot.run_for_cycles((int)numberOfCycles);
|
_riot.run_for_cycles((int)numberOfCycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)irqLine
|
||||||
|
{
|
||||||
|
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
|
@end
|
||||||
|
@ -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.
|
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,
|
Sign = 0x80,
|
||||||
Overflow = 0x40,
|
Overflow = 0x40,
|
||||||
Always = 0x20,
|
Always = 0x20,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user