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:
commit
e90d128a26
171
ClockReceiver/ClockReceiver.hpp
Normal file
171
ClockReceiver/ClockReceiver.hpp
Normal 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 */
|
@ -124,10 +124,11 @@ uint8_t WD1770::get_register(int address) {
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
|
||||
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
|
||||
void WD1770::run_for(const Cycles &cycles) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
|
||||
if(delay_time_) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event(Event::Timer);
|
||||
|
@ -43,7 +43,8 @@ class WD1770: public Storage::Disk::Controller {
|
||||
uint8_t get_register(int address);
|
||||
|
||||
/// 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 {
|
||||
NotReady = 0x80,
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
/*!
|
||||
@ -26,7 +28,7 @@ namespace MOS {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522 {
|
||||
template <class T> class MOS6522: public ClockReceiver<MOS6522<T>> {
|
||||
private:
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
@ -250,32 +252,22 @@ template <class T> class MOS6522 {
|
||||
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_) {
|
||||
phase2();
|
||||
number_of_cycles--;
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_cycles >= 2) {
|
||||
while(number_of_half_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_cycles -= 2;
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_cycles) {
|
||||
if(number_of_half_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
@ -283,13 +275,9 @@ template <class T> class MOS6522 {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for a specified number of cycles.
|
||||
|
||||
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) {
|
||||
/*! Runs for a specified number of cycles. */
|
||||
inline void run_for(const Cycles &cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
phase1();
|
||||
phase2();
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
/*!
|
||||
@ -25,7 +27,7 @@ namespace MOS {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6532 {
|
||||
template <class T> class MOS6532: public ClockReceiver<MOS6532<T>> {
|
||||
public:
|
||||
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
|
||||
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
|
||||
@ -104,7 +106,10 @@ template <class T> class MOS6532 {
|
||||
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
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
timer_.value -= number_of_cycles;
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
@ -40,7 +41,7 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
|
||||
@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:
|
||||
MOS6560() :
|
||||
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.
|
||||
*/
|
||||
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
|
||||
cycles_since_speaker_update_ += number_of_cycles;
|
||||
cycles_since_speaker_update_ += cycles;
|
||||
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
// 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_;
|
||||
@ -406,10 +409,9 @@ template <class T> class MOS6560 {
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||
cycles_since_speaker_update_ &= 3;
|
||||
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
|
||||
// register state
|
||||
|
@ -44,7 +44,7 @@ class Machine:
|
||||
virtual void close_output();
|
||||
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 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
|
||||
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
|
||||
|
@ -14,17 +14,17 @@
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_input_value_{0xff, 0xff},
|
||||
cycles_since_speaker_update_(0),
|
||||
cycles_since_video_update_(0),
|
||||
cycles_since_6532_update_(0) {}
|
||||
cycles_since_speaker_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;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
@ -37,25 +37,21 @@ class Bus {
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3);
|
||||
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
|
||||
speaker_->run_for_cycles(audio_cycles);
|
||||
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
unsigned int cycles_since_video_update_;
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_->run_for_cycles((int)cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
tia_->run_for(cycles_since_video_update_.flush());
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
unsigned int cycles_since_6532_update_;
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for_cycles(cycles_since_6532_update_);
|
||||
cycles_since_6532_update_ = 0;
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -22,25 +22,25 @@ template<class T> class Cartridge:
|
||||
Cartridge(const std::vector<uint8_t> &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 advance_cycles(unsigned int cycles) {}
|
||||
void advance_cycles(int cycles) {}
|
||||
|
||||
// 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;
|
||||
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
|
||||
// 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
|
||||
// skips to the end of the line.
|
||||
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_video_update_ += cycles_run_for;
|
||||
cycles_since_6532_update_ += (cycles_run_for / 3);
|
||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
|
||||
|
||||
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);
|
||||
|
||||
return cycles_run_for / 3;
|
||||
return Cycles(cycles_run_for / 3);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
|
@ -22,7 +22,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void advance_cycles(unsigned int cycles) {
|
||||
void advance_cycles(int cycles) {
|
||||
cycles_since_audio_update_ += cycles;
|
||||
}
|
||||
|
||||
@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
|
||||
inline uint8_t update_audio() {
|
||||
const unsigned int clock_divisor = 57;
|
||||
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor;
|
||||
cycles_since_audio_update_ %= clock_divisor;
|
||||
int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int();
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
@ -126,7 +125,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
uint8_t random_number_generator_;
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t audio_channel_[3];
|
||||
unsigned int cycles_since_audio_update_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
/* 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(horizontal_counter_) {
|
||||
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
|
||||
output_for_cycles(cycles);
|
||||
number_of_cycles -= cycles;
|
||||
int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
|
||||
output_for_cycles(output_cycles);
|
||||
number_of_cycles -= output_cycles;
|
||||
}
|
||||
|
||||
// output full lines for as long as possible
|
||||
@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) {
|
||||
void TIA::reset_horizontal_counter() {
|
||||
}
|
||||
|
||||
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) {
|
||||
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line;
|
||||
int TIA::get_cycles_until_horizontal_blank(const Cycles &from_offset) {
|
||||
return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line;
|
||||
}
|
||||
|
||||
void TIA::set_background_colour(uint8_t colour) {
|
||||
|
@ -10,11 +10,13 @@
|
||||
#define TIA_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class TIA {
|
||||
class TIA: public ClockReceiver<TIA> {
|
||||
public:
|
||||
TIA();
|
||||
// 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
|
||||
first cycle performed.
|
||||
Advances the TIA by @c cycles. Any queued setters take effect in the 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_sync(bool sync);
|
||||
@ -41,7 +43,7 @@ class TIA {
|
||||
@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].
|
||||
*/
|
||||
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);
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "../Outputs/CRT/CRT.hpp"
|
||||
#include "../Outputs/Speaker.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
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.
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
|
||||
|
||||
/// Runs the machine for @c number_of_cycle cycles.
|
||||
virtual void run_for_cycles(int number_of_cycles) = 0;
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles &cycles) = 0;
|
||||
|
||||
// TODO: sever the clock-rate stuff.
|
||||
double get_clock_rate() {
|
||||
|
@ -34,7 +34,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
|
||||
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):
|
||||
|
||||
@ -63,10 +63,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
|
||||
drive_VIA_.set_register(address, *value);
|
||||
}
|
||||
|
||||
serial_port_VIA_->run_for_cycles(1);
|
||||
drive_VIA_.run_for_cycles(1);
|
||||
serial_port_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) {
|
||||
@ -79,11 +79,11 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
set_drive(drive);
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
void Machine::run_for(const Cycles &cycles) {
|
||||
CPU::MOS6502::Processor<Machine>::run_for(cycles);
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
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
|
||||
|
@ -138,11 +138,11 @@ class Machine:
|
||||
*/
|
||||
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);
|
||||
|
||||
// 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
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
@ -97,9 +97,9 @@ Machine::~Machine() {
|
||||
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
|
||||
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
|
||||
if(isReadOperation(operation)) {
|
||||
@ -179,18 +179,18 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
|
||||
}
|
||||
}
|
||||
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
user_port_via_->run_for(Cycles(1));
|
||||
keyboard_via_->run_for(Cycles(1));
|
||||
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
tape_->run_for_cycles(1);
|
||||
if(c1540_) c1540_->run_for_cycles(1);
|
||||
tape_->run_for(Cycles(1));
|
||||
if(c1540_) c1540_->run_for(Cycles(1));
|
||||
|
||||
return 1;
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
@ -315,7 +315,7 @@ void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
|
||||
void Machine::install_disk_rom() {
|
||||
if(!drive_rom_.empty() && c1540_) {
|
||||
c1540_->set_rom(drive_rom_);
|
||||
c1540_->run_for_cycles(2000000);
|
||||
c1540_->run_for(Cycles(2000000));
|
||||
drive_rom_.clear();
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ class Machine:
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
|
||||
// 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(); }
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
@ -174,7 +174,7 @@ class Machine:
|
||||
virtual void close_output();
|
||||
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 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
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
@ -15,7 +15,6 @@ using namespace Electron;
|
||||
Machine::Machine() :
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_display_update_(0),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
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
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
// 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 {
|
||||
switch(address & 0xff0f) {
|
||||
case 0xfe00:
|
||||
@ -316,10 +315,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_display_update_ += cycles;
|
||||
cycles_since_audio_update_ += cycles;
|
||||
if(cycles_since_audio_update_ > 16384) update_audio();
|
||||
tape_.run_for_cycles(cycles);
|
||||
cycles_since_display_update_ += Cycles((int)cycles);
|
||||
cycles_since_audio_update_ += Cycles((int)cycles);
|
||||
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
||||
tape_.run_for(Cycles((int)cycles));
|
||||
|
||||
cycles_until_display_interrupt_ -= cycles;
|
||||
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(plus3_) plus3_->run_for_cycles(4*cycles);
|
||||
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
|
||||
if(shift_restart_counter_) {
|
||||
shift_restart_counter_ -= cycles;
|
||||
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() {
|
||||
@ -353,8 +352,7 @@ void Machine::flush() {
|
||||
|
||||
inline void Machine::update_display() {
|
||||
if(cycles_since_display_update_) {
|
||||
video_output_->run_for_cycles((int)cycles_since_display_update_);
|
||||
cycles_since_display_update_ = 0;
|
||||
video_output_->run_for(cycles_since_display_update_.flush());
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,9 +364,7 @@ inline void Machine::queue_next_display_interrupt() {
|
||||
|
||||
inline void Machine::update_audio() {
|
||||
if(cycles_since_audio_update_) {
|
||||
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
|
||||
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
|
||||
speaker_->run_for_cycles(difference);
|
||||
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ class Machine:
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
|
||||
// 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();
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
@ -94,7 +94,7 @@ class Machine:
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
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
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape);
|
||||
@ -128,8 +128,8 @@ class Machine:
|
||||
uint8_t key_states_[14];
|
||||
|
||||
// Counters related to simultaneous subsystems
|
||||
unsigned int cycles_since_display_update_;
|
||||
unsigned int cycles_since_audio_update_;
|
||||
Cycles cycles_since_display_update_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
int cycles_until_display_interrupt_;
|
||||
Interrupt next_display_interrupt_;
|
||||
VideoOutput::Range video_access_range_;
|
||||
|
@ -80,14 +80,14 @@ void Tape::acorn_shifter_output_bit(int 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_in_input_mode_) {
|
||||
if(is_running_) {
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
TapePlayer::run_for(cycles);
|
||||
}
|
||||
} 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
|
||||
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
push_tape_bit(1);
|
||||
|
@ -9,12 +9,13 @@
|
||||
#ifndef Electron_Tape_h
|
||||
#define Electron_Tape_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Tape:
|
||||
@ -23,7 +24,8 @@ class Tape:
|
||||
public:
|
||||
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();
|
||||
void set_data_register(uint8_t value);
|
||||
|
@ -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;
|
||||
while(number_of_cycles) {
|
||||
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Machines_Electron_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
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
|
||||
is accessing it the CPU may not.
|
||||
*/
|
||||
class VideoOutput {
|
||||
class VideoOutput: public ClockReceiver<VideoOutput> {
|
||||
public:
|
||||
/*!
|
||||
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.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Produces the next @c number_of_cycles cycles of video output.
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
/// Produces the next @c cycles of video output.
|
||||
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,
|
||||
@ -53,14 +55,14 @@ class VideoOutput {
|
||||
/*!
|
||||
@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
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
||||
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
WD::WD1770::run_for_cycles(number_of_cycles);
|
||||
WD::WD1770::run_for(cycles);
|
||||
}
|
||||
|
||||
bool Microdisc::get_drive_is_ready() {
|
||||
|
@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
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 {
|
||||
BASICDisable = (1 << 0),
|
||||
|
@ -12,7 +12,6 @@
|
||||
using namespace Oric;
|
||||
|
||||
Machine::Machine() :
|
||||
cycles_since_video_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
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(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);
|
||||
if(microdisc_is_enabled_) microdisc_.run_for_cycles(8);
|
||||
via_.run_for(Cycles(1));
|
||||
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
|
||||
cycles_since_video_update_++;
|
||||
return 1;
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void Machine::flush() {
|
||||
@ -142,8 +141,7 @@ void Machine::flush() {
|
||||
}
|
||||
|
||||
void Machine::update_video() {
|
||||
video_output_->run_for_cycles(cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
video_output_->run_for(cycles_since_video_update_.flush());
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
@ -198,15 +196,14 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return via_.ay8910;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU::MOS6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
void Machine::run_for(const Cycles &cycles) {
|
||||
CPU::MOS6502::Processor<Machine>::run_for(cycles);
|
||||
}
|
||||
|
||||
#pragma mark - The 6522
|
||||
|
||||
Machine::VIA::VIA() :
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
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() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->flush();
|
||||
cycles_since_ay_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
|
||||
cycles_since_ay_update_ += number_of_cycles;
|
||||
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
||||
tape->run_for_cycles((int)number_of_cycles);
|
||||
void Machine::VIA::run_for(const Cycles &cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
MOS::MOS6522<VIA>::run_for(cycles);
|
||||
tape->run_for(cycles);
|
||||
}
|
||||
|
||||
void Machine::VIA::update_ay() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
cycles_since_ay_update_ = 0;
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ class Machine:
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
|
||||
// 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();
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
@ -85,7 +85,7 @@ class Machine:
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
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
|
||||
void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
@ -104,7 +104,7 @@ class Machine:
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
|
||||
uint8_t ram_[65536], rom_[16384];
|
||||
int cycles_since_video_update_;
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video();
|
||||
|
||||
// ROM bookkeeping
|
||||
@ -143,7 +143,7 @@ class Machine:
|
||||
void set_control_line_output(Port port, Line line, bool value);
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
|
||||
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::unique_ptr<TapePlayer> tape;
|
||||
@ -154,7 +154,7 @@ class Machine:
|
||||
private:
|
||||
void update_ay();
|
||||
bool ay_bdir_, ay_bc1_;
|
||||
unsigned int cycles_since_ay_update_;
|
||||
Cycles cycles_since_ay_update_;
|
||||
};
|
||||
VIA via_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
|
@ -74,13 +74,14 @@ std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
void VideoOutput::run_for(const Cycles &cycles) {
|
||||
// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst
|
||||
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
||||
|
||||
#define clamp(action) \
|
||||
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) {
|
||||
int h_counter = counter_ & 63;
|
||||
int cycles_run_for = 0;
|
||||
|
@ -10,14 +10,16 @@
|
||||
#define Machines_Oric_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class VideoOutput {
|
||||
class VideoOutput: public ClockReceiver<VideoOutput> {
|
||||
public:
|
||||
VideoOutput(uint8_t *memory);
|
||||
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_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
|
||||
|
@ -29,9 +29,9 @@ Video::Video() :
|
||||
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.
|
||||
cycles_since_update_ += (unsigned int)number_of_cycles << 1;
|
||||
cycles_since_update_ += (unsigned int)half_cycles.as_int();
|
||||
}
|
||||
|
||||
void Video::flush() {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Machines_ZX8081_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
@ -23,15 +24,16 @@ namespace ZX8081 {
|
||||
a 1-bit graphic and output over the next 4 cycles, picking between the white level
|
||||
and the black level.
|
||||
*/
|
||||
class Video {
|
||||
class Video: public ClockReceiver<Video> {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video();
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Advances time by @c number_of_cycles cycles.
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
/// Advances time by @c cycles.
|
||||
void run_for(const HalfCycles &);
|
||||
using ClockReceiver<Video>::run_for;
|
||||
/// Forces output to catch up to the current output position.
|
||||
void flush();
|
||||
|
||||
|
@ -29,35 +29,35 @@ Machine::Machine() :
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
int previous_counter = horizontal_counter_;
|
||||
Cycles Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
HalfCycles previous_counter = horizontal_counter_;
|
||||
horizontal_counter_ += cycle.length;
|
||||
|
||||
if(previous_counter < vsync_start_cycle_ && horizontal_counter_ >= vsync_start_cycle_) {
|
||||
video_->run_for_cycles(vsync_start_cycle_ - previous_counter);
|
||||
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
|
||||
video_->run_for(vsync_start_ - previous_counter);
|
||||
set_hsync(true);
|
||||
line_counter_ = (line_counter_ + 1) & 7;
|
||||
if(nmi_is_enabled_) {
|
||||
set_non_maskable_interrupt_line(true);
|
||||
}
|
||||
video_->run_for_cycles(horizontal_counter_ - vsync_start_cycle_);
|
||||
} else if(previous_counter < vsync_end_cycle_ && horizontal_counter_ >= vsync_end_cycle_) {
|
||||
video_->run_for_cycles(vsync_end_cycle_ - previous_counter);
|
||||
video_->run_for(horizontal_counter_ - vsync_start_);
|
||||
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
|
||||
video_->run_for(vsync_end_ - previous_counter);
|
||||
set_hsync(false);
|
||||
if(nmi_is_enabled_) {
|
||||
set_non_maskable_interrupt_line(false);
|
||||
set_wait_line(false);
|
||||
}
|
||||
video_->run_for_cycles(horizontal_counter_ - vsync_end_cycle_);
|
||||
video_->run_for(horizontal_counter_ - vsync_end_);
|
||||
} 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_) {
|
||||
tape_player_.run_for_cycles(cycle.length);
|
||||
tape_player_.run_for(cycle.length);
|
||||
} 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()) {
|
||||
@ -65,7 +65,7 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
}
|
||||
|
||||
if(!cycle.is_terminal()) {
|
||||
return 0;
|
||||
return Cycles(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;
|
||||
}
|
||||
|
||||
if(typer_) typer_->update(cycle.length);
|
||||
if(typer_) typer_->update(cycle.length.as_int());
|
||||
|
||||
return 0;
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
void Machine::flush() {
|
||||
@ -205,8 +205,8 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU::Z80::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
void Machine::run_for(const Cycles &cycles) {
|
||||
CPU::Z80::Processor<Machine>::run_for(cycles);
|
||||
}
|
||||
|
||||
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_;
|
||||
tape_trap_address_ = 0x37c;
|
||||
tape_return_address_ = 0x380;
|
||||
vsync_start_cycle_ = 16;
|
||||
vsync_end_cycle_ = 32;
|
||||
vsync_start_ = HalfCycles(32);
|
||||
vsync_end_ = HalfCycles(64);
|
||||
automatic_tape_motor_start_address_ = 0x0340;
|
||||
automatic_tape_motor_end_address_ = 0x03c3;
|
||||
} else {
|
||||
rom_ = zx80_rom_;
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_cycle_ = 13;
|
||||
vsync_end_cycle_ = 33;
|
||||
vsync_start_ = HalfCycles(26);
|
||||
vsync_end_ = HalfCycles(66);
|
||||
automatic_tape_motor_start_address_ = 0x0206;
|
||||
automatic_tape_motor_end_address_ = 0x024d;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class Machine:
|
||||
public:
|
||||
Machine();
|
||||
|
||||
int perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle);
|
||||
Cycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle);
|
||||
void flush();
|
||||
|
||||
void setup_output(float aspect_ratio);
|
||||
@ -56,7 +56,7 @@ class Machine:
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
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);
|
||||
|
||||
@ -103,17 +103,18 @@ class Machine:
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
Storage::Tape::ZX8081::Parser parser_;
|
||||
|
||||
int horizontal_counter_;
|
||||
bool is_zx81_;
|
||||
bool nmi_is_enabled_;
|
||||
int vsync_start_cycle_, vsync_end_cycle_;
|
||||
|
||||
HalfCycles vsync_start_, vsync_end_;
|
||||
HalfCycles horizontal_counter_;
|
||||
|
||||
uint8_t latched_video_byte_;
|
||||
bool has_latched_video_byte_;
|
||||
|
||||
bool use_fast_tape_hack_;
|
||||
bool use_automatic_tape_motor_control_;
|
||||
int tape_advance_delay_;
|
||||
HalfCycles tape_advance_delay_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1020,6 +1020,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1809,6 +1810,7 @@
|
||||
4BB73EA01B587A5100552FC2 /* Clock Signal */,
|
||||
4BB73EB51B587A5100552FC2 /* Clock SignalTests */,
|
||||
4BB73EC01B587A5100552FC2 /* Clock SignalUITests */,
|
||||
4BF660691F281573002CB053 /* ClockReceiver */,
|
||||
4BC9DF4A1D04691600F44158 /* Components */,
|
||||
4B3940E81DA83C8700427841 /* Concurrency */,
|
||||
4BB73EDC1B587CA500552FC2 /* Machines */,
|
||||
@ -2156,6 +2158,15 @@
|
||||
name = StaticAnalyser;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BF660691F281573002CB053 /* ClockReceiver */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -75,7 +75,6 @@
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
enableAddressSanitizer = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
|
@ -104,7 +104,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles {
|
||||
@synchronized(self) {
|
||||
self.machine->run_for_cycles(numberOfCycles);
|
||||
self.machine->run_for(Cycles(numberOfCycles));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,7 @@
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
if(self) {
|
||||
[self setOSROM:[self rom:@"os"]];
|
||||
[self setBASICROM:[self rom:@"basic"]];
|
||||
[self setDFSROM:[self rom:@"DFS-1770-2.20"]];
|
||||
@ -38,8 +37,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name
|
||||
{
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"];
|
||||
}
|
||||
|
||||
|
@ -15,16 +15,13 @@
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
@implementation CSOric
|
||||
{
|
||||
@implementation CSOric {
|
||||
Oric::Machine _oric;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
if(self) {
|
||||
NSData *basic10 = [self rom:@"basic10"];
|
||||
NSData *basic11 = [self rom:@"basic11"];
|
||||
NSData *colour = [self rom:@"colour"];
|
||||
@ -38,23 +35,19 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name
|
||||
{
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine
|
||||
{
|
||||
- (CRTMachine::Machine * const)machine {
|
||||
return &_oric;
|
||||
}
|
||||
|
||||
#pragma mark - CSKeyboardMachine
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed
|
||||
{
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key)
|
||||
{
|
||||
switch(key) {
|
||||
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_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break;
|
||||
@ -137,8 +130,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllKeys
|
||||
{
|
||||
- (void)clearAllKeys {
|
||||
_oric.clear_all_keys();
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ class VanillaSerialPort: public Commodore::Serial::Port {
|
||||
}
|
||||
|
||||
- (void)runForCycles:(NSUInteger)numberOfCycles {
|
||||
_c1540.run_for_cycles((int)numberOfCycles);
|
||||
_c1540.run_for(Cycles((int)numberOfCycles));
|
||||
}
|
||||
|
||||
- (void)setAttentionLine:(BOOL)attentionLine {
|
||||
|
@ -40,7 +40,7 @@ class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::De
|
||||
}
|
||||
|
||||
- (void)runForCycles:(NSUInteger)cycles {
|
||||
_digitalPhaseLockedLoop->run_for_cycles((unsigned int)cycles);
|
||||
_digitalPhaseLockedLoop->run_for(Cycles((int)cycles));
|
||||
}
|
||||
|
||||
- (void)addPulse {
|
||||
|
@ -18,69 +18,56 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
|
||||
uint8_t port_a_value;
|
||||
uint8_t port_b_value;
|
||||
|
||||
void set_interrupt_status(bool new_status)
|
||||
{
|
||||
void set_interrupt_status(bool 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;
|
||||
}
|
||||
};
|
||||
|
||||
@implementation MOS6522Bridge
|
||||
{
|
||||
@implementation MOS6522Bridge {
|
||||
VanillaVIA _via;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
if(self) {
|
||||
_via.bridge = 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);
|
||||
}
|
||||
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber
|
||||
{
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber {
|
||||
return _via.get_register((int)registerNumber);
|
||||
}
|
||||
|
||||
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles
|
||||
{
|
||||
_via.run_for_half_cycles((int)numberOfHalfCycles);
|
||||
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles {
|
||||
_via.run_for(HalfCycles((int)numberOfHalfCycles));
|
||||
}
|
||||
|
||||
- (BOOL)irqLine
|
||||
{
|
||||
- (BOOL)irqLine {
|
||||
return _via.irq_line;
|
||||
}
|
||||
|
||||
- (void)setPortAInput:(uint8_t)portAInput
|
||||
{
|
||||
- (void)setPortAInput:(uint8_t)portAInput {
|
||||
_via.port_a_value = portAInput;
|
||||
}
|
||||
|
||||
- (uint8_t)portAInput
|
||||
{
|
||||
- (uint8_t)portAInput {
|
||||
return _via.port_a_value;
|
||||
}
|
||||
|
||||
- (void)setPortBInput:(uint8_t)portBInput
|
||||
{
|
||||
- (void)setPortBInput:(uint8_t)portBInput {
|
||||
_via.port_b_value = portBInput;
|
||||
}
|
||||
|
||||
- (uint8_t)portBInput
|
||||
{
|
||||
- (uint8_t)portBInput {
|
||||
return _via.port_b_value;
|
||||
}
|
||||
|
||||
|
@ -11,45 +11,37 @@
|
||||
|
||||
class VanillaRIOT: public MOS::MOS6532<VanillaRIOT> {
|
||||
public:
|
||||
uint8_t get_port_input(int port)
|
||||
{
|
||||
uint8_t get_port_input(int port) {
|
||||
return input[port];
|
||||
}
|
||||
uint8_t input[2];
|
||||
};
|
||||
|
||||
@implementation MOS6532Bridge
|
||||
{
|
||||
@implementation MOS6532Bridge {
|
||||
VanillaRIOT _riot;
|
||||
}
|
||||
|
||||
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber
|
||||
{
|
||||
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber {
|
||||
_riot.set_register((int)registerNumber, value);
|
||||
}
|
||||
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber
|
||||
{
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber {
|
||||
return _riot.get_register((int)registerNumber);
|
||||
}
|
||||
|
||||
- (void)runForCycles:(NSUInteger)numberOfCycles
|
||||
{
|
||||
_riot.run_for_cycles((int)numberOfCycles);
|
||||
- (void)runForCycles:(NSUInteger)numberOfCycles {
|
||||
_riot.run_for(Cycles((int)numberOfCycles));
|
||||
}
|
||||
|
||||
- (BOOL)irqLine
|
||||
{
|
||||
- (BOOL)irqLine {
|
||||
return _riot.get_inerrupt_line();
|
||||
}
|
||||
|
||||
- (void)setPortAInput:(uint8_t)portAInput
|
||||
{
|
||||
- (void)setPortAInput:(uint8_t)portAInput {
|
||||
_riot.input[0] = _portAInput = portAInput;
|
||||
}
|
||||
|
||||
- (void)setPortBInput:(uint8_t)portBInput
|
||||
{
|
||||
- (void)setPortBInput:(uint8_t)portBInput {
|
||||
_riot.input[1] = _portBInput = portBInput;
|
||||
}
|
||||
|
||||
|
@ -13,20 +13,6 @@
|
||||
|
||||
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
|
||||
|
||||
static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) {
|
||||
@ -45,7 +31,6 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
|
||||
|
||||
@implementation CSTestMachine6502 {
|
||||
CPU::MOS6502::AllRAMProcessor *_processor;
|
||||
// MachineJamHandler *_cppJamHandler;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
@ -117,7 +102,7 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
|
||||
}
|
||||
|
||||
- (void)runForNumberOfCycles:(int)cycles {
|
||||
_processor->run_for_cycles(cycles);
|
||||
_processor->run_for(Cycles(cycles));
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -127,7 +127,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
}
|
||||
|
||||
- (void)runForNumberOfCycles:(int)cycles {
|
||||
_processor->run_for_cycles(cycles);
|
||||
_processor->run_for(Cycles(cycles));
|
||||
}
|
||||
|
||||
- (void)setValue:(uint16_t)value forRegister:(CSTestMachineZ80Register)reg {
|
||||
|
@ -47,7 +47,7 @@ static void receive_line(uint8_t *next_line)
|
||||
_tia->set_playfield(0, 0x10);
|
||||
_tia->set_playfield(1, 0xf0);
|
||||
_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");
|
||||
|
||||
@ -70,7 +70,7 @@ static void receive_line(uint8_t *next_line)
|
||||
_tia->set_playfield(1, 0xf0);
|
||||
_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");
|
||||
|
||||
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_position(0);
|
||||
|
||||
_tia->run_for_cycles(228);
|
||||
_tia->run_for(Cycles(228));
|
||||
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,
|
||||
@ -103,7 +103,7 @@ static void receive_line(uint8_t *next_line)
|
||||
XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line)));
|
||||
line = nullptr;
|
||||
|
||||
_tia->run_for_cycles(228);
|
||||
_tia->run_for(Cycles(228));
|
||||
uint8_t second_expected_line[] = {
|
||||
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,
|
||||
|
@ -9,9 +9,9 @@
|
||||
#ifndef Speaker_hpp
|
||||
#define Speaker_hpp
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
@ -20,6 +20,7 @@
|
||||
#include "../SignalProcessing/Stepper.hpp"
|
||||
#include "../SignalProcessing/FIRFilter.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Outputs {
|
||||
|
||||
@ -132,17 +133,17 @@ class Speaker {
|
||||
`get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)`
|
||||
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:
|
||||
~Filter() {
|
||||
_queue->flush();
|
||||
}
|
||||
|
||||
void run_for_cycles(unsigned int input_cycles) {
|
||||
void run_for(const Cycles &cycles) {
|
||||
enqueue([=]() {
|
||||
unsigned int cycles_remaining = input_cycles;
|
||||
unsigned int cycles_remaining = (unsigned int)cycles.as_int();
|
||||
if(coefficients_are_dirty_) update_filter_coefficients();
|
||||
|
||||
// if input and output rates exactly match, just accumulate results and pass on
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "../RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace CPU {
|
||||
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
|
||||
jammed state.
|
||||
*/
|
||||
template <class T> class Processor: public ProcessorBase {
|
||||
template <class T> class Processor: public ProcessorBase, public ClockReceiver<Processor<T>> {
|
||||
private:
|
||||
const MicroOp *scheduled_program_counter_;
|
||||
|
||||
@ -180,7 +181,7 @@ template <class T> class Processor: public ProcessorBase {
|
||||
}
|
||||
|
||||
bool is_jammed_;
|
||||
int cycles_left_to_run_;
|
||||
Cycles cycles_left_to_run_;
|
||||
|
||||
enum InterruptRequestFlags: uint8_t {
|
||||
Reset = 0x80,
|
||||
@ -263,7 +264,6 @@ template <class T> class Processor: public ProcessorBase {
|
||||
protected:
|
||||
Processor() :
|
||||
is_jammed_(false),
|
||||
cycles_left_to_run_(0),
|
||||
ready_line_is_enabled_(false),
|
||||
ready_is_active_(false),
|
||||
inverse_interrupt_flag_(0),
|
||||
@ -283,6 +283,7 @@ template <class T> class Processor: public ProcessorBase {
|
||||
}
|
||||
|
||||
public:
|
||||
using ClockReceiver<Processor<T>>::run_for;
|
||||
/*!
|
||||
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.
|
||||
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[] = {
|
||||
CycleReadFromPC,
|
||||
CycleAddSignedOperandToPC,
|
||||
@ -336,14 +337,14 @@ template <class T> class Processor: public ProcessorBase {
|
||||
irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \
|
||||
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); \
|
||||
nextBusOperation = BusOperation::None; \
|
||||
if(number_of_cycles <= 0) break;
|
||||
if(number_of_cycles <= Cycles(0)) break;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
@ -20,7 +20,7 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
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_++;
|
||||
|
||||
if(operation == BusOperation::ReadOpcode) {
|
||||
@ -33,11 +33,11 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
memory_[address] = *value;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void run_for_cycles(int number_of_cycles) {
|
||||
Processor<ConcreteAllRAMProcessor>::run_for_cycles(number_of_cycles);
|
||||
void run_for(const Cycles &cycles) {
|
||||
Processor<ConcreteAllRAMProcessor>::run_for(cycles);
|
||||
}
|
||||
|
||||
bool is_jammed() {
|
||||
|
@ -22,7 +22,7 @@ class AllRAMProcessor:
|
||||
static AllRAMProcessor *Processor();
|
||||
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 void set_irq_line(bool value) = 0;
|
||||
virtual void set_nmi_line(bool value) = 0;
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace Z80 {
|
||||
@ -87,7 +88,7 @@ struct PartialMachineCycle {
|
||||
InputStart,
|
||||
OutputStart,
|
||||
} operation;
|
||||
int length;
|
||||
Cycles length;
|
||||
uint16_t *address;
|
||||
uint8_t *value;
|
||||
bool was_requested;
|
||||
@ -104,28 +105,28 @@ struct PartialMachineCycle {
|
||||
};
|
||||
|
||||
// Elemental bus operations
|
||||
#define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, 2, &pc_.full, &operation_, false}
|
||||
#define ReadOpcodeWait(length, f) {PartialMachineCycle::ReadOpcodeWait, length, &pc_.full, &operation_, f}
|
||||
#define Refresh(len) {PartialMachineCycle::Refresh, len, &refresh_addr_.full, nullptr, false}
|
||||
#define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, Cycles(2), &pc_.full, &operation_, false}
|
||||
#define ReadOpcodeWait(length, f) {PartialMachineCycle::ReadOpcodeWait, Cycles(length), &pc_.full, &operation_, f}
|
||||
#define Refresh(len) {PartialMachineCycle::Refresh, Cycles(len), &refresh_addr_.full, nullptr, false}
|
||||
|
||||
#define ReadStart(addr, val) {PartialMachineCycle::ReadStart, 2, &addr.full, &val, false}
|
||||
#define ReadWait(l, addr, val, f) {PartialMachineCycle::ReadWait, l, &addr.full, &val, f}
|
||||
#define ReadEnd(addr, val) {PartialMachineCycle::Read, 1, &addr.full, &val, false}
|
||||
#define ReadStart(addr, val) {PartialMachineCycle::ReadStart, Cycles(2), &addr.full, &val, false}
|
||||
#define ReadWait(l, addr, val, f) {PartialMachineCycle::ReadWait, Cycles(l), &addr.full, &val, f}
|
||||
#define ReadEnd(addr, val) {PartialMachineCycle::Read, Cycles(1), &addr.full, &val, false}
|
||||
|
||||
#define WriteStart(addr, val) {PartialMachineCycle::WriteStart, 2, &addr.full, &val, false}
|
||||
#define WriteWait(l, addr, val, f) {PartialMachineCycle::WriteWait, l, &addr.full, &val, f}
|
||||
#define WriteEnd(addr, val) {PartialMachineCycle::Write, 1, &addr.full, &val, false}
|
||||
#define WriteStart(addr, val) {PartialMachineCycle::WriteStart, Cycles(2), &addr.full, &val, false}
|
||||
#define WriteWait(l, addr, val, f) {PartialMachineCycle::WriteWait, Cycles(l), &addr.full, &val, f}
|
||||
#define WriteEnd(addr, val) {PartialMachineCycle::Write, Cycles(1), &addr.full, &val, false}
|
||||
|
||||
#define InputStart(addr, val) {PartialMachineCycle::InputStart, 2, &addr.full, &val, false}
|
||||
#define InputWait(addr, val, f) {PartialMachineCycle::InputWait, 1, &addr.full, &val, f}
|
||||
#define InputEnd(addr, val) {PartialMachineCycle::Input, 1, &addr.full, &val, false}
|
||||
#define InputStart(addr, val) {PartialMachineCycle::InputStart, Cycles(2), &addr.full, &val, false}
|
||||
#define InputWait(addr, val, f) {PartialMachineCycle::InputWait, Cycles(1), &addr.full, &val, f}
|
||||
#define InputEnd(addr, val) {PartialMachineCycle::Input, Cycles(1), &addr.full, &val, false}
|
||||
|
||||
#define OutputStart(addr, val) {PartialMachineCycle::OutputStart, 2, &addr.full, &val, false}
|
||||
#define OutputWait(addr, val, f) {PartialMachineCycle::OutputWait, 1, &addr.full, &val, f}
|
||||
#define OutputEnd(addr, val) {PartialMachineCycle::Output, 1, &addr.full, &val, false}
|
||||
#define OutputStart(addr, val) {PartialMachineCycle::OutputStart, Cycles(2), &addr.full, &val, false}
|
||||
#define OutputWait(addr, val, f) {PartialMachineCycle::OutputWait, Cycles(1), &addr.full, &val, f}
|
||||
#define OutputEnd(addr, val) {PartialMachineCycle::Output, Cycles(1), &addr.full, &val, false}
|
||||
|
||||
#define IntAck(length, val) {PartialMachineCycle::Interrupt, length, nullptr, &val, false}
|
||||
#define IntWait(val) {PartialMachineCycle::InterruptWait, 1, nullptr, &val, true}
|
||||
#define IntAck(length, val) {PartialMachineCycle::Interrupt, Cycles(length), nullptr, &val, false}
|
||||
#define IntWait(val) {PartialMachineCycle::InterruptWait, Cycles(1), nullptr, &val, true}
|
||||
|
||||
// A wrapper to express a bus operation as a micro-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
|
||||
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:
|
||||
uint8_t a_;
|
||||
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 halt_mask_;
|
||||
|
||||
int number_of_cycles_;
|
||||
Cycles number_of_cycles_;
|
||||
|
||||
enum Interrupt: uint8_t {
|
||||
IRQ = 0x01,
|
||||
@ -428,7 +429,7 @@ template <class T> class Processor {
|
||||
target.instructions[c] = &target.all_operations[destination];
|
||||
for(size_t t = 0; t < lengths[c];) {
|
||||
// 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++;
|
||||
continue;
|
||||
}
|
||||
@ -765,7 +766,6 @@ template <class T> class Processor {
|
||||
public:
|
||||
Processor() :
|
||||
halt_mask_(0xff),
|
||||
number_of_cycles_(0),
|
||||
interrupt_mode_(0),
|
||||
wait_line_(false),
|
||||
request_status_(Interrupt::PowerOn),
|
||||
@ -849,6 +849,7 @@ template <class T> class Processor {
|
||||
copy_program(irq_mode2_program, irq_program_[2]);
|
||||
}
|
||||
|
||||
using ClockReceiver<Processor<T>>::run_for;
|
||||
/*!
|
||||
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.
|
||||
|
||||
@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() \
|
||||
pc_increment_ = 1; \
|
||||
@ -878,7 +879,7 @@ template <class T> class Processor {
|
||||
scheduled_program_counter_ = base_page_.fetch_decode_execute_data; \
|
||||
}
|
||||
|
||||
number_of_cycles_ += number_of_cycles;
|
||||
number_of_cycles_ += cycles;
|
||||
if(!scheduled_program_counter_) {
|
||||
advance_operation();
|
||||
}
|
||||
@ -887,7 +888,7 @@ template <class T> class Processor {
|
||||
|
||||
while(bus_request_line_) {
|
||||
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_) {
|
||||
static_cast<T *>(this)->flush();
|
||||
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.
|
||||
*/
|
||||
void flush() {}
|
||||
|
||||
int perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
return 0;
|
||||
Cycles perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -16,10 +16,10 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
public:
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor() {}
|
||||
|
||||
inline int perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
timestamp_ += cycle.length;
|
||||
inline Cycles perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
timestamp_ += cycle.length.as_int();
|
||||
if(!cycle.is_terminal()) {
|
||||
return 0;
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
void run_for_cycles(int cycles) {
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor>::run_for_cycles(cycles);
|
||||
void run_for(const Cycles &cycles) {
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor>::run_for(cycles);
|
||||
}
|
||||
|
||||
uint16_t get_value_of_register(Register r) {
|
||||
|
@ -28,7 +28,7 @@ class AllRAMProcessor:
|
||||
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 void set_value_of_register(Register r, uint16_t value) = 0;
|
||||
virtual bool get_halt_line() = 0;
|
||||
|
@ -78,13 +78,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < 2) {
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < 2) {
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
@ -92,14 +92,14 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
unsigned int get_next_byte() {
|
||||
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_);
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
index_count_ = 0;
|
||||
while(shift_register_ != shift_value && index_count_ < 2) {
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,9 @@ DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, size_t length
|
||||
offset_history_(length_of_history, 0),
|
||||
offset_(0) {}
|
||||
|
||||
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) {
|
||||
offset_ += number_of_cycles;
|
||||
phase_ += number_of_cycles;
|
||||
void DigitalPhaseLockedLoop::run_for(const Cycles &cycles) {
|
||||
offset_ += cycles.as_int();
|
||||
phase_ += cycles.as_int();
|
||||
if(phase_ >= window_length_) {
|
||||
int windows_crossed = phase_ / window_length_;
|
||||
|
||||
|
@ -12,9 +12,11 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class DigitalPhaseLockedLoop {
|
||||
class DigitalPhaseLockedLoop: public ClockReceiver<DigitalPhaseLockedLoop> {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a @c DigitalPhaseLockedLoop.
|
||||
@ -29,7 +31,8 @@ class DigitalPhaseLockedLoop {
|
||||
|
||||
@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.
|
||||
|
@ -11,17 +11,17 @@
|
||||
|
||||
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_multiplier_(clock_rate_multiplier),
|
||||
rotational_multiplier_(60u, revolutions_per_minute),
|
||||
rotational_multiplier_(60, revolutions_per_minute),
|
||||
|
||||
cycles_since_index_hole_(0),
|
||||
motor_is_on_(false),
|
||||
|
||||
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
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
@ -40,13 +40,13 @@ void Controller::setup_track() {
|
||||
get_next_event(offset);
|
||||
}
|
||||
|
||||
void Controller::run_for_cycles(int number_of_cycles) {
|
||||
void Controller::run_for(const Cycles &cycles) {
|
||||
Time zero(0);
|
||||
|
||||
if(drive_ && drive_->has_disk() && motor_is_on_) {
|
||||
if(!track_) setup_track();
|
||||
|
||||
number_of_cycles *= clock_rate_multiplier_;
|
||||
int number_of_cycles = clock_rate_multiplier_ * cycles.as_int();
|
||||
while(number_of_cycles) {
|
||||
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);
|
||||
@ -60,7 +60,7 @@ void Controller::run_for_cycles(int number_of_cycles) {
|
||||
|
||||
number_of_cycles -= cycles_to_run_for;
|
||||
if(is_reading_) {
|
||||
pll_->run_for_cycles(cycles_to_run_for);
|
||||
pll_->run_for(Cycles(cycles_to_run_for));
|
||||
} else {
|
||||
if(cycles_until_bits_written_ > zero) {
|
||||
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) {
|
||||
process_input_bit(value, cycles_since_index_hole_);
|
||||
process_input_bit(value, (unsigned int)cycles_since_index_hole_);
|
||||
}
|
||||
|
||||
#pragma mark - Drive actions
|
||||
|
@ -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,
|
||||
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.
|
||||
@ -43,7 +43,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
/*!
|
||||
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.
|
||||
@ -113,14 +114,14 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
|
||||
private:
|
||||
Time bit_length_;
|
||||
unsigned int clock_rate_;
|
||||
unsigned int clock_rate_multiplier_;
|
||||
int clock_rate_;
|
||||
int clock_rate_multiplier_;
|
||||
Time rotational_multiplier_;
|
||||
|
||||
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
|
||||
std::shared_ptr<Drive> drive_;
|
||||
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);
|
||||
Track::Event current_event_;
|
||||
|
@ -294,7 +294,7 @@ uint8_t Parser::get_byte_for_shift_value(uint16_t value) {
|
||||
|
||||
uint8_t Parser::get_next_byte() {
|
||||
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_);
|
||||
crc_generator_.add(byte);
|
||||
return byte;
|
||||
@ -309,7 +309,7 @@ std::vector<uint8_t> Parser::get_track() {
|
||||
|
||||
// align to the next index hole
|
||||
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
|
||||
index_count_ = 0;
|
||||
@ -319,7 +319,7 @@ std::vector<uint8_t> Parser::get_track() {
|
||||
bool found_sync = false;
|
||||
while(!index_count_ && !found_sync && bit_count_ < 16) {
|
||||
int previous_bit_count = bit_count_;
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
|
||||
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) {
|
||||
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
|
||||
bool id_found = false;
|
||||
while(!id_found) {
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
if(is_mfm_) {
|
||||
while(shift_register_ == MFMSync) {
|
||||
uint8_t mark = get_next_byte();
|
||||
@ -424,7 +424,7 @@ std::shared_ptr<Sector> Parser::get_next_sector()
|
||||
// look for data mark
|
||||
bool data_found = false;
|
||||
while(!data_found) {
|
||||
run_for_cycles(1);
|
||||
run_for(Cycles(1));
|
||||
if(is_mfm_) {
|
||||
while(shift_register_ == MFMSync) {
|
||||
uint8_t mark = get_next_byte();
|
||||
|
@ -74,7 +74,7 @@ Shifter::Shifter() :
|
||||
}
|
||||
|
||||
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;
|
||||
if(is_high != was_high_) {
|
||||
|
@ -92,9 +92,9 @@ void TapePlayer::get_next_pulse() {
|
||||
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()) {
|
||||
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_;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::run_for_cycles(int number_of_cycles) {
|
||||
if(motor_is_running_) TapePlayer::run_for_cycles(number_of_cycles);
|
||||
void BinaryTapePlayer::run_for(const Cycles &cycles) {
|
||||
if(motor_is_running_) TapePlayer::run_for(cycles);
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_delegate(Delegate *delegate) {
|
||||
|
@ -10,6 +10,8 @@
|
||||
#define Tape_hpp
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
@ -99,7 +101,9 @@ class TapePlayer: public TimedEventLoop {
|
||||
bool has_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();
|
||||
|
||||
protected:
|
||||
@ -128,7 +132,8 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
void set_tape_output(bool set);
|
||||
bool get_input();
|
||||
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
using TapePlayer::run_for;
|
||||
void run_for(const Cycles &cycles);
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
|
@ -15,8 +15,8 @@ using namespace Storage;
|
||||
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
|
||||
input_clock_rate_(input_clock_rate) {}
|
||||
|
||||
void TimedEventLoop::run_for_cycles(int number_of_cycles) {
|
||||
cycles_until_event_ -= number_of_cycles;
|
||||
void TimedEventLoop::run_for(const Cycles &cycles) {
|
||||
cycles_until_event_ -= cycles.as_int();
|
||||
while(cycles_until_event_ <= 0) {
|
||||
process_next_event();
|
||||
}
|
||||
|
@ -11,9 +11,11 @@
|
||||
|
||||
#include "Storage.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../SignalProcessing/Stepper.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
@ -22,7 +24,7 @@ namespace Storage {
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
loop and fast forward immediately to the next event.
|
||||
*/
|
||||
class TimedEventLoop {
|
||||
class TimedEventLoop: public ClockReceiver<TimedEventLoop> {
|
||||
public:
|
||||
/*!
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
|
Loading…
Reference in New Issue
Block a user