1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

Merge pull request #158 from TomHarte/HalfCycles

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

View File

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

View File

@ -124,10 +124,11 @@ uint8_t WD1770::get_register(int address) {
}
}
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
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);

View File

@ -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,

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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());
}
};

View File

@ -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() {

View File

@ -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_;
};
}

View File

@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
}
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) {

View File

@ -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);

View File

@ -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() {

View File

@ -34,7 +34,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
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

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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)));
}
}

View File

@ -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_;

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) {
}
}
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
void Microdisc::run_for(const Cycles &cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) {
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() {

View File

@ -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),

View File

@ -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));
}

View File

@ -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_;

View File

@ -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: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst
// Horizontal: 0223: pixels; otherwise blank; 256259 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;

View File

@ -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);

View File

@ -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() {

View File

@ -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();

View File

@ -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;
}

View File

@ -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_;
};
}

View File

@ -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 */

View File

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

View File

@ -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));
}
}

View File

@ -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"];
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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.
*/

View File

@ -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() {

View File

@ -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;

View File

@ -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);
}
/*!

View File

@ -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) {

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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_;

View File

@ -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.

View File

@ -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

View File

@ -33,7 +33,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
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_;

View File

@ -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();

View File

@ -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_) {

View File

@ -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) {

View File

@ -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:

View File

@ -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();
}

View File

@ -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.