1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #26 from TomHarte/FactoredOut6532

Factored out the 6532 RIOT
This commit is contained in:
Thomas Harte 2016-06-19 20:26:47 -04:00 committed by GitHub
commit 96d538359d
18 changed files with 548 additions and 101 deletions

View File

@ -1,12 +0,0 @@
//
// 6522.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "6522.hpp"
using namespace MOS;

View File

@ -14,6 +14,17 @@
namespace MOS {
/*!
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
The VIA provides:
* two timers, each of which may trigger interrupts and one of which may repeat;
* two digial input/output ports; and
* a serial-to-parallel shifter.
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522 {
private:
enum InterruptFlag: uint8_t {
@ -27,7 +38,8 @@ template <class T> class MOS6522 {
};
public:
void set_register(int address, uint8_t value)
/*! Sets a register value. */
inline void set_register(int address, uint8_t value)
{
address &= 0xf;
// printf("6522 %p: %d <- %02x\n", this, address, value);
@ -98,7 +110,8 @@ template <class T> class MOS6522 {
}
}
uint8_t get_register(int address)
/*! Gets a register value. */
inline uint8_t get_register(int address)
{
address &= 0xf;
// printf("6522 %p: %d\n", this, address);
@ -139,11 +152,21 @@ template <class T> class MOS6522 {
return 0xff;
}
void set_control_line_input(int port, int line, bool value)
inline void set_control_line_input(int port, int line, bool value)
{
}
void run_for_half_cycles(unsigned int number_of_cycles)
/*!
Runs for a specified number of half cycles.
Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring
1.5 cycles after the timer hits zero. It is therefore necessary to emulate at half-cycle precision.
The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the
next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be
that which occurs during phase-2.
*/
inline void run_for_half_cycles(unsigned int number_of_cycles)
{
while(number_of_cycles--)
{
@ -188,7 +211,8 @@ template <class T> class MOS6522 {
}
}
bool get_interrupt_line()
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
inline bool get_interrupt_line()
{
uint8_t interrupt_status = _registers.interrupt_flags & _registers.interrupt_enable & 0x7f;
return !!interrupt_status;
@ -249,6 +273,10 @@ template <class T> class MOS6522 {
bool _timer_is_running[2];
};
/*!
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
that will receive IRQ line change notifications.
*/
class MOS6522IRQDelegate {
public:
class Delegate {

9
Components/6532/6532.cpp Normal file
View File

@ -0,0 +1,9 @@
//
// 6532.cpp
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "6532.hpp"

137
Components/6532/6532.hpp Normal file
View File

@ -0,0 +1,137 @@
//
// 6532.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef _532_hpp
#define _532_hpp
#include <cstdint>
namespace MOS {
/*!
Implements a template for emulation of the MOS 6532 RAM-I/O-Timer ('RIOT').
The RIOT provides:
* 128 bytes of static RAM;
* an interval timer; and
* two digital input/output ports.
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6532 {
public:
inline void set_ram(uint16_t address, uint8_t value) { _ram[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return _ram[address & 0x7f]; }
inline void set_register(int address, uint8_t value)
{
const uint8_t decodedAddress = address & 0x0f;
switch(decodedAddress) {
case 0x00:
case 0x02:
static_cast<T *>(this)->set_port_output(decodedAddress / 2, value, _port[decodedAddress / 2].direction);
_port[decodedAddress/2].output = value;
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;
break;
}
}
inline uint8_t get_register(int address)
{
const uint8_t decodedAddress = address & 0xf;
switch(decodedAddress) {
case 0x00:
case 0x02:
{
const int port = decodedAddress / 2;
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~_port[port].direction) | (_port[port].output & _port[port].direction);
}
break;
case 0x01:
case 0x03:
return _port[decodedAddress / 2].direction;
break;
case 0x04:
case 0x06:
{
uint8_t value = (uint8_t)(_timer.value >> _timer.activeShift);
if(_timer.activeShift != _timer.writtenShift) {
unsigned int shift = _timer.writtenShift - _timer.activeShift;
_timer.value = (_timer.value << shift) | ((1 << shift) - 1);
_timer.activeShift = _timer.writtenShift;
}
return value;
}
break;
case 0x05:
case 0x07:
{
uint8_t value = _timer.status;
_timer.status &= ~0x40;
return value;
}
break;
}
return 0xff;
}
inline void run_for_cycles(unsigned int number_of_cycles)
{
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;
}
}
MOS6532() :
_timer({.status = 0})
{}
private:
uint8_t _ram[128];
struct {
unsigned int value;
unsigned int activeShift, writtenShift;
uint8_t status;
} _timer;
struct {
uint8_t direction, output;
} _port[2];
// 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) {}
};
}
#endif /* _532_hpp */

View File

@ -15,9 +15,9 @@
namespace MOS {
/*!
The 6560 is a video and audio output chip; it therefore vends both a @c CRT and a @c Speaker.
The 6560 Video Interface Chip ('VIC') is a video and audio output chip; it therefore vends both a @c CRT and a @c Speaker.
To run the 6560 for a cycle, the caller should call @c get_address, make the requested bus access
To run the VIC for a cycle, the caller should call @c get_address, make the requested bus access
and call @c set_graphics_value with the result.
@c set_register and @c get_register provide register access.

View File

@ -19,9 +19,7 @@ Machine::Machine() :
_horizontalTimer(0),
_lastOutputStateDuration(0),
_lastOutputState(OutputState::Sync),
_piaTimerStatus(0xff),
_rom(nullptr),
_piaDataValue{0xff, 0xff},
_tiaInputValue{0xff, 0xff},
_upcomingEventsPointer(0),
_objectCounterPointer(0),
@ -455,9 +453,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// check for a RAM access
if((address&0x1280) == 0x80) {
if(isReadOperation(operation)) {
returnValue &= _ram[address&0x7f];
returnValue &= _mos6532.get_ram(address);
} else {
_ram[address&0x7f] = *value;
_mos6532.set_ram(address, *value);
}
}
@ -687,44 +685,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// check for a PIA access
if((address&0x1280) == 0x280) {
if(isReadOperation(operation)) {
const uint8_t decodedAddress = address & 0xf;
switch(address & 0xf) {
case 0x00:
case 0x02:
returnValue &= _piaDataValue[decodedAddress / 2];
break;
case 0x01:
case 0x03:
// TODO: port DDR
printf("!!!DDR!!!");
break;
case 0x04:
case 0x06:
returnValue &= _piaTimerValue >> _piaTimerShift;
if(_writtenPiaTimerShift != _piaTimerShift) {
_piaTimerShift = _writtenPiaTimerShift;
_piaTimerValue <<= _writtenPiaTimerShift;
}
break;
case 0x05:
case 0x07:
returnValue &= _piaTimerStatus;
_piaTimerStatus &= ~0x80;
break;
}
returnValue &= _mos6532.get_register(address);
} else {
const uint8_t decodedAddress = address & 0x0f;
switch(decodedAddress) {
case 0x04:
case 0x05:
case 0x06:
case 0x07:
_writtenPiaTimerShift = _piaTimerShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
_piaTimerValue = ((unsigned int)(*value) << _piaTimerShift) | ((1 << _piaTimerShift)-1);
_piaTimerStatus &= ~0x40;
break;
}
_mos6532.set_register(address, *value);
}
}
@ -733,24 +696,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
}
if(_piaTimerValue >= cycles_run_for / 3) {
_piaTimerValue -= cycles_run_for / 3;
} else {
_piaTimerValue = 0x100 + ((_piaTimerValue - (cycles_run_for / 3)) >> _piaTimerShift);
_piaTimerShift = 0;
_piaTimerStatus |= 0xc0;
}
// static unsigned int total_cycles = 0;
// total_cycles += cycles_run_for / 3;
// static time_t logged_time = 0;
// time_t time_now = time(nullptr);
// if(time_now - logged_time > 0)
// {
// printf("[c] %ld : %d\n", time_now - logged_time, total_cycles);
// total_cycles = 0;
// logged_time = time_now;
// }
_mos6532.run_for_cycles(cycles_run_for / 3);
return cycles_run_for / 3;
}
@ -758,15 +704,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
void Machine::set_digital_input(Atari2600DigitalInput input, bool state)
{
switch (input) {
case Atari2600DigitalInputJoy1Up: if(state) _piaDataValue[0] &= ~0x10; else _piaDataValue[0] |= 0x10; break;
case Atari2600DigitalInputJoy1Down: if(state) _piaDataValue[0] &= ~0x20; else _piaDataValue[0] |= 0x20; break;
case Atari2600DigitalInputJoy1Left: if(state) _piaDataValue[0] &= ~0x40; else _piaDataValue[0] |= 0x40; break;
case Atari2600DigitalInputJoy1Right: if(state) _piaDataValue[0] &= ~0x80; else _piaDataValue[0] |= 0x80; break;
case Atari2600DigitalInputJoy1Up: _mos6532.update_port_input(0, 0x10, state); break;
case Atari2600DigitalInputJoy1Down: _mos6532.update_port_input(0, 0x20, state); break;
case Atari2600DigitalInputJoy1Left: _mos6532.update_port_input(0, 0x40, state); break;
case Atari2600DigitalInputJoy1Right: _mos6532.update_port_input(0, 0x80, state); break;
case Atari2600DigitalInputJoy2Up: if(state) _piaDataValue[0] &= ~0x01; else _piaDataValue[0] |= 0x01; break;
case Atari2600DigitalInputJoy2Down: if(state) _piaDataValue[0] &= ~0x02; else _piaDataValue[0] |= 0x02; break;
case Atari2600DigitalInputJoy2Left: if(state) _piaDataValue[0] &= ~0x04; else _piaDataValue[0] |= 0x04; break;
case Atari2600DigitalInputJoy2Right: if(state) _piaDataValue[0] &= ~0x08; else _piaDataValue[0] |= 0x08; break;
case Atari2600DigitalInputJoy2Up: _mos6532.update_port_input(0, 0x01, state); break;
case Atari2600DigitalInputJoy2Down: _mos6532.update_port_input(0, 0x02, state); break;
case Atari2600DigitalInputJoy2Left: _mos6532.update_port_input(0, 0x04, state); break;
case Atari2600DigitalInputJoy2Right: _mos6532.update_port_input(0, 0x08, state); break;
// TODO: latching
case Atari2600DigitalInputJoy1Fire: if(state) _tiaInputValue[0] &= ~0x80; else _tiaInputValue[0] |= 0x80; break;
@ -776,6 +722,18 @@ void Machine::set_digital_input(Atari2600DigitalInput input, bool state)
}
}
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state)
{
switch(input) {
case Atari2600SwitchReset: _mos6532.update_port_input(1, 0x01, state); break;
case Atari2600SwitchSelect: _mos6532.update_port_input(1, 0x02, state); break;
case Atari2600SwitchColour: _mos6532.update_port_input(1, 0x08, state); break;
case Atari2600SwitchLeftPlayerDifficulty: _mos6532.update_port_input(1, 0x40, state); break;
case Atari2600SwitchRightPlayerDifficulty: _mos6532.update_port_input(1, 0x80, state); break;
}
}
void Machine::set_rom(size_t length, const uint8_t *data)
{
_rom_size = 1024;

View File

@ -9,9 +9,12 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#include "../../Processors/6502/CPU6502.hpp"
#include "../CRTMachine.hpp"
#include <stdint.h>
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Components/6532/6532.hpp"
#include "../CRTMachine.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
@ -47,6 +50,28 @@ class Speaker: public ::Outputs::Filter<Speaker> {
int _patterns[16][512];
};
class PIA: public MOS::MOS6532<PIA> {
public:
inline uint8_t get_port_input(int port)
{
return _portValues[port];
}
inline void update_port_input(int port, uint8_t mask, bool set)
{
if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask;
}
PIA() :
_portValues{0xff, 0xff}
{}
private:
uint8_t _portValues[2];
};
class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
public:
@ -57,6 +82,7 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
void switch_region();
void set_digital_input(Atari2600DigitalInput input, bool state);
void set_switch_is_enabled(Atari2600Switch input, bool state);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
@ -72,13 +98,11 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
// TODO: different rate for PAL
private:
uint8_t *_rom, *_romPages[4], _ram[128];
uint8_t *_rom, *_romPages[4];
size_t _rom_size;
// the timer
unsigned int _piaTimerValue;
unsigned int _piaTimerShift, _writtenPiaTimerShift;
uint8_t _piaTimerStatus;
// the RIOT
PIA _mos6532;
// playfield registers
uint8_t _playfieldControl;
@ -162,8 +186,6 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
uint8_t _hMoveFlags;
// joystick state
uint8_t _piaDataDirection[2];
uint8_t _piaDataValue[2];
uint8_t _tiaInputValue[2];
// collisions

View File

@ -27,6 +27,14 @@ typedef enum {
Atari2600DigitalInputJoy2Fire,
} Atari2600DigitalInput;
typedef enum {
Atari2600SwitchReset,
Atari2600SwitchSelect,
Atari2600SwitchColour,
Atari2600SwitchLeftPlayerDifficulty,
Atari2600SwitchRightPlayerDifficulty
} Atari2600Switch;
#ifdef __cplusplus
}
#endif

View File

@ -14,6 +14,9 @@
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
4B1E857C1D174DEC001EF87D /* 6532.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E857A1D174DEC001EF87D /* 6532.cpp */; };
4B1E857F1D17644D001EF87D /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E857E1D17644D001EF87D /* MOS6532Bridge.mm */; };
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; };
@ -320,7 +323,6 @@
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
4BCA98C31D065CA20062F44C /* 6522.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCA98C11D065CA20062F44C /* 6522.cpp */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
/* End PBXBuildFile section */
@ -355,6 +357,11 @@
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
4B1E85731D170228001EF87D /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B1E857A1D174DEC001EF87D /* 6532.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6532.cpp; sourceTree = "<group>"; };
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
4B1E857D1D17644D001EF87D /* MOS6532Bridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOS6532Bridge.h; sourceTree = "<group>"; };
4B1E857E1D17644D001EF87D /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = "<group>"; };
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
@ -699,7 +706,6 @@
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = "<group>"; };
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
4BCA98C11D065CA20062F44C /* 6522.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6522.cpp; sourceTree = "<group>"; };
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
@ -765,6 +771,15 @@
name = "Test Binaries";
sourceTree = "<group>";
};
4B1E85791D174DEC001EF87D /* 6532 */ = {
isa = PBXGroup;
children = (
4B1E857A1D174DEC001EF87D /* 6532.cpp */,
4B1E857B1D174DEC001EF87D /* 6532.hpp */,
);
path = 6532;
sourceTree = "<group>";
};
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
isa = PBXGroup;
children = (
@ -1224,11 +1239,14 @@
children = (
4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */,
4BC751B41D157EB3006C31D9 /* MOS6522Bridge.h */,
4B1E857D1D17644D001EF87D /* MOS6532Bridge.h */,
4BB297E21B587D8300A49093 /* TestMachine.h */,
4B1E857E1D17644D001EF87D /* MOS6532Bridge.mm */,
4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */,
4BB297E31B587D8300A49093 /* TestMachine.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4B1E85801D176468001EF87D /* 6532Tests.swift */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4B92EAC91B7C112B00246143 /* TimingTests.swift */,
@ -1304,6 +1322,7 @@
isa = PBXGroup;
children = (
4BC9DF4B1D04691600F44158 /* 6522 */,
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
);
name = Components;
@ -1313,7 +1332,6 @@
4BC9DF4B1D04691600F44158 /* 6522 */ = {
isa = PBXGroup;
children = (
4BCA98C11D065CA20062F44C /* 6522.cpp */,
4BCA98C21D065CA20062F44C /* 6522.hpp */,
);
path = 6522;
@ -1764,8 +1782,8 @@
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4BCA98C31D065CA20062F44C /* 6522.cpp in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4B1E857C1D174DEC001EF87D /* 6532.cpp in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
@ -1791,11 +1809,13 @@
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */,
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */,
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */,
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */,
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
4BB298F01B587D8400A49093 /* TestMachine.mm in Sources */,
4B1E857F1D17644D001EF87D /* MOS6532Bridge.mm in Sources */,
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -6,7 +6,13 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Atari2600Document" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="colourButton" destination="3qw-C1-NYW" id="WAL-Is-Ux6"/>
<outlet property="leftPlayerDifficultyButton" destination="Xbc-cw-Sc2" id="1vg-wY-j1w"/>
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
<outlet property="optionsPanel" destination="gsl-7V-TTU" id="BEE-05-h0B"/>
<outlet property="resetButton" destination="rQO-uD-fwn" id="DHc-IK-zBm"/>
<outlet property="rightPlayerDifficultyButton" destination="kPV-Tm-TTc" id="N2H-KG-R8Z"/>
<outlet property="selectButton" destination="nt7-8K-xY9" id="bu6-U1-AZb"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
</connections>
</customObject>
@ -37,6 +43,89 @@
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
</connections>
<point key="canvasLocation" x="272" y="446"/>
</window>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="121"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<value key="minSize" type="size" width="200" height="83"/>
<value key="maxSize" type="size" width="200" height="83"/>
<view key="contentView" id="aQh-Pm-DEo">
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rQO-uD-fwn">
<rect key="frame" x="14" y="73" width="86" height="32"/>
<buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l3H-0m-aK0">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionWasPressed:" target="-2" id="XCT-S9-qhg"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="3qw-C1-NYW">
<rect key="frame" x="18" y="58" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Black and White" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="UP7-mf-IKo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionDidChange:" target="-2" id="jXW-nl-ePC"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Xbc-cw-Sc2">
<rect key="frame" x="18" y="38" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Left Player Difficulty" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="wlJ-8s-PEh">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionDidChange:" target="-2" id="Cwv-nj-FY1"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="kPV-Tm-TTc">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Right Player Difficulty" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="F05-cA-66S">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionDidChange:" target="-2" id="L4v-Zp-Ndu"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nt7-8K-xY9">
<rect key="frame" x="100" y="73" width="86" height="32"/>
<buttonCell key="cell" type="push" title="Select" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8Na-Z1-EXS">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionWasPressed:" target="-2" id="pKQ-6M-BB4"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="3qw-C1-NYW" firstAttribute="leading" secondItem="aQh-Pm-DEo" secondAttribute="leading" constant="20" id="1Ri-ZO-GJy"/>
<constraint firstItem="nt7-8K-xY9" firstAttribute="leading" secondItem="rQO-uD-fwn" secondAttribute="trailing" constant="12" id="46p-Z3-kgW"/>
<constraint firstItem="nt7-8K-xY9" firstAttribute="top" secondItem="aQh-Pm-DEo" secondAttribute="top" constant="20" id="6Uc-12-11y"/>
<constraint firstItem="Xbc-cw-Sc2" firstAttribute="leading" secondItem="aQh-Pm-DEo" secondAttribute="leading" constant="20" id="7es-iv-JOh"/>
<constraint firstItem="kPV-Tm-TTc" firstAttribute="top" secondItem="Xbc-cw-Sc2" secondAttribute="bottom" constant="6" id="Env-nl-M2e"/>
<constraint firstAttribute="trailing" secondItem="kPV-Tm-TTc" secondAttribute="trailing" constant="20" id="Fim-Ej-8Ux"/>
<constraint firstAttribute="trailing" secondItem="Xbc-cw-Sc2" secondAttribute="trailing" constant="20" id="HkS-6c-WZm"/>
<constraint firstItem="3qw-C1-NYW" firstAttribute="top" secondItem="nt7-8K-xY9" secondAttribute="bottom" constant="6" id="Hxq-Pm-o4G"/>
<constraint firstAttribute="trailing" secondItem="nt7-8K-xY9" secondAttribute="trailing" constant="20" id="JRO-de-WQp"/>
<constraint firstItem="rQO-uD-fwn" firstAttribute="top" secondItem="aQh-Pm-DEo" secondAttribute="top" constant="20" id="N3p-aY-2Nx"/>
<constraint firstItem="nt7-8K-xY9" firstAttribute="width" secondItem="rQO-uD-fwn" secondAttribute="width" id="NOc-hJ-8Mm"/>
<constraint firstItem="Xbc-cw-Sc2" firstAttribute="top" secondItem="3qw-C1-NYW" secondAttribute="bottom" constant="6" id="ORX-bF-2WS"/>
<constraint firstItem="kPV-Tm-TTc" firstAttribute="leading" secondItem="aQh-Pm-DEo" secondAttribute="leading" constant="20" id="x8p-Hm-xeu"/>
<constraint firstItem="rQO-uD-fwn" firstAttribute="leading" secondItem="aQh-Pm-DEo" secondAttribute="leading" constant="20" id="xhD-iY-vt2"/>
<constraint firstAttribute="trailing" secondItem="3qw-C1-NYW" secondAttribute="trailing" constant="20" id="yff-e9-OBY"/>
</constraints>
</view>
<point key="canvasLocation" x="157" y="12.5"/>
</window>
</objects>
</document>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
</dependencies>

View File

@ -29,7 +29,6 @@ class Atari2600Document: MachineDocument {
}
// MARK: CSOpenGLViewResponderDelegate
private func inputForKey(event: NSEvent) -> Atari2600DigitalInput? {
switch event.keyCode {
case 123: return Atari2600DigitalInputJoy1Left
@ -64,4 +63,25 @@ class Atari2600Document: MachineDocument {
atari2600.setResetLineEnabled(false)
}
}
// MARK: Options
@IBOutlet var resetButton: NSButton!
@IBOutlet var selectButton: NSButton!
@IBOutlet var colourButton: NSButton!
@IBOutlet var leftPlayerDifficultyButton: NSButton!
@IBOutlet var rightPlayerDifficultyButton: NSButton!
@IBAction func optionDidChange(sender: AnyObject!) {
atari2600.colourButton = colourButton.state == NSOnState
atari2600.leftPlayerDifficultyButton = leftPlayerDifficultyButton.state == NSOnState
atari2600.rightPlayerDifficultyButton = rightPlayerDifficultyButton.state == NSOnState
}
@IBAction func optionWasPressed(sender: NSButton!) {
if sender == resetButton {
atari2600.pressResetButton()
} else {
atari2600.pressSelectButton()
}
}
}

View File

@ -15,4 +15,10 @@
- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput;
- (void)setResetLineEnabled:(BOOL)enabled;
@property (nonatomic, assign) BOOL colourButton;
@property (nonatomic, assign) BOOL leftPlayerDifficultyButton;
@property (nonatomic, assign) BOOL rightPlayerDifficultyButton;
- (void)pressResetButton;
- (void)pressSelectButton;
@end

View File

@ -79,4 +79,46 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
return &_atari2600;
}
#pragma mark - Switches
- (void)setColourButton:(BOOL)colourButton {
_colourButton = colourButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchColour, colourButton);
}
}
- (void)setLeftPlayerDifficultyButton:(BOOL)leftPlayerDifficultyButton {
_leftPlayerDifficultyButton = leftPlayerDifficultyButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton);
}
}
- (void)setRightPlayerDifficultyButton:(BOOL)rightPlayerDifficultyButton {
_rightPlayerDifficultyButton = rightPlayerDifficultyButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton);
}
}
- (void)toggleSwitch:(Atari2600Switch)toggleSwitch {
@synchronized(self) {
_atari2600.set_switch_is_enabled(toggleSwitch, true);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@synchronized(self) {
_atari2600.set_switch_is_enabled(toggleSwitch, false);
}
});
}
- (void)pressResetButton {
[self toggleSwitch:Atari2600SwitchReset];
}
- (void)pressSelectButton {
[self toggleSwitch:Atari2600SwitchSelect];
}
@end

View File

@ -0,0 +1,62 @@
//
// 6532Tests.swift
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
import XCTest
import Foundation
class MOS6532Tests: XCTestCase {
private func with6532(action: (MOS6532Bridge) -> ()) {
let bridge = MOS6532Bridge()
action(bridge)
}
// MARK: Timer tests
func testOneTickTimer() {
with6532 {
// set a count of 128 at single-clock intervals
$0.setValue(128, forRegister:4)
// run for one clock and the count should now be 127
$0.runForCycles(1)
XCTAssert($0.valueForRegister(4) == 127, "A single tick should decrease the counter once")
// run for a further 200 clock counts; timer should reach -73 = 183
$0.runForCycles(200)
XCTAssert($0.valueForRegister(4) == 183, "Timer should underflow and keep counting")
}
}
// TODO: the test 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)
// run for seven clock and the count should still be 28
$0.runForCycles(7)
XCTAssert($0.valueForRegister(4) == 28, "The timer should remain unchanged for seven clocks")
// run for a further clock and the count should now be 27
$0.runForCycles(1)
XCTAssert($0.valueForRegister(4) == 27, "The timer should have decremented once after 8 cycles")
// run for a further 7 + 27*8 + 5 = 228 clock counts; timer should reach -5 = 0xfb
$0.runForCycles(228)
XCTAssert($0.valueForRegister(4) == 0xfb, "Timer should underflow and start counting at single-clock pace")
// timer should now resume dividing by eight
$0.runForCycles(7)
XCTAssert($0.valueForRegister(4) == 0xfb, "Timer should remain unchanged for seven cycles")
// timer should now resume dividing by eight
$0.runForCycles(1)
XCTAssert($0.valueForRegister(4) == 0xfa, "Timer should decrement after eighth cycle")
}
}
}

View File

@ -4,3 +4,4 @@
#import "TestMachine.h"
#import "MOS6522Bridge.h"
#import "MOS6532Bridge.h"

View File

@ -0,0 +1,22 @@
//
// MOS6532Bridge.h
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MOS6532Bridge : NSObject
@property (nonatomic, readonly) BOOL irqLine;
@property (nonatomic) uint8_t portBInput;
@property (nonatomic) uint8_t portAInput;
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber;
- (uint8_t)valueForRegister:(NSUInteger)registerNumber;
- (void)runForCycles:(NSUInteger)numberOfCycles;
@end

View File

@ -0,0 +1,35 @@
//
// MOS6532Bridge.m
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "MOS6532Bridge.h"
#include "6532.hpp"
class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
};
@implementation MOS6532Bridge
{
VanillaRIOT _riot;
}
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber
{
_riot.set_register((int)registerNumber, value);
}
- (uint8_t)valueForRegister:(NSUInteger)registerNumber
{
return _riot.get_register((int)registerNumber);
}
- (void)runForCycles:(NSUInteger)numberOfCycles
{
_riot.run_for_cycles((int)numberOfCycles);
}
@end