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

Merge pull request #158 from TomHarte/HalfCycles

Formalises run_for_cycles, offering half- and full-length cycle options
This commit is contained in:
Thomas Harte 2017-07-25 22:34:35 -04:00 committed by GitHub
commit e90d128a26
63 changed files with 524 additions and 375 deletions

View File

@ -0,0 +1,171 @@
//
// ClockReceiver.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp
/*!
Provides a class that wraps a plain int, providing most of the basic arithmetic and
Boolean operators, but forcing callers and receivers to be explicit as to usage.
*/
template <class T> class WrappedInt {
public:
inline WrappedInt(int l) : length_(l) {}
inline WrappedInt() : length_(0) {}
inline T &operator =(const T &rhs) {
length_ = rhs.length_;
return *this;
}
inline T &operator +=(const T &rhs) {
length_ += rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator -=(const T &rhs) {
length_ -= rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator ++() {
++ length_;
return *static_cast<T *>(this);
}
inline T &operator ++(int) {
length_ ++;
return *static_cast<T *>(this);
}
inline T &operator --() {
-- length_;
return *static_cast<T *>(this);
}
inline T &operator --(int) {
length_ --;
return *static_cast<T *>(this);
}
inline T &operator %=(const T &rhs) {
length_ %= rhs.length_;
return *static_cast<T *>(this);
}
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
inline bool operator !() const { return !length_; }
inline operator bool() const { return !!length_; }
inline int as_int() const { return length_; }
/*!
Severs from @c this the effect of dividing by @c divisor @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
inline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
}
/*!
Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero.
*/
inline T flush() {
T result(length_);
length_ = 0;
return result;
}
// operator int() is deliberately not provided, to avoid accidental subtitution of
// classes that use this template.
protected:
int length_;
};
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
inline Cycles() : WrappedInt<Cycles>() {}
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
};
/// Describes an integer number of half cycles — single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
inline HalfCycles() : WrappedInt<HalfCycles>() {}
inline HalfCycles(const Cycles &cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {}
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
};
/*!
ClockReceiver is a template for components that receove a clock, measured either
in cycles or in half cycles. They are expected to implement either of the run_for
methods and to declare that they are `using` the other; buying into the template
means that the other run_for will automatically map appropriately to the implemented
one, so callers may use either.
Alignment rule:
run_for(Cycles) may be called only at the start of a cycle. E.g. the following
sequence will have undefined results:
run_for(HalfCycles(1))
run_for(Cycles(1))
An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
run_for(HalfCycles) to use.
Reasoning:
Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
where there is a need to implement at half-cycle precision but a faster execution
path can be offered for full-cycle precision. Those users are permitted to assume
phase in run_for(Cycles) and should do so to be compatible with callers that use
only run_for(Cycles).
Corollary:
Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
of a full cycle. The second will do the second half. Etc.
*/
template <class T> class ClockReceiver {
public:
ClockReceiver() : half_cycle_carry_(0) {}
inline void run_for(const Cycles &cycles) {
static_cast<T *>(this)->run_for(HalfCycles(cycles));
}
inline void run_for(const HalfCycles &half_cycles) {
int cycles = half_cycles.as_int() + half_cycle_carry_;
half_cycle_carry_ = cycles & 1;
static_cast<T *>(this)->run_for(Cycles(cycles >> 1));
}
private:
int half_cycle_carry_;
};
#endif /* ClockReceiver_hpp */

View File

@ -124,10 +124,11 @@ uint8_t WD1770::get_register(int address) {
} }
} }
void WD1770::run_for_cycles(unsigned int number_of_cycles) { void WD1770::run_for(const Cycles &cycles) {
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); Storage::Disk::Controller::run_for(cycles);
if(delay_time_) { if(delay_time_) {
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
if(delay_time_ <= number_of_cycles) { if(delay_time_ <= number_of_cycles) {
delay_time_ = 0; delay_time_ = 0;
posit_event(Event::Timer); posit_event(Event::Timer);

View File

@ -43,7 +43,8 @@ class WD1770: public Storage::Disk::Controller {
uint8_t get_register(int address); uint8_t get_register(int address);
/// Runs the controller for @c number_of_cycles cycles. /// Runs the controller for @c number_of_cycles cycles.
void run_for_cycles(unsigned int number_of_cycles); void run_for(const Cycles &cycles);
using Storage::Disk::Controller::run_for;
enum Flag: uint8_t { enum Flag: uint8_t {
NotReady = 0x80, NotReady = 0x80,

View File

@ -13,6 +13,8 @@
#include <typeinfo> #include <typeinfo>
#include <cstdio> #include <cstdio>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS { namespace MOS {
/*! /*!
@ -26,7 +28,7 @@ namespace MOS {
Consumers should derive their own curiously-recurring-template-pattern subclass, Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required. implementing bus communications as required.
*/ */
template <class T> class MOS6522 { template <class T> class MOS6522: public ClockReceiver<MOS6522<T>> {
private: private:
enum InterruptFlag: uint8_t { enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0, CA2ActiveEdge = 1 << 0,
@ -250,32 +252,22 @@ template <class T> class MOS6522 {
timer_is_running_[0] = false;\ timer_is_running_[0] = false;\
} }
/*! /*! Runs for a specified number of half cycles. */
Runs for a specified number of half cycles. inline void run_for(const HalfCycles &half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
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 therefore may be 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.
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_half_cycles(unsigned int number_of_cycles) {
if(is_phase2_) { if(is_phase2_) {
phase2(); phase2();
number_of_cycles--; number_of_half_cycles--;
} }
while(number_of_cycles >= 2) { while(number_of_half_cycles >= 2) {
phase1(); phase1();
phase2(); phase2();
number_of_cycles -= 2; number_of_half_cycles -= 2;
} }
if(number_of_cycles) { if(number_of_half_cycles) {
phase1(); phase1();
is_phase2_ = true; is_phase2_ = true;
} else { } else {
@ -283,13 +275,9 @@ template <class T> class MOS6522 {
} }
} }
/*! /*! Runs for a specified number of cycles. */
Runs for a specified number of cycles. inline void run_for(const Cycles &cycles) {
int number_of_cycles = cycles.as_int();
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_cycles(unsigned int number_of_cycles) {
while(number_of_cycles--) { while(number_of_cycles--) {
phase1(); phase1();
phase2(); phase2();

View File

@ -12,6 +12,8 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS { namespace MOS {
/*! /*!
@ -25,7 +27,7 @@ namespace MOS {
Consumers should derive their own curiously-recurring-template-pattern subclass, Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required. implementing bus communications as required.
*/ */
template <class T> class MOS6532 { template <class T> class MOS6532: public ClockReceiver<MOS6532<T>> {
public: public:
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; } 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 uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
@ -104,7 +106,10 @@ template <class T> class MOS6532 {
return 0xff; return 0xff;
} }
inline void run_for_cycles(unsigned int number_of_cycles) { using ClockReceiver<MOS6532<T>>::run_for;
inline void run_for(const Cycles &cycles) {
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour // 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;

View File

@ -11,6 +11,7 @@
#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker.hpp" #include "../../Outputs/Speaker.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS { namespace MOS {
@ -40,7 +41,7 @@ class Speaker: public ::Outputs::Filter<Speaker> {
@c set_register and @c get_register provide register access. @c set_register and @c get_register provide register access.
*/ */
template <class T> class MOS6560 { template <class T> class MOS6560: public ClockReceiver<MOS6560<T>> {
public: public:
MOS6560() : MOS6560() :
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)), crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
@ -146,13 +147,15 @@ template <class T> class MOS6560 {
} }
} }
using ClockReceiver<MOS6560<T>>::run_for;
/*! /*!
Runs for cycles. Derr. Runs for cycles. Derr.
*/ */
inline void run_for_cycles(unsigned int number_of_cycles) { inline void run_for(const Cycles &cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied // keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += number_of_cycles; cycles_since_speaker_update_ += cycles;
int number_of_cycles = cycles.as_int();
while(number_of_cycles--) { while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes // keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_; int previous_vertical_counter = vertical_counter_;
@ -406,10 +409,9 @@ template <class T> class MOS6560 {
std::shared_ptr<Outputs::CRT::CRT> crt_; std::shared_ptr<Outputs::CRT::CRT> crt_;
std::shared_ptr<Speaker> speaker_; std::shared_ptr<Speaker> speaker_;
unsigned int cycles_since_speaker_update_; Cycles cycles_since_speaker_update_;
void update_audio() { void update_audio() {
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2); speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
cycles_since_speaker_update_ &= 3;
} }
// register state // register state

View File

@ -44,7 +44,7 @@ class Machine:
virtual void close_output(); virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); } virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; } virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); } virtual void run_for(const Cycles &cycles) { bus_->run_for(cycles); }
// to satisfy Outputs::CRT::Delegate // to satisfy Outputs::CRT::Delegate
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);

View File

@ -14,17 +14,17 @@
#include "Speaker.hpp" #include "Speaker.hpp"
#include "TIA.hpp" #include "TIA.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Atari2600 { namespace Atari2600 {
class Bus { class Bus {
public: public:
Bus() : Bus() :
tia_input_value_{0xff, 0xff}, tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0), cycles_since_speaker_update_(0) {}
cycles_since_video_update_(0),
cycles_since_6532_update_(0) {}
virtual void run_for_cycles(int number_of_cycles) = 0; virtual void run_for(const Cycles &cycles) = 0;
virtual void set_reset_line(bool state) = 0; virtual void set_reset_line(bool state) = 0;
// the RIOT, TIA and speaker // the RIOT, TIA and speaker
@ -37,25 +37,21 @@ class Bus {
protected: protected:
// speaker backlog accumlation counter // speaker backlog accumlation counter
unsigned int cycles_since_speaker_update_; Cycles cycles_since_speaker_update_;
inline void update_audio() { inline void update_audio() {
unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3); speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
speaker_->run_for_cycles(audio_cycles);
} }
// video backlog accumulation counter // video backlog accumulation counter
unsigned int cycles_since_video_update_; Cycles cycles_since_video_update_;
inline void update_video() { inline void update_video() {
tia_->run_for_cycles((int)cycles_since_video_update_); tia_->run_for(cycles_since_video_update_.flush());
cycles_since_video_update_ = 0;
} }
// RIOT backlog accumulation counter // RIOT backlog accumulation counter
unsigned int cycles_since_6532_update_; Cycles cycles_since_6532_update_;
inline void update_6532() { inline void update_6532() {
mos6532_.run_for_cycles(cycles_since_6532_update_); mos6532_.run_for(cycles_since_6532_update_.flush());
cycles_since_6532_update_ = 0;
} }
}; };

View File

@ -22,25 +22,25 @@ template<class T> class Cartridge:
Cartridge(const std::vector<uint8_t> &rom) : Cartridge(const std::vector<uint8_t> &rom) :
rom_(rom) {} rom_(rom) {}
void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); } void run_for(const Cycles &cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for(cycles); }
void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); } void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); }
void advance_cycles(unsigned int cycles) {} void advance_cycles(int cycles) {}
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
uint8_t returnValue = 0xff; uint8_t returnValue = 0xff;
unsigned int cycles_run_for = 3; int cycles_run_for = 3;
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately // effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line. // skips to the end of the line.
if(operation == CPU::MOS6502::BusOperation::Ready) if(operation == CPU::MOS6502::BusOperation::Ready)
cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_since_speaker_update_ += cycles_run_for; cycles_since_speaker_update_ += Cycles(cycles_run_for);
cycles_since_video_update_ += cycles_run_for; cycles_since_video_update_ += Cycles(cycles_run_for);
cycles_since_6532_update_ += (cycles_run_for / 3); cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3); static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
if(operation != CPU::MOS6502::BusOperation::Ready) { if(operation != CPU::MOS6502::BusOperation::Ready) {
@ -158,7 +158,7 @@ template<class T> class Cartridge:
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(false); if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(false);
return cycles_run_for / 3; return Cycles(cycles_run_for / 3);
} }
void flush() { void flush() {

View File

@ -22,7 +22,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
rom_ptr_ = rom_.data(); rom_ptr_ = rom_.data();
} }
void advance_cycles(unsigned int cycles) { void advance_cycles(int cycles) {
cycles_since_audio_update_ += cycles; cycles_since_audio_update_ += cycles;
} }
@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
inline uint8_t update_audio() { inline uint8_t update_audio() {
const unsigned int clock_divisor = 57; const unsigned int clock_divisor = 57;
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor; int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int();
cycles_since_audio_update_ %= clock_divisor;
int table_position = 0; int table_position = 0;
for(int c = 0; c < 3; c++) { for(int c = 0; c < 3; c++) {
@ -126,7 +125,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
uint8_t random_number_generator_; uint8_t random_number_generator_;
uint8_t *rom_ptr_; uint8_t *rom_ptr_;
uint8_t audio_channel_[3]; uint8_t audio_channel_[3];
unsigned int cycles_since_audio_update_; Cycles cycles_since_audio_update_;
}; };
} }

View File

@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ /* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
} }
void TIA::run_for_cycles(int number_of_cycles) void TIA::run_for(const Cycles &cycles) {
{ int number_of_cycles = cycles.as_int();
// if part way through a line, definitely perform a partial, at most up to the end of the line // if part way through a line, definitely perform a partial, at most up to the end of the line
if(horizontal_counter_) { if(horizontal_counter_) {
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
output_for_cycles(cycles); output_for_cycles(output_cycles);
number_of_cycles -= cycles; number_of_cycles -= output_cycles;
} }
// output full lines for as long as possible // output full lines for as long as possible
@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) {
void TIA::reset_horizontal_counter() { void TIA::reset_horizontal_counter() {
} }
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) { int TIA::get_cycles_until_horizontal_blank(const Cycles &from_offset) {
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line; return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line;
} }
void TIA::set_background_colour(uint8_t colour) { void TIA::set_background_colour(uint8_t colour) {

View File

@ -10,11 +10,13 @@
#define TIA_hpp #define TIA_hpp
#include <cstdint> #include <cstdint>
#include "../CRTMachine.hpp" #include "../CRTMachine.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Atari2600 { namespace Atari2600 {
class TIA { class TIA: public ClockReceiver<TIA> {
public: public:
TIA(); TIA();
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will // The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
@ -27,10 +29,10 @@ class TIA {
}; };
/*! /*!
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
first cycle performed.
*/ */
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using ClockReceiver<TIA>::run_for;
void set_output_mode(OutputMode output_mode); void set_output_mode(OutputMode output_mode);
void set_sync(bool sync); void set_sync(bool sync);
@ -41,7 +43,7 @@ class TIA {
@returns the number of cycles between (current TIA time) + from_offset to the current or @returns the number of cycles between (current TIA time) + from_offset to the current or
next horizontal blanking period. Returns numbers in the range [0, 227]. next horizontal blanking period. Returns numbers in the range [0, 227].
*/ */
int get_cycles_until_horizontal_blank(unsigned int from_offset); int get_cycles_until_horizontal_blank(const Cycles &from_offset);
void set_background_colour(uint8_t colour); void set_background_colour(uint8_t colour);

View File

@ -11,6 +11,7 @@
#include "../Outputs/CRT/CRT.hpp" #include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker.hpp" #include "../Outputs/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace CRTMachine { namespace CRTMachine {
@ -41,8 +42,8 @@ class Machine {
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0; virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
/// Runs the machine for @c number_of_cycle cycles. /// Runs the machine for @c cycles.
virtual void run_for_cycles(int number_of_cycles) = 0; virtual void run_for(const Cycles &cycles) = 0;
// TODO: sever the clock-rate stuff. // TODO: sever the clock-rate stuff.
double get_clock_rate() { double get_clock_rate() {

View File

@ -34,7 +34,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
} }
unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
/* /*
Memory map (given that I'm unsure yet on any potential mirroring): Memory map (given that I'm unsure yet on any potential mirroring):
@ -63,10 +63,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
drive_VIA_.set_register(address, *value); drive_VIA_.set_register(address, *value);
} }
serial_port_VIA_->run_for_cycles(1); serial_port_VIA_->run_for(Cycles(1));
drive_VIA_.run_for_cycles(1); drive_VIA_.run_for(Cycles(1));
return 1; return Cycles(1);
} }
void Machine::set_rom(const std::vector<uint8_t> &rom) { void Machine::set_rom(const std::vector<uint8_t> &rom) {
@ -79,11 +79,11 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
set_drive(drive); set_drive(drive);
} }
void Machine::run_for_cycles(int number_of_cycles) { void Machine::run_for(const Cycles &cycles) {
CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles); CPU::MOS6502::Processor<Machine>::run_for(cycles);
set_motor_on(drive_VIA_.get_motor_enabled()); set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
Storage::Disk::Controller::run_for_cycles(number_of_cycles); Storage::Disk::Controller::run_for(cycles);
} }
#pragma mark - 6522 delegate #pragma mark - 6522 delegate

View File

@ -138,11 +138,11 @@ class Machine:
*/ */
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
// to satisfy MOS::MOS6522::Delegate // to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522); virtual void mos6522_did_change_interrupt_status(void *mos6522);

View File

@ -97,9 +97,9 @@ Machine::~Machine() {
delete[] rom_; delete[] rom_;
} }
unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
// run the phase-1 part of this cycle, in which the VIC accesses memory // run the phase-1 part of this cycle, in which the VIC accesses memory
if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1); if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1));
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be // run the phase-2 part of the cycle, which is whatever the 6502 said it should be
if(isReadOperation(operation)) { if(isReadOperation(operation)) {
@ -179,18 +179,18 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
} }
} }
user_port_via_->run_for_cycles(1); user_port_via_->run_for(Cycles(1));
keyboard_via_->run_for_cycles(1); keyboard_via_->run_for(Cycles(1));
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
if(!typer_->type_next_character()) { if(!typer_->type_next_character()) {
clear_all_keys(); clear_all_keys();
typer_.reset(); typer_.reset();
} }
} }
tape_->run_for_cycles(1); tape_->run_for(Cycles(1));
if(c1540_) c1540_->run_for_cycles(1); if(c1540_) c1540_->run_for(Cycles(1));
return 1; return Cycles(1);
} }
#pragma mark - 6522 delegate #pragma mark - 6522 delegate
@ -315,7 +315,7 @@ void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
void Machine::install_disk_rom() { void Machine::install_disk_rom() {
if(!drive_rom_.empty() && c1540_) { if(!drive_rom_.empty() && c1540_) {
c1540_->set_rom(drive_rom_); c1540_->set_rom(drive_rom_);
c1540_->run_for_cycles(2000000); c1540_->run_for(Cycles(2000000));
drive_rom_.clear(); drive_rom_.clear();
} }
} }

View File

@ -166,7 +166,7 @@ class Machine:
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush() { mos6560_->flush(); } void flush() { mos6560_->flush(); }
// to satisfy CRTMachine::Machine // to satisfy CRTMachine::Machine
@ -174,7 +174,7 @@ class Machine:
virtual void close_output(); virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); } virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); } virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
virtual void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles); } virtual void run_for(const Cycles &cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
// to satisfy MOS::MOS6522::Delegate // to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522); virtual void mos6522_did_change_interrupt_status(void *mos6522);

View File

@ -15,7 +15,6 @@ using namespace Electron;
Machine::Machine() : Machine::Machine() :
interrupt_control_(0), interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80), interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_display_update_(0),
cycles_since_audio_update_(0), cycles_since_audio_update_(0),
use_fast_tape_hack_(false), use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0) { cycles_until_display_interrupt_(0) {
@ -123,7 +122,7 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable
#pragma mark - The bus #pragma mark - The bus
unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
unsigned int cycles = 1; unsigned int cycles = 1;
if(address < 0x8000) { if(address < 0x8000) {
@ -136,7 +135,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions // it's also accessible only outside of the pixel regions
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1)); cycles += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1);
} else { } else {
switch(address & 0xff0f) { switch(address & 0xff0f) {
case 0xfe00: case 0xfe00:
@ -316,10 +315,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
} }
} }
cycles_since_display_update_ += cycles; cycles_since_display_update_ += Cycles((int)cycles);
cycles_since_audio_update_ += cycles; cycles_since_audio_update_ += Cycles((int)cycles);
if(cycles_since_audio_update_ > 16384) update_audio(); if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
tape_.run_for_cycles(cycles); tape_.run_for(Cycles((int)cycles));
cycles_until_display_interrupt_ -= cycles; cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0) { if(cycles_until_display_interrupt_ < 0) {
@ -329,7 +328,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
} }
if(typer_) typer_->update((int)cycles); if(typer_) typer_->update((int)cycles);
if(plus3_) plus3_->run_for_cycles(4*cycles); if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
if(shift_restart_counter_) { if(shift_restart_counter_) {
shift_restart_counter_ -= cycles; shift_restart_counter_ -= cycles;
if(shift_restart_counter_ <= 0) { if(shift_restart_counter_ <= 0) {
@ -340,7 +339,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
} }
} }
return cycles; return Cycles(cycles);
} }
void Machine::flush() { void Machine::flush() {
@ -353,8 +352,7 @@ void Machine::flush() {
inline void Machine::update_display() { inline void Machine::update_display() {
if(cycles_since_display_update_) { if(cycles_since_display_update_) {
video_output_->run_for_cycles((int)cycles_since_display_update_); video_output_->run_for(cycles_since_display_update_.flush());
cycles_since_display_update_ = 0;
} }
} }
@ -366,9 +364,7 @@ inline void Machine::queue_next_display_interrupt() {
inline void Machine::update_audio() { inline void Machine::update_audio() {
if(cycles_since_audio_update_) { if(cycles_since_audio_update_) {
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider; speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
speaker_->run_for_cycles(difference);
} }
} }

View File

@ -86,7 +86,7 @@ class Machine:
void configure_as_target(const StaticAnalyser::Target &target); void configure_as_target(const StaticAnalyser::Target &target);
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush(); void flush();
// to satisfy CRTMachine::Machine // to satisfy CRTMachine::Machine
@ -94,7 +94,7 @@ class Machine:
virtual void close_output(); virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt(); virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker(); virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles); } virtual void run_for(const Cycles &cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
// to satisfy Tape::Delegate // to satisfy Tape::Delegate
virtual void tape_did_change_interrupt_status(Tape *tape); virtual void tape_did_change_interrupt_status(Tape *tape);
@ -128,8 +128,8 @@ class Machine:
uint8_t key_states_[14]; uint8_t key_states_[14];
// Counters related to simultaneous subsystems // Counters related to simultaneous subsystems
unsigned int cycles_since_display_update_; Cycles cycles_since_display_update_;
unsigned int cycles_since_audio_update_; Cycles cycles_since_audio_update_;
int cycles_until_display_interrupt_; int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_; Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_; VideoOutput::Range video_access_range_;

View File

@ -80,14 +80,14 @@ void Tape::acorn_shifter_output_bit(int value) {
push_tape_bit((uint16_t)value); push_tape_bit((uint16_t)value);
} }
void Tape::run_for_cycles(unsigned int number_of_cycles) { void Tape::run_for(const Cycles &cycles) {
if(is_enabled_) { if(is_enabled_) {
if(is_in_input_mode_) { if(is_in_input_mode_) {
if(is_running_) { if(is_running_) {
TapePlayer::run_for_cycles((int)number_of_cycles); TapePlayer::run_for(cycles);
} }
} else { } else {
output_.cycles_into_pulse += number_of_cycles; output_.cycles_into_pulse += (unsigned int)cycles.as_int();
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off. output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
push_tape_bit(1); push_tape_bit(1);

View File

@ -9,12 +9,13 @@
#ifndef Electron_Tape_h #ifndef Electron_Tape_h
#define Electron_Tape_h #define Electron_Tape_h
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/Acorn.hpp" #include "../../Storage/Tape/Parsers/Acorn.hpp"
#include "Interrupts.hpp" #include "Interrupts.hpp"
#include <cstdint>
namespace Electron { namespace Electron {
class Tape: class Tape:
@ -23,7 +24,8 @@ class Tape:
public: public:
Tape(); Tape();
void run_for_cycles(unsigned int number_of_cycles); void run_for(const Cycles &cycles);
using Storage::Tape::TapePlayer::run_for;
uint8_t get_data_register(); uint8_t get_data_register();
void set_data_register(uint8_t value); void set_data_register(uint8_t value);

View File

@ -223,7 +223,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
} }
} }
void VideoOutput::run_for_cycles(int number_of_cycles) { void VideoOutput::run_for(const Cycles &cycles) {
int number_of_cycles = cycles.as_int();
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
while(number_of_cycles) { while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length; int draw_action_length = screen_map_[screen_map_pointer_].length;

View File

@ -10,6 +10,7 @@
#define Machines_Electron_Video_hpp #define Machines_Electron_Video_hpp
#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "Interrupts.hpp" #include "Interrupts.hpp"
namespace Electron { namespace Electron {
@ -21,7 +22,7 @@ namespace Electron {
running either at 40 or 80 columns. Memory is shared between video and CPU; when the video running either at 40 or 80 columns. Memory is shared between video and CPU; when the video
is accessing it the CPU may not. is accessing it the CPU may not.
*/ */
class VideoOutput { class VideoOutput: public ClockReceiver<VideoOutput> {
public: public:
/*! /*!
Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied
@ -32,8 +33,9 @@ class VideoOutput {
/// @returns the CRT to which output is being painted. /// @returns the CRT to which output is being painted.
std::shared_ptr<Outputs::CRT::CRT> get_crt(); std::shared_ptr<Outputs::CRT::CRT> get_crt();
/// Produces the next @c number_of_cycles cycles of video output. /// Produces the next @c cycles of video output.
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using ClockReceiver<VideoOutput>::run_for;
/*! /*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@ -53,14 +55,14 @@ class VideoOutput {
/*! /*!
@returns the next interrupt that should be generated as a result of the video hardware. @returns the next interrupt that should be generated as a result of the video hardware.
The time until signalling returned is the number of cycles after the final one triggered The time until signalling returned is the number of cycles after the final one triggered
by the most recent call to @c run_for_cycles. by the most recent call to @c run_for.
This result may be mutated by calls to @c set_register. This result may be mutated by calls to @c set_register.
*/ */
Interrupt get_next_interrupt(); Interrupt get_next_interrupt();
/*! /*!
@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time) @returns the number of cycles after (final cycle of last run_for batch + @c from_time)
before the video circuits will allow the CPU to access RAM. before the video circuits will allow the CPU to access RAM.
*/ */
unsigned int get_cycles_until_next_ram_availability(int from_time); unsigned int get_cycles_until_next_ram_availability(int from_time);

View File

@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) {
} }
} }
void Microdisc::run_for_cycles(unsigned int number_of_cycles) { void Microdisc::run_for(const Cycles &cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) { if(head_load_request_counter_ < head_load_request_counter_target) {
head_load_request_counter_ += number_of_cycles; head_load_request_counter_ += cycles.as_int();
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true); if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
} }
WD::WD1770::run_for_cycles(number_of_cycles); WD::WD1770::run_for(cycles);
} }
bool Microdisc::get_drive_is_ready() { bool Microdisc::get_drive_is_ready() {

View File

@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 {
bool get_interrupt_request_line(); bool get_interrupt_request_line();
void run_for_cycles(unsigned int number_of_cycles); void run_for(const Cycles &cycles);
using WD::WD1770::run_for;
enum PagingFlags { enum PagingFlags {
BASICDisable = (1 << 0), BASICDisable = (1 << 0),

View File

@ -12,7 +12,6 @@
using namespace Oric; using namespace Oric;
Machine::Machine() : Machine::Machine() :
cycles_since_video_update_(0),
use_fast_tape_hack_(false), use_fast_tape_hack_(false),
typer_delay_(2500000), typer_delay_(2500000),
keyboard_read_count_(0), keyboard_read_count_(0),
@ -78,7 +77,7 @@ void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
} }
} }
unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) { if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
@ -130,10 +129,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
} }
} }
via_.run_for_cycles(1); via_.run_for(Cycles(1));
if(microdisc_is_enabled_) microdisc_.run_for_cycles(8); if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
cycles_since_video_update_++; cycles_since_video_update_++;
return 1; return Cycles(1);
} }
void Machine::flush() { void Machine::flush() {
@ -142,8 +141,7 @@ void Machine::flush() {
} }
void Machine::update_video() { void Machine::update_video() {
video_output_->run_for_cycles(cycles_since_video_update_); video_output_->run_for(cycles_since_video_update_.flush());
cycles_since_video_update_ = 0;
} }
void Machine::setup_output(float aspect_ratio) { void Machine::setup_output(float aspect_ratio) {
@ -198,15 +196,14 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return via_.ay8910; return via_.ay8910;
} }
void Machine::run_for_cycles(int number_of_cycles) { void Machine::run_for(const Cycles &cycles) {
CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles); CPU::MOS6502::Processor<Machine>::run_for(cycles);
} }
#pragma mark - The 6522 #pragma mark - The 6522
Machine::VIA::VIA() : Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(), MOS::MOS6522<Machine::VIA>(),
cycles_since_ay_update_(0),
tape(new TapePlayer) {} tape(new TapePlayer) {}
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) { void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
@ -235,20 +232,18 @@ uint8_t Machine::VIA::get_port_input(Port port) {
} }
void Machine::VIA::flush() { void Machine::VIA::flush() {
ay8910->run_for_cycles(cycles_since_ay_update_); ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->flush(); ay8910->flush();
cycles_since_ay_update_ = 0;
} }
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) { void Machine::VIA::run_for(const Cycles &cycles) {
cycles_since_ay_update_ += number_of_cycles; cycles_since_ay_update_ += cycles;
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles); MOS::MOS6522<VIA>::run_for(cycles);
tape->run_for_cycles((int)number_of_cycles); tape->run_for(cycles);
} }
void Machine::VIA::update_ay() { void Machine::VIA::update_ay() {
ay8910->run_for_cycles(cycles_since_ay_update_); ay8910->run_for(cycles_since_ay_update_.flush());
cycles_since_ay_update_ = 0;
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
} }

View File

@ -77,7 +77,7 @@ class Machine:
void configure_as_target(const StaticAnalyser::Target &target); void configure_as_target(const StaticAnalyser::Target &target);
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush(); void flush();
// to satisfy CRTMachine::Machine // to satisfy CRTMachine::Machine
@ -85,7 +85,7 @@ class Machine:
virtual void close_output(); virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt(); virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker(); virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for_cycles(int number_of_cycles); virtual void run_for(const Cycles &cyclesß);
// to satisfy MOS::MOS6522IRQDelegate::Delegate // to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522); void mos6522_did_change_interrupt_status(void *mos6522);
@ -104,7 +104,7 @@ class Machine:
// RAM and ROM // RAM and ROM
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_; std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384]; uint8_t ram_[65536], rom_[16384];
int cycles_since_video_update_; Cycles cycles_since_video_update_;
inline void update_video(); inline void update_video();
// ROM bookkeeping // ROM bookkeeping
@ -143,7 +143,7 @@ class Machine:
void set_control_line_output(Port port, Line line, bool value); void set_control_line_output(Port port, Line line, bool value);
void set_port_output(Port port, uint8_t value, uint8_t direction_mask); void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
uint8_t get_port_input(Port port); uint8_t get_port_input(Port port);
inline void run_for_cycles(unsigned int number_of_cycles); inline void run_for(const Cycles &cycles);
std::shared_ptr<GI::AY38910> ay8910; std::shared_ptr<GI::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape; std::unique_ptr<TapePlayer> tape;
@ -154,7 +154,7 @@ class Machine:
private: private:
void update_ay(); void update_ay();
bool ay_bdir_, ay_bc1_; bool ay_bdir_, ay_bc1_;
unsigned int cycles_since_ay_update_; Cycles cycles_since_ay_update_;
}; };
VIA via_; VIA via_;
std::shared_ptr<Keyboard> keyboard_; std::shared_ptr<Keyboard> keyboard_;

View File

@ -74,13 +74,14 @@ std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_; return crt_;
} }
void VideoOutput::run_for_cycles(int number_of_cycles) { void VideoOutput::run_for(const Cycles &cycles) {
// Vertical: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst // Vertical: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst
// Horizontal: 0223: pixels; otherwise blank; 256259 sync // Horizontal: 0223: pixels; otherwise blank; 256259 sync
#define clamp(action) \ #define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles; if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
int number_of_cycles = cycles.as_int();
while(number_of_cycles) { while(number_of_cycles) {
int h_counter = counter_ & 63; int h_counter = counter_ & 63;
int cycles_run_for = 0; int cycles_run_for = 0;

View File

@ -10,14 +10,16 @@
#define Machines_Oric_Video_hpp #define Machines_Oric_Video_hpp
#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Oric { namespace Oric {
class VideoOutput { class VideoOutput: public ClockReceiver<VideoOutput> {
public: public:
VideoOutput(uint8_t *memory); VideoOutput(uint8_t *memory);
std::shared_ptr<Outputs::CRT::CRT> get_crt(); std::shared_ptr<Outputs::CRT::CRT> get_crt();
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using ClockReceiver<VideoOutput>::run_for;
void set_colour_rom(const std::vector<uint8_t> &rom); void set_colour_rom(const std::vector<uint8_t> &rom);
void set_output_device(Outputs::CRT::OutputDevice output_device); void set_output_device(Outputs::CRT::OutputDevice output_device);

View File

@ -29,9 +29,9 @@ Video::Video() :
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
} }
void Video::run_for_cycles(int number_of_cycles) { void Video::run_for(const HalfCycles &half_cycles) {
// Just keep a running total of the amount of time that remains owed to the CRT. // Just keep a running total of the amount of time that remains owed to the CRT.
cycles_since_update_ += (unsigned int)number_of_cycles << 1; cycles_since_update_ += (unsigned int)half_cycles.as_int();
} }
void Video::flush() { void Video::flush() {

View File

@ -10,6 +10,7 @@
#define Machines_ZX8081_Video_hpp #define Machines_ZX8081_Video_hpp
#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace ZX8081 { namespace ZX8081 {
@ -23,15 +24,16 @@ namespace ZX8081 {
a 1-bit graphic and output over the next 4 cycles, picking between the white level a 1-bit graphic and output over the next 4 cycles, picking between the white level
and the black level. and the black level.
*/ */
class Video { class Video: public ClockReceiver<Video> {
public: public:
/// Constructs an instance of the video feed; a CRT is also created. /// Constructs an instance of the video feed; a CRT is also created.
Video(); Video();
/// @returns The CRT this video feed is feeding. /// @returns The CRT this video feed is feeding.
std::shared_ptr<Outputs::CRT::CRT> get_crt(); std::shared_ptr<Outputs::CRT::CRT> get_crt();
/// Advances time by @c number_of_cycles cycles. /// Advances time by @c cycles.
void run_for_cycles(int number_of_cycles); void run_for(const HalfCycles &);
using ClockReceiver<Video>::run_for;
/// Forces output to catch up to the current output position. /// Forces output to catch up to the current output position.
void flush(); void flush();

View File

@ -29,35 +29,35 @@ Machine::Machine() :
clear_all_keys(); clear_all_keys();
} }
int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { Cycles Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
int previous_counter = horizontal_counter_; HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length; horizontal_counter_ += cycle.length;
if(previous_counter < vsync_start_cycle_ && horizontal_counter_ >= vsync_start_cycle_) { if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_->run_for_cycles(vsync_start_cycle_ - previous_counter); video_->run_for(vsync_start_ - previous_counter);
set_hsync(true); set_hsync(true);
line_counter_ = (line_counter_ + 1) & 7; line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) { if(nmi_is_enabled_) {
set_non_maskable_interrupt_line(true); set_non_maskable_interrupt_line(true);
} }
video_->run_for_cycles(horizontal_counter_ - vsync_start_cycle_); video_->run_for(horizontal_counter_ - vsync_start_);
} else if(previous_counter < vsync_end_cycle_ && horizontal_counter_ >= vsync_end_cycle_) { } else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
video_->run_for_cycles(vsync_end_cycle_ - previous_counter); video_->run_for(vsync_end_ - previous_counter);
set_hsync(false); set_hsync(false);
if(nmi_is_enabled_) { if(nmi_is_enabled_) {
set_non_maskable_interrupt_line(false); set_non_maskable_interrupt_line(false);
set_wait_line(false); set_wait_line(false);
} }
video_->run_for_cycles(horizontal_counter_ - vsync_end_cycle_); video_->run_for(horizontal_counter_ - vsync_end_);
} else { } else {
video_->run_for_cycles(cycle.length); video_->run_for(cycle.length);
} }
if(is_zx81_) horizontal_counter_ %= 207; if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207));
if(!tape_advance_delay_) { if(!tape_advance_delay_) {
tape_player_.run_for_cycles(cycle.length); tape_player_.run_for(cycle.length);
} else { } else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, 0); tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
} }
if(nmi_is_enabled_ && !get_halt_line() && get_non_maskable_interrupt_line()) { if(nmi_is_enabled_ && !get_halt_line() && get_non_maskable_interrupt_line()) {
@ -65,7 +65,7 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
} }
if(!cycle.is_terminal()) { if(!cycle.is_terminal()) {
return 0; return Cycles(0);
} }
uint16_t address = cycle.address ? *cycle.address : 0; uint16_t address = cycle.address ? *cycle.address : 0;
@ -180,9 +180,9 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
default: break; default: break;
} }
if(typer_) typer_->update(cycle.length); if(typer_) typer_->update(cycle.length.as_int());
return 0; return Cycles(0);
} }
void Machine::flush() { void Machine::flush() {
@ -205,8 +205,8 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return nullptr; return nullptr;
} }
void Machine::run_for_cycles(int number_of_cycles) { void Machine::run_for(const Cycles &cycles) {
CPU::Z80::Processor<Machine>::run_for_cycles(number_of_cycles); CPU::Z80::Processor<Machine>::run_for(cycles);
} }
void Machine::configure_as_target(const StaticAnalyser::Target &target) { void Machine::configure_as_target(const StaticAnalyser::Target &target) {
@ -215,16 +215,16 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) {
rom_ = zx81_rom_; rom_ = zx81_rom_;
tape_trap_address_ = 0x37c; tape_trap_address_ = 0x37c;
tape_return_address_ = 0x380; tape_return_address_ = 0x380;
vsync_start_cycle_ = 16; vsync_start_ = HalfCycles(32);
vsync_end_cycle_ = 32; vsync_end_ = HalfCycles(64);
automatic_tape_motor_start_address_ = 0x0340; automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3; automatic_tape_motor_end_address_ = 0x03c3;
} else { } else {
rom_ = zx80_rom_; rom_ = zx80_rom_;
tape_trap_address_ = 0x220; tape_trap_address_ = 0x220;
tape_return_address_ = 0x248; tape_return_address_ = 0x248;
vsync_start_cycle_ = 13; vsync_start_ = HalfCycles(26);
vsync_end_cycle_ = 33; vsync_end_ = HalfCycles(66);
automatic_tape_motor_start_address_ = 0x0206; automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d; automatic_tape_motor_end_address_ = 0x024d;
} }

View File

@ -47,7 +47,7 @@ class Machine:
public: public:
Machine(); Machine();
int perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle); Cycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle);
void flush(); void flush();
void setup_output(float aspect_ratio); void setup_output(float aspect_ratio);
@ -56,7 +56,7 @@ class Machine:
std::shared_ptr<Outputs::CRT::CRT> get_crt(); std::shared_ptr<Outputs::CRT::CRT> get_crt();
std::shared_ptr<Outputs::Speaker> get_speaker(); std::shared_ptr<Outputs::Speaker> get_speaker();
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
void configure_as_target(const StaticAnalyser::Target &target); void configure_as_target(const StaticAnalyser::Target &target);
@ -103,17 +103,18 @@ class Machine:
Storage::Tape::BinaryTapePlayer tape_player_; Storage::Tape::BinaryTapePlayer tape_player_;
Storage::Tape::ZX8081::Parser parser_; Storage::Tape::ZX8081::Parser parser_;
int horizontal_counter_;
bool is_zx81_; bool is_zx81_;
bool nmi_is_enabled_; bool nmi_is_enabled_;
int vsync_start_cycle_, vsync_end_cycle_;
HalfCycles vsync_start_, vsync_end_;
HalfCycles horizontal_counter_;
uint8_t latched_video_byte_; uint8_t latched_video_byte_;
bool has_latched_video_byte_; bool has_latched_video_byte_;
bool use_fast_tape_hack_; bool use_fast_tape_hack_;
bool use_automatic_tape_motor_control_; bool use_automatic_tape_motor_control_;
int tape_advance_delay_; HalfCycles tape_advance_delay_;
}; };
} }

View File

@ -1020,6 +1020,7 @@
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; }; 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = "<group>"; }; 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; }; 4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; }; 4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; }; 4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; }; 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
@ -1809,6 +1810,7 @@
4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EA01B587A5100552FC2 /* Clock Signal */,
4BB73EB51B587A5100552FC2 /* Clock SignalTests */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */,
4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */,
4BF660691F281573002CB053 /* ClockReceiver */,
4BC9DF4A1D04691600F44158 /* Components */, 4BC9DF4A1D04691600F44158 /* Components */,
4B3940E81DA83C8700427841 /* Concurrency */, 4B3940E81DA83C8700427841 /* Concurrency */,
4BB73EDC1B587CA500552FC2 /* Machines */, 4BB73EDC1B587CA500552FC2 /* Machines */,
@ -2156,6 +2158,15 @@
name = StaticAnalyser; name = StaticAnalyser;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BF660691F281573002CB053 /* ClockReceiver */ = {
isa = PBXGroup;
children = (
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */

View File

@ -75,7 +75,6 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
enableAddressSanitizer = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "NO"> allowLocationSimulation = "NO">
<BuildableProductRunnable <BuildableProductRunnable

View File

@ -104,7 +104,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (void)runForNumberOfCycles:(int)numberOfCycles { - (void)runForNumberOfCycles:(int)numberOfCycles {
@synchronized(self) { @synchronized(self) {
self.machine->run_for_cycles(numberOfCycles); self.machine->run_for(Cycles(numberOfCycles));
} }
} }

View File

@ -25,8 +25,7 @@
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
if(self) if(self) {
{
[self setOSROM:[self rom:@"os"]]; [self setOSROM:[self rom:@"os"]];
[self setBASICROM:[self rom:@"basic"]]; [self setBASICROM:[self rom:@"basic"]];
[self setDFSROM:[self rom:@"DFS-1770-2.20"]]; [self setDFSROM:[self rom:@"DFS-1770-2.20"]];
@ -38,8 +37,7 @@
return self; return self;
} }
- (NSData *)rom:(NSString *)name - (NSData *)rom:(NSString *)name {
{
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"]; return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"];
} }

View File

@ -15,16 +15,13 @@
#import "NSData+StdVector.h" #import "NSData+StdVector.h"
#import "NSBundle+DataResource.h" #import "NSBundle+DataResource.h"
@implementation CSOric @implementation CSOric {
{
Oric::Machine _oric; Oric::Machine _oric;
} }
- (instancetype)init - (instancetype)init {
{
self = [super init]; self = [super init];
if(self) if(self) {
{
NSData *basic10 = [self rom:@"basic10"]; NSData *basic10 = [self rom:@"basic10"];
NSData *basic11 = [self rom:@"basic11"]; NSData *basic11 = [self rom:@"basic11"];
NSData *colour = [self rom:@"colour"]; NSData *colour = [self rom:@"colour"];
@ -38,23 +35,19 @@
return self; return self;
} }
- (NSData *)rom:(NSString *)name - (NSData *)rom:(NSString *)name {
{
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
} }
- (CRTMachine::Machine * const)machine - (CRTMachine::Machine * const)machine {
{
return &_oric; return &_oric;
} }
#pragma mark - CSKeyboardMachine #pragma mark - CSKeyboardMachine
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
{
@synchronized(self) { @synchronized(self) {
switch(key) switch(key) {
{
case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break; case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break;
case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break; case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break;
case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break; case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break;
@ -137,8 +130,7 @@
} }
} }
- (void)clearAllKeys - (void)clearAllKeys {
{
_oric.clear_all_keys(); _oric.clear_all_keys();
} }

View File

@ -42,7 +42,7 @@ class VanillaSerialPort: public Commodore::Serial::Port {
} }
- (void)runForCycles:(NSUInteger)numberOfCycles { - (void)runForCycles:(NSUInteger)numberOfCycles {
_c1540.run_for_cycles((int)numberOfCycles); _c1540.run_for(Cycles((int)numberOfCycles));
} }
- (void)setAttentionLine:(BOOL)attentionLine { - (void)setAttentionLine:(BOOL)attentionLine {

View File

@ -40,7 +40,7 @@ class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::De
} }
- (void)runForCycles:(NSUInteger)cycles { - (void)runForCycles:(NSUInteger)cycles {
_digitalPhaseLockedLoop->run_for_cycles((unsigned int)cycles); _digitalPhaseLockedLoop->run_for(Cycles((int)cycles));
} }
- (void)addPulse { - (void)addPulse {

View File

@ -18,69 +18,56 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
uint8_t port_a_value; uint8_t port_a_value;
uint8_t port_b_value; uint8_t port_b_value;
void set_interrupt_status(bool new_status) void set_interrupt_status(bool new_status) {
{
irq_line = new_status; irq_line = new_status;
} }
uint8_t get_port_input(Port port) uint8_t get_port_input(Port port) {
{
return port ? port_b_value : port_a_value; return port ? port_b_value : port_a_value;
} }
}; };
@implementation MOS6522Bridge @implementation MOS6522Bridge {
{
VanillaVIA _via; VanillaVIA _via;
} }
- (instancetype)init - (instancetype)init {
{
self = [super init]; self = [super init];
if(self) if(self) {
{
_via.bridge = self; _via.bridge = self;
} }
return self; return self;
} }
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber - (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber {
{
_via.set_register((int)registerNumber, value); _via.set_register((int)registerNumber, value);
} }
- (uint8_t)valueForRegister:(NSUInteger)registerNumber - (uint8_t)valueForRegister:(NSUInteger)registerNumber {
{
return _via.get_register((int)registerNumber); return _via.get_register((int)registerNumber);
} }
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles - (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles {
{ _via.run_for(HalfCycles((int)numberOfHalfCycles));
_via.run_for_half_cycles((int)numberOfHalfCycles);
} }
- (BOOL)irqLine - (BOOL)irqLine {
{
return _via.irq_line; return _via.irq_line;
} }
- (void)setPortAInput:(uint8_t)portAInput - (void)setPortAInput:(uint8_t)portAInput {
{
_via.port_a_value = portAInput; _via.port_a_value = portAInput;
} }
- (uint8_t)portAInput - (uint8_t)portAInput {
{
return _via.port_a_value; return _via.port_a_value;
} }
- (void)setPortBInput:(uint8_t)portBInput - (void)setPortBInput:(uint8_t)portBInput {
{
_via.port_b_value = portBInput; _via.port_b_value = portBInput;
} }
- (uint8_t)portBInput - (uint8_t)portBInput {
{
return _via.port_b_value; return _via.port_b_value;
} }

View File

@ -11,45 +11,37 @@
class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> { class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
public: public:
uint8_t get_port_input(int port) uint8_t get_port_input(int port) {
{
return input[port]; return input[port];
} }
uint8_t input[2]; uint8_t input[2];
}; };
@implementation MOS6532Bridge @implementation MOS6532Bridge {
{
VanillaRIOT _riot; VanillaRIOT _riot;
} }
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber - (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber {
{
_riot.set_register((int)registerNumber, value); _riot.set_register((int)registerNumber, value);
} }
- (uint8_t)valueForRegister:(NSUInteger)registerNumber - (uint8_t)valueForRegister:(NSUInteger)registerNumber {
{
return _riot.get_register((int)registerNumber); return _riot.get_register((int)registerNumber);
} }
- (void)runForCycles:(NSUInteger)numberOfCycles - (void)runForCycles:(NSUInteger)numberOfCycles {
{ _riot.run_for(Cycles((int)numberOfCycles));
_riot.run_for_cycles((int)numberOfCycles);
} }
- (BOOL)irqLine - (BOOL)irqLine {
{
return _riot.get_inerrupt_line(); return _riot.get_inerrupt_line();
} }
- (void)setPortAInput:(uint8_t)portAInput - (void)setPortAInput:(uint8_t)portAInput {
{
_riot.input[0] = _portAInput = portAInput; _riot.input[0] = _portAInput = portAInput;
} }
- (void)setPortBInput:(uint8_t)portBInput - (void)setPortBInput:(uint8_t)portBInput {
{
_riot.input[1] = _portBInput = portBInput; _riot.input[1] = _portBInput = portBInput;
} }

View File

@ -13,20 +13,6 @@
const uint8_t CSTestMachine6502JamOpcode = CPU::MOS6502::JamOpcode; const uint8_t CSTestMachine6502JamOpcode = CPU::MOS6502::JamOpcode;
#pragma mark - C++ jam handler
//class MachineJamHandler: public CPU::MOS6502::AllRAMProcessor::JamHandler {
// public:
// MachineJamHandler(CSTestMachine6502 *targetMachine) : _targetMachine(targetMachine) {}
//
// void processor_did_jam(CPU::MOS6502::AllRAMProcessor::Processor *processor, uint16_t address) override {
// [_targetMachine.jamHandler testMachine:_targetMachine didJamAtAddress:address];
// }
//
// private:
// CSTestMachine6502 *_targetMachine;
//};
#pragma mark - Register enum map #pragma mark - Register enum map
static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) { static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) {
@ -45,7 +31,6 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
@implementation CSTestMachine6502 { @implementation CSTestMachine6502 {
CPU::MOS6502::AllRAMProcessor *_processor; CPU::MOS6502::AllRAMProcessor *_processor;
// MachineJamHandler *_cppJamHandler;
} }
#pragma mark - Lifecycle #pragma mark - Lifecycle
@ -117,7 +102,7 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
} }
- (void)runForNumberOfCycles:(int)cycles { - (void)runForNumberOfCycles:(int)cycles {
_processor->run_for_cycles(cycles); _processor->run_for(Cycles(cycles));
} }
@end @end

View File

@ -127,7 +127,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
} }
- (void)runForNumberOfCycles:(int)cycles { - (void)runForNumberOfCycles:(int)cycles {
_processor->run_for_cycles(cycles); _processor->run_for(Cycles(cycles));
} }
- (void)setValue:(uint16_t)value forRegister:(CSTestMachineZ80Register)reg { - (void)setValue:(uint16_t)value forRegister:(CSTestMachineZ80Register)reg {

View File

@ -47,7 +47,7 @@ static void receive_line(uint8_t *next_line)
_tia->set_playfield(0, 0x10); _tia->set_playfield(0, 0x10);
_tia->set_playfield(1, 0xf0); _tia->set_playfield(1, 0xf0);
_tia->set_playfield(2, 0x0e); _tia->set_playfield(2, 0x0e);
_tia->run_for_cycles(228); _tia->run_for(Cycles(228));
XCTAssert(line != nullptr, @"228 cycles should have ended the line"); XCTAssert(line != nullptr, @"228 cycles should have ended the line");
@ -70,7 +70,7 @@ static void receive_line(uint8_t *next_line)
_tia->set_playfield(1, 0xf0); _tia->set_playfield(1, 0xf0);
_tia->set_playfield(2, 0x0e); _tia->set_playfield(2, 0x0e);
_tia->run_for_cycles(228); _tia->run_for(Cycles(228));
XCTAssert(line != nullptr, @"228 cycles should have ended the line"); XCTAssert(line != nullptr, @"228 cycles should have ended the line");
uint8_t expected_line[] = { uint8_t expected_line[] = {
@ -90,7 +90,7 @@ static void receive_line(uint8_t *next_line)
_tia->set_player_graphic(0, 0xff); _tia->set_player_graphic(0, 0xff);
_tia->set_player_position(0); _tia->set_player_position(0);
_tia->run_for_cycles(228); _tia->run_for(Cycles(228));
uint8_t first_expected_line[] = { uint8_t first_expected_line[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -103,7 +103,7 @@ static void receive_line(uint8_t *next_line)
XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line))); XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line)));
line = nullptr; line = nullptr;
_tia->run_for_cycles(228); _tia->run_for(Cycles(228));
uint8_t second_expected_line[] = { uint8_t second_expected_line[] = {
0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

View File

@ -9,9 +9,9 @@
#ifndef Speaker_hpp #ifndef Speaker_hpp
#define Speaker_hpp #define Speaker_hpp
#include <stdint.h> #include <cstdint>
#include <stdio.h> #include <cstdio>
#include <time.h> #include <ctime>
#include <memory> #include <memory>
#include <list> #include <list>
@ -20,6 +20,7 @@
#include "../SignalProcessing/Stepper.hpp" #include "../SignalProcessing/Stepper.hpp"
#include "../SignalProcessing/FIRFilter.hpp" #include "../SignalProcessing/FIRFilter.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp" #include "../Concurrency/AsyncTaskQueue.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace Outputs { namespace Outputs {
@ -132,17 +133,17 @@ class Speaker {
`get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)` `get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)`
to provide source data. to provide source data.
Call `run_for_cycles(n)` to request that the next n cycles of input data are collected. Call `run_for` to request that the next period of input data is collected.
*/ */
template <class T> class Filter: public Speaker { template <class T> class Filter: public Speaker, public ClockReceiver<Filter<T>> {
public: public:
~Filter() { ~Filter() {
_queue->flush(); _queue->flush();
} }
void run_for_cycles(unsigned int input_cycles) { void run_for(const Cycles &cycles) {
enqueue([=]() { enqueue([=]() {
unsigned int cycles_remaining = input_cycles; unsigned int cycles_remaining = (unsigned int)cycles.as_int();
if(coefficients_are_dirty_) update_filter_coefficients(); if(coefficients_are_dirty_) update_filter_coefficients();
// if input and output rates exactly match, just accumulate results and pass on // if input and output rates exactly match, just accumulate results and pass on

View File

@ -13,6 +13,7 @@
#include <cstdint> #include <cstdint>
#include "../RegisterSizes.hpp" #include "../RegisterSizes.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace CPU { namespace CPU {
namespace MOS6502 { namespace MOS6502 {
@ -127,7 +128,7 @@ class ProcessorBase {
that will cause call outs when the program counter reaches those addresses. @c return_from_subroutine can be used to exit from a that will cause call outs when the program counter reaches those addresses. @c return_from_subroutine can be used to exit from a
jammed state. jammed state.
*/ */
template <class T> class Processor: public ProcessorBase { template <class T> class Processor: public ProcessorBase, public ClockReceiver<Processor<T>> {
private: private:
const MicroOp *scheduled_program_counter_; const MicroOp *scheduled_program_counter_;
@ -180,7 +181,7 @@ template <class T> class Processor: public ProcessorBase {
} }
bool is_jammed_; bool is_jammed_;
int cycles_left_to_run_; Cycles cycles_left_to_run_;
enum InterruptRequestFlags: uint8_t { enum InterruptRequestFlags: uint8_t {
Reset = 0x80, Reset = 0x80,
@ -263,7 +264,6 @@ template <class T> class Processor: public ProcessorBase {
protected: protected:
Processor() : Processor() :
is_jammed_(false), is_jammed_(false),
cycles_left_to_run_(0),
ready_line_is_enabled_(false), ready_line_is_enabled_(false),
ready_is_active_(false), ready_is_active_(false),
inverse_interrupt_flag_(0), inverse_interrupt_flag_(0),
@ -283,6 +283,7 @@ template <class T> class Processor: public ProcessorBase {
} }
public: public:
using ClockReceiver<Processor<T>>::run_for;
/*! /*!
Runs the 6502 for a supplied number of cycles. Runs the 6502 for a supplied number of cycles.
@ -290,9 +291,9 @@ template <class T> class Processor: public ProcessorBase {
The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle. The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle.
If it is a read operation then @c value will be seeded with the value 0xff. If it is a read operation then @c value will be seeded with the value 0xff.
@param number_of_cycles The number of cycles to run the 6502 for. @param cycles The number of cycles to run the 6502 for.
*/ */
void run_for_cycles(int number_of_cycles) { void run_for(const Cycles &cycles) {
static const MicroOp doBranch[] = { static const MicroOp doBranch[] = {
CycleReadFromPC, CycleReadFromPC,
CycleAddSignedOperandToPC, CycleAddSignedOperandToPC,
@ -336,14 +337,14 @@ template <class T> class Processor: public ProcessorBase {
irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \ irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); \ number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); \
nextBusOperation = BusOperation::None; \ nextBusOperation = BusOperation::None; \
if(number_of_cycles <= 0) break; if(number_of_cycles <= Cycles(0)) break;
checkSchedule(); checkSchedule();
number_of_cycles += cycles_left_to_run_; Cycles number_of_cycles = cycles + cycles_left_to_run_;
while(number_of_cycles > 0) { while(number_of_cycles > Cycles(0)) {
while (ready_is_active_ && number_of_cycles > 0) { while (ready_is_active_ && number_of_cycles > Cycles(0)) {
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue); number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue);
} }
@ -829,7 +830,7 @@ template <class T> class Processor: public ProcessorBase {
} }
/*! /*!
Called to announce the end of a run_for_cycles period, allowing deferred work to take place. Called to announce the end of a run_for period, allowing deferred work to take place.
Users of the 6502 template may override this. Users of the 6502 template may override this.
*/ */

View File

@ -20,7 +20,7 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
set_power_on(false); set_power_on(false);
} }
inline int perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) { inline Cycles perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) {
timestamp_++; timestamp_++;
if(operation == BusOperation::ReadOpcode) { if(operation == BusOperation::ReadOpcode) {
@ -33,11 +33,11 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
memory_[address] = *value; memory_[address] = *value;
} }
return 1; return Cycles(1);
} }
void run_for_cycles(int number_of_cycles) { void run_for(const Cycles &cycles) {
Processor<ConcreteAllRAMProcessor>::run_for_cycles(number_of_cycles); Processor<ConcreteAllRAMProcessor>::run_for(cycles);
} }
bool is_jammed() { bool is_jammed() {

View File

@ -22,7 +22,7 @@ class AllRAMProcessor:
static AllRAMProcessor *Processor(); static AllRAMProcessor *Processor();
virtual ~AllRAMProcessor() {} virtual ~AllRAMProcessor() {}
virtual void run_for_cycles(int number_of_cycles) = 0; virtual void run_for(const Cycles &cycles) = 0;
virtual bool is_jammed() = 0; virtual bool is_jammed() = 0;
virtual void set_irq_line(bool value) = 0; virtual void set_irq_line(bool value) = 0;
virtual void set_nmi_line(bool value) = 0; virtual void set_nmi_line(bool value) = 0;

View File

@ -15,6 +15,7 @@
#include <vector> #include <vector>
#include "../RegisterSizes.hpp" #include "../RegisterSizes.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace CPU { namespace CPU {
namespace Z80 { namespace Z80 {
@ -87,7 +88,7 @@ struct PartialMachineCycle {
InputStart, InputStart,
OutputStart, OutputStart,
} operation; } operation;
int length; Cycles length;
uint16_t *address; uint16_t *address;
uint8_t *value; uint8_t *value;
bool was_requested; bool was_requested;
@ -104,28 +105,28 @@ struct PartialMachineCycle {
}; };
// Elemental bus operations // Elemental bus operations
#define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, 2, &pc_.full, &operation_, false} #define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, Cycles(2), &pc_.full, &operation_, false}
#define ReadOpcodeWait(length, f) {PartialMachineCycle::ReadOpcodeWait, length, &pc_.full, &operation_, f} #define ReadOpcodeWait(length, f) {PartialMachineCycle::ReadOpcodeWait, Cycles(length), &pc_.full, &operation_, f}
#define Refresh(len) {PartialMachineCycle::Refresh, len, &refresh_addr_.full, nullptr, false} #define Refresh(len) {PartialMachineCycle::Refresh, Cycles(len), &refresh_addr_.full, nullptr, false}
#define ReadStart(addr, val) {PartialMachineCycle::ReadStart, 2, &addr.full, &val, false} #define ReadStart(addr, val) {PartialMachineCycle::ReadStart, Cycles(2), &addr.full, &val, false}
#define ReadWait(l, addr, val, f) {PartialMachineCycle::ReadWait, l, &addr.full, &val, f} #define ReadWait(l, addr, val, f) {PartialMachineCycle::ReadWait, Cycles(l), &addr.full, &val, f}
#define ReadEnd(addr, val) {PartialMachineCycle::Read, 1, &addr.full, &val, false} #define ReadEnd(addr, val) {PartialMachineCycle::Read, Cycles(1), &addr.full, &val, false}
#define WriteStart(addr, val) {PartialMachineCycle::WriteStart, 2, &addr.full, &val, false} #define WriteStart(addr, val) {PartialMachineCycle::WriteStart, Cycles(2), &addr.full, &val, false}
#define WriteWait(l, addr, val, f) {PartialMachineCycle::WriteWait, l, &addr.full, &val, f} #define WriteWait(l, addr, val, f) {PartialMachineCycle::WriteWait, Cycles(l), &addr.full, &val, f}
#define WriteEnd(addr, val) {PartialMachineCycle::Write, 1, &addr.full, &val, false} #define WriteEnd(addr, val) {PartialMachineCycle::Write, Cycles(1), &addr.full, &val, false}
#define InputStart(addr, val) {PartialMachineCycle::InputStart, 2, &addr.full, &val, false} #define InputStart(addr, val) {PartialMachineCycle::InputStart, Cycles(2), &addr.full, &val, false}
#define InputWait(addr, val, f) {PartialMachineCycle::InputWait, 1, &addr.full, &val, f} #define InputWait(addr, val, f) {PartialMachineCycle::InputWait, Cycles(1), &addr.full, &val, f}
#define InputEnd(addr, val) {PartialMachineCycle::Input, 1, &addr.full, &val, false} #define InputEnd(addr, val) {PartialMachineCycle::Input, Cycles(1), &addr.full, &val, false}
#define OutputStart(addr, val) {PartialMachineCycle::OutputStart, 2, &addr.full, &val, false} #define OutputStart(addr, val) {PartialMachineCycle::OutputStart, Cycles(2), &addr.full, &val, false}
#define OutputWait(addr, val, f) {PartialMachineCycle::OutputWait, 1, &addr.full, &val, f} #define OutputWait(addr, val, f) {PartialMachineCycle::OutputWait, Cycles(1), &addr.full, &val, f}
#define OutputEnd(addr, val) {PartialMachineCycle::Output, 1, &addr.full, &val, false} #define OutputEnd(addr, val) {PartialMachineCycle::Output, Cycles(1), &addr.full, &val, false}
#define IntAck(length, val) {PartialMachineCycle::Interrupt, length, nullptr, &val, false} #define IntAck(length, val) {PartialMachineCycle::Interrupt, Cycles(length), nullptr, &val, false}
#define IntWait(val) {PartialMachineCycle::InterruptWait, 1, nullptr, &val, true} #define IntWait(val) {PartialMachineCycle::InterruptWait, Cycles(1), nullptr, &val, true}
// A wrapper to express a bus operation as a micro-op // A wrapper to express a bus operation as a micro-op
#define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op} #define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op}
@ -163,7 +164,7 @@ struct PartialMachineCycle {
order to provide the bus on which the Z80 operates and @c flush(), which is called upon completion of a continuous run order to provide the bus on which the Z80 operates and @c flush(), which is called upon completion of a continuous run
of cycles to allow a subclass to bring any on-demand activities up to date. of cycles to allow a subclass to bring any on-demand activities up to date.
*/ */
template <class T> class Processor { template <class T> class Processor: public ClockReceiver<Processor<T>> {
private: private:
uint8_t a_; uint8_t a_;
RegisterPair bc_, de_, hl_; RegisterPair bc_, de_, hl_;
@ -182,7 +183,7 @@ template <class T> class Processor {
uint8_t carry_result_; // the carry flag is set if bit 0 of carry_result_ is set uint8_t carry_result_; // the carry flag is set if bit 0 of carry_result_ is set
uint8_t halt_mask_; uint8_t halt_mask_;
int number_of_cycles_; Cycles number_of_cycles_;
enum Interrupt: uint8_t { enum Interrupt: uint8_t {
IRQ = 0x01, IRQ = 0x01,
@ -428,7 +429,7 @@ template <class T> class Processor {
target.instructions[c] = &target.all_operations[destination]; target.instructions[c] = &target.all_operations[destination];
for(size_t t = 0; t < lengths[c];) { for(size_t t = 0; t < lengths[c];) {
// Skip zero-length bus cycles. // Skip zero-length bus cycles.
if(table[c][t].type == MicroOp::BusOperation && table[c][t].machine_cycle.length == 0) { if(table[c][t].type == MicroOp::BusOperation && table[c][t].machine_cycle.length.as_int() == 0) {
t++; t++;
continue; continue;
} }
@ -765,7 +766,6 @@ template <class T> class Processor {
public: public:
Processor() : Processor() :
halt_mask_(0xff), halt_mask_(0xff),
number_of_cycles_(0),
interrupt_mode_(0), interrupt_mode_(0),
wait_line_(false), wait_line_(false),
request_status_(Interrupt::PowerOn), request_status_(Interrupt::PowerOn),
@ -849,6 +849,7 @@ template <class T> class Processor {
copy_program(irq_mode2_program, irq_program_[2]); copy_program(irq_mode2_program, irq_program_[2]);
} }
using ClockReceiver<Processor<T>>::run_for;
/*! /*!
Runs the Z80 for a supplied number of cycles. Runs the Z80 for a supplied number of cycles.
@ -856,9 +857,9 @@ template <class T> class Processor {
If it is a read operation then @c value will be seeded with the value 0xff. If it is a read operation then @c value will be seeded with the value 0xff.
@param number_of_cycles The number of cycles to run the Z80 for. @param cycles The number of cycles to run for.
*/ */
void run_for_cycles(int number_of_cycles) { void run_for(const Cycles &cycles) {
#define advance_operation() \ #define advance_operation() \
pc_increment_ = 1; \ pc_increment_ = 1; \
@ -878,7 +879,7 @@ template <class T> class Processor {
scheduled_program_counter_ = base_page_.fetch_decode_execute_data; \ scheduled_program_counter_ = base_page_.fetch_decode_execute_data; \
} }
number_of_cycles_ += number_of_cycles; number_of_cycles_ += cycles;
if(!scheduled_program_counter_) { if(!scheduled_program_counter_) {
advance_operation(); advance_operation();
} }
@ -887,7 +888,7 @@ template <class T> class Processor {
while(bus_request_line_) { while(bus_request_line_) {
static PartialMachineCycle bus_acknowledge_cycle = {PartialMachineCycle::BusAcknowledge, 1, nullptr, nullptr, false}; static PartialMachineCycle bus_acknowledge_cycle = {PartialMachineCycle::BusAcknowledge, 1, nullptr, nullptr, false};
number_of_cycles_ -= static_cast<T *>(this)->perform_machine_cycle(bus_acknowledge_cycle) + 1; number_of_cycles_ -= static_cast<T *>(this)->perform_machine_cycle(bus_acknowledge_cycle) + Cycles(1);
if(!number_of_cycles_) { if(!number_of_cycles_) {
static_cast<T *>(this)->flush(); static_cast<T *>(this)->flush();
return; return;
@ -1696,14 +1697,14 @@ template <class T> class Processor {
} }
/*! /*!
Called to announce the end of a run_for_cycles period, allowing deferred work to take place. Called to announce the end of a run_for period, allowing deferred work to take place.
Users of the Z80 template may override this. Users of the Z80 template may override this.
*/ */
void flush() {} void flush() {}
int perform_machine_cycle(const PartialMachineCycle &cycle) { Cycles perform_machine_cycle(const PartialMachineCycle &cycle) {
return 0; return Cycles(0);
} }
/*! /*!

View File

@ -16,10 +16,10 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
public: public:
ConcreteAllRAMProcessor() : AllRAMProcessor() {} ConcreteAllRAMProcessor() : AllRAMProcessor() {}
inline int perform_machine_cycle(const PartialMachineCycle &cycle) { inline Cycles perform_machine_cycle(const PartialMachineCycle &cycle) {
timestamp_ += cycle.length; timestamp_ += cycle.length.as_int();
if(!cycle.is_terminal()) { if(!cycle.is_terminal()) {
return 0; return Cycles(0);
} }
uint16_t address = cycle.address ? *cycle.address : 0x0000; uint16_t address = cycle.address ? *cycle.address : 0x0000;
@ -60,11 +60,11 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_); delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
} }
return 0; return Cycles(0);
} }
void run_for_cycles(int cycles) { void run_for(const Cycles &cycles) {
CPU::Z80::Processor<ConcreteAllRAMProcessor>::run_for_cycles(cycles); CPU::Z80::Processor<ConcreteAllRAMProcessor>::run_for(cycles);
} }
uint16_t get_value_of_register(Register r) { uint16_t get_value_of_register(Register r) {

View File

@ -28,7 +28,7 @@ class AllRAMProcessor:
delegate_ = delegate; delegate_ = delegate;
} }
virtual void run_for_cycles(int cycles) = 0; virtual void run_for(const Cycles &cycles) = 0;
virtual uint16_t get_value_of_register(Register r) = 0; virtual uint16_t get_value_of_register(Register r) = 0;
virtual void set_value_of_register(Register r, uint16_t value) = 0; virtual void set_value_of_register(Register r, uint16_t value) = 0;
virtual bool get_halt_line() = 0; virtual bool get_halt_line() = 0;

View File

@ -78,13 +78,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
// find end of lead-in // find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2) { while(shift_register_ == 0x3ff && index_count_ < 2) {
run_for_cycles(1); run_for(Cycles(1));
} }
// continue for a further nine bits // continue for a further nine bits
bit_count_ = 0; bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2) { while(bit_count_ < 9 && index_count_ < 2) {
run_for_cycles(1); run_for(Cycles(1));
} }
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_); return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
@ -92,14 +92,14 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
unsigned int get_next_byte() { unsigned int get_next_byte() {
bit_count_ = 0; bit_count_ = 0;
while(bit_count_ < 10) run_for_cycles(1); while(bit_count_ < 10) run_for(Cycles(1));
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_); return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
} }
void proceed_to_shift_value(unsigned int shift_value) { void proceed_to_shift_value(unsigned int shift_value) {
index_count_ = 0; index_count_ = 0;
while(shift_register_ != shift_value && index_count_ < 2) { while(shift_register_ != shift_value && index_count_ < 2) {
run_for_cycles(1); run_for(Cycles(1));
} }
} }

View File

@ -20,9 +20,9 @@ DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, size_t length
offset_history_(length_of_history, 0), offset_history_(length_of_history, 0),
offset_(0) {} offset_(0) {}
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) { void DigitalPhaseLockedLoop::run_for(const Cycles &cycles) {
offset_ += number_of_cycles; offset_ += cycles.as_int();
phase_ += number_of_cycles; phase_ += cycles.as_int();
if(phase_ >= window_length_) { if(phase_ >= window_length_) {
int windows_crossed = phase_ / window_length_; int windows_crossed = phase_ / window_length_;

View File

@ -12,9 +12,11 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Storage { namespace Storage {
class DigitalPhaseLockedLoop { class DigitalPhaseLockedLoop: public ClockReceiver<DigitalPhaseLockedLoop> {
public: public:
/*! /*!
Instantiates a @c DigitalPhaseLockedLoop. Instantiates a @c DigitalPhaseLockedLoop.
@ -29,7 +31,8 @@ class DigitalPhaseLockedLoop {
@c number_of_cycles The time to run the loop for. @c number_of_cycles The time to run the loop for.
*/ */
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using ClockReceiver<DigitalPhaseLockedLoop>::run_for;
/*! /*!
Announces a pulse at the current time. Announces a pulse at the current time.

View File

@ -11,17 +11,17 @@
using namespace Storage::Disk; using namespace Storage::Disk;
Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) : Controller::Controller(int clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
clock_rate_(clock_rate * clock_rate_multiplier), clock_rate_(clock_rate * clock_rate_multiplier),
clock_rate_multiplier_(clock_rate_multiplier), clock_rate_multiplier_(clock_rate_multiplier),
rotational_multiplier_(60u, revolutions_per_minute), rotational_multiplier_(60, revolutions_per_minute),
cycles_since_index_hole_(0), cycles_since_index_hole_(0),
motor_is_on_(false), motor_is_on_(false),
is_reading_(true), is_reading_(true),
TimedEventLoop(clock_rate * clock_rate_multiplier) { TimedEventLoop((unsigned int)(clock_rate * clock_rate_multiplier)) {
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
Time one(1); Time one(1);
set_expected_bit_length(one); set_expected_bit_length(one);
@ -40,13 +40,13 @@ void Controller::setup_track() {
get_next_event(offset); get_next_event(offset);
} }
void Controller::run_for_cycles(int number_of_cycles) { void Controller::run_for(const Cycles &cycles) {
Time zero(0); Time zero(0);
if(drive_ && drive_->has_disk() && motor_is_on_) { if(drive_ && drive_->has_disk() && motor_is_on_) {
if(!track_) setup_track(); if(!track_) setup_track();
number_of_cycles *= clock_rate_multiplier_; int number_of_cycles = clock_rate_multiplier_ * cycles.as_int();
while(number_of_cycles) { while(number_of_cycles) {
int cycles_until_next_event = (int)get_cycles_until_next_event(); int cycles_until_next_event = (int)get_cycles_until_next_event();
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
@ -60,7 +60,7 @@ void Controller::run_for_cycles(int number_of_cycles) {
number_of_cycles -= cycles_to_run_for; number_of_cycles -= cycles_to_run_for;
if(is_reading_) { if(is_reading_) {
pll_->run_for_cycles(cycles_to_run_for); pll_->run_for(Cycles(cycles_to_run_for));
} else { } else {
if(cycles_until_bits_written_ > zero) { if(cycles_until_bits_written_ > zero) {
Storage::Time cycles_to_run_for_time(cycles_to_run_for); Storage::Time cycles_to_run_for_time(cycles_to_run_for);
@ -75,7 +75,7 @@ void Controller::run_for_cycles(int number_of_cycles) {
} }
} }
} }
TimedEventLoop::run_for_cycles(cycles_to_run_for); TimedEventLoop::run_for(Cycles(cycles_to_run_for));
} }
} }
} }
@ -171,7 +171,7 @@ void Controller::set_expected_bit_length(Time bit_length) {
} }
void Controller::digital_phase_locked_loop_output_bit(int value) { void Controller::digital_phase_locked_loop_output_bit(int value) {
process_input_bit(value, cycles_since_index_hole_); process_input_bit(value, (unsigned int)cycles_since_index_hole_);
} }
#pragma mark - Drive actions #pragma mark - Drive actions

View File

@ -33,7 +33,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
spinning inserted disks at @c revolutions_per_minute. spinning inserted disks at @c revolutions_per_minute.
*/ */
Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); Controller(int clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
/*! /*!
Communicates to the PLL the expected length of a bit as a fraction of a second. Communicates to the PLL the expected length of a bit as a fraction of a second.
@ -43,7 +43,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
/*! /*!
Advances the drive by @c number_of_cycles cycles. Advances the drive by @c number_of_cycles cycles.
*/ */
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using TimedEventLoop::run_for;
/*! /*!
Sets the current drive. Sets the current drive.
@ -113,14 +114,14 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
private: private:
Time bit_length_; Time bit_length_;
unsigned int clock_rate_; int clock_rate_;
unsigned int clock_rate_multiplier_; int clock_rate_multiplier_;
Time rotational_multiplier_; Time rotational_multiplier_;
std::shared_ptr<DigitalPhaseLockedLoop> pll_; std::shared_ptr<DigitalPhaseLockedLoop> pll_;
std::shared_ptr<Drive> drive_; std::shared_ptr<Drive> drive_;
std::shared_ptr<Track> track_; std::shared_ptr<Track> track_;
unsigned int cycles_since_index_hole_; int cycles_since_index_hole_;
inline void get_next_event(const Time &duration_already_passed); inline void get_next_event(const Time &duration_already_passed);
Track::Event current_event_; Track::Event current_event_;

View File

@ -294,7 +294,7 @@ uint8_t Parser::get_byte_for_shift_value(uint16_t value) {
uint8_t Parser::get_next_byte() { uint8_t Parser::get_next_byte() {
bit_count_ = 0; bit_count_ = 0;
while(bit_count_ < 16) run_for_cycles(1); while(bit_count_ < 16) run_for(Cycles(1));
uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_); uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_);
crc_generator_.add(byte); crc_generator_.add(byte);
return byte; return byte;
@ -309,7 +309,7 @@ std::vector<uint8_t> Parser::get_track() {
// align to the next index hole // align to the next index hole
index_count_ = 0; index_count_ = 0;
while(!index_count_) run_for_cycles(1); while(!index_count_) run_for(Cycles(1));
// capture every other bit until the next index hole // capture every other bit until the next index hole
index_count_ = 0; index_count_ = 0;
@ -319,7 +319,7 @@ std::vector<uint8_t> Parser::get_track() {
bool found_sync = false; bool found_sync = false;
while(!index_count_ && !found_sync && bit_count_ < 16) { while(!index_count_ && !found_sync && bit_count_ < 16) {
int previous_bit_count = bit_count_; int previous_bit_count = bit_count_;
run_for_cycles(1); run_for(Cycles(1));
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) { if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) {
uint16_t low_shift_register = (shift_register_&0xffff); uint16_t low_shift_register = (shift_register_&0xffff);
@ -393,7 +393,7 @@ std::shared_ptr<Sector> Parser::get_next_sector()
// look for an ID address mark // look for an ID address mark
bool id_found = false; bool id_found = false;
while(!id_found) { while(!id_found) {
run_for_cycles(1); run_for(Cycles(1));
if(is_mfm_) { if(is_mfm_) {
while(shift_register_ == MFMSync) { while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte(); uint8_t mark = get_next_byte();
@ -424,7 +424,7 @@ std::shared_ptr<Sector> Parser::get_next_sector()
// look for data mark // look for data mark
bool data_found = false; bool data_found = false;
while(!data_found) { while(!data_found) {
run_for_cycles(1); run_for(Cycles(1));
if(is_mfm_) { if(is_mfm_) {
while(shift_register_ == MFMSync) { while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte(); uint8_t mark = get_next_byte();

View File

@ -74,7 +74,7 @@ Shifter::Shifter() :
} }
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
pll_.run_for_cycles((int)((float)PLLClockRate * pulse.length.get_float())); pll_.run_for(Cycles((int)((float)PLLClockRate * pulse.length.get_float())));
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
if(is_high != was_high_) { if(is_high != was_high_) {

View File

@ -92,9 +92,9 @@ void TapePlayer::get_next_pulse() {
set_next_event_time_interval(current_pulse_.length); set_next_event_time_interval(current_pulse_.length);
} }
void TapePlayer::run_for_cycles(int number_of_cycles) { void TapePlayer::run_for(const Cycles &cycles) {
if(has_tape()) { if(has_tape()) {
TimedEventLoop::run_for_cycles(number_of_cycles); TimedEventLoop::run_for(cycles);
} }
} }
@ -125,8 +125,8 @@ bool BinaryTapePlayer::get_input() {
return motor_is_running_ && input_level_; return motor_is_running_ && input_level_;
} }
void BinaryTapePlayer::run_for_cycles(int number_of_cycles) { void BinaryTapePlayer::run_for(const Cycles &cycles) {
if(motor_is_running_) TapePlayer::run_for_cycles(number_of_cycles); if(motor_is_running_) TapePlayer::run_for(cycles);
} }
void BinaryTapePlayer::set_delegate(Delegate *delegate) { void BinaryTapePlayer::set_delegate(Delegate *delegate) {

View File

@ -10,6 +10,8 @@
#define Tape_hpp #define Tape_hpp
#include <memory> #include <memory>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../TimedEventLoop.hpp" #include "../TimedEventLoop.hpp"
namespace Storage { namespace Storage {
@ -99,7 +101,9 @@ class TapePlayer: public TimedEventLoop {
bool has_tape(); bool has_tape();
std::shared_ptr<Storage::Tape::Tape> get_tape(); std::shared_ptr<Storage::Tape::Tape> get_tape();
void run_for_cycles(int number_of_cycles); using TimedEventLoop::run_for;
void run_for(const Cycles &cycles);
void run_for_input_pulse(); void run_for_input_pulse();
protected: protected:
@ -128,7 +132,8 @@ class BinaryTapePlayer: public TapePlayer {
void set_tape_output(bool set); void set_tape_output(bool set);
bool get_input(); bool get_input();
void run_for_cycles(int number_of_cycles); using TapePlayer::run_for;
void run_for(const Cycles &cycles);
class Delegate { class Delegate {
public: public:

View File

@ -15,8 +15,8 @@ using namespace Storage;
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
input_clock_rate_(input_clock_rate) {} input_clock_rate_(input_clock_rate) {}
void TimedEventLoop::run_for_cycles(int number_of_cycles) { void TimedEventLoop::run_for(const Cycles &cycles) {
cycles_until_event_ -= number_of_cycles; cycles_until_event_ -= cycles.as_int();
while(cycles_until_event_ <= 0) { while(cycles_until_event_ <= 0) {
process_next_event(); process_next_event();
} }

View File

@ -11,9 +11,11 @@
#include "Storage.hpp" #include "Storage.hpp"
#include <memory> #include "../ClockReceiver/ClockReceiver.hpp"
#include "../SignalProcessing/Stepper.hpp" #include "../SignalProcessing/Stepper.hpp"
#include <memory>
namespace Storage { namespace Storage {
/*! /*!
@ -22,7 +24,7 @@ namespace Storage {
Subclasses are responsible for calling @c set_next_event_time_interval to establish the time Subclasses are responsible for calling @c set_next_event_time_interval to establish the time
until a next event; @c process_next_event will be called when that event occurs, with progression until a next event; @c process_next_event will be called when that event occurs, with progression
determined via @c run_for_cycles. determined via @c run_for.
Due to the aggregation of total timing information between events e.g. if an event loop has Due to the aggregation of total timing information between events e.g. if an event loop has
a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second, a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second,
@ -36,7 +38,7 @@ namespace Storage {
@c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing @c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing
loop and fast forward immediately to the next event. loop and fast forward immediately to the next event.
*/ */
class TimedEventLoop { class TimedEventLoop: public ClockReceiver<TimedEventLoop> {
public: public:
/*! /*!
Constructs a timed event loop that will be clocked at @c input_clock_rate. Constructs a timed event loop that will be clocked at @c input_clock_rate.
@ -46,7 +48,8 @@ namespace Storage {
/*! /*!
Advances the event loop by @c number_of_cycles cycles. Advances the event loop by @c number_of_cycles cycles.
*/ */
void run_for_cycles(int number_of_cycles); void run_for(const Cycles &cycles);
using ClockReceiver<TimedEventLoop>::run_for;
/*! /*!
@returns the number of whole cycles remaining until the next event is triggered. @returns the number of whole cycles remaining until the next event is triggered.