1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-21 02:37:44 +00:00

Take another big swing at indentation, some consts.

This commit is contained in:
Thomas Harte 2024-12-01 21:44:14 -05:00
parent 31c878b654
commit d3ed485e7a
158 changed files with 12552 additions and 12483 deletions

View File

@ -27,18 +27,18 @@ private:
std::vector<MachineTypes::KeyboardMachine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
public:
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
private:
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
private:
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
std::unique_ptr<MultiKeyboard> keyboard_;

View File

@ -285,14 +285,14 @@ private:
automatically to gain run_for(HalfCycles).
*/
template <class T> class HalfClockReceiver: public T {
public:
using T::T;
public:
using T::T;
forceinline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush<Cycles>());
}
forceinline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush<Cycles>());
}
private:
HalfCycles half_cycles_;
private:
HalfCycles half_cycles_;
};

View File

@ -98,28 +98,28 @@ private:
This list is efficient only for short queues.
*/
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Runs for @c length units of time.
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
target_(time_to_next);
length -= time_to_next;
DeferredQueue<TimeUnit>::advance(time_to_next);
}
DeferredQueue<TimeUnit>::advance(length);
target_(length);
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
target_(time_to_next);
length -= time_to_next;
DeferredQueue<TimeUnit>::advance(time_to_next);
}
private:
std::function<void(TimeUnit)> target_;
DeferredQueue<TimeUnit>::advance(length);
target_(length);
}
private:
std::function<void(TimeUnit)> target_;
};

View File

@ -104,40 +104,40 @@ public:
private:
class VarianceCollector {
public:
VarianceCollector(Time::Nanos default_value) {
sum_ = default_value * 128;
for(int c = 0; c < 128; ++c) {
history_[c] = default_value;
}
public:
VarianceCollector(Time::Nanos default_value) {
sum_ = default_value * 128;
for(int c = 0; c < 128; ++c) {
history_[c] = default_value;
}
}
void post(Time::Nanos value) {
sum_ -= history_[write_pointer_];
sum_ += value;
history_[write_pointer_] = value;
write_pointer_ = (write_pointer_ + 1) & 127;
void post(Time::Nanos value) {
sum_ -= history_[write_pointer_];
sum_ += value;
history_[write_pointer_] = value;
write_pointer_ = (write_pointer_ + 1) & 127;
}
Time::Nanos mean() {
return sum_ / 128;
}
Time::Nanos variance() {
// I haven't yet come up with a better solution that calculating this
// in whole every time, given the way that the mean mutates.
Time::Nanos variance = 0;
for(int c = 0; c < 128; ++c) {
const auto difference = ((history_[c] * 128) - sum_) / 128;
variance += (difference * difference);
}
return variance / 128;
}
Time::Nanos mean() {
return sum_ / 128;
}
Time::Nanos variance() {
// I haven't yet come up with a better solution that calculating this
// in whole every time, given the way that the mean mutates.
Time::Nanos variance = 0;
for(int c = 0; c < 128; ++c) {
const auto difference = ((history_[c] * 128) - sum_) / 128;
variance += (difference * difference);
}
return variance / 128;
}
private:
Time::Nanos sum_;
Time::Nanos history_[128];
size_t write_pointer_ = 0;
private:
Time::Nanos sum_;
Time::Nanos history_[128];
size_t write_pointer_ = 0;
};
Nanos redraw_begin_time_ = 0;

View File

@ -64,36 +64,36 @@ public:
private:
class Channel {
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line() const;
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line() const;
private:
uint8_t data_ = 0xff;
private:
uint8_t data_ = 0xff;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
int clock_rate_multiplier_ = 1;
int clock_rate_multiplier_ = 1;
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
bool dcd_ = false;
bool dcd_ = false;
} channels_[2];
uint8_t pointer_ = 0;

View File

@ -192,111 +192,111 @@ constexpr SpriteMode sprite_mode(ScreenMode screen_mode) {
// TODO: should this be extended to include Master System sprites?
template <Personality personality, SpriteMode mode>
class SpriteFetcher {
public:
using AddressT = typename Base<personality>::AddressT;
public:
using AddressT = typename Base<personality>::AddressT;
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
// table, which is intended to fill the 512 bytes before the programmer-located sprite
// attribute table.
//
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
//
// AttributeAddressMask is used to enable or disable that behaviour.
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
// table, which is intended to fill the 512 bytes before the programmer-located sprite
// attribute table.
//
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
//
// AttributeAddressMask is used to enable or disable that behaviour.
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
SpriteFetcher(Base<personality> *base, uint8_t y) :
base(base),
y(y) {}
SpriteFetcher(Base<personality> *base, uint8_t y) :
base(base),
y(y) {}
void fetch_location(int slot) {
fetch_xy(slot);
void fetch_location(int slot) {
fetch_xy(slot);
if constexpr (mode == SpriteMode::Mode2) {
fetch_xy(slot + 1);
if constexpr (mode == SpriteMode::Mode2) {
fetch_xy(slot + 1);
base->name_[0] = name(slot);
base->name_[1] = name(slot + 1);
}
base->name_[0] = name(slot);
base->name_[1] = name(slot + 1);
}
}
void fetch_pattern(int slot) {
switch(mode) {
case SpriteMode::Mode1:
fetch_image(slot, name(slot));
break;
void fetch_pattern(int slot) {
switch(mode) {
case SpriteMode::Mode1:
fetch_image(slot, name(slot));
break;
case SpriteMode::Mode2:
fetch_image(slot, base->name_[0]);
fetch_image(slot + 1, base->name_[1]);
break;
}
case SpriteMode::Mode2:
fetch_image(slot, base->name_[0]);
fetch_image(slot + 1, base->name_[1]);
break;
}
}
void fetch_y(int sprite) {
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
const uint8_t sprite_y = base->ram_[address];
base->posit_sprite(sprite, sprite_y, y);
}
void fetch_y(int sprite) {
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
const uint8_t sprite_y = base->ram_[address];
base->posit_sprite(sprite, sprite_y, y);
}
private:
void fetch_xy(int slot) {
auto &buffer = *base->fetch_sprite_buffer_;
buffer.active_sprites[slot].x =
base->ram_[
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
private:
void fetch_xy(int slot) {
auto &buffer = *base->fetch_sprite_buffer_;
buffer.active_sprites[slot].x =
base->ram_[
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
];
}
uint8_t name(int slot) {
auto &buffer = *base->fetch_sprite_buffer_;
const AddressT address =
base->sprite_attribute_table_address_ &
AttributeAddressMask &
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
return name;
}
void fetch_image(int slot, uint8_t name) {
uint8_t colour = 0;
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
switch(mode) {
case SpriteMode::Mode1:
// Fetch colour from the attribute table, per this sprite's slot.
colour = base->ram_[
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
];
break;
case SpriteMode::Mode2: {
// Fetch colour from the colour table, per this sprite's slot and row.
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
colour = base->ram_[
colour_table_address &
bits<9>(
AddressT(sprite.index << 4) |
AddressT(sprite.row)
)
];
} break;
}
sprite.image[2] = colour;
sprite.x -= sprite.early_clock();
uint8_t name(int slot) {
auto &buffer = *base->fetch_sprite_buffer_;
const AddressT address =
base->sprite_attribute_table_address_ &
AttributeAddressMask &
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
return name;
}
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
sprite.image[0] = base->ram_[graphic_location];
sprite.image[1] = base->ram_[graphic_location+16];
void fetch_image(int slot, uint8_t name) {
uint8_t colour = 0;
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
switch(mode) {
case SpriteMode::Mode1:
// Fetch colour from the attribute table, per this sprite's slot.
colour = base->ram_[
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
];
break;
case SpriteMode::Mode2: {
// Fetch colour from the colour table, per this sprite's slot and row.
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
colour = base->ram_[
colour_table_address &
bits<9>(
AddressT(sprite.index << 4) |
AddressT(sprite.row)
)
];
} break;
}
sprite.image[2] = colour;
sprite.x -= sprite.early_clock();
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
sprite.image[0] = base->ram_[graphic_location];
sprite.image[1] = base->ram_[graphic_location+16];
if constexpr (SpriteBuffer::test_is_filling) {
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
base->fetch_sprite_buffer_->is_filling = false;
}
if constexpr (SpriteBuffer::test_is_filling) {
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
base->fetch_sprite_buffer_->is_filling = false;
}
}
}
Base<personality> *const base;
const uint8_t y;
Base<personality> *const base;
const uint8_t y;
};
template <Personality personality>

View File

@ -439,12 +439,12 @@ struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>:
}
}
private:
static constexpr auto refresh_events = events<RefreshGenerator>();
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
static constexpr auto text_events = events<TextGenerator>();
static constexpr auto character_events = events<CharacterGenerator>();
private:
static constexpr auto refresh_events = events<RefreshGenerator>();
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
static constexpr auto text_events = events<TextGenerator>();
static constexpr auto character_events = events<CharacterGenerator>();
};
// Master System-specific storage.

View File

@ -20,109 +20,109 @@
namespace Yamaha::OPL {
class OPLL: public OPLBase<OPLL, false> {
public:
/// Creates a new OPLL or VRC7.
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
public:
/// Creates a new OPLL or VRC7.
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
/// As per ::SampleSource; provides audio output.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const { return false; } // TODO.
/// As per ::SampleSource; provides audio output.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const { return false; } // TODO.
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
// rhythm mode, but it's correct for melodic output.
double average_output_peak() const { return 0.5; }
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
// rhythm mode, but it's correct for melodic output.
double average_output_peak() const { return 0.5; }
/// Reads from the OPL.
uint8_t read(uint16_t address);
/// Reads from the OPL.
uint8_t read(uint16_t address);
private:
friend OPLBase<OPLL, false>;
void write_register(uint8_t address, uint8_t value);
private:
friend OPLBase<OPLL, false>;
void write_register(uint8_t address, uint8_t value);
int audio_divider_ = 0;
int audio_offset_ = 0;
std::atomic<int> total_volume_;
int audio_divider_ = 0;
int audio_offset_ = 0;
std::atomic<int> total_volume_;
int16_t output_levels_[18];
void update_all_channels();
int16_t output_levels_[18];
void update_all_channels();
int melodic_output(int channel);
int bass_drum() const;
int tom_tom() const;
int snare_drum() const;
int cymbal() const;
int high_hat() const;
int melodic_output(int channel);
int bass_drum() const;
int tom_tom() const;
int snare_drum() const;
int cymbal() const;
int high_hat() const;
static constexpr int period_precision = 9;
static constexpr int envelope_precision = 7;
static constexpr int period_precision = 9;
static constexpr int envelope_precision = 7;
// Standard melodic phase and envelope generators;
//
// These are assigned as:
//
// [x], 0 <= x < 9 = carrier for channel x;
// [x+9] = modulator for channel x.
//
PhaseGenerator<period_precision> phase_generators_[18];
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
KeyLevelScaler<period_precision> key_level_scalers_[18];
// Standard melodic phase and envelope generators;
//
// These are assigned as:
//
// [x], 0 <= x < 9 = carrier for channel x;
// [x+9] = modulator for channel x.
//
PhaseGenerator<period_precision> phase_generators_[18];
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
KeyLevelScaler<period_precision> key_level_scalers_[18];
// Dedicated rhythm envelope generators and attenuations.
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
enum RhythmIndices {
HighHat = 0,
Cymbal = 1,
TomTom = 2,
Snare = 3,
BassCarrier = 4,
BassModulator = 5
};
// Dedicated rhythm envelope generators and attenuations.
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
enum RhythmIndices {
HighHat = 0,
Cymbal = 1,
TomTom = 2,
Snare = 3,
BassCarrier = 4,
BassModulator = 5
};
// Channel specifications.
struct Channel {
int octave = 0;
int period = 0;
int instrument = 0;
// Channel specifications.
struct Channel {
int octave = 0;
int period = 0;
int instrument = 0;
int attenuation = 0;
int modulator_attenuation = 0;
int attenuation = 0;
int modulator_attenuation = 0;
Waveform carrier_waveform = Waveform::Sine;
Waveform modulator_waveform = Waveform::Sine;
Waveform carrier_waveform = Waveform::Sine;
Waveform modulator_waveform = Waveform::Sine;
int carrier_key_rate_scale_multiplier = 0;
int modulator_key_rate_scale_multiplier = 0;
int carrier_key_rate_scale_multiplier = 0;
int modulator_key_rate_scale_multiplier = 0;
LogSign modulator_output;
int modulator_feedback = 0;
LogSign modulator_output;
int modulator_feedback = 0;
bool use_sustain = false;
} channels_[9];
bool use_sustain = false;
} channels_[9];
// The low-frequency oscillator.
LowFrequencyOscillator oscillator_;
bool rhythm_mode_enabled_ = false;
bool is_vrc7_ = false;
// The low-frequency oscillator.
LowFrequencyOscillator oscillator_;
bool rhythm_mode_enabled_ = false;
bool is_vrc7_ = false;
// Contains the current configuration of the custom instrument.
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
// Contains the current configuration of the custom instrument.
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
// Helpers to push per-channel information.
// Helpers to push per-channel information.
/// Pushes the current octave and period to channel @c channel.
void set_channel_period(int channel);
/// Pushes the current octave and period to channel @c channel.
void set_channel_period(int channel);
/// Installs the appropriate instrument on channel @c channel.
void install_instrument(int channel);
/// Installs the appropriate instrument on channel @c channel.
void install_instrument(int channel);
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
/// and the user's selection.
void set_use_sustain(int channel);
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
/// and the user's selection.
void set_use_sustain(int channel);
/// @returns The 8-byte definition of instrument @c instrument.
const uint8_t *instrument_definition(int instrument, int channel) const;
/// @returns The 8-byte definition of instrument @c instrument.
const uint8_t *instrument_definition(int instrument, int channel) const;
};
}

View File

@ -26,15 +26,15 @@ template <typename Performer> struct TaskQueueStorage {
Performer performer;
protected:
void update() {
auto time_now = Time::nanos_now();
performer.perform(time_now - last_fired_);
last_fired_ = time_now;
}
protected:
void update() {
auto time_now = Time::nanos_now();
performer.perform(time_now - last_fired_);
last_fired_ = time_now;
}
private:
Time::Nanos last_fired_;
private:
Time::Nanos last_fired_;
};
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.

View File

@ -45,344 +45,344 @@ enum class Mode {
/// Appropriate prefetch offsets are left to other code to handle.
/// This is to try to keep this structure independent of a specific ARM implementation.
struct Registers {
public:
// Don't allow copying.
Registers(const Registers &) = delete;
Registers &operator =(const Registers &) = delete;
Registers() = default;
public:
// Don't allow copying.
Registers(const Registers &) = delete;
Registers &operator =(const Registers &) = delete;
Registers() = default;
/// Sets the N and Z flags according to the value of @c result.
void set_nz(const uint32_t value) {
zero_result_ = negative_flag_ = value;
/// Sets the N and Z flags according to the value of @c result.
void set_nz(const uint32_t value) {
zero_result_ = negative_flag_ = value;
}
/// Sets C if @c value is non-zero; resets it otherwise.
void set_c(const uint32_t value) {
carry_flag_ = value;
}
/// @returns @c 1 if carry is set; @c 0 otherwise.
uint32_t c() const {
return carry_flag_ ? 1 : 0;
}
/// Sets V if the highest bit of @c value is set; resets it otherwise.
void set_v(const uint32_t value) {
overflow_flag_ = value;
}
/// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags.
uint32_t status() const {
return
uint32_t(mode_) |
(negative_flag_ & ConditionCode::Negative) |
(zero_result_ ? 0 : ConditionCode::Zero) |
(carry_flag_ ? ConditionCode::Carry : 0) |
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
interrupt_flags_;
}
/// @returns The full PC + status bits.
uint32_t pc_status(const uint32_t offset) const {
return
((active_[15] + offset) & ConditionCode::Address) |
status();
}
/// Sets status bits only, subject to mode.
void set_status(const uint32_t status) {
// ... in user mode the other flags (I, F, M1, M0) are protected from direct change
// but in non-user modes these will also be affected, accepting copies of bits 27, 26,
// 1 and 0 of the result respectively.
negative_flag_ = status;
overflow_flag_ = status << 3;
carry_flag_ = status & ConditionCode::Carry;
zero_result_ = ~status & ConditionCode::Zero;
if(mode_ != Mode::User) {
set_mode(Mode(status & 3));
interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable);
}
}
/// Sets C if @c value is non-zero; resets it otherwise.
void set_c(const uint32_t value) {
carry_flag_ = value;
/// @returns The current mode.
Mode mode() const {
return mode_;
}
/// Sets a new PC.
void set_pc(const uint32_t value) {
active_[15] = value & ConditionCode::Address;
}
/// @returns The stored PC plus @c offset limited to 26 bits.
uint32_t pc(const uint32_t offset) const {
return (active_[15] + offset) & ConditionCode::Address;
}
// MARK: - Exceptions.
enum class Exception {
/// Reset line went from high to low.
Reset = 0x00,
/// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found.
UndefinedInstruction = 0x04,
/// Code executed a software interrupt.
SoftwareInterrupt = 0x08,
/// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue.
PrefetchAbort = 0x0c,
/// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled
/// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM
/// otherwise complete, including pointer writeback.
DataAbort = 0x10,
/// The first data transfer attempted within an instruction was above address 0x3ff'ffff.
Address = 0x14,
/// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set.
IRQ = 0x18,
/// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set.
FIQ = 0x1c,
};
static constexpr uint32_t pc_offset_during(const Exception exception) {
// The below is somewhat convoluted by the assumed execution model:
//
// * exceptions occuring during execution of an instruction are taken
// to occur after R15 has already been incremented by 4; but
// * exceptions occurring instead of execution of an instruction are
// taken to occur with R15 pointing to an instruction that hasn't begun.
//
// i.e. in net R15 always refers to the next instruction
// that has not yet started.
switch(exception) {
// "To return normally from FIQ use SUBS PC, R14_fiq, #4".
case Exception::FIQ: return 4;
// "To return normally from IRQ use SUBS PC, R14_irq, #4".
case Exception::IRQ: return 4;
// "If a return is required from [address exception traps], use
// SUBS PC, R14_svc, #4. This will return to the instruction after
// the one causing the trap".
case Exception::Address: return 4;
// "A Data Abort requires [work before a return], the return being
// done by SUBS PC, R14_svc, #8" (and returning to the instruction
// that aborted).
case Exception::DataAbort: return 4;
// "To continue after a Prefetch Abort use SUBS PC, R14_svc #4.
// This will attempt to re-execute the aborting instruction."
case Exception::PrefetchAbort: return 4;
// "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction
// following the SWI".
case Exception::SoftwareInterrupt: return 0;
// "To return from [an undefined instruction trap] use MOVS PC, R14_svc.
// This returns to the instruction following the undefined instruction".
case Exception::UndefinedInstruction: return 0;
// Unspecified; a guess.
case Exception::Reset: return 0;
}
return 4;
}
/// @returns @c 1 if carry is set; @c 0 otherwise.
uint32_t c() const {
return carry_flag_ ? 1 : 0;
/// Updates the program counter, interupt flags and link register if applicable to begin @c exception.
template <Exception type>
void exception() {
const auto r14 = pc_status(pc_offset_during(type));
switch(type) {
case Exception::IRQ: set_mode(Mode::IRQ); break;
case Exception::FIQ: set_mode(Mode::FIQ); break;
default: set_mode(Mode::Supervisor); break;
}
active_[14] = r14;
/// Sets V if the highest bit of @c value is set; resets it otherwise.
void set_v(const uint32_t value) {
overflow_flag_ = value;
interrupt_flags_ |= ConditionCode::IRQDisable;
if constexpr (type == Exception::Reset || type == Exception::FIQ) {
interrupt_flags_ |= ConditionCode::FIQDisable;
}
set_pc(uint32_t(type));
}
/// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags.
uint32_t status() const {
return
uint32_t(mode_) |
(negative_flag_ & ConditionCode::Negative) |
(zero_result_ ? 0 : ConditionCode::Zero) |
(carry_flag_ ? ConditionCode::Carry : 0) |
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
interrupt_flags_;
/// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts.
/// Otherwise returns @c false.
template <Exception type>
bool would_interrupt() {
switch(type) {
case Exception::IRQ:
if(interrupt_flags_ & ConditionCode::IRQDisable) {
return false;
}
break;
case Exception::FIQ:
if(interrupt_flags_ & ConditionCode::FIQDisable) {
return false;
}
break;
default: return false;
}
return true;
}
/// @returns The full PC + status bits.
uint32_t pc_status(const uint32_t offset) const {
return
((active_[15] + offset) & ConditionCode::Address) |
status();
}
// MARK: - Condition tests.
/// Sets status bits only, subject to mode.
void set_status(const uint32_t status) {
// ... in user mode the other flags (I, F, M1, M0) are protected from direct change
// but in non-user modes these will also be affected, accepting copies of bits 27, 26,
// 1 and 0 of the result respectively.
negative_flag_ = status;
overflow_flag_ = status << 3;
carry_flag_ = status & ConditionCode::Carry;
zero_result_ = ~status & ConditionCode::Zero;
if(mode_ != Mode::User) {
set_mode(Mode(status & 3));
interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable);
}
}
/// @returns The current mode.
Mode mode() const {
return mode_;
}
/// Sets a new PC.
void set_pc(const uint32_t value) {
active_[15] = value & ConditionCode::Address;
}
/// @returns The stored PC plus @c offset limited to 26 bits.
uint32_t pc(const uint32_t offset) const {
return (active_[15] + offset) & ConditionCode::Address;
}
// MARK: - Exceptions.
enum class Exception {
/// Reset line went from high to low.
Reset = 0x00,
/// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found.
UndefinedInstruction = 0x04,
/// Code executed a software interrupt.
SoftwareInterrupt = 0x08,
/// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue.
PrefetchAbort = 0x0c,
/// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled
/// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM
/// otherwise complete, including pointer writeback.
DataAbort = 0x10,
/// The first data transfer attempted within an instruction was above address 0x3ff'ffff.
Address = 0x14,
/// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set.
IRQ = 0x18,
/// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set.
FIQ = 0x1c,
/// @returns @c true if @c condition tests as true; @c false otherwise.
bool test(const Condition condition) const {
const auto ne = [&]() -> bool {
return zero_result_;
};
const auto cs = [&]() -> bool {
return carry_flag_;
};
const auto mi = [&]() -> bool {
return negative_flag_ & ConditionCode::Negative;
};
const auto vs = [&]() -> bool {
return overflow_flag_ & ConditionCode::Negative;
};
const auto hi = [&]() -> bool {
return carry_flag_ && zero_result_;
};
const auto lt = [&]() -> bool {
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
};
const auto le = [&]() -> bool {
return !zero_result_ || lt();
};
static constexpr uint32_t pc_offset_during(const Exception exception) {
// The below is somewhat convoluted by the assumed execution model:
//
// * exceptions occuring during execution of an instruction are taken
// to occur after R15 has already been incremented by 4; but
// * exceptions occurring instead of execution of an instruction are
// taken to occur with R15 pointing to an instruction that hasn't begun.
//
// i.e. in net R15 always refers to the next instruction
// that has not yet started.
switch(exception) {
// "To return normally from FIQ use SUBS PC, R14_fiq, #4".
case Exception::FIQ: return 4;
// "To return normally from IRQ use SUBS PC, R14_irq, #4".
case Exception::IRQ: return 4;
switch(condition) {
case Condition::EQ: return !ne();
case Condition::NE: return ne();
case Condition::CS: return cs();
case Condition::CC: return !cs();
case Condition::MI: return mi();
case Condition::PL: return !mi();
case Condition::VS: return vs();
case Condition::VC: return !vs();
// "If a return is required from [address exception traps], use
// SUBS PC, R14_svc, #4. This will return to the instruction after
// the one causing the trap".
case Exception::Address: return 4;
case Condition::HI: return hi();
case Condition::LS: return !hi();
case Condition::GE: return !lt();
case Condition::LT: return lt();
case Condition::GT: return !le();
case Condition::LE: return le();
// "A Data Abort requires [work before a return], the return being
// done by SUBS PC, R14_svc, #8" (and returning to the instruction
// that aborted).
case Exception::DataAbort: return 4;
// "To continue after a Prefetch Abort use SUBS PC, R14_svc #4.
// This will attempt to re-execute the aborting instruction."
case Exception::PrefetchAbort: return 4;
// "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction
// following the SWI".
case Exception::SoftwareInterrupt: return 0;
// "To return from [an undefined instruction trap] use MOVS PC, R14_svc.
// This returns to the instruction following the undefined instruction".
case Exception::UndefinedInstruction: return 0;
// Unspecified; a guess.
case Exception::Reset: return 0;
}
return 4;
case Condition::AL: return true;
case Condition::NV: return false;
}
/// Updates the program counter, interupt flags and link register if applicable to begin @c exception.
template <Exception type>
void exception() {
const auto r14 = pc_status(pc_offset_during(type));
switch(type) {
case Exception::IRQ: set_mode(Mode::IRQ); break;
case Exception::FIQ: set_mode(Mode::FIQ); break;
default: set_mode(Mode::Supervisor); break;
}
active_[14] = r14;
return false;
}
interrupt_flags_ |= ConditionCode::IRQDisable;
if constexpr (type == Exception::Reset || type == Exception::FIQ) {
interrupt_flags_ |= ConditionCode::FIQDisable;
}
set_pc(uint32_t(type));
/// Sets current execution mode.
void set_mode(const Mode target_mode) {
if(mode_ == target_mode) {
return;
}
/// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts.
/// Otherwise returns @c false.
template <Exception type>
bool would_interrupt() {
switch(type) {
case Exception::IRQ:
if(interrupt_flags_ & ConditionCode::IRQDisable) {
return false;
}
break;
// For outgoing modes other than FIQ, only save the final two registers for now;
// if the incoming mode is FIQ then the other five will be saved in the next switch.
// For FIQ, save all seven up front.
switch(mode_) {
// FIQ outgoing: save R8 to R14.
case Mode::FIQ:
std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin());
break;
case Exception::FIQ:
if(interrupt_flags_ & ConditionCode::FIQDisable) {
return false;
}
break;
default: return false;
}
return true;
// Non-FIQ outgoing: save R13 and R14. If saving to the user registers,
// use only the final two slots.
case Mode::User:
std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5);
break;
case Mode::Supervisor:
std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin());
break;
case Mode::IRQ:
std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin());
break;
}
// MARK: - Condition tests.
// For all modes except FIQ: restore the final two registers to their appropriate values.
// For FIQ: save an additional five, then overwrite seven.
switch(target_mode) {
case Mode::FIQ:
// FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers.
std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin());
/// @returns @c true if @c condition tests as true; @c false otherwise.
bool test(const Condition condition) const {
const auto ne = [&]() -> bool {
return zero_result_;
};
const auto cs = [&]() -> bool {
return carry_flag_;
};
const auto mi = [&]() -> bool {
return negative_flag_ & ConditionCode::Negative;
};
const auto vs = [&]() -> bool {
return overflow_flag_ & ConditionCode::Negative;
};
const auto hi = [&]() -> bool {
return carry_flag_ && zero_result_;
};
const auto lt = [&]() -> bool {
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
};
const auto le = [&]() -> bool {
return !zero_result_ || lt();
};
switch(condition) {
case Condition::EQ: return !ne();
case Condition::NE: return ne();
case Condition::CS: return cs();
case Condition::CC: return !cs();
case Condition::MI: return mi();
case Condition::PL: return !mi();
case Condition::VS: return vs();
case Condition::VC: return !vs();
case Condition::HI: return hi();
case Condition::LS: return !hi();
case Condition::GE: return !lt();
case Condition::LT: return lt();
case Condition::GT: return !le();
case Condition::LE: return le();
case Condition::AL: return true;
case Condition::NV: return false;
}
return false;
// Replace R8 to R14.
std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8);
break;
case Mode::User:
std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13);
break;
case Mode::Supervisor:
std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13);
break;
case Mode::IRQ:
std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13);
break;
}
/// Sets current execution mode.
void set_mode(const Mode target_mode) {
if(mode_ == target_mode) {
return;
}
// For outgoing modes other than FIQ, only save the final two registers for now;
// if the incoming mode is FIQ then the other five will be saved in the next switch.
// For FIQ, save all seven up front.
switch(mode_) {
// FIQ outgoing: save R8 to R14.
case Mode::FIQ:
std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin());
break;
// Non-FIQ outgoing: save R13 and R14. If saving to the user registers,
// use only the final two slots.
case Mode::User:
std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5);
break;
case Mode::Supervisor:
std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin());
break;
case Mode::IRQ:
std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin());
break;
}
// For all modes except FIQ: restore the final two registers to their appropriate values.
// For FIQ: save an additional five, then overwrite seven.
switch(target_mode) {
case Mode::FIQ:
// FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers.
std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin());
// Replace R8 to R14.
std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8);
break;
case Mode::User:
std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13);
break;
case Mode::Supervisor:
std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13);
break;
case Mode::IRQ:
std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13);
break;
}
// If FIQ is outgoing then there's another five registers to restore.
if(mode_ == Mode::FIQ) {
std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8);
}
mode_ = target_mode;
// If FIQ is outgoing then there's another five registers to restore.
if(mode_ == Mode::FIQ) {
std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8);
}
uint32_t &operator[](const uint32_t offset) {
return active_[static_cast<size_t>(offset)];
mode_ = target_mode;
}
uint32_t &operator[](const uint32_t offset) {
return active_[static_cast<size_t>(offset)];
}
uint32_t operator[](const uint32_t offset) const {
return active_[static_cast<size_t>(offset)];
}
/// @returns A reference to the register at @c offset. If @c force_user_mode is true,
/// this will the the user-mode register. Otherwise it'll be that for the current mode. These references
/// are guaranteed to remain valid only until the next mode change.
uint32_t &reg(const bool force_user_mode, const uint32_t offset) {
switch(mode_) {
default:
case Mode::User: return active_[offset];
case Mode::Supervisor:
case Mode::IRQ:
if(force_user_mode && (offset == 13 || offset == 14)) {
return user_registers_[offset - 8];
}
return active_[offset];
case Mode::FIQ:
if(force_user_mode && (offset >= 8 && offset < 15)) {
return user_registers_[offset - 8];
}
return active_[offset];
}
}
uint32_t operator[](const uint32_t offset) const {
return active_[static_cast<size_t>(offset)];
}
private:
Mode mode_ = Mode::Supervisor;
/// @returns A reference to the register at @c offset. If @c force_user_mode is true,
/// this will the the user-mode register. Otherwise it'll be that for the current mode. These references
/// are guaranteed to remain valid only until the next mode change.
uint32_t &reg(const bool force_user_mode, const uint32_t offset) {
switch(mode_) {
default:
case Mode::User: return active_[offset];
uint32_t zero_result_ = 1;
uint32_t negative_flag_ = 0;
uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable;
uint32_t carry_flag_ = 0;
uint32_t overflow_flag_ = 0;
case Mode::Supervisor:
case Mode::IRQ:
if(force_user_mode && (offset == 13 || offset == 14)) {
return user_registers_[offset - 8];
}
return active_[offset];
// Various shadow registers.
std::array<uint32_t, 7> user_registers_{};
std::array<uint32_t, 7> fiq_registers_{};
std::array<uint32_t, 2> irq_registers_{};
std::array<uint32_t, 2> supervisor_registers_{};
case Mode::FIQ:
if(force_user_mode && (offset >= 8 && offset < 15)) {
return user_registers_[offset - 8];
}
return active_[offset];
}
}
private:
Mode mode_ = Mode::Supervisor;
uint32_t zero_result_ = 1;
uint32_t negative_flag_ = 0;
uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable;
uint32_t carry_flag_ = 0;
uint32_t overflow_flag_ = 0;
// Various shadow registers.
std::array<uint32_t, 7> user_registers_{};
std::array<uint32_t, 7> fiq_registers_{};
std::array<uint32_t, 2> irq_registers_{};
std::array<uint32_t, 2> supervisor_registers_{};
// The active register set.
std::array<uint32_t, 16> active_{};
// The active register set.
std::array<uint32_t, 16> active_{};
};
}

View File

@ -76,37 +76,37 @@ private:
Provides dynamic lookup of @c perform(Executor*).
*/
class PerformerLookup {
public:
PerformerLookup() {
fill<int(MinOperation)>(performers_);
public:
PerformerLookup() {
fill<int(MinOperation)>(performers_);
}
Performer performer(const Operation operation, const AddressingMode addressing_mode) {
const auto index =
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
(int(addressing_mode) - MinAddressingMode);
return performers_[index];
}
private:
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
fill_operation<operation, addressing_mode+1>(target + 1);
}
}
Performer performer(const Operation operation, const AddressingMode addressing_mode) {
const auto index =
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
(int(addressing_mode) - MinAddressingMode);
return performers_[index];
}
private:
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
fill_operation<operation, addressing_mode+1>(target + 1);
}
}
template<int operation> void fill(Performer *target) {
fill_operation<operation, int(MinAddressingMode)>(target);
target += 1 + MaxAddressingMode - MinAddressingMode;
if constexpr (operation+1 <= MaxOperation) {
fill<operation+1>(target);
}
template<int operation> void fill(Performer *target) {
fill_operation<operation, int(MinAddressingMode)>(target);
target += 1 + MaxAddressingMode - MinAddressingMode;
if constexpr (operation+1 <= MaxOperation) {
fill<operation+1>(target);
}
}
};
inline static PerformerLookup performer_lookup_;

View File

@ -85,79 +85,79 @@ public:
private:
class State: public NullFlowController {
public:
State(BusHandler &handler) : bus_handler_(handler) {}
public:
State(BusHandler &handler) : bus_handler_(handler) {}
void run(int &);
bool stopped = false;
void run(int &);
bool stopped = false;
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
template <typename IntT> void write(uint32_t address, IntT value);
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
template <typename IntT> void write(uint32_t address, IntT value);
template <typename IntT> IntT read_pc();
template <typename IntT> IntT read_pc();
// Processor state.
Status status;
CPU::SlicedInt32 program_counter;
CPU::SlicedInt32 registers[16]; // D0D7 followed by A0A7.
CPU::SlicedInt32 stack_pointers[2];
uint32_t instruction_address;
uint16_t instruction_opcode;
// Processor state.
Status status;
CPU::SlicedInt32 program_counter;
CPU::SlicedInt32 registers[16]; // D0D7 followed by A0A7.
CPU::SlicedInt32 stack_pointers[2];
uint32_t instruction_address;
uint16_t instruction_opcode;
// Things that are ephemerally duplicative of Status.
int active_stack_pointer = 0;
Status::FlagT should_trace = 0;
// Things that are ephemerally duplicative of Status.
int active_stack_pointer = 0;
Status::FlagT should_trace = 0;
// Bus state.
int interrupt_input = 0;
// Bus state.
int interrupt_input = 0;
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
// postincrement and predecrement mode.
static constexpr uint32_t byte_increments[] = {
1, 1, 1, 1, 1, 1, 1, 2
};
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
// postincrement and predecrement mode.
static constexpr uint32_t byte_increments[] = {
1, 1, 1, 1, 1, 1, 1, 2
};
// Flow control; Cf. Perform.hpp.
template <bool use_current_instruction_pc = true> void raise_exception(int);
// Flow control; Cf. Perform.hpp.
template <bool use_current_instruction_pc = true> void raise_exception(int);
void did_update_status();
void did_update_status();
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
void bsr(uint32_t offset);
void jmp(uint32_t);
void jsr(uint32_t offset);
void rtr();
void rts();
void rte();
void stop();
void reset();
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
void bsr(uint32_t offset);
void jmp(uint32_t);
void jsr(uint32_t offset);
void rtr();
void rts();
void rte();
void stop();
void reset();
void link(Preinstruction instruction, uint32_t offset);
void unlink(uint32_t &address);
void pea(uint32_t address);
void link(Preinstruction instruction, uint32_t offset);
void unlink(uint32_t &address);
void pea(uint32_t address);
void move_to_usp(uint32_t address);
void move_from_usp(uint32_t &address);
void move_to_usp(uint32_t address);
void move_from_usp(uint32_t &address);
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
void tas(Preinstruction instruction, uint32_t address);
void tas(Preinstruction instruction, uint32_t address);
private:
BusHandler &bus_handler_;
Predecoder<model> decoder_;
private:
BusHandler &bus_handler_;
Predecoder<model> decoder_;
struct EffectiveAddress {
CPU::SlicedInt32 value;
bool requires_fetch;
};
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
uint32_t index_8bitdisplacement(uint32_t);
struct EffectiveAddress {
CPU::SlicedInt32 value;
bool requires_fetch;
};
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
uint32_t index_8bitdisplacement(uint32_t);
} state_;
};

View File

@ -233,11 +233,11 @@ private:
//
// So here's a thin non-templated shim to unblock initial PC Compatible development.
class Decoder8086 {
public:
std::pair<int, Instruction<false>> decode(const uint8_t *source, std::size_t length);
public:
std::pair<int, Instruction<false>> decode(const uint8_t *source, std::size_t length);
private:
Decoder<Model::i8086> decoder;
private:
Decoder<Model::i8086> decoder;
};
}

View File

@ -563,60 +563,60 @@ constexpr Operation rep_operation(Operation operation, Repetition repetition) {
///
/// It cannot natively describe a base of ::None.
class ScaleIndexBase {
public:
constexpr ScaleIndexBase() noexcept = default;
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
sib_(uint8_t(
scale << 6 |
(int(index != Source::None ? index : Source::eSP) << 3) |
int(base)
)) {}
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
public:
constexpr ScaleIndexBase() noexcept = default;
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
sib_(uint8_t(
scale << 6 |
(int(index != Source::None ? index : Source::eSP) << 3) |
int(base)
)) {}
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
constexpr int scale() const {
return sib_ >> 6;
}
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
constexpr int scale() const {
return sib_ >> 6;
}
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
constexpr Source index() const {
constexpr Source sources[] = {
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
};
static_assert(sizeof(sources) == 8);
return sources[(sib_ >> 3) & 0x7];
}
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
constexpr Source index() const {
constexpr Source sources[] = {
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
};
static_assert(sizeof(sources) == 8);
return sources[(sib_ >> 3) & 0x7];
}
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
constexpr Source base() const {
return Source(sib_ & 0x7);
}
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
constexpr Source base() const {
return Source(sib_ & 0x7);
}
constexpr uint8_t without_base() const {
return sib_ & ~0x3;
}
constexpr uint8_t without_base() const {
return sib_ & ~0x3;
}
bool operator ==(const ScaleIndexBase &rhs) const {
// Permit either exact equality or index and base being equal
// but transposed with a scale of 1.
return
(sib_ == rhs.sib_) ||
(
!scale() && !rhs.scale() &&
rhs.index() == base() &&
rhs.base() == index()
);
}
bool operator ==(const ScaleIndexBase &rhs) const {
// Permit either exact equality or index and base being equal
// but transposed with a scale of 1.
return
(sib_ == rhs.sib_) ||
(
!scale() && !rhs.scale() &&
rhs.index() == base() &&
rhs.base() == index()
);
}
operator uint8_t() const {
return sib_;
}
operator uint8_t() const {
return sib_;
}
private:
// Data is stored directly as an 80386 SIB byte.
uint8_t sib_ = 0;
private:
// Data is stored directly as an 80386 SIB byte.
uint8_t sib_ = 0;
};
static_assert(sizeof(ScaleIndexBase) == 1);
static_assert(alignof(ScaleIndexBase) == 1);
@ -632,69 +632,69 @@ static_assert(alignof(ScaleIndexBase) == 1);
///
/// In all cases, the applicable segment is indicated by the instruction.
class DataPointer {
public:
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
constexpr DataPointer(Source source) noexcept : source_(source) {}
public:
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
constexpr DataPointer(Source source) noexcept : source_(source) {}
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
/// Constructs a DataPointer with a source and SIB; use the source to indicate
/// whether the base field of the SIB is effective.
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
/// Constructs a DataPointer with a source and SIB; use the source to indicate
/// whether the base field of the SIB is effective.
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
/// Constructs an indirect DataPointer referencing the given base, index and scale.
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
constexpr DataPointer(Source base, Source index, int scale) noexcept :
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
sib_(scale, index, base) {}
/// Constructs an indirect DataPointer referencing the given base, index and scale.
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
constexpr DataPointer(Source base, Source index, int scale) noexcept :
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
sib_(scale, index, base) {}
constexpr bool operator ==(const DataPointer &rhs) const {
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
return
source_ == rhs.source_ && (
source_ < Source::IndirectNoBase ||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
);
constexpr bool operator ==(const DataPointer &rhs) const {
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
return
source_ == rhs.source_ && (
source_ < Source::IndirectNoBase ||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
);
}
constexpr Source source() const {
return source_;
}
constexpr int scale() const {
return sib_.scale();
}
constexpr Source index() const {
return sib_.index();
}
/// @returns The default segment to use for this access.
constexpr Source default_segment() const {
switch(source_) {
default:
case Source::IndirectNoBase:
return Source::None;
case Source::Indirect:
switch(base()) {
default: return Source::DS;
case Source::eBP:
case Source::eSP: return Source::SS;
case Source::eDI: return Source::ES;
}
}
}
constexpr Source source() const {
return source_;
}
constexpr Source base() const {
return sib_.base();
}
constexpr int scale() const {
return sib_.scale();
}
constexpr Source index() const {
return sib_.index();
}
/// @returns The default segment to use for this access.
constexpr Source default_segment() const {
switch(source_) {
default:
case Source::IndirectNoBase:
return Source::None;
case Source::Indirect:
switch(base()) {
default: return Source::DS;
case Source::eBP:
case Source::eSP: return Source::SS;
case Source::eDI: return Source::ES;
}
}
}
constexpr Source base() const {
return sib_.base();
}
private:
Source source_ = Source::Indirect;
ScaleIndexBase sib_;
private:
Source source_ = Source::Indirect;
ScaleIndexBase sib_;
};
template<bool is_32bit> class Instruction {

File diff suppressed because it is too large Load Diff

View File

@ -236,275 +236,275 @@ struct MemoryController {
ioc_.set_activity_observer(observer);
}
private:
Log::Logger<Log::Source::ARMIOC> logger;
private:
Log::Logger<Log::Source::ARMIOC> logger;
enum class ReadZone {
LogicallyMappedRAM,
PhysicallyMappedRAM,
IOControllers,
LowROM,
HighROM,
};
enum class WriteZone {
LogicallyMappedRAM,
PhysicallyMappedRAM,
IOControllers,
VideoController,
DMAAndMEMC,
AddressTranslator,
};
template <bool is_read>
using Zone = std::conditional_t<is_read, ReadZone, WriteZone>;
enum class ReadZone {
LogicallyMappedRAM,
PhysicallyMappedRAM,
IOControllers,
LowROM,
HighROM,
};
enum class WriteZone {
LogicallyMappedRAM,
PhysicallyMappedRAM,
IOControllers,
VideoController,
DMAAndMEMC,
AddressTranslator,
};
template <bool is_read>
using Zone = std::conditional_t<is_read, ReadZone, WriteZone>;
template <bool is_read>
static std::array<Zone<is_read>, 0x20> zones() {
std::array<Zone<is_read>, 0x20> zones{};
for(size_t c = 0; c < zones.size(); c++) {
const auto address = c << 21;
if(address < 0x200'0000) {
zones[c] = Zone<is_read>::LogicallyMappedRAM;
} else if(address < 0x300'0000) {
zones[c] = Zone<is_read>::PhysicallyMappedRAM;
} else if(address < 0x340'0000) {
zones[c] = Zone<is_read>::IOControllers;
} else if(address < 0x360'0000) {
if constexpr (is_read) {
zones[c] = Zone<is_read>::LowROM;
} else {
zones[c] = Zone<is_read>::VideoController;
}
} else if(address < 0x380'0000) {
if constexpr (is_read) {
zones[c] = Zone<is_read>::LowROM;
} else {
zones[c] = Zone<is_read>::DMAAndMEMC;
}
template <bool is_read>
static std::array<Zone<is_read>, 0x20> zones() {
std::array<Zone<is_read>, 0x20> zones{};
for(size_t c = 0; c < zones.size(); c++) {
const auto address = c << 21;
if(address < 0x200'0000) {
zones[c] = Zone<is_read>::LogicallyMappedRAM;
} else if(address < 0x300'0000) {
zones[c] = Zone<is_read>::PhysicallyMappedRAM;
} else if(address < 0x340'0000) {
zones[c] = Zone<is_read>::IOControllers;
} else if(address < 0x360'0000) {
if constexpr (is_read) {
zones[c] = Zone<is_read>::LowROM;
} else {
if constexpr (is_read) {
zones[c] = Zone<is_read>::HighROM;
} else {
zones[c] = Zone<is_read>::AddressTranslator;
}
zones[c] = Zone<is_read>::VideoController;
}
} else if(address < 0x380'0000) {
if constexpr (is_read) {
zones[c] = Zone<is_read>::LowROM;
} else {
zones[c] = Zone<is_read>::DMAAndMEMC;
}
} else {
if constexpr (is_read) {
zones[c] = Zone<is_read>::HighROM;
} else {
zones[c] = Zone<is_read>::AddressTranslator;
}
}
return zones;
}
return zones;
}
bool has_moved_rom_ = false;
std::array<uint8_t, 2*1024*1024> rom_;
std::array<uint8_t, 4*1024*1024> ram_{};
InputOutputController<InterruptObserverT, ClockRateObserverT> ioc_;
template <typename IntT>
IntT &physical_ram(uint32_t address) {
address = aligned<IntT>(address);
address &= (ram_.size() - 1);
return *reinterpret_cast<IntT *>(&ram_[address]);
}
template <typename IntT>
IntT &high_rom(uint32_t address) {
address = aligned<IntT>(address);
return *reinterpret_cast<IntT *>(&rom_[address & (rom_.size() - 1)]);
}
std::array<ReadZone, 0x20> read_zones_ = zones<true>();
const std::array<WriteZone, 0x20> write_zones_ = zones<false>();
// Control register values.
bool os_mode_ = false;
bool sound_dma_enable_ = false;
bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily.
enum class DynamicRAMRefresh {
None = 0b00,
DuringFlyback = 0b01,
Continuous = 0b11,
} dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value.
enum class ROMAccessTime {
ns450 = 0b00,
ns325 = 0b01,
ns200 = 0b10,
ns200with60nsNibble = 0b11,
} high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450;
enum class PageSize {
kb4 = 0b00,
kb8 = 0b01,
kb16 = 0b10,
kb32 = 0b11,
} page_size_ = PageSize::kb4;
int page_address_shift_ = 12;
uint32_t page_adddress_mask_ = 0xffff;
// Address translator.
//
// MEMC contains one entry per a physical page number, indicating where it goes logically.
// Any logical access is tested against all 128 mappings. So that's backwards compared to
// the ideal for an emulator, which would map from logical to physical, even if a lot more
// compact — there are always 128 physical pages; there are up to 8192 logical pages.
//
// So captured here are both the physical -> logical map as representative of the real
// hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt)
// from the other.
// Physical to logical mapping.
std::array<uint32_t, 128> pages_{};
// Logical to physical mapping; this is divided by 'access mode'
// (i.e. the combination of read/write, trans and OS mode flags,
// as multipliexed by the @c mapping() function) because mapping
// varies by mode — not just in terms of restricting access, but
// actually presenting different memory.
using MapTarget = std::array<uint8_t *, 8192>;
std::array<MapTarget, 6> mapping_;
template <bool is_read>
MapTarget &mapping(bool trans, bool os_mode) {
const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0);
return mapping_[index];
}
bool map_dirty_ = true;
/// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise.
template <typename IntT, bool is_read>
IntT *logical_ram(uint32_t address, bool trans) {
// Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive
// mapping process. It can be eliminated when the process is improved.
if(map_dirty_) {
update_mapping();
map_dirty_ = false;
}
address = aligned<IntT>(address);
address &= 0x1ff'ffff;
const size_t page = address >> page_address_shift_;
const auto &map = mapping<is_read>(trans, os_mode_);
address &= page_adddress_mask_;
return reinterpret_cast<IntT *>(&map[page][address]);
}
void update_mapping() {
// For each physical page, project it into logical space.
switch(page_size_) {
default:
case PageSize::kb4: update_mapping<PageSize::kb4>(); break;
case PageSize::kb8: update_mapping<PageSize::kb8>(); break;
case PageSize::kb16: update_mapping<PageSize::kb16>(); break;
case PageSize::kb32: update_mapping<PageSize::kb32>(); break;
}
}
template <PageSize size>
void update_mapping() {
// Clear all logical mappings.
for(auto &map: mapping_) {
// Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark
// the entire page as unmapped no matter what offset is added.
std::fill(map.begin(), map.end(), ram_.data() - 32768);
}
bool has_moved_rom_ = false;
std::array<uint8_t, 2*1024*1024> rom_;
std::array<uint8_t, 4*1024*1024> ram_{};
InputOutputController<InterruptObserverT, ClockRateObserverT> ioc_;
// For each physical page, project it into logical space
// and store it.
for(const auto page: pages_) {
uint32_t physical, logical;
template <typename IntT>
IntT &physical_ram(uint32_t address) {
address = aligned<IntT>(address);
address &= (ram_.size() - 1);
return *reinterpret_cast<IntT *>(&ram_[address]);
}
switch(size) {
case PageSize::kb4:
// 4kb:
// A[6:0] -> PPN[6:0]
// A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages
physical = page & BitMask<6, 0>::value;
template <typename IntT>
IntT &high_rom(uint32_t address) {
address = aligned<IntT>(address);
return *reinterpret_cast<IntT *>(&rom_[address & (rom_.size() - 1)]);
}
physical <<= 12;
std::array<ReadZone, 0x20> read_zones_ = zones<true>();
const std::array<WriteZone, 0x20> write_zones_ = zones<false>();
logical = (page & BitMask<11, 10>::value) << 1;
logical |= (page & BitMask<22, 12>::value) >> 12;
break;
// Control register values.
bool os_mode_ = false;
bool sound_dma_enable_ = false;
bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily.
case PageSize::kb8:
// 8kb:
// A[0] -> PPN[6]; A[6:1] -> PPN[5:0]
// A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages
physical = (page & BitMask<0, 0>::value) << 6;
physical |= (page & BitMask<6, 1>::value) >> 1;
enum class DynamicRAMRefresh {
None = 0b00,
DuringFlyback = 0b01,
Continuous = 0b11,
} dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value.
physical <<= 13;
enum class ROMAccessTime {
ns450 = 0b00,
ns325 = 0b01,
ns200 = 0b10,
ns200with60nsNibble = 0b11,
} high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450;
logical = page & BitMask<11, 10>::value;
logical |= (page & BitMask<22, 13>::value) >> 13;
break;
enum class PageSize {
kb4 = 0b00,
kb8 = 0b01,
kb16 = 0b10,
kb32 = 0b11,
} page_size_ = PageSize::kb4;
int page_address_shift_ = 12;
uint32_t page_adddress_mask_ = 0xffff;
case PageSize::kb16:
// 16kb:
// A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0]
// A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages
physical = (page & BitMask<1, 0>::value) << 5;
physical |= (page & BitMask<6, 2>::value) >> 2;
// Address translator.
//
// MEMC contains one entry per a physical page number, indicating where it goes logically.
// Any logical access is tested against all 128 mappings. So that's backwards compared to
// the ideal for an emulator, which would map from logical to physical, even if a lot more
// compact — there are always 128 physical pages; there are up to 8192 logical pages.
//
// So captured here are both the physical -> logical map as representative of the real
// hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt)
// from the other.
physical <<= 14;
// Physical to logical mapping.
std::array<uint32_t, 128> pages_{};
logical = (page & BitMask<11, 10>::value) >> 1;
logical |= (page & BitMask<22, 14>::value) >> 14;
break;
// Logical to physical mapping; this is divided by 'access mode'
// (i.e. the combination of read/write, trans and OS mode flags,
// as multipliexed by the @c mapping() function) because mapping
// varies by mode — not just in terms of restricting access, but
// actually presenting different memory.
using MapTarget = std::array<uint8_t *, 8192>;
std::array<MapTarget, 6> mapping_;
case PageSize::kb32:
// 32kb:
// A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0]
// A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages
physical = (page & BitMask<1, 1>::value) << 5;
physical |= (page & BitMask<2, 2>::value) << 3;
physical |= (page & BitMask<0, 0>::value) << 4;
physical |= (page & BitMask<6, 3>::value) >> 3;
template <bool is_read>
MapTarget &mapping(bool trans, bool os_mode) {
const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0);
return mapping_[index];
}
physical <<= 15;
bool map_dirty_ = true;
/// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise.
template <typename IntT, bool is_read>
IntT *logical_ram(uint32_t address, bool trans) {
// Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive
// mapping process. It can be eliminated when the process is improved.
if(map_dirty_) {
update_mapping();
map_dirty_ = false;
logical = (page & BitMask<11, 10>::value) >> 2;
logical |= (page & BitMask<22, 15>::value) >> 15;
break;
}
address = aligned<IntT>(address);
address &= 0x1ff'ffff;
const size_t page = address >> page_address_shift_;
const auto &map = mapping<is_read>(trans, os_mode_);
address &= page_adddress_mask_;
return reinterpret_cast<IntT *>(&map[page][address]);
}
void update_mapping() {
// For each physical page, project it into logical space.
switch(page_size_) {
default:
case PageSize::kb4: update_mapping<PageSize::kb4>(); break;
case PageSize::kb8: update_mapping<PageSize::kb8>(); break;
case PageSize::kb16: update_mapping<PageSize::kb16>(); break;
case PageSize::kb32: update_mapping<PageSize::kb32>(); break;
}
}
template <PageSize size>
void update_mapping() {
// Clear all logical mappings.
for(auto &map: mapping_) {
// Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark
// the entire page as unmapped no matter what offset is added.
std::fill(map.begin(), map.end(), ram_.data() - 32768);
}
// For each physical page, project it into logical space
// and store it.
for(const auto page: pages_) {
uint32_t physical, logical;
switch(size) {
case PageSize::kb4:
// 4kb:
// A[6:0] -> PPN[6:0]
// A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages
physical = page & BitMask<6, 0>::value;
physical <<= 12;
logical = (page & BitMask<11, 10>::value) << 1;
logical |= (page & BitMask<22, 12>::value) >> 12;
break;
case PageSize::kb8:
// 8kb:
// A[0] -> PPN[6]; A[6:1] -> PPN[5:0]
// A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages
physical = (page & BitMask<0, 0>::value) << 6;
physical |= (page & BitMask<6, 1>::value) >> 1;
physical <<= 13;
logical = page & BitMask<11, 10>::value;
logical |= (page & BitMask<22, 13>::value) >> 13;
break;
case PageSize::kb16:
// 16kb:
// A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0]
// A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages
physical = (page & BitMask<1, 0>::value) << 5;
physical |= (page & BitMask<6, 2>::value) >> 2;
physical <<= 14;
logical = (page & BitMask<11, 10>::value) >> 1;
logical |= (page & BitMask<22, 14>::value) >> 14;
break;
case PageSize::kb32:
// 32kb:
// A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0]
// A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages
physical = (page & BitMask<1, 1>::value) << 5;
physical |= (page & BitMask<2, 2>::value) << 3;
physical |= (page & BitMask<0, 0>::value) << 4;
physical |= (page & BitMask<6, 3>::value) >> 3;
physical <<= 15;
logical = (page & BitMask<11, 10>::value) >> 2;
logical |= (page & BitMask<22, 15>::value) >> 15;
break;
}
// printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical);
// TODO: consider clashes.
// TODO: what if there's less than 4mb present?
const auto target = &ram_[physical];
// TODO: consider clashes.
// TODO: what if there's less than 4mb present?
const auto target = &ram_[physical];
const auto set_supervisor = [&](bool read, bool write) {
if(read) mapping<true>(false, false)[logical] = target;
if(write) mapping<false>(false, false)[logical] = target;
};
const auto set_supervisor = [&](bool read, bool write) {
if(read) mapping<true>(false, false)[logical] = target;
if(write) mapping<false>(false, false)[logical] = target;
};
const auto set_os = [&](bool read, bool write) {
if(read) mapping<true>(true, true)[logical] = target;
if(write) mapping<false>(true, true)[logical] = target;
};
const auto set_os = [&](bool read, bool write) {
if(read) mapping<true>(true, true)[logical] = target;
if(write) mapping<false>(true, true)[logical] = target;
};
const auto set_user = [&](bool read, bool write) {
if(read) mapping<true>(true, false)[logical] = target;
if(write) mapping<false>(true, false)[logical] = target;
};
const auto set_user = [&](bool read, bool write) {
if(read) mapping<true>(true, false)[logical] = target;
if(write) mapping<false>(true, false)[logical] = target;
};
set_supervisor(true, true);
switch((page >> 8) & 3) {
case 0b00:
set_os(true, true);
set_user(true, true);
break;
case 0b01:
set_os(true, true);
set_user(true, false);
break;
default:
set_os(true, false);
set_user(false, false);
break;
}
set_supervisor(true, true);
switch((page >> 8) & 3) {
case 0b00:
set_os(true, true);
set_user(true, true);
break;
case 0b01:
set_os(true, true);
set_user(true, false);
break;
default:
set_os(true, false);
set_user(false, false);
break;
}
}
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -14,19 +14,19 @@
namespace Electron {
class Plus3 final : public WD::WD1770 {
public:
Plus3();
public:
Plus3();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_control_register(uint8_t control);
void set_activity_observer(Activity::Observer *observer);
void set_disk(std::shared_ptr<Storage::Disk::Disk>, size_t drive);
void set_control_register(uint8_t control);
void set_activity_observer(Activity::Observer *);
private:
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
private:
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
void set_motor_on(bool on) override;
std::string drive_name(size_t drive);
void set_motor_on(bool) override;
std::string drive_name(size_t);
};
}

View File

@ -14,26 +14,25 @@
namespace Electron {
class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, false> {
public:
SoundGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
public:
SoundGenerator(Concurrency::AsyncTaskQueue<false> &);
void set_divider(uint8_t divider);
void set_divider(uint8_t);
void set_is_enabled(bool);
void set_is_enabled(bool is_enabled);
static constexpr unsigned int clock_rate_divider = 8;
static constexpr unsigned int clock_rate_divider = 8;
// For BufferSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
void set_sample_volume_range(std::int16_t range);
// For BufferSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
unsigned int volume_ = 0;
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
unsigned int volume_ = 0;
};
}

View File

@ -20,56 +20,56 @@ namespace Electron {
class Tape:
public Storage::Tape::TapePlayer,
public Storage::Tape::Acorn::Shifter::Delegate {
public:
Tape();
public:
Tape();
void run_for(const Cycles cycles);
using Storage::Tape::TapePlayer::run_for;
void run_for(const Cycles cycles);
using Storage::Tape::TapePlayer::run_for;
uint8_t get_data_register();
void set_data_register(uint8_t value);
void set_counter(uint8_t value);
uint8_t get_data_register();
void set_data_register(uint8_t value);
void set_counter(uint8_t value);
inline uint8_t get_interrupt_status() { return interrupt_status_; }
void clear_interrupts(uint8_t interrupts);
inline uint8_t get_interrupt_status() { return interrupt_status_; }
void clear_interrupts(uint8_t interrupts);
class Delegate {
public:
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
class Delegate {
public:
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
inline void set_is_running(bool is_running) { is_running_ = is_running; }
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
void set_is_in_input_mode(bool is_in_input_mode);
inline void set_is_running(bool is_running) { is_running_ = is_running; }
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
void set_is_in_input_mode(bool is_in_input_mode);
void acorn_shifter_output_bit(int value);
void acorn_shifter_output_bit(int value);
private:
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
private:
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
struct {
int minimum_bits_until_full = 0;
} input_;
struct {
unsigned int cycles_into_pulse = 0;
unsigned int bits_remaining_until_empty = 0;
} output_;
struct {
int minimum_bits_until_full = 0;
} input_;
struct {
unsigned int cycles_into_pulse = 0;
unsigned int bits_remaining_until_empty = 0;
} output_;
bool is_running_ = false;
bool is_enabled_ = false;
bool is_in_input_mode_ = false;
bool is_running_ = false;
bool is_enabled_ = false;
bool is_in_input_mode_ = false;
inline void evaluate_interrupts();
uint16_t data_register_ = 0;
inline void evaluate_interrupts();
uint16_t data_register_ = 0;
uint8_t interrupt_status_ = 0;
uint8_t last_posted_interrupt_status_ = 0;
Delegate *delegate_ = nullptr;
uint8_t interrupt_status_ = 0;
uint8_t last_posted_interrupt_status_ = 0;
Delegate *delegate_ = nullptr;
::Storage::Tape::Acorn::Shifter shifter_;
::Storage::Tape::Acorn::Shifter shifter_;
};
}

View File

@ -24,160 +24,160 @@ namespace Electron {
is accessing it the CPU may not.
*/
class VideoOutput {
public:
/*!
Instantiates a VideoOutput that will read its pixels from @c memory.
public:
/*!
Instantiates a VideoOutput that will read its pixels from @c memory.
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
*/
VideoOutput(const uint8_t *memory);
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
*/
VideoOutput(const uint8_t *memory);
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Produces the next @c cycles of video output.
///
/// @returns a bit mask of all interrupts triggered.
uint8_t run_for(const Cycles cycles);
/// Produces the next @c cycles of video output.
///
/// @returns a bit mask of all interrupts triggered.
uint8_t run_for(const Cycles cycles);
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
/// IO [/1Mhz] access that is first signalled in the upcoming cycle.
Cycles io_delay() {
return 2 + ((h_count_ >> 3)&1);
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
/// IO [/1Mhz] access that is first signalled in the upcoming cycle.
Cycles io_delay() {
return 2 + ((h_count_ >> 3)&1);
}
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
/// RAM access that is first signalled in the upcoming cycle.
Cycles ram_delay() {
if(!mode_40_ && !in_blank()) {
return 2 + ((h_active - h_count_) >> 3);
}
return io_delay();
}
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
/// RAM access that is first signalled in the upcoming cycle.
Cycles ram_delay() {
if(!mode_40_ && !in_blank()) {
return 2 + ((h_active - h_count_) >> 3);
}
return io_delay();
/*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
*/
void write(int address, uint8_t value);
/*!
@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);
private:
const uint8_t *ram_ = nullptr;
// CRT output
enum class OutputStage {
Sync, Blank, Pixels, ColourBurst,
};
OutputStage output_ = OutputStage::Blank;
int output_length_ = 0;
int screen_pitch_ = 0;
uint8_t *current_output_target_ = nullptr;
uint8_t *initial_output_target_ = nullptr;
int current_output_divider_ = 1;
Outputs::CRT::CRT crt_;
// Palettes.
uint8_t palette_[8]{};
uint8_t palette1bpp_[2]{};
uint8_t palette2bpp_[4]{};
uint8_t palette4bpp_[16]{};
template <int index, int source_bit, int target_bit>
uint8_t channel() {
if constexpr (source_bit < target_bit) {
return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit);
} else {
return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit);
}
}
/*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
*/
void write(int address, uint8_t value);
template <int r_index, int r_bit, int g_index, int g_bit, int b_index, int b_bit>
uint8_t palette_entry() {
return channel<r_index, r_bit, 2>() | channel<g_index, g_bit, 1>() | channel<b_index, b_bit, 0>();
}
/*!
@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);
// User-selected base address; constrained to a 64-byte boundary by the setter.
uint16_t screen_base_ = 0;
private:
const uint8_t *ram_ = nullptr;
// Parameters implied by mode selection.
uint16_t mode_base_ = 0;
bool mode_40_ = true;
bool mode_text_ = false;
enum class Bpp {
One = 1, Two = 2, Four = 4
} mode_bpp_ = Bpp::One;
// CRT output
enum class OutputStage {
Sync, Blank, Pixels, ColourBurst,
};
OutputStage output_ = OutputStage::Blank;
int output_length_ = 0;
int screen_pitch_ = 0;
// Frame position.
int v_count_ = 0;
int h_count_ = 0;
bool field_ = true;
uint8_t *current_output_target_ = nullptr;
uint8_t *initial_output_target_ = nullptr;
int current_output_divider_ = 1;
Outputs::CRT::CRT crt_;
// Current working address.
uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row.
uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line.
int char_row_ = 0; // Character row; 09 in text mode, 07 in graphics.
// Palettes.
uint8_t palette_[8]{};
uint8_t palette1bpp_[2]{};
uint8_t palette2bpp_[4]{};
uint8_t palette4bpp_[16]{};
// Sync states.
bool vsync_int_ = false; // True => vsync active.
bool hsync_int_ = false; // True => hsync active.
template <int index, int source_bit, int target_bit>
uint8_t channel() {
if constexpr (source_bit < target_bit) {
return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit);
} else {
return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit);
}
}
// Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all
// divisible by 8, so it's safe to count time with a 2Mhz input.
static constexpr int h_active = 640;
static constexpr int hsync_start = 768;
static constexpr int hsync_end = 832;
static constexpr int h_reset_addr = 1016;
static constexpr int h_total = 1024; // Minor digression from the FPGA original here;
// in this implementation the value is tested
// _after_ position increment rather than before/instead.
// So it needs to be one higher. Which is baked into
// the constant to emphasise the all-divisible-by-8 property.
template <int r_index, int r_bit, int g_index, int g_bit, int b_index, int b_bit>
uint8_t palette_entry() {
return channel<r_index, r_bit, 2>() | channel<g_index, g_bit, 1>() | channel<b_index, b_bit, 0>();
}
static constexpr int h_half = h_total / 2;
static constexpr int hburst_start = 856;
static constexpr int hburst_end = 896;
// User-selected base address; constrained to a 64-byte boundary by the setter.
uint16_t screen_base_ = 0;
// Vertical timing parameters; all in terms of lines. As per the horizontal parameters above,
// lines begin with their first visible pixel (or the equivalent position).
static constexpr int v_active_gph = 256;
static constexpr int v_active_txt = 250;
static constexpr int v_disp_gph = v_active_gph - 1;
static constexpr int v_disp_txt = v_active_txt - 1;
static constexpr int vsync_start = 274;
static constexpr int vsync_end = 276;
static constexpr int v_rtc = 99;
// Parameters implied by mode selection.
uint16_t mode_base_ = 0;
bool mode_40_ = true;
bool mode_text_ = false;
enum class Bpp {
One = 1, Two = 2, Four = 4
} mode_bpp_ = Bpp::One;
// Various signals that it was convenient to factor out.
int v_total() const {
return field_ ? 312 : 311;
}
// Frame position.
int v_count_ = 0;
int h_count_ = 0;
bool field_ = true;
bool last_line() const {
return char_row_ == (mode_text_ ? 9 : 7);
}
// Current working address.
uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row.
uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line.
int char_row_ = 0; // Character row; 09 in text mode, 07 in graphics.
bool in_blank() const {
return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8;
}
// Sync states.
bool vsync_int_ = false; // True => vsync active.
bool hsync_int_ = false; // True => hsync active.
// Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all
// divisible by 8, so it's safe to count time with a 2Mhz input.
static constexpr int h_active = 640;
static constexpr int hsync_start = 768;
static constexpr int hsync_end = 832;
static constexpr int h_reset_addr = 1016;
static constexpr int h_total = 1024; // Minor digression from the FPGA original here;
// in this implementation the value is tested
// _after_ position increment rather than before/instead.
// So it needs to be one higher. Which is baked into
// the constant to emphasise the all-divisible-by-8 property.
static constexpr int h_half = h_total / 2;
static constexpr int hburst_start = 856;
static constexpr int hburst_end = 896;
// Vertical timing parameters; all in terms of lines. As per the horizontal parameters above,
// lines begin with their first visible pixel (or the equivalent position).
static constexpr int v_active_gph = 256;
static constexpr int v_active_txt = 250;
static constexpr int v_disp_gph = v_active_gph - 1;
static constexpr int v_disp_txt = v_active_txt - 1;
static constexpr int vsync_start = 274;
static constexpr int vsync_end = 276;
static constexpr int v_rtc = 99;
// Various signals that it was convenient to factor out.
int v_total() const {
return field_ ? 312 : 311;
}
bool last_line() const {
return char_row_ == (mode_text_ ? 9 : 7);
}
bool in_blank() const {
return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8;
}
bool is_v_end() const {
return v_count_ == v_total();
}
bool is_v_end() const {
return v_count_ == v_total();
}
};
}

View File

@ -50,201 +50,201 @@ class ConcreteMachine:
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
memory_(target.chip_ram, target.fast_ram),
chipset_(memory_, PALClockRate)
{
// Temporary: use a hard-coded Kickstart selection.
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
public:
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
memory_(target.chip_ram, target.fast_ram),
chipset_(memory_, PALClockRate)
{
// Temporary: use a hard-coded Kickstart selection.
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
// For now, also hard-code assumption of PAL.
// (Assumption is both here and in the video timing of the Chipset).
set_clock_rate(PALClockRate);
// For now, also hard-code assumption of PAL.
// (Assumption is both here and in the video timing of the Chipset).
set_clock_rate(PALClockRate);
// Insert supplied media.
insert_media(target.media);
// Insert supplied media.
insert_media(target.media);
}
// MARK: - MediaTarget.
bool insert_media(const Analyser::Static::Media &media) final {
return chipset_.insert(media.disks);
}
// MARK: - MC68000::BusHandler.
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
HalfCycles total_length;
if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
total_length = chipset_.run_until_after_cpu_slot().duration;
assert(total_length >= cycle.length);
} else {
total_length = cycle.length;
chipset_.run_for(total_length);
}
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
// Check for assertion of reset.
if(cycle.operation & CPU::MC68000::Operation::Reset) {
memory_.reset();
logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter);
}
// MARK: - MediaTarget.
bool insert_media(const Analyser::Static::Media &media) final {
return chipset_.insert(media.disks);
}
// MARK: - MC68000::BusHandler.
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
HalfCycles total_length;
if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
total_length = chipset_.run_until_after_cpu_slot().duration;
assert(total_length >= cycle.length);
} else {
total_length = cycle.length;
chipset_.run_for(total_length);
}
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
// Check for assertion of reset.
if(cycle.operation & CPU::MC68000::Operation::Reset) {
memory_.reset();
logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter);
}
// Autovector interrupts.
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
mc68000_.set_is_peripheral_address(true);
return total_length - cycle.length;
}
// Do nothing if no address is exposed.
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
// Set VPA if this is [going to be] a CIA access.
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
if(!memory_.regions[address >> 18].read_write_mask) {
if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
// Check for various potential chip accesses.
// Per the manual:
//
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
//
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
// these might be listed the wrong way around.
//
// Additional assumption: the relevant CIA select lines are connected
// directly to the chip enables.
if((address & 0xe0'0000) == 0xa0'0000) {
const int reg = address >> 8;
const bool select_a = !(address & 0x1000);
const bool select_b = !(address & 0x2000);
if(cycle.operation & CPU::MC68000::Operation::Read) {
uint16_t result = 0xffff;
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
cycle.set_value16(result);
} else {
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
}
// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16());
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
chipset_.perform(cycle);
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
// This is the Autoconf space; right now the only
// Autoconf device this emulator implements is fast RAM,
// which if present is provided as part of the memory map.
//
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
memory_.perform(cycle);
} else {
// This'll do for open bus, for now.
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(0xffff);
}
// Log only for the region that is definitely not just ROM this machine doesn't have.
if(address < 0xf0'0000) {
logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16());
}
}
}
} else {
// A regular memory access.
cycle.apply(
&memory_.regions[address >> 18].contents[address],
memory_.regions[address >> 18].read_write_mask
);
}
// Autovector interrupts.
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
mc68000_.set_is_peripheral_address(true);
return total_length - cycle.length;
}
private:
CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_;
// Do nothing if no address is exposed.
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
// MARK: - Memory map.
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
MemoryMap memory_;
// Set VPA if this is [going to be] a CIA access.
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
// MARK: - Chipset.
if(!memory_.regions[address >> 18].read_write_mask) {
if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
// Check for various potential chip accesses.
Chipset chipset_;
// Per the manual:
//
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
//
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
// these might be listed the wrong way around.
//
// Additional assumption: the relevant CIA select lines are connected
// directly to the chip enables.
if((address & 0xe0'0000) == 0xa0'0000) {
const int reg = address >> 8;
const bool select_a = !(address & 0x1000);
const bool select_b = !(address & 0x2000);
// MARK: - Activity Source
if(cycle.operation & CPU::MC68000::Operation::Read) {
uint16_t result = 0xffff;
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
cycle.set_value16(result);
} else {
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
}
void set_activity_observer(Activity::Observer *observer) final {
chipset_.set_activity_observer(observer);
// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16());
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
chipset_.perform(cycle);
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
// This is the Autoconf space; right now the only
// Autoconf device this emulator implements is fast RAM,
// which if present is provided as part of the memory map.
//
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
memory_.perform(cycle);
} else {
// This'll do for open bus, for now.
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(0xffff);
}
// Log only for the region that is definitely not just ROM this machine doesn't have.
if(address < 0xf0'0000) {
logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16());
}
}
}
} else {
// A regular memory access.
cycle.apply(
&memory_.regions[address >> 18].contents[address],
memory_.regions[address >> 18].read_write_mask
);
}
// MARK: - MachineTypes::AudioProducer.
return total_length - cycle.length;
}
Outputs::Speaker::Speaker *get_speaker() final {
return chipset_.get_speaker();
}
private:
CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_;
// MARK: - MachineTypes::ScanProducer.
// MARK: - Memory map.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
chipset_.set_scan_target(scan_target);
}
MemoryMap memory_;
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return chipset_.get_scaled_scan_status();
}
// MARK: - Chipset.
// MARK: - MachineTypes::TimedMachine.
Chipset chipset_;
void run_for(const Cycles cycles) final {
mc68000_.run_for(cycles);
}
// MARK: - Activity Source
void flush_output(int) final {
chipset_.flush();
}
void set_activity_observer(Activity::Observer *observer) final {
chipset_.set_activity_observer(observer);
}
// MARK: - MachineTypes::MouseMachine.
// MARK: - MachineTypes::AudioProducer.
Inputs::Mouse &get_mouse() final {
return chipset_.get_mouse();
}
Outputs::Speaker::Speaker *get_speaker() final {
return chipset_.get_speaker();
}
// MARK: - MachineTypes::JoystickMachine.
// MARK: - MachineTypes::ScanProducer.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return chipset_.get_joysticks();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
chipset_.set_scan_target(scan_target);
}
// MARK: - Keyboard.
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return chipset_.get_scaled_scan_status();
}
Amiga::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() {
return &keyboard_mapper_;
}
// MARK: - MachineTypes::TimedMachine.
void set_key_state(uint16_t key, bool is_pressed) {
chipset_.get_keyboard().set_key_state(key, is_pressed);
}
void run_for(const Cycles cycles) final {
mc68000_.run_for(cycles);
}
void clear_all_keys() {
chipset_.get_keyboard().clear_all_keys();
}
};
void flush_output(int) final {
chipset_.flush();
}
// MARK: - MachineTypes::MouseMachine.
Inputs::Mouse &get_mouse() final {
return chipset_.get_mouse();
}
// MARK: - MachineTypes::JoystickMachine.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return chipset_.get_joysticks();
}
// MARK: - Keyboard.
Amiga::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) {
chipset_.get_keyboard().set_key_state(key, is_pressed);
}
void clear_all_keys() {
chipset_.get_keyboard().clear_all_keys();
}
};
}

View File

@ -19,142 +19,142 @@
namespace Amiga {
class Audio: public DMADevice<4> {
public:
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
public:
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
/// Idiomatic call-in for DMA scheduling; indicates that this class may
/// perform a DMA access for the stated channel now.
bool advance_dma(int channel);
/// Idiomatic call-in for DMA scheduling; indicates that this class may
/// perform a DMA access for the stated channel now.
bool advance_dma(int channel);
/// Advances output by one DMA window, which is implicitly two cycles
/// at the output rate that was specified to the constructor.
void output();
/// Advances output by one DMA window, which is implicitly two cycles
/// at the output rate that was specified to the constructor.
void output();
/// Sets the total number of words to fetch for the given channel.
void set_length(int channel, uint16_t);
/// Sets the total number of words to fetch for the given channel.
void set_length(int channel, uint16_t);
/// Sets the number of DMA windows between each 8-bit output,
/// in the same time base as @c ticks_per_line.
void set_period(int channel, uint16_t);
/// Sets the number of DMA windows between each 8-bit output,
/// in the same time base as @c ticks_per_line.
void set_period(int channel, uint16_t);
/// Sets the output volume for the given channel; if bit 6 is set
/// then output is maximal; otherwise bits 05 select
/// a volume of [063]/64, on a logarithmic scale.
void set_volume(int channel, uint16_t);
/// Sets the output volume for the given channel; if bit 6 is set
/// then output is maximal; otherwise bits 05 select
/// a volume of [063]/64, on a logarithmic scale.
void set_volume(int channel, uint16_t);
/// Sets the next two samples of audio to output.
template <bool is_external = true> void set_data(int channel, uint16_t);
/// Sets the next two samples of audio to output.
template <bool is_external = true> void set_data(int channel, uint16_t);
/// Provides a copy of the DMA enable flags, for the purpose of
/// determining which channels are enabled for DMA.
void set_channel_enables(uint16_t);
/// Provides a copy of the DMA enable flags, for the purpose of
/// determining which channels are enabled for DMA.
void set_channel_enables(uint16_t);
/// Sets which channels, if any, modulate period or volume of
/// their neighbours.
void set_modulation_flags(uint16_t);
/// Sets which channels, if any, modulate period or volume of
/// their neighbours.
void set_modulation_flags(uint16_t);
/// Sets which interrupt requests are currently active.
void set_interrupt_requests(uint16_t);
/// Sets which interrupt requests are currently active.
void set_interrupt_requests(uint16_t);
/// Obtains the output source.
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
/// Obtains the output source.
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
}
private:
struct Channel {
// The data latch plus a count of unused samples
// in the latch, which will always be 0, 1 or 2.
uint16_t data = 0x0000;
bool wants_data = false;
uint16_t data_latch = 0x0000;
// The DMA address; unlike most of the Amiga Chipset,
// the user posts a value to feed a pointer, rather
// than having access to the pointer itself.
bool should_reload_address = false;
uint32_t data_address = 0x0000'0000;
// Number of words remaining in DMA data.
uint16_t length = 0;
uint16_t length_counter = 0;
// Number of ticks between each sample, plus the
// current counter, which counts downward.
uint16_t period = 0;
uint16_t period_counter = 0;
// Modulation / attach flags.
bool attach_period = false;
bool attach_volume = false;
// Output volume, [0, 64].
uint8_t volume = 0;
uint8_t volume_latch = 0;
// Indicates whether DMA is enabled for this channel.
bool dma_enabled = false;
// Records whether this audio interrupt is pending.
bool interrupt_pending = false;
bool will_request_interrupt = false;
// Replicates the Hardware Reference Manual state machine;
// comments indicate which of the documented states each
// label refers to.
enum class State {
Disabled, // 000
WaitingForDummyDMA, // 001
WaitingForDMA, // 101
PlayingHigh, // 010
PlayingLow, // 011
} state = State::Disabled;
/// Dispatches to the appropriate templatised output for the current state.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
bool output(Channel *moduland);
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State state> bool output(Channel *moduland);
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
/// and taking any steps specific to that particular transition.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State begin, State end> bool transit(Channel *moduland);
/// Begins @c state, performing all fixed logic that would otherwise have to be
/// repeated endlessly in the relevant @c output.
/// @param moduland The channel to modulate, if modulation is enabled.
template <State state> void begin_state(Channel *moduland);
/// Provides the common length-decrementing logic used when transitioning
/// between PlayingHigh and PlayingLow in either direction.
void decrement_length();
// Output state.
int8_t output_level = 0;
uint8_t output_phase = 0;
bool output_enabled = false;
void reset_output_phase() {
output_phase = 0;
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
}
} channels_[4];
private:
struct Channel {
// The data latch plus a count of unused samples
// in the latch, which will always be 0, 1 or 2.
uint16_t data = 0x0000;
bool wants_data = false;
uint16_t data_latch = 0x0000;
// Transient output state, and its destination.
Outputs::Speaker::PushLowpass<true> speaker_;
Concurrency::AsyncTaskQueue<true> queue_;
// The DMA address; unlike most of the Amiga Chipset,
// the user posts a value to feed a pointer, rather
// than having access to the pointer itself.
bool should_reload_address = false;
uint32_t data_address = 0x0000'0000;
// Number of words remaining in DMA data.
uint16_t length = 0;
uint16_t length_counter = 0;
// Number of ticks between each sample, plus the
// current counter, which counts downward.
uint16_t period = 0;
uint16_t period_counter = 0;
// Modulation / attach flags.
bool attach_period = false;
bool attach_volume = false;
// Output volume, [0, 64].
uint8_t volume = 0;
uint8_t volume_latch = 0;
// Indicates whether DMA is enabled for this channel.
bool dma_enabled = false;
// Records whether this audio interrupt is pending.
bool interrupt_pending = false;
bool will_request_interrupt = false;
// Replicates the Hardware Reference Manual state machine;
// comments indicate which of the documented states each
// label refers to.
enum class State {
Disabled, // 000
WaitingForDummyDMA, // 001
WaitingForDMA, // 101
PlayingHigh, // 010
PlayingLow, // 011
} state = State::Disabled;
/// Dispatches to the appropriate templatised output for the current state.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
bool output(Channel *moduland);
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State state> bool output(Channel *moduland);
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
/// and taking any steps specific to that particular transition.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State begin, State end> bool transit(Channel *moduland);
/// Begins @c state, performing all fixed logic that would otherwise have to be
/// repeated endlessly in the relevant @c output.
/// @param moduland The channel to modulate, if modulation is enabled.
template <State state> void begin_state(Channel *moduland);
/// Provides the common length-decrementing logic used when transitioning
/// between PlayingHigh and PlayingLow in either direction.
void decrement_length();
// Output state.
int8_t output_level = 0;
uint8_t output_phase = 0;
bool output_enabled = false;
void reset_output_phase() {
output_phase = 0;
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
}
} channels_[4];
// Transient output state, and its destination.
Outputs::Speaker::PushLowpass<true> speaker_;
Concurrency::AsyncTaskQueue<true> queue_;
using AudioBuffer = std::array<int16_t, 4096>;
static constexpr int BufferCount = 3;
AudioBuffer buffer_[BufferCount];
std::atomic<bool> buffer_available_[BufferCount];
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
using AudioBuffer = std::array<int16_t, 4096>;
static constexpr int BufferCount = 3;
AudioBuffer buffer_[BufferCount];
std::atomic<bool> buffer_available_[BufferCount];
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
};
}

View File

@ -31,18 +31,18 @@ struct BitplaneData: public std::array<uint16_t, 6> {
};
class Bitplanes: public DMADevice<6, 2> {
public:
using DMADevice::DMADevice;
public:
using DMADevice::DMADevice;
bool advance_dma(int cycle);
void do_end_of_line();
void set_control(uint16_t);
bool advance_dma(int cycle);
void do_end_of_line();
void set_control(uint16_t);
private:
bool is_high_res_ = false;
int plane_count_ = 0;
private:
bool is_high_res_ = false;
int plane_count_ = 0;
BitplaneData next;
BitplaneData next;
};
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
@ -55,44 +55,44 @@ template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
}
class BitplaneShifter {
public:
/// Installs a new set of output pixels.
void set(
const BitplaneData &previous,
const BitplaneData &next,
int odd_delay,
int even_delay);
public:
/// Installs a new set of output pixels.
void set(
const BitplaneData &previous,
const BitplaneData &next,
int odd_delay,
int even_delay);
/// Shifts either two pixels (in low-res mode) or four pixels (in high-res).
void shift(bool high_res) {
constexpr int shifts[] = {16, 32};
/// Shifts either two pixels (in low-res mode) or four pixels (in high-res).
void shift(bool high_res) {
constexpr int shifts[] = {16, 32};
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
data_[0] <<= shifts[high_res];
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
data_[0] <<= shifts[high_res];
}
/// @returns The next four pixels to output; in low-resolution mode only two
/// of them will be unique.
///
/// The value is arranges so that MSB = first pixel to output, LSB = last.
///
/// Each byte is swizzled to provide easier playfield separation, being in the form:
/// b6, b7 = 0;
/// b3b5: planes 1, 3 and 5;
/// b0b2: planes 0, 2 and 4.
uint32_t get(bool high_res) {
if(high_res) {
return uint32_t(data_[1] >> 32);
} else {
uint32_t result = uint16_t(data_[1] >> 48);
result = ((result & 0xff00) << 8) | (result & 0x00ff);
result |= result << 8;
return result;
}
}
/// @returns The next four pixels to output; in low-resolution mode only two
/// of them will be unique.
///
/// The value is arranges so that MSB = first pixel to output, LSB = last.
///
/// Each byte is swizzled to provide easier playfield separation, being in the form:
/// b6, b7 = 0;
/// b3b5: planes 1, 3 and 5;
/// b0b2: planes 0, 2 and 4.
uint32_t get(bool high_res) {
if(high_res) {
return uint32_t(data_[1] >> 32);
} else {
uint32_t result = uint16_t(data_[1] >> 48);
result = ((result & 0xff00) << 8) | (result & 0x00ff);
result |= result << 8;
return result;
}
}
private:
std::array<uint64_t, 2> data_{};
private:
std::array<uint64_t, 2> data_{};
};

View File

@ -24,104 +24,104 @@ namespace Amiga {
and can subsequently be retrieved. This is included for testing purposes.
*/
template <bool record_bus = false> class Blitter: public DMADevice<4, 4> {
public:
using DMADevice::DMADevice;
public:
using DMADevice::DMADevice;
template <int id, int shift> void set_pointer(uint16_t value) {
DMADevice<4, 4>::set_pointer<id, shift>(value);
}
template <int id, int shift> void set_pointer(uint16_t value) {
DMADevice<4, 4>::set_pointer<id, shift>(value);
}
// Various setters; it's assumed that address decoding is handled externally.
//
// In all cases where a channel is identified numerically, it's taken that
// 0 = A, 1 = B, 2 = C, 3 = D.
void set_control(int index, uint16_t value);
void set_first_word_mask(uint16_t value);
void set_last_word_mask(uint16_t value);
// Various setters; it's assumed that address decoding is handled externally.
//
// In all cases where a channel is identified numerically, it's taken that
// 0 = A, 1 = B, 2 = C, 3 = D.
void set_control(int index, uint16_t value);
void set_first_word_mask(uint16_t value);
void set_last_word_mask(uint16_t value);
void set_size(uint16_t value);
void set_minterms(uint16_t value);
// void set_vertical_size(uint16_t value);
// void set_horizontal_size(uint16_t value);
void set_data(int channel, uint16_t value);
void set_size(uint16_t value);
void set_minterms(uint16_t value);
// void set_vertical_size(uint16_t value);
// void set_horizontal_size(uint16_t value);
void set_data(int channel, uint16_t value);
uint16_t get_status();
uint16_t get_status();
template <bool complete_immediately> bool advance_dma();
template <bool complete_immediately> bool advance_dma();
struct Transaction {
enum class Type {
SkippedSlot,
ReadA,
ReadB,
ReadC,
AddToPipeline,
WriteFromPipeline
} type = Type::SkippedSlot;
struct Transaction {
enum class Type {
SkippedSlot,
ReadA,
ReadB,
ReadC,
AddToPipeline,
WriteFromPipeline
} type = Type::SkippedSlot;
uint32_t address = 0;
uint16_t value = 0;
uint32_t address = 0;
uint16_t value = 0;
Transaction() = default;
Transaction(Type type) : type(type) {}
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
Transaction() = default;
Transaction(Type type) : type(type) {}
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
std::string to_string() const {
std::string result;
std::string to_string() const {
std::string result;
switch(type) {
case Type::SkippedSlot: result = "SkippedSlot"; break;
case Type::ReadA: result = "ReadA"; break;
case Type::ReadB: result = "ReadB"; break;
case Type::ReadC: result = "ReadC"; break;
case Type::AddToPipeline: result = "AddToPipeline"; break;
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
}
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
return result;
switch(type) {
case Type::SkippedSlot: result = "SkippedSlot"; break;
case Type::ReadA: result = "ReadA"; break;
case Type::ReadB: result = "ReadB"; break;
case Type::ReadC: result = "ReadC"; break;
case Type::AddToPipeline: result = "AddToPipeline"; break;
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
}
};
std::vector<Transaction> get_and_reset_transactions();
private:
int width_ = 0, height_ = 0;
int shifts_[2]{};
uint16_t a_mask_[2] = {0xffff, 0xffff};
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
return result;
}
};
std::vector<Transaction> get_and_reset_transactions();
bool line_mode_ = false;
bool one_dot_ = false;
int line_direction_ = 0;
int line_sign_ = 1;
private:
int width_ = 0, height_ = 0;
int shifts_[2]{};
uint16_t a_mask_[2] = {0xffff, 0xffff};
uint32_t direction_ = 1;
bool inclusive_fill_ = false;
bool exclusive_fill_ = false;
bool fill_carry_ = false;
bool line_mode_ = false;
bool one_dot_ = false;
int line_direction_ = 0;
int line_sign_ = 1;
uint8_t minterms_ = 0;
uint32_t a32_ = 0, b32_ = 0;
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
uint32_t direction_ = 1;
bool inclusive_fill_ = false;
bool exclusive_fill_ = false;
bool fill_carry_ = false;
bool not_zero_flag_ = false;
uint8_t minterms_ = 0;
uint32_t a32_ = 0, b32_ = 0;
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
BlitterSequencer sequencer_;
uint32_t write_address_ = 0xffff'ffff;
uint16_t write_value_ = 0;
enum WritePhase {
Starting, Full
} write_phase_;
int y_, x_;
uint16_t transient_a_mask_;
bool busy_ = false;
int loop_index_ = -1;
bool not_zero_flag_ = false;
int error_ = 0;
bool draw_ = false;
bool has_c_data_ = false;
BlitterSequencer sequencer_;
uint32_t write_address_ = 0xffff'ffff;
uint16_t write_value_ = 0;
enum WritePhase {
Starting, Full
} write_phase_;
int y_, x_;
uint16_t transient_a_mask_;
bool busy_ = false;
int loop_index_ = -1;
void add_modulos();
std::vector<Transaction> transactions_;
int error_ = 0;
bool draw_ = false;
bool has_c_data_ = false;
void add_modulos();
std::vector<Transaction> transactions_;
};
}

View File

@ -19,146 +19,146 @@ namespace Amiga {
relying on tables.
*/
class BlitterSequencer {
public:
enum class Channel {
/// Tells the caller to calculate and load a new piece of output
/// into the output pipeline.
///
/// If any inputs are enabled then a one-slot output pipeline applies:
/// output will rest in the pipeline for one write phase before being written.
Write,
/// Indicates that a write should occur if anything is in the pipeline, otherwise
/// no activity should occur.
FlushPipeline,
/// The caller should read from channel C.
C,
/// The caller should read from channel B.
B,
/// The caller should read from channel A.
A,
/// Indicates an unused DMA slot.
None
};
public:
enum class Channel {
/// Tells the caller to calculate and load a new piece of output
/// into the output pipeline.
///
/// If any inputs are enabled then a one-slot output pipeline applies:
/// output will rest in the pipeline for one write phase before being written.
Write,
/// Indicates that a write should occur if anything is in the pipeline, otherwise
/// no activity should occur.
FlushPipeline,
/// The caller should read from channel C.
C,
/// The caller should read from channel B.
B,
/// The caller should read from channel A.
A,
/// Indicates an unused DMA slot.
None
};
/// Sets the current control value, which indicates which
/// channels are enabled.
void set_control(int control) {
control_ = control & 0xf;
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
// of control values during a blit.
/// Sets the current control value, which indicates which
/// channels are enabled.
void set_control(int control) {
control_ = control & 0xf;
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
// of control values during a blit.
}
/// Indicates that blitting should conclude after this step, i.e.
/// whatever is being fetched now is part of the final set of input data;
/// this is safe to call following a fetch request on any channel.
void complete() {
next_phase_ =
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
Phase::PauseAndComplete : Phase::Complete;
}
/// Begins a blit operation.
void begin() {
phase_ = next_phase_ = Phase::Ongoing;
index_ = loop_ = 0;
}
/// Provides the next channel to fetch from, or that a write is required,
/// along with a count of complete channel iterations so far completed.
std::pair<Channel, int> next() {
switch(phase_) {
default: break;
case Phase::Complete:
return std::make_pair(Channel::FlushPipeline, loop_);
case Phase::PauseAndComplete:
phase_ = Phase::Complete;
return std::make_pair(Channel::None, loop_);
}
/// Indicates that blitting should conclude after this step, i.e.
/// whatever is being fetched now is part of the final set of input data;
/// this is safe to call following a fetch request on any channel.
void complete() {
next_phase_ =
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
Phase::PauseAndComplete : Phase::Complete;
Channel next = Channel::None;
switch(control_) {
default: break;
case 0: next = next_channel(pattern0); break;
case 1: next = next_channel(pattern1); break;
case 2: next = next_channel(pattern2); break;
case 3: next = next_channel(pattern3); break;
case 4: next = next_channel(pattern4); break;
case 5: next = next_channel(pattern5); break;
case 6: next = next_channel(pattern6); break;
case 7: next = next_channel(pattern7); break;
case 8: next = next_channel(pattern8); break;
case 9: next = next_channel(pattern9); break;
case 10: next = next_channel(patternA); break;
case 11: next = next_channel(patternB); break;
case 12: next = next_channel(patternC); break;
case 13: next = next_channel(patternD); break;
case 14: next = next_channel(patternE); break;
case 15: next = next_channel(patternF); break;
}
/// Begins a blit operation.
void begin() {
phase_ = next_phase_ = Phase::Ongoing;
index_ = loop_ = 0;
return std::make_pair(next, loop_);
}
template <int channel> bool channel_enabled() {
return control_ & (8 >> channel);
}
private:
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
loop_ += index_ / list.size();
index_ %= list.size();
const Channel result = list[index_];
++index_;
if(index_ == list.size()) {
phase_ = next_phase_;
}
return result;
}
/// Provides the next channel to fetch from, or that a write is required,
/// along with a count of complete channel iterations so far completed.
std::pair<Channel, int> next() {
switch(phase_) {
default: break;
// Current control flags, i.e. which channels are enabled.
int control_ = 0;
case Phase::Complete:
return std::make_pair(Channel::FlushPipeline, loop_);
// Index into the pattern table for this blit.
size_t index_ = 0;
case Phase::PauseAndComplete:
phase_ = Phase::Complete;
return std::make_pair(Channel::None, loop_);
}
// Number of times the entire pattern table has been completed.
int loop_ = 0;
Channel next = Channel::None;
enum class Phase {
/// Return the next thing in the pattern table and advance.
/// If looping from the end of the pattern table to the start,
/// set phase_ to next_phase_.
Ongoing,
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
PauseAndComplete,
/// Return Channel::Write indefinitely.
Complete
};
switch(control_) {
default: break;
case 0: next = next_channel(pattern0); break;
case 1: next = next_channel(pattern1); break;
case 2: next = next_channel(pattern2); break;
case 3: next = next_channel(pattern3); break;
case 4: next = next_channel(pattern4); break;
case 5: next = next_channel(pattern5); break;
case 6: next = next_channel(pattern6); break;
case 7: next = next_channel(pattern7); break;
case 8: next = next_channel(pattern8); break;
case 9: next = next_channel(pattern9); break;
case 10: next = next_channel(patternA); break;
case 11: next = next_channel(patternB); break;
case 12: next = next_channel(patternC); break;
case 13: next = next_channel(patternD); break;
case 14: next = next_channel(patternE); break;
case 15: next = next_channel(patternF); break;
}
return std::make_pair(next, loop_);
}
template <int channel> bool channel_enabled() {
return control_ & (8 >> channel);
}
private:
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
loop_ += index_ / list.size();
index_ %= list.size();
const Channel result = list[index_];
++index_;
if(index_ == list.size()) {
phase_ = next_phase_;
}
return result;
}
// Current control flags, i.e. which channels are enabled.
int control_ = 0;
// Index into the pattern table for this blit.
size_t index_ = 0;
// Number of times the entire pattern table has been completed.
int loop_ = 0;
enum class Phase {
/// Return the next thing in the pattern table and advance.
/// If looping from the end of the pattern table to the start,
/// set phase_ to next_phase_.
Ongoing,
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
PauseAndComplete,
/// Return Channel::Write indefinitely.
Complete
};
// Current sequencer pahse.
Phase phase_ = Phase::Complete;
// Phase to assume at the end of this iteration of the sequence table.
Phase next_phase_ = Phase::Complete;
// Current sequencer pahse.
Phase phase_ = Phase::Complete;
// Phase to assume at the end of this iteration of the sequence table.
Phase next_phase_ = Phase::Complete;
};
}

View File

@ -37,341 +37,340 @@
namespace Amiga {
class Chipset: private ClockingHint::Observer {
public:
Chipset(MemoryMap &memory_map, int input_clock_rate);
struct Changes {
int interrupt_level = 0;
HalfCycles duration;
Changes &operator += (const Changes &rhs) {
duration += rhs.duration;
return *this;
}
};
/// Advances the stated amount of time.
Changes run_for(HalfCycles);
/// Advances to the end of the next available CPU slot.
Changes run_until_after_cpu_slot();
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
template <typename Microcycle>
void perform(const Microcycle &cycle) {
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(read(register_address));
} else {
write(register_address, cycle.value16());
}
}
/// Sets the current state of the CIA interrupt lines.
void set_cia_interrupts(bool cia_a, bool cia_b);
/// Provides the chipset's current interrupt level.
int get_interrupt_level() {
return interrupt_level_;
}
/// Inserts the disks provided.
/// @returns @c true if anything was inserted; @c false otherwise.
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
// The standard CRT set.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_display_type(Outputs::Display::DisplayType);
Outputs::Display::DisplayType get_display_type() const;
// Activity observation.
void set_activity_observer(Activity::Observer *observer) {
cia_a_handler_.set_activity_observer(observer);
disk_controller_.set_activity_observer(observer);
}
// Keyboard and mouse exposure.
Keyboard &get_keyboard() {
return keyboard_;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
// Synchronisation.
void flush();
// Input for receiving collected bitplanes.
void post_bitplanes(const BitplaneData &data);
// Obtains the source of audio output.
Outputs::Speaker::Speaker *get_speaker() {
return audio_.get_speaker();
}
private:
friend class DMADeviceBase;
// MARK: - Register read/write functions.
uint16_t read(uint32_t address, bool allow_conversion = true);
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
friend class Copper;
// MARK: - E Clock and keyboard dividers.
HalfCycles cia_divider_;
HalfCycles keyboard_divider_;
// MARK: - Interrupts.
uint16_t interrupt_enable_ = 0;
uint16_t interrupt_requests_ = 0;
int interrupt_level_ = 0;
void update_interrupts();
void posit_interrupt(InterruptFlag::FlagT);
// MARK: - Scheduler.
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
template <bool stop_on_cpu> int advance_slots(int, int);
template <int cycle, bool stop_if_cpu> bool perform_cycle();
template <int cycle> void output();
void output_pixels(int cycles_until_sync);
void apply_ham(uint8_t);
// MARK: - DMA Control, Scheduler and Blitter.
uint16_t dma_control_ = 0;
Blitter<false> blitter_;
// MARK: - Sprites and collision flags.
std::array<Sprite, 8> sprites_;
std::array<TwoSpriteShifter, 4> sprite_shifters_;
uint16_t collisions_ = 0, collisions_flags_= 0;
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
// MARK: - Raster position and state.
// Definitions related to PAL/NTSC.
// (Default values are PAL).
int line_length_ = 227;
int short_field_height_ = 312;
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
// Current raster position.
int line_cycle_ = 0, y_ = 0;
// Parameters affecting bitplane collection and output.
uint16_t display_window_start_[2] = {0, 0};
uint16_t display_window_stop_[2] = {0, 0};
uint16_t fetch_window_[2] = {0, 0};
// Ephemeral bitplane collection state.
bool fetch_vertical_ = false;
bool display_horizontal_ = false;
bool did_fetch_ = false;
int horizontal_offset_ = 0;
enum HorizontalFetch {
Started, WillRequestStop, StopRequested, Stopped
} horizontal_fetch_ = HorizontalFetch::Stopped;
// Output state.
uint16_t border_colour_ = 0;
bool is_border_ = true;
int zone_duration_ = 0;
uint16_t *pixels_ = nullptr;
uint16_t last_colour_ = 0; // Retained for HAM mode.
void flush_output();
Bitplanes bitplanes_;
BitplaneData next_bitplanes_, previous_bitplanes_;
bool has_next_bitplanes_ = false;
int odd_priority_ = 0, even_priority_ = 0;
bool even_over_odd_ = false;
bool hold_and_modify_ = false;
bool dual_playfields_ = false;
bool interlace_ = false;
bool is_long_field_ = false;
BitplaneShifter bitplane_pixels_;
int odd_delay_ = 0, even_delay_ = 0;
bool is_high_res_ = false;
// MARK: - Copper.
Copper copper_;
// MARK: - Audio.
Audio audio_;
// MARK: - Serial port.
class SerialPort {
public:
Chipset(MemoryMap &memory_map, int input_clock_rate);
struct Changes {
int interrupt_level = 0;
HalfCycles duration;
Changes &operator += (const Changes &rhs) {
duration += rhs.duration;
return *this;
}
};
/// Advances the stated amount of time.
Changes run_for(HalfCycles);
/// Advances to the end of the next available CPU slot.
Changes run_until_after_cpu_slot();
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
template <typename Microcycle>
void perform(const Microcycle &cycle) {
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(read(register_address));
} else {
write(register_address, cycle.value16());
}
}
/// Sets the current state of the CIA interrupt lines.
void set_cia_interrupts(bool cia_a, bool cia_b);
/// Provides the chipset's current interrupt level.
int get_interrupt_level() {
return interrupt_level_;
}
/// Inserts the disks provided.
/// @returns @c true if anything was inserted; @c false otherwise.
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
// The standard CRT set.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_display_type(Outputs::Display::DisplayType);
Outputs::Display::DisplayType get_display_type() const;
// Activity observation.
void set_activity_observer(Activity::Observer *observer) {
cia_a_handler_.set_activity_observer(observer);
disk_controller_.set_activity_observer(observer);
}
// Keyboard and mouse exposure.
Keyboard &get_keyboard() {
return keyboard_;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
// Synchronisation.
void flush();
// Input for receiving collected bitplanes.
void post_bitplanes(const BitplaneData &data);
// Obtains the source of audio output.
Outputs::Speaker::Speaker *get_speaker() {
return audio_.get_speaker();
}
void set_control(uint16_t);
void set_data(uint16_t);
uint16_t get_status();
private:
friend class DMADeviceBase;
// uint16_t value = 0, reload = 0;
// uint16_t shift = 0, receive_shift = 0;
// uint16_t status;
} serial_;
// MARK: - Register read/write functions.
uint16_t read(uint32_t address, bool allow_conversion = true);
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
friend class Copper;
// MARK: - Pixel output.
// MARK: - E Clock and keyboard dividers.
Outputs::CRT::CRT crt_;
uint16_t palette_[32]{};
uint16_t swizzled_palette_[64]{};
HalfCycles cia_divider_;
HalfCycles keyboard_divider_;
// MARK: - Mouse.
private:
Mouse mouse_;
// MARK: - Interrupts.
public:
Inputs::Mouse &get_mouse() {
return mouse_;
}
uint16_t interrupt_enable_ = 0;
uint16_t interrupt_requests_ = 0;
int interrupt_level_ = 0;
// MARK: - Joystick.
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Joystick &joystick(size_t index) const {
return *static_cast<Joystick *>(joysticks_[index].get());
}
void update_interrupts();
void posit_interrupt(InterruptFlag::FlagT);
// MARK: - Scheduler.
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
template <bool stop_on_cpu> int advance_slots(int, int);
template <int cycle, bool stop_if_cpu> bool perform_cycle();
template <int cycle> void output();
void output_pixels(int cycles_until_sync);
void apply_ham(uint8_t);
// MARK: - DMA Control, Scheduler and Blitter.
uint16_t dma_control_ = 0;
Blitter<false> blitter_;
// MARK: - Sprites and collision flags.
std::array<Sprite, 8> sprites_;
std::array<TwoSpriteShifter, 4> sprite_shifters_;
uint16_t collisions_ = 0, collisions_flags_= 0;
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
// MARK: - Raster position and state.
// Definitions related to PAL/NTSC.
// (Default values are PAL).
int line_length_ = 227;
int short_field_height_ = 312;
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
// Current raster position.
int line_cycle_ = 0, y_ = 0;
// Parameters affecting bitplane collection and output.
uint16_t display_window_start_[2] = {0, 0};
uint16_t display_window_stop_[2] = {0, 0};
uint16_t fetch_window_[2] = {0, 0};
// Ephemeral bitplane collection state.
bool fetch_vertical_ = false;
bool display_horizontal_ = false;
bool did_fetch_ = false;
int horizontal_offset_ = 0;
enum HorizontalFetch {
Started, WillRequestStop, StopRequested, Stopped
} horizontal_fetch_ = HorizontalFetch::Stopped;
// Output state.
uint16_t border_colour_ = 0;
bool is_border_ = true;
int zone_duration_ = 0;
uint16_t *pixels_ = nullptr;
uint16_t last_colour_ = 0; // Retained for HAM mode.
void flush_output();
Bitplanes bitplanes_;
BitplaneData next_bitplanes_, previous_bitplanes_;
bool has_next_bitplanes_ = false;
int odd_priority_ = 0, even_priority_ = 0;
bool even_over_odd_ = false;
bool hold_and_modify_ = false;
bool dual_playfields_ = false;
bool interlace_ = false;
bool is_long_field_ = false;
BitplaneShifter bitplane_pixels_;
int odd_delay_ = 0, even_delay_ = 0;
bool is_high_res_ = false;
// MARK: - Copper.
Copper copper_;
// MARK: - Audio.
Audio audio_;
// MARK: - Serial port.
class SerialPort {
public:
void set_control(uint16_t);
void set_data(uint16_t);
uint16_t get_status();
private:
// uint16_t value = 0, reload = 0;
// uint16_t shift = 0, receive_shift = 0;
// uint16_t status;
} serial_;
// MARK: - Pixel output.
Outputs::CRT::CRT crt_;
uint16_t palette_[32]{};
uint16_t swizzled_palette_[64]{};
// MARK: - Mouse.
private:
Mouse mouse_;
// MARK: - CIAs.
private:
class DiskController;
class CIAAHandler: public MOS::MOS6526::PortHandler {
public:
Inputs::Mouse &get_mouse() {
return mouse_;
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port port);
void set_activity_observer(Activity::Observer *observer);
// TEMPORARY.
// TODO: generalise mice and joysticks.
// This is a hack. A TEMPORARY HACK.
void set_joystick(Joystick *joystick) {
joystick_ = joystick;
}
// MARK: - Joystick.
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Joystick &joystick(size_t index) const {
return *static_cast<Joystick *>(joysticks_[index].get());
}
// MARK: - CIAs.
private:
class DiskController;
class CIAAHandler: public MOS::MOS6526::PortHandler {
public:
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port port);
void set_activity_observer(Activity::Observer *observer);
// TEMPORARY.
// TODO: generalise mice and joysticks.
// This is a hack. A TEMPORARY HACK.
void set_joystick(Joystick *joystick) {
joystick_ = joystick;
}
private:
MemoryMap &map_;
DiskController &controller_;
Mouse &mouse_;
Joystick *joystick_ = nullptr;
Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power";
} cia_a_handler_;
class CIABHandler: public MOS::MOS6526::PortHandler {
public:
CIABHandler(DiskController &controller);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port);
private:
DiskController &controller_;
} cia_b_handler_;
MemoryMap &map_;
DiskController &controller_;
Mouse &mouse_;
Joystick *joystick_ = nullptr;
Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power";
} cia_a_handler_;
class CIABHandler: public MOS::MOS6526::PortHandler {
public:
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
// CIAs are provided for direct access; it's up to the caller properly
// to distinguish relevant accesses.
CIAA cia_a;
CIAB cia_b;
CIABHandler(DiskController &controller);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port);
private:
// MARK: - Disk drives.
DiskController &controller_;
} cia_b_handler_;
class DiskDMA: public DMADevice<1> {
public:
using DMADevice::DMADevice;
public:
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
void set_length(uint16_t);
void set_control(uint16_t);
bool advance_dma();
// CIAs are provided for direct access; it's up to the caller properly
// to distinguish relevant accesses.
CIAA cia_a;
CIAB cia_b;
void enqueue(uint16_t value, bool matches_sync);
private:
// MARK: - Disk drives.
private:
uint16_t length_;
bool dma_enable_ = false;
bool write_ = false;
uint16_t last_set_length_ = 0;
bool sync_with_word_ = false;
class DiskDMA: public DMADevice<1> {
public:
using DMADevice::DMADevice;
std::array<uint16_t, 4> buffer_;
size_t buffer_read_ = 0, buffer_write_ = 0;
void set_length(uint16_t);
void set_control(uint16_t);
bool advance_dma();
enum class State {
Inactive,
WaitingForSync,
Reading,
} state_ = State::Inactive;
} disk_;
void enqueue(uint16_t value, bool matches_sync);
class DiskController: public Storage::Disk::Controller {
public:
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
private:
uint16_t length_;
bool dma_enable_ = false;
bool write_ = false;
uint16_t last_set_length_ = 0;
bool sync_with_word_ = false;
void set_mtr_sel_side_dir_step(uint8_t);
uint8_t get_rdy_trk0_wpro_chng();
std::array<uint16_t, 4> buffer_;
size_t buffer_read_ = 0, buffer_write_ = 0;
void run_for(Cycles duration) {
Storage::Disk::Controller::run_for(duration);
}
enum class State {
Inactive,
WaitingForSync,
Reading,
} state_ = State::Inactive;
} disk_;
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
void set_activity_observer(Activity::Observer *);
class DiskController: public Storage::Disk::Controller {
public:
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
void set_sync_word(uint16_t);
void set_control(uint16_t);
void set_mtr_sel_side_dir_step(uint8_t);
uint8_t get_rdy_trk0_wpro_chng();
private:
void process_input_bit(int value) final;
void process_index_hole() final;
void run_for(Cycles duration) {
Storage::Disk::Controller::run_for(duration);
}
// Implement the Amiga's drive ID shift registers
// directly in the controller for now.
uint32_t drive_ids_[4]{};
uint32_t previous_select_ = 0;
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
void set_activity_observer(Activity::Observer *);
uint16_t data_ = 0;
int bit_count_ = 0;
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
bool sync_with_word_ = false;
void set_sync_word(uint16_t);
void set_control(uint16_t);
Chipset &chipset_;
DiskDMA &disk_dma_;
CIAB &cia_;
private:
void process_input_bit(int value) final;
void process_index_hole() final;
} disk_controller_;
friend DiskController;
// Implement the Amiga's drive ID shift registers
// directly in the controller for now.
uint32_t drive_ids_[4]{};
uint32_t previous_select_ = 0;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
bool disk_controller_is_sleeping_ = false;
uint16_t paula_disk_control_ = 0;
uint16_t data_ = 0;
int bit_count_ = 0;
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
bool sync_with_word_ = false;
// MARK: - Keyboard.
Chipset &chipset_;
DiskDMA &disk_dma_;
CIAB &cia_;
} disk_controller_;
friend DiskController;
Keyboard keyboard_;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
bool disk_controller_is_sleeping_ = false;
uint16_t paula_disk_control_ = 0;
// MARK: - Keyboard.
Keyboard keyboard_;
};
}

View File

@ -13,39 +13,39 @@
namespace Amiga {
class Copper: public DMADevice<2> {
public:
using DMADevice<2>::DMADevice;
public:
using DMADevice<2>::DMADevice;
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
///
/// @returns @c true if the slot was used; @c false otherwise.
bool advance_dma(uint16_t position, uint16_t blitter_status);
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
///
/// @returns @c true if the slot was used; @c false otherwise.
bool advance_dma(uint16_t position, uint16_t blitter_status);
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
template <int id> void reload() {
address_ = pointer_[id];
state_ = State::FetchFirstWord;
}
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
template <int id> void reload() {
address_ = pointer_[id];
state_ = State::FetchFirstWord;
}
/// Sets the Copper control word.
void set_control(uint16_t c) {
control_ = c;
}
/// Sets the Copper control word.
void set_control(uint16_t c) {
control_ = c;
}
/// Forces the Copper into the stopped state.
void stop() {
state_ = State::Stopped;
}
/// Forces the Copper into the stopped state.
void stop() {
state_ = State::Stopped;
}
private:
uint32_t address_ = 0;
uint16_t control_ = 0;
private:
uint32_t address_ = 0;
uint16_t control_ = 0;
enum class State {
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
} state_ = State::Stopped;
bool skip_next_ = false;
uint16_t instruction_[2]{};
enum class State {
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
} state_ = State::Stopped;
bool skip_next_ = false;
uint16_t instruction_[2]{};
};
}

View File

@ -32,40 +32,40 @@ class DMADeviceBase {
};
template <size_t num_addresses, size_t num_modulos = 0> class DMADevice: public DMADeviceBase {
public:
using DMADeviceBase::DMADeviceBase;
public:
using DMADeviceBase::DMADeviceBase;
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
template <int id, int shift> void set_pointer(uint16_t value) {
static_assert(id < num_addresses);
static_assert(shift == 0 || shift == 16);
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
template <int id, int shift> void set_pointer(uint16_t value) {
static_assert(id < num_addresses);
static_assert(shift == 0 || shift == 16);
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
pointer_[id] = byte_pointer_[id] >> 1;
}
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
pointer_[id] = byte_pointer_[id] >> 1;
}
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
template <int id> void set_modulo(uint16_t value) {
static_assert(id < num_modulos);
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
template <int id> void set_modulo(uint16_t value) {
static_assert(id < num_modulos);
// Convert by sign extension.
modulos_[id] = uint32_t(int16_t(value) >> 1);
}
// Convert by sign extension.
modulos_[id] = uint32_t(int16_t(value) >> 1);
}
template <int id, int shift> uint16_t get_pointer() {
// Restore the original least-significant bit.
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
return uint16_t(source >> shift);
}
template <int id, int shift> uint16_t get_pointer() {
// Restore the original least-significant bit.
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
return uint16_t(source >> shift);
}
protected:
// These are shifted right one to provide word-indexing pointers;
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
std::array<uint32_t, num_addresses> pointer_{};
std::array<uint32_t, num_modulos> modulos_{};
protected:
// These are shifted right one to provide word-indexing pointers;
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
std::array<uint32_t, num_addresses> pointer_{};
std::array<uint32_t, num_modulos> modulos_{};
private:
std::array<uint32_t, num_addresses> byte_pointer_{};
private:
std::array<uint32_t, num_addresses> byte_pointer_{};
};
}

View File

@ -77,42 +77,42 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe
};
class Keyboard {
public:
Keyboard(Serial::Line<true> &output);
public:
Keyboard(Serial::Line<true> &output);
// enum Lines: uint8_t {
// Data = (1 << 0),
// Clock = (1 << 1),
// };
// enum Lines: uint8_t {
// Data = (1 << 0),
// Clock = (1 << 1),
// };
//
// uint8_t update(uint8_t);
// uint8_t update(uint8_t);
void set_key_state(uint16_t, bool);
void clear_all_keys();
void set_key_state(uint16_t, bool);
void clear_all_keys();
void run_for(HalfCycles duration) {
output_.advance_writer(duration);
}
void run_for(HalfCycles duration) {
output_.advance_writer(duration);
}
private:
// enum class ShiftState {
// Shifting,
// AwaitingHandshake,
// Idle,
// } shift_state_ = ShiftState::Idle;
private:
// enum class ShiftState {
// Shifting,
// AwaitingHandshake,
// Idle,
// } shift_state_ = ShiftState::Idle;
//
// enum class State {
// Startup,
// } state_ = State::Startup;
//
// int bit_phase_ = 0;
// uint32_t shift_sequence_ = 0;
// int bits_remaining_ = 0;
//
// uint8_t lines_ = 0;
// enum class State {
// Startup,
// } state_ = State::Startup;
// int bit_phase_ = 0;
// uint32_t shift_sequence_ = 0;
// int bits_remaining_ = 0;
// uint8_t lines_ = 0;
Serial::Line<true> &output_;
std::array<bool, 128> pressed_{};
Serial::Line<true> &output_;
std::array<bool, 128> pressed_{};
};
}

View File

@ -17,178 +17,178 @@
namespace Amiga {
class MemoryMap {
private:
static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
private:
static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
public:
std::array<uint8_t, 512*1024> kickstart{0xff};
std::vector<uint8_t> chip_ram{};
public:
std::array<uint8_t, 512*1024> kickstart{0xff};
std::vector<uint8_t> chip_ram{};
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
// Address spaces that matter:
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
// Address spaces that matter:
//
// 00'0000 08'0000: chip RAM. [or overlayed KickStart]
// 10'0000: extended chip ram for ECS.
// 20'0000: slow RAM and further chip RAM.
// a0'0000: auto-config space (/fast RAM).
// ...
// bf'd000 c0'0000: 8250s.
// c0'0000 d8'0000: pseudo-fast RAM.
// ...
// dc'0000 dd'0000: optional real-time clock.
// df'f000 - e0'0000: custom chip registers.
// ...
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
// fc'0000 : 256kb Kickstart otherwise.
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
switch(chip_ram_size) {
default:
case ChipRAM::FiveHundredAndTwelveKilobytes:
chip_ram.resize(512 * 1024);
break;
case ChipRAM::OneMegabyte:
chip_ram.resize(1 * 1024 * 1024);
break;
case ChipRAM::TwoMegabytes:
chip_ram.resize(2 * 1024 * 1024);
break;
}
switch(fast_ram_size) {
default:
fast_autoconf_visible_ = false;
break;
case FastRAM::OneMegabyte:
fast_ram_.resize(1 * 1024 * 1024);
fast_ram_size_ = 5;
break;
case FastRAM::TwoMegabytes:
fast_ram_.resize(2 * 1024 * 1024);
fast_ram_size_ = 6;
break;
case FastRAM::FourMegabytes:
fast_ram_.resize(4 * 1024 * 1024);
fast_ram_size_ = 7;
break;
case FastRAM::EightMegabytes:
fast_ram_.resize(8 * 1024 * 1024);
fast_ram_size_ = 0;
break;
}
reset();
}
void reset() {
set_overlay(true);
}
void set_overlay(bool enabled) {
if(overlay_ == enabled) {
return;
}
overlay_ = enabled;
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
if(enabled) {
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
}
}
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
/// and in the Zorro register range.
template <typename Microcycle>
bool perform(const Microcycle &cycle) {
if(!fast_autoconf_visible_) return false;
const uint32_t register_address = *cycle.address & 0xfe;
if(cycle.operation & CPU::MC68000::Operation::Read) {
// Re: Autoconf:
//
// 00'0000 08'0000: chip RAM. [or overlayed KickStart]
// 10'0000: extended chip ram for ECS.
// 20'0000: slow RAM and further chip RAM.
// a0'0000: auto-config space (/fast RAM).
// ...
// bf'd000 c0'0000: 8250s.
// c0'0000 d8'0000: pseudo-fast RAM.
// ...
// dc'0000 dd'0000: optional real-time clock.
// df'f000 - e0'0000: custom chip registers.
// ...
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
// fc'0000 : 256kb Kickstart otherwise.
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
// "All read registers physically return only the top 4 bits of data, on D31-D28";
// (this is from Zorro III documentation; I'm assuming it to be D15D11 for the
// 68000's 16-bit bus);
//
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
// 8 bits actually being nybbles from two paired addresses."
switch(chip_ram_size) {
default:
case ChipRAM::FiveHundredAndTwelveKilobytes:
chip_ram.resize(512 * 1024);
uint8_t value = 0xf;
switch(register_address) {
default: break;
case 0x00: // er_Type (high)
value =
0xc | // Zoro II-style PIC.
0x2; // Memory will be linked into the free pool
break;
case ChipRAM::OneMegabyte:
chip_ram.resize(1 * 1024 * 1024);
case 0x02: // er_Type (low)
value = fast_ram_size_;
break;
case ChipRAM::TwoMegabytes:
chip_ram.resize(2 * 1024 * 1024);
// er_Manufacturer
//
// On the manufacturer number: this is supposed to be assigned
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
//
// (0xffff seems to be invalid, so _something_ needs to be supplied)
case 0x10: case 0x12:
value = 0xa; // Manufacturer's number, high byte.
break;
case 0x14: case 0x16:
value = 0xb; // Manufacturer's number, low byte.
break;
}
switch(fast_ram_size) {
default:
// Shove the value into the top of the data bus.
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
} else {
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
switch(register_address) {
default: break;
case 0x48: { // ec_BaseAddress (A23A16)
const auto address = uint32_t(cycle.value8_high()) << 16;
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
fast_autoconf_visible_ = false;
break;
case FastRAM::OneMegabyte:
fast_ram_.resize(1 * 1024 * 1024);
fast_ram_size_ = 5;
break;
case FastRAM::TwoMegabytes:
fast_ram_.resize(2 * 1024 * 1024);
fast_ram_size_ = 6;
break;
case FastRAM::FourMegabytes:
fast_ram_.resize(4 * 1024 * 1024);
fast_ram_size_ = 7;
break;
case FastRAM::EightMegabytes:
fast_ram_.resize(8 * 1024 * 1024);
fast_ram_size_ = 0;
break;
}
reset();
}
void reset() {
set_overlay(true);
}
void set_overlay(bool enabled) {
if(overlay_ == enabled) {
return;
}
overlay_ = enabled;
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
if(enabled) {
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
} break;
}
}
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
/// and in the Zorro register range.
template <typename Microcycle>
bool perform(const Microcycle &cycle) {
if(!fast_autoconf_visible_) return false;
return true;
}
const uint32_t register_address = *cycle.address & 0xfe;
private:
std::vector<uint8_t> fast_ram_{};
uint8_t fast_ram_size_ = 0;
if(cycle.operation & CPU::MC68000::Operation::Read) {
// Re: Autoconf:
//
// "All read registers physically return only the top 4 bits of data, on D31-D28";
// (this is from Zorro III documentation; I'm assuming it to be D15D11 for the
// 68000's 16-bit bus);
//
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
// 8 bits actually being nybbles from two paired addresses."
bool fast_autoconf_visible_ = true;
bool overlay_ = false;
uint8_t value = 0xf;
switch(register_address) {
default: break;
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
assert(!(start & precision_loss_mask));
assert(!((end - (1 << 18)) & precision_loss_mask));
assert(end > start);
case 0x00: // er_Type (high)
value =
0xc | // Zoro II-style PIC.
0x2; // Memory will be linked into the free pool
break;
case 0x02: // er_Type (low)
value = fast_ram_size_;
break;
// er_Manufacturer
//
// On the manufacturer number: this is supposed to be assigned
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
//
// (0xffff seems to be invalid, so _something_ needs to be supplied)
case 0x10: case 0x12:
value = 0xa; // Manufacturer's number, high byte.
break;
case 0x14: case 0x16:
value = 0xb; // Manufacturer's number, low byte.
break;
}
// Shove the value into the top of the data bus.
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
} else {
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
switch(register_address) {
default: break;
case 0x48: { // ec_BaseAddress (A23A16)
const auto address = uint32_t(cycle.value8_high()) << 16;
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
fast_autoconf_visible_ = false;
} break;
}
}
return true;
}
private:
std::vector<uint8_t> fast_ram_{};
uint8_t fast_ram_size_ = 0;
bool fast_autoconf_visible_ = true;
bool overlay_ = false;
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
assert(!(start & precision_loss_mask));
assert(!((end - (1 << 18)) & precision_loss_mask));
assert(end > start);
if(base) base -= start;
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
regions[c].contents = base;
regions[c].read_write_mask = read_write_mask;
}
if(base) base -= start;
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
regions[c].contents = base;
regions[c].read_write_mask = read_write_mask;
}
}
};
}

View File

@ -22,33 +22,33 @@ struct MouseJoystickInput {
};
class Mouse: public Inputs::Mouse, public MouseJoystickInput {
public:
uint16_t get_position() final;
uint8_t get_cia_button() const final;
public:
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
int get_number_of_buttons() const final;
void set_button_pressed(int, bool) final;
void reset_all_buttons() final;
void move(int, int) final;
private:
int get_number_of_buttons() const final;
void set_button_pressed(int, bool) final;
void reset_all_buttons() final;
void move(int, int) final;
uint8_t declared_position_[2]{};
uint8_t cia_state_ = 0xff;
std::array<std::atomic<int>, 2> position_{};
uint8_t declared_position_[2]{};
uint8_t cia_state_ = 0xff;
std::array<std::atomic<int>, 2> position_{};
};
class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
public:
Joystick();
public:
Joystick();
uint16_t get_position() final;
uint8_t get_cia_button() const final;
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
void did_set_input(const Input &input, bool is_active) final;
private:
void did_set_input(const Input &input, bool is_active) final;
bool inputs_[Joystick::Input::Type::Max]{};
uint16_t position_ = 0;
bool inputs_[Joystick::Input::Type::Max]{};
uint16_t position_ = 0;
};
}

View File

@ -15,52 +15,52 @@
namespace Amiga {
class Sprite: public DMADevice<1> {
public:
using DMADevice::DMADevice;
public:
using DMADevice::DMADevice;
void set_start_position(uint16_t value);
void set_stop_and_control(uint16_t value);
void set_image_data(int slot, uint16_t value);
void set_start_position(uint16_t value);
void set_stop_and_control(uint16_t value);
void set_image_data(int slot, uint16_t value);
bool advance_dma(int offset, int y, bool is_first_line);
bool advance_dma(int offset, int y, bool is_first_line);
uint16_t data[2]{};
bool attached = false;
bool visible = false;
uint16_t h_start = 0;
uint16_t data[2]{};
bool attached = false;
bool visible = false;
uint16_t h_start = 0;
private:
uint16_t v_start_ = 0, v_stop_ = 0;
private:
uint16_t v_start_ = 0, v_stop_ = 0;
};
class TwoSpriteShifter {
public:
/// Installs new pixel data for @c sprite (either 0 or 1),
/// with @c delay being either 0 or 1 to indicate whether
/// output should begin now or in one pixel's time.
template <int sprite> void load(
uint16_t lsb,
uint16_t msb,
int delay);
public:
/// Installs new pixel data for @c sprite (either 0 or 1),
/// with @c delay being either 0 or 1 to indicate whether
/// output should begin now or in one pixel's time.
template <int sprite> void load(
uint16_t lsb,
uint16_t msb,
int delay);
/// Shifts two pixels.
void shift() {
data_ <<= 8;
data_ |= overflow_;
overflow_ = 0;
}
/// Shifts two pixels.
void shift() {
data_ <<= 8;
data_ |= overflow_;
overflow_ = 0;
}
/// @returns The next two pixels to output, formulated as
/// abcd efgh where ab and ef are two pixels of the first sprite
/// and cd and gh are two pixels of the second. In each case the
/// more significant two are output first.
uint8_t get() {
return uint8_t(data_ >> 56);
}
/// @returns The next two pixels to output, formulated as
/// abcd efgh where ab and ef are two pixels of the first sprite
/// and cd and gh are two pixels of the second. In each case the
/// more significant two are output first.
uint8_t get() {
return uint8_t(data_ >> 56);
}
private:
uint64_t data_;
uint8_t overflow_;
private:
uint64_t data_;
uint8_t overflow_;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -17,32 +17,32 @@ namespace Amstrad {
exposes motor control, applying the same value to all drives.
*/
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
private:
Intel::i8272::BusHandler bus_handler_;
public:
FDC(Cycles clock_rate = Cycles(8000000)) :
i8272(bus_handler_, clock_rate)
{
emplace_drive(clock_rate.as<int>(), 300, 1);
set_drive(1);
}
public:
FDC(Cycles clock_rate = Cycles(8000000)) :
i8272(bus_handler_, clock_rate)
{
emplace_drive(clock_rate.as<int>(), 300, 1);
set_drive(1);
}
void set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void select_drive(int) {
// TODO: support more than one drive. (and in set_disk)
}
void select_drive(int) {
// TODO: support more than one drive. (and in set_disk)
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
get_drive().set_disk(disk);
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
get_drive().set_disk(disk);
}
void set_activity_observer(Activity::Observer *observer) {
get_drive().set_activity_observer(observer, "Drive 1", true);
}
void set_activity_observer(Activity::Observer *observer) {
get_drive().set_activity_observer(observer, "Drive 1", true);
}
};
}

View File

@ -93,75 +93,75 @@ inline Command decode_command(uint8_t code) {
update @c set_device_output.
*/
class Bus {
public:
Bus(HalfCycles clock_speed);
public:
Bus(HalfCycles clock_speed);
/*!
Advances time; ADB is a clocked serial signal.
*/
void run_for(HalfCycles);
/*!
Advances time; ADB is a clocked serial signal.
*/
void run_for(HalfCycles);
/*!
Adds a device to the bus, returning the index it should use
to refer to itself in subsequent calls to set_device_output.
*/
size_t add_device();
/*!
Adds a device to the bus, returning the index it should use
to refer to itself in subsequent calls to set_device_output.
*/
size_t add_device();
/*!
Sets the current data line output for @c device.
*/
void set_device_output(size_t device_id, bool output);
/*!
Sets the current data line output for @c device.
*/
void set_device_output(size_t device_id, bool output);
/*!
@returns The current state of the ADB data line.
*/
bool get_state() const;
/*!
@returns The current state of the ADB data line.
*/
bool get_state() const;
enum class Event {
Reset,
Attention,
Byte,
ServiceRequest,
enum class Event {
Reset,
Attention,
Byte,
ServiceRequest,
Unrecognised
};
Unrecognised
};
struct Device {
/// Reports to an observer that @c event was observed in the activity
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
struct Device {
/// Reports to an observer that @c event was observed in the activity
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
/// Requests that the device update itself @c microseconds and, if necessary, post a
/// new value ot @c set_device_output. This will be called only when the bus needs
/// to reevaluate its current level. It cannot reliably be used to track the timing between
/// observed events.
virtual void advance_state(double microseconds, bool current_level) = 0;
};
/*!
Adds a device.
*/
size_t add_device(Device *);
/// Requests that the device update itself @c microseconds and, if necessary, post a
/// new value ot @c set_device_output. This will be called only when the bus needs
/// to reevaluate its current level. It cannot reliably be used to track the timing between
/// observed events.
virtual void advance_state(double microseconds, bool current_level) = 0;
};
/*!
Adds a device.
*/
size_t add_device(Device *);
private:
HalfCycles time_in_state_;
mutable HalfCycles time_since_get_state_;
private:
HalfCycles time_in_state_;
mutable HalfCycles time_since_get_state_;
double half_cycles_to_microseconds_ = 1.0;
std::vector<Device *> devices_;
unsigned int shift_register_ = 0;
unsigned int start_target_ = 8;
bool data_level_ = true;
double half_cycles_to_microseconds_ = 1.0;
std::vector<Device *> devices_;
unsigned int shift_register_ = 0;
unsigned int start_target_ = 8;
bool data_level_ = true;
// ADB addressing supports at most 16 devices but that doesn't include
// the controller. So assume a maximum of 17 connected devices.
std::bitset<17> bus_state_{0xffffffff};
size_t next_device_id_ = 0;
// ADB addressing supports at most 16 devices but that doesn't include
// the controller. So assume a maximum of 17 connected devices.
std::bitset<17> bus_state_{0xffffffff};
size_t next_device_id_ = 0;
inline void shift(unsigned int);
enum class Phase {
PacketCapture,
AttentionCapture
} phase_ = Phase::AttentionCapture;
inline void shift(unsigned int);
enum class Phase {
PacketCapture,
AttentionCapture
} phase_ = Phase::AttentionCapture;
};
}

View File

@ -94,20 +94,20 @@ enum class Key: uint16_t {
};
class Keyboard: public ReactiveDevice {
public:
Keyboard(Bus &);
public:
Keyboard(Bus &);
bool set_key_pressed(Key key, bool is_pressed);
void clear_all_keys();
bool set_key_pressed(Key key, bool is_pressed);
void clear_all_keys();
private:
void perform_command(const Command &command) override;
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
private:
void perform_command(const Command &command) override;
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
std::mutex keys_mutex_;
std::array<bool, 128> pressed_keys_{};
std::vector<uint8_t> pending_events_;
uint16_t modifiers_ = 0xffff;
std::mutex keys_mutex_;
std::array<bool, 128> pressed_keys_{};
std::vector<uint8_t> pending_events_;
uint16_t modifiers_ = 0xffff;
};
/*!

View File

@ -14,20 +14,20 @@
namespace Apple::ADB {
class Mouse: public ReactiveDevice, public Inputs::Mouse {
public:
Mouse(Bus &);
public:
Mouse(Bus &);
private:
void perform_command(const Command &command) override;
private:
void perform_command(const Command &command) override;
void move(int x, int y) override;
int get_number_of_buttons() const override;
void set_button_pressed(int index, bool is_pressed) override;
void reset_all_buttons() override;
void move(int x, int y) override;
int get_number_of_buttons() const override;
void set_button_pressed(int index, bool is_pressed) override;
void reset_all_buttons() override;
std::atomic<int16_t> delta_x_, delta_y_;
std::atomic<int> button_flags_ = 0;
uint16_t last_posted_reg0_ = 0;
std::atomic<int16_t> delta_x_, delta_y_;
std::atomic<int> button_flags_ = 0;
uint16_t last_posted_reg0_ = 0;
};
}

View File

@ -17,45 +17,44 @@
namespace Apple::ADB {
class ReactiveDevice: public Bus::Device {
protected:
ReactiveDevice(Bus &bus, uint8_t adb_device_id);
protected:
ReactiveDevice(Bus &bus, uint8_t adb_device_id);
void post_response(const std::vector<uint8_t> &&response);
void post_service_request();
void receive_bytes(size_t count);
void post_response(const std::vector<uint8_t> &&response);
void post_service_request();
void receive_bytes(size_t count);
virtual void perform_command(const Command &command) = 0;
virtual void did_receive_data(const Command &, const std::vector<uint8_t> &) {}
virtual void perform_command(const Command &command) = 0;
virtual void did_receive_data(const Command &, const std::vector<uint8_t> &) {}
private:
void advance_state(double microseconds, bool current_level) override;
void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override;
private:
void advance_state(double microseconds, bool current_level) override;
void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override;
private:
Bus &bus_;
const size_t device_id_;
Bus &bus_;
const size_t device_id_;
std::vector<uint8_t> response_;
int bit_offset_ = 0;
double microseconds_at_bit_ = 0;
std::vector<uint8_t> response_;
int bit_offset_ = 0;
double microseconds_at_bit_ = 0;
enum class Phase {
AwaitingAttention,
AwaitingCommand,
AwaitingContent,
ServiceRequestPending,
} phase_ = Phase::AwaitingAttention;
std::vector<uint8_t> content_;
size_t expected_content_size_ = 0;
Command command_;
bool stop_has_begin_ = false;
enum class Phase {
AwaitingAttention,
AwaitingCommand,
AwaitingContent,
ServiceRequestPending,
} phase_ = Phase::AwaitingAttention;
std::vector<uint8_t> content_;
size_t expected_content_size_ = 0;
Command command_;
bool stop_has_begin_ = false;
uint16_t register3_;
const uint8_t default_adb_device_id_;
uint16_t register3_;
const uint8_t default_adb_device_id_;
std::atomic<bool> service_desired_ = false;
std::atomic<bool> service_desired_ = false;
void reset();
void reset();
};
}

File diff suppressed because it is too large Load Diff

View File

@ -22,23 +22,23 @@
namespace Apple::II {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
static ROM::Request rom_request(bool is_16_sector);
DiskIICard(ROM::Map &, bool is_16_sector);
public:
static ROM::Request rom_request(bool is_16_sector);
DiskIICard(ROM::Map &, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;
void set_activity_observer(Activity::Observer *observer) final;
void set_activity_observer(Activity::Observer *observer) final;
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
Storage::Disk::Drive &get_drive(int drive);
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
Storage::Disk::Drive &get_drive(int drive);
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
};
}

View File

@ -16,92 +16,92 @@
namespace Apple::II {
class JoystickPair {
public:
JoystickPair() {
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
public:
JoystickPair() {
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
inline bool button(size_t index) {
return joystick(0)->buttons[index] || joystick(1)->buttons[2-index];
}
inline bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
inline void update_charge(float one_mhz_cycles = 1.0f) {
analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f);
}
inline void access_c070() {
// Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
inline bool button(size_t index) {
return joystick(0)->buttons[index] || joystick(1)->buttons[2-index];
}
inline bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
inline void update_charge(float one_mhz_cycles = 1.0f) {
analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f);
}
inline void access_c070() {
// Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
analogue_charge_ = 0.0f;
}
analogue_charge_ = 0.0f;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
private:
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
inline Joystick *joystick(size_t index) {
return static_cast<Joystick *>(joysticks_[index].get());
}
inline Joystick *joystick(size_t index) {
return static_cast<Joystick *>(joysticks_[index].get());
}
};
}

View File

@ -19,95 +19,95 @@ namespace Apple::II {
* machine.set_language_card_paging() if the proper mapped state changes.
*/
template <typename Machine> class LanguageCardSwitches {
public:
struct State {
/// When RAM is visible in the range $D000$FFFF:
/// @c true indicates that bank 2 should be used between $D000 and $DFFF;
/// @c false indicates bank 1.
bool bank2 = true;
public:
struct State {
/// When RAM is visible in the range $D000$FFFF:
/// @c true indicates that bank 2 should be used between $D000 and $DFFF;
/// @c false indicates bank 1.
bool bank2 = true;
/// @c true indicates that RAM should be readable in the range $D000$FFFF;
/// @c false indicates ROM should be readable.
bool read = false;
/// @c true indicates that RAM should be readable in the range $D000$FFFF;
/// @c false indicates ROM should be readable.
bool read = false;
/// @c true indicates that ROM is selected for 'writing' in the range $D000$FFFF (i.e. writes are a no-op);
/// @c false indicates that RAM is selected for writing.
bool write = false;
/// @c true indicates that ROM is selected for 'writing' in the range $D000$FFFF (i.e. writes are a no-op);
/// @c false indicates that RAM is selected for writing.
bool write = false;
bool operator != (const State &rhs) const {
return
bank2 != rhs.bank2 ||
read != rhs.read ||
write != rhs.write;
}
};
LanguageCardSwitches(Machine &machine) : machine_(machine) {}
/// Used by an owner to forward any access to $c08x.
void access(uint16_t address, bool is_read) {
const auto previous_state = state_;
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1.
state_.bank2 = !(address & 8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
state_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(pre_write_ && is_read && (address&1)) state_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) state_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
pre_write_ = is_read ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.template set_paging<PagingType::LanguageCard>();
}
}
/// Provides read-only access to the current language card switch state.
const State &state() const {
return state_;
}
/// Provides relevant parts of the IIgs interface.
void set_state(uint8_t value) {
const auto previous_state = state_;
// Bit 3: 1 => enable ROM, 0 => enable RAM.
state_.read = !(value & 0x08);
// Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference
// correcting the original, which lists them the other way around]
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.template set_paging<PagingType::LanguageCard>();
}
}
uint8_t get_state() const {
bool operator != (const State &rhs) const {
return
(state_.read ? 0x00 : 0x08) |
(state_.bank2 ? 0x04 : 0x00);
bank2 != rhs.bank2 ||
read != rhs.read ||
write != rhs.write;
}
};
private:
Machine &machine_;
State state_;
LanguageCardSwitches(Machine &machine) : machine_(machine) {}
// This is an additional flip flop contained on the language card, but
// it is one step removed from current banking state, so I've excluded it
// from the State struct.
bool pre_write_ = false;
/// Used by an owner to forward any access to $c08x.
void access(uint16_t address, bool is_read) {
const auto previous_state = state_;
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1.
state_.bank2 = !(address & 8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
state_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(pre_write_ && is_read && (address&1)) state_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) state_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
pre_write_ = is_read ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.template set_paging<PagingType::LanguageCard>();
}
}
/// Provides read-only access to the current language card switch state.
const State &state() const {
return state_;
}
/// Provides relevant parts of the IIgs interface.
void set_state(uint8_t value) {
const auto previous_state = state_;
// Bit 3: 1 => enable ROM, 0 => enable RAM.
state_.read = !(value & 0x08);
// Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference
// correcting the original, which lists them the other way around]
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.template set_paging<PagingType::LanguageCard>();
}
}
uint8_t get_state() const {
return
(state_.read ? 0x00 : 0x08) |
(state_.bank2 ? 0x04 : 0x00);
}
private:
Machine &machine_;
State state_;
// This is an additional flip flop contained on the language card, but
// it is one step removed from current banking state, so I've excluded it
// from the State struct.
bool pre_write_ = false;
};
}

View File

@ -15,121 +15,121 @@
namespace Apple::II {
class AYPair {
public:
AYPair(Concurrency::AsyncTaskQueue<false> &queue) :
ays_{
{GI::AY38910::Personality::AY38910, queue},
{GI::AY38910::Personality::AY38910, queue},
} {}
public:
AYPair(Concurrency::AsyncTaskQueue<false> &queue) :
ays_{
{GI::AY38910::Personality::AY38910, queue},
{GI::AY38910::Personality::AY38910, queue},
} {}
void advance() {
ays_[0].advance();
ays_[1].advance();
}
void advance() {
ays_[0].advance();
ays_[1].advance();
}
void set_sample_volume_range(std::int16_t range) {
ays_[0].set_sample_volume_range(range);
ays_[1].set_sample_volume_range(range);
}
void set_sample_volume_range(std::int16_t range) {
ays_[0].set_sample_volume_range(range);
ays_[1].set_sample_volume_range(range);
}
bool is_zero_level() const {
return ays_[0].is_zero_level() && ays_[1].is_zero_level();
}
bool is_zero_level() const {
return ays_[0].is_zero_level() && ays_[1].is_zero_level();
}
Outputs::Speaker::StereoSample level() const {
Outputs::Speaker::StereoSample level;
level.left = ays_[0].level();
level.right = ays_[1].level();
return level;
}
Outputs::Speaker::StereoSample level() const {
Outputs::Speaker::StereoSample level;
level.left = ays_[0].level();
level.right = ays_[1].level();
return level;
}
GI::AY38910::AY38910SampleSource<false> &get(int index) {
return ays_[index];
}
GI::AY38910::AY38910SampleSource<false> &get(int index) {
return ays_[index];
}
private:
GI::AY38910::AY38910SampleSource<false> ays_[2];
private:
GI::AY38910::AY38910SampleSource<false> ays_[2];
};
class Mockingboard: public Card {
public:
Mockingboard(AYPair &ays) :
vias_{ {handlers_[0]}, {handlers_[1]} },
handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} {
set_select_constraints(0);
public:
Mockingboard(AYPair &ays) :
vias_{ {handlers_[0]}, {handlers_[1]} },
handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} {
set_select_constraints(0);
}
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final {
if(!(select & IO)) {
return;
}
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final {
if(!(select & IO)) {
return;
}
int index = (address >> 7) & 1;
if(is_read) {
*value = vias_[index].read(address);
} else {
vias_[index].write(address, *value);
}
}
int index = (address >> 7) & 1;
if(is_read) {
*value = vias_[index].read(address);
void run_for(Cycles cycles, int) final {
vias_[0].run_for(cycles);
vias_[1].run_for(cycles);
}
bool nmi() final {
return handlers_[1].interrupt;
}
bool irq() final {
return handlers_[0].interrupt;
}
void did_change_interrupt_flags() {
delegate_->card_did_change_interrupt_flags(this);
}
private:
struct AYVIA: public MOS::MOS6522::PortHandler {
AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource<false> &ay) :
card(card), ay(ay) {}
void set_interrupt_status(bool status) {
interrupt = status;
card.did_change_interrupt_flags();
}
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
if(port) {
using ControlLines = GI::AY38910::ControlLines;
ay.set_control_lines(
ControlLines(
((value & 1) ? ControlLines::BC1 : 0) |
((value & 2) ? ControlLines::BDIR : 0) |
ControlLines::BC2
)
);
ay.set_reset(!(value & 4));
} else {
vias_[index].write(address, *value);
ay.set_data_input(value);
}
}
void run_for(Cycles cycles, int) final {
vias_[0].run_for(cycles);
vias_[1].run_for(cycles);
}
bool nmi() final {
return handlers_[1].interrupt;
}
bool irq() final {
return handlers_[0].interrupt;
}
void did_change_interrupt_flags() {
delegate_->card_did_change_interrupt_flags(this);
}
private:
struct AYVIA: public MOS::MOS6522::PortHandler {
AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource<false> &ay) :
card(card), ay(ay) {}
void set_interrupt_status(bool status) {
interrupt = status;
card.did_change_interrupt_flags();
uint8_t get_port_input(MOS::MOS6522::Port port) {
if(!port) {
return ay.get_data_output();
}
return 0xff;
}
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
if(port) {
using ControlLines = GI::AY38910::ControlLines;
ay.set_control_lines(
ControlLines(
((value & 1) ? ControlLines::BC1 : 0) |
((value & 2) ? ControlLines::BDIR : 0) |
ControlLines::BC2
)
);
bool interrupt;
Mockingboard &card;
GI::AY38910::AY38910SampleSource<false> &ay;
};
ay.set_reset(!(value & 4));
} else {
ay.set_data_input(value);
}
}
uint8_t get_port_input(MOS::MOS6522::Port port) {
if(!port) {
return ay.get_data_output();
}
return 0xff;
}
bool interrupt;
Mockingboard &card;
GI::AY38910::AY38910SampleSource<false> &ay;
};
MOS::MOS6522::MOS6522<AYVIA> vias_[2];
AYVIA handlers_[2];
MOS::MOS6522::MOS6522<AYVIA> vias_[2];
AYVIA handlers_[2];
};
}

View File

@ -25,31 +25,31 @@
namespace Apple::II {
class SCSICard: public Card {
public:
static ROM::Request rom_request();
SCSICard(ROM::Map &, int clock_rate);
public:
static ROM::Request rom_request();
SCSICard(ROM::Map &, int clock_rate);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
void set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final {
scsi_bus_.run_for(cycles);
}
void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final {
scsi_bus_.run_for(cycles);
}
void set_activity_observer(Activity::Observer *observer) final;
void set_activity_observer(Activity::Observer *observer) final;
private:
uint8_t *ram_pointer_ = nullptr;
uint8_t *rom_pointer_ = nullptr;
private:
uint8_t *ram_pointer_ = nullptr;
uint8_t *rom_pointer_ = nullptr;
std::array<uint8_t, 8*1024> ram_;
std::array<uint8_t, 16*1024> rom_;
std::array<uint8_t, 8*1024> ram_;
std::array<uint8_t, 16*1024> rom_;
SCSI::Bus scsi_bus_;
NCR::NCR5380::NCR5380 ncr5380_;
SCSI::Target::Target<SCSI::DirectAccessDevice> storage_;
Log::Logger<Log::Source::AppleIISCSICard> logger_;
SCSI::Bus scsi_bus_;
NCR::NCR5380::NCR5380 ncr5380_;
SCSI::Target::Target<SCSI::DirectAccessDevice> storage_;
Log::Logger<Log::Source::AppleIISCSICard> logger_;
};
}

View File

@ -116,398 +116,398 @@ class VideoBase: public VideoSwitches<Cycles> {
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
/*!
Obtains the last value the video read prior to time now+offset, according to the *current*
video mode, i.e. not allowing for any changes still enqueued.
*/
uint8_t get_last_read_value(Cycles offset) {
// Rules of generation:
/*!
Obtains the last value the video read prior to time now+offset, according to the *current*
video mode, i.e. not allowing for any changes still enqueued.
*/
uint8_t get_last_read_value(Cycles offset) {
// Rules of generation:
// FOR ALL MODELS IN ALL MODES:
//
// - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the
// SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*)
//
// - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte
// wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*)
//
// - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the
// displayed area, using 128-byte wraparound addressing." (5-13*)
//
// - "The first address of HBL is always addressed twice consecutively" (5-11*)
//
// - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255,
// so those six 64-byte sections are scanned twice" (5-13*)
// FOR ALL MODELS IN ALL MODES:
//
// - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the
// SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*)
//
// - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte
// wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*)
//
// - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the
// displayed area, using 128-byte wraparound addressing." (5-13*)
//
// - "The first address of HBL is always addressed twice consecutively" (5-11*)
//
// - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255,
// so those six 64-byte sections are scanned twice" (5-13*)
// FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES):
//
// - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*)
//
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
// before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is
// $147F." (5-11*)
//
// - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*)
//
// - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC
// variables" (5-37*)
//
// - Figure 5.17 Screen Memory Scanning (5-37*)
// FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES):
//
// - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*)
//
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
// before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is
// $147F." (5-11*)
//
// - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*)
//
// - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC
// variables" (5-37*)
//
// - Figure 5.17 Screen Memory Scanning (5-37*)
// FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE:
//
// - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**)
//
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
// before address $400 is $47F." (5-11**)
//
// - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*)
//
// - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of
// differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other
// display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned
// memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**)
//
// - Figure 5.17 Display Memory Scanning (5-41**)
// FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE:
//
// - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**)
//
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
// before address $400 is $47F." (5-11**)
//
// - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*)
//
// - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of
// differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other
// display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned
// memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**)
//
// - Figure 5.17 Display Memory Scanning (5-41**)
// Source: * Understanding the Apple II by Jim Sather
// Source: ** Understanding the Apple IIe by Jim Sather
// Source: * Understanding the Apple II by Jim Sather
// Source: ** Understanding the Apple IIe by Jim Sather
// Determine column at offset.
int mapped_column = column_ + int(offset.as_integral());
// Determine column at offset.
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_row %= 262;
mapped_column %= 65;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_row %= 262;
mapped_column %= 65;
// Remember if we're in a horizontal blanking interval.
int hbl = mapped_column < 25;
// Remember if we're in a horizontal blanking interval.
int hbl = mapped_column < 25;
// The first column is read twice.
if(mapped_column == 0) {
mapped_column = 1;
}
// Vertical blanking rows read eight bytes earlier.
if(mapped_row >= 192) {
mapped_column -= 8;
}
// Apple out-of-bounds row logic.
if(mapped_row >= 256) {
mapped_row = 0x3a + (mapped_row&255);
} else {
mapped_row %= 192;
}
// Calculate the address.
uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25);
// Wraparound addressing within 128 byte sections.
if(mapped_row < 64 && mapped_column < 25) {
read_address += 128;
}
if(hbl && !is_iie_) {
// On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal
// blanking bytes read from $1000 higher.
const GraphicsMode pixel_mode = graphics_mode(mapped_row);
if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) {
read_address += 0x1000;
}
}
// Read the address and return the value.
uint8_t value, aux_value;
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
return value;
// The first column is read twice.
if(mapped_column == 0) {
mapped_column = 1;
}
/*!
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
*/
bool get_is_vertical_blank(Cycles offset) {
// Determine column at offset.
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_row %= 262;
// Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html
// "on the IIe, the screen is blanked when the bit is low".
return mapped_row < 192;
// Vertical blanking rows read eight bytes earlier.
if(mapped_row >= 192) {
mapped_column -= 8;
}
private:
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of
line.
// Apple out-of-bounds row logic.
if(mapped_row >= 256) {
mapped_row = 0x3a + (mapped_row&255);
} else {
mapped_row %= 192;
}
// Calculate the address.
uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25);
// Wraparound addressing within 128 byte sections.
if(mapped_row < 64 && mapped_column < 25) {
read_address += 128;
}
if(hbl && !is_iie_) {
// On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal
// blanking bytes read from $1000 higher.
const GraphicsMode pixel_mode = graphics_mode(mapped_row);
if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) {
read_address += 0x1000;
}
}
// Read the address and return the value.
uint8_t value, aux_value;
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
return value;
}
/*!
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
*/
bool get_is_vertical_blank(Cycles offset) {
// Determine column at offset.
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_row %= 262;
// Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html
// "on the IIe, the screen is blanked when the bit is low".
return mapped_row < 192;
}
private:
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of
line.
*/
void advance(Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
A frame is oriented around 65 cycles across, 262 lines down.
*/
void advance(Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int first_sync_column = 49; // Also a guess.
constexpr int sync_length = 4; // One of the two likely candidates.
A frame is oriented around 65 cycles across, 262 lines down.
*/
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int first_sync_column = 49; // Also a guess.
constexpr int sync_length = 4; // One of the two likely candidates.
int int_cycles = int(cycles.as_integral());
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;
const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
int int_cycles = int(cycles.as_integral());
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;
const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
if(is_vertical_sync_line) {
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
// pulses (and hence keep hsync approximately where it should be during vsync).
const int blank_start = std::max(first_sync_column - sync_length, column_);
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_.output_sync((blank_start - column_) * 14);
}
crt_.output_blank((blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_.output_sync((ending_column - blank_end) * 14);
}
} else {
crt_.output_sync(cycles_this_line * 14);
if(is_vertical_sync_line) {
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
// pulses (and hence keep hsync approximately where it should be during vsync).
const int blank_start = std::max(first_sync_column - sync_length, column_);
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_.output_sync((blank_start - column_) * 14);
}
crt_.output_blank((blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_.output_sync((ending_column - blank_end) * 14);
}
} else {
const GraphicsMode line_mode = graphics_mode(row_);
crt_.output_sync(cycles_this_line * 14);
}
} else {
const GraphicsMode line_mode = graphics_mode(row_);
// Determine whether there's any fetching to do. Fetching occurs during the first
// 40 columns of rows prior to 192.
if(row_ < 192 && column_ < 40) {
const int character_row = row_ >> 3;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
// Determine whether there's any fetching to do. Fetching occurs during the first
// 40 columns of rows prior to 192.
if(row_ < 192 && column_ < 40) {
const int character_row = row_ >> 3;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
// Grab the memory contents that'll be needed momentarily.
const int fetch_end = std::min(40, ending_column);
uint16_t fetch_address;
switch(line_mode) {
default:
case GraphicsMode::Text:
case GraphicsMode::DoubleText:
case GraphicsMode::LowRes:
case GraphicsMode::FatLowRes:
case GraphicsMode::DoubleLowRes: {
const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address);
fetch_address = uint16_t(text_address + column_);
} break;
// Grab the memory contents that'll be needed momentarily.
const int fetch_end = std::min(40, ending_column);
uint16_t fetch_address;
switch(line_mode) {
default:
case GraphicsMode::Text:
case GraphicsMode::DoubleText:
case GraphicsMode::LowRes:
case GraphicsMode::FatLowRes:
case GraphicsMode::DoubleLowRes: {
const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address);
fetch_address = uint16_t(text_address + column_);
} break;
case GraphicsMode::HighRes:
case GraphicsMode::DoubleHighRes:
fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
break;
}
bus_handler_.perform_read(
fetch_address,
size_t(fetch_end - column_),
&base_stream_[size_t(column_)],
&auxiliary_stream_[size_t(column_)]);
case GraphicsMode::HighRes:
case GraphicsMode::DoubleHighRes:
fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
break;
}
if(row_ < 192) {
// The pixel area is the first 40.5 columns; base contents
// remain where they would naturally be but auxiliary
// graphics appear to the left of that.
if(!column_) {
pixel_pointer_ = crt_.begin_data(568);
graphics_carry_ = 0;
was_double_ = true;
bus_handler_.perform_read(
fetch_address,
size_t(fetch_end - column_),
&base_stream_[size_t(column_)],
&auxiliary_stream_[size_t(column_)]);
}
if(row_ < 192) {
// The pixel area is the first 40.5 columns; base contents
// remain where they would naturally be but auxiliary
// graphics appear to the left of that.
if(!column_) {
pixel_pointer_ = crt_.begin_data(568);
graphics_carry_ = 0;
was_double_ = true;
}
if(column_ < 40) {
const int pixel_start = std::max(0, column_);
const int pixel_end = std::min(40, ending_column);
const int pixel_row = row_ & 7;
const bool is_double = is_double_mode(line_mode);
if(!is_double && was_double_ && pixel_pointer_) {
pixel_pointer_[pixel_start*14 + 0] =
pixel_pointer_[pixel_start*14 + 1] =
pixel_pointer_[pixel_start*14 + 2] =
pixel_pointer_[pixel_start*14 + 3] =
pixel_pointer_[pixel_start*14 + 4] =
pixel_pointer_[pixel_start*14 + 5] =
pixel_pointer_[pixel_start*14 + 6] = 0;
}
was_double_ = is_double;
if(pixel_pointer_) {
switch(line_mode) {
case GraphicsMode::Text:
output_text(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
size_t(pixel_row));
break;
case GraphicsMode::DoubleText:
output_double_text(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
size_t(pixel_row));
break;
case GraphicsMode::LowRes:
output_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::FatLowRes:
output_fat_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::DoubleLowRes:
output_double_low_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::HighRes:
output_high_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start));
break;
case GraphicsMode::DoubleHighRes:
output_double_high_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start));
break;
default: break;
}
}
if(column_ < 40) {
const int pixel_start = std::max(0, column_);
const int pixel_end = std::min(40, ending_column);
const int pixel_row = row_ & 7;
const bool is_double = is_double_mode(line_mode);
if(!is_double && was_double_ && pixel_pointer_) {
pixel_pointer_[pixel_start*14 + 0] =
pixel_pointer_[pixel_start*14 + 1] =
pixel_pointer_[pixel_start*14 + 2] =
pixel_pointer_[pixel_start*14 + 3] =
pixel_pointer_[pixel_start*14 + 4] =
pixel_pointer_[pixel_start*14 + 5] =
pixel_pointer_[pixel_start*14 + 6] = 0;
}
was_double_ = is_double;
if(pixel_end == 40) {
if(pixel_pointer_) {
switch(line_mode) {
case GraphicsMode::Text:
output_text(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
size_t(pixel_row));
break;
case GraphicsMode::DoubleText:
output_double_text(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
size_t(pixel_row));
break;
case GraphicsMode::LowRes:
output_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::FatLowRes:
output_fat_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::DoubleLowRes:
output_double_low_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::HighRes:
output_high_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start));
break;
case GraphicsMode::DoubleHighRes:
output_double_high_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[size_t(pixel_start)],
&auxiliary_stream_[size_t(pixel_start)],
size_t(pixel_end - pixel_start));
break;
default: break;
if(was_double_) {
pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
} else {
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
pixel_pointer_[567] = graphics_carry_;
else
pixel_pointer_[567] = 0;
}
}
if(pixel_end == 40) {
if(pixel_pointer_) {
if(was_double_) {
pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
} else {
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
pixel_pointer_[567] = graphics_carry_;
else
pixel_pointer_[567] = 0;
}
}
crt_.output_data(568, 568);
pixel_pointer_ = nullptr;
}
}
} else {
if(column_ < 40 && ending_column >= 40) {
crt_.output_blank(568);
crt_.output_data(568, 568);
pixel_pointer_ = nullptr;
}
}
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
if(column_ < first_sync_column && ending_column >= first_sync_column) {
crt_.output_blank(first_sync_column*14 - 568);
}
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
crt_.output_sync(sync_length*14);
}
int second_blank_start;
// Colour burst is present on all lines of the display if graphics mode is enabled on the top
// portion; therefore use the graphics mode on line 0 rather than the current line, to avoid
// disabling it in mixed modes.
if(!is_text_mode(graphics_mode(0))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
// UGLY HACK AHOY!
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
// Supply the real phase value if this is an Apple build.
// TODO: eliminate UGLY HACK.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
constexpr uint8_t phase = 224;
#else
constexpr uint8_t phase = 192;
#endif
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
} else {
second_blank_start = std::max(first_sync_column + sync_length, column_);
}
if(ending_column > second_blank_start) {
crt_.output_blank((ending_column - second_blank_start) * 14);
} else {
if(column_ < 40 && ending_column >= 40) {
crt_.output_blank(568);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
did_end_line();
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised. If this is a vertical sync line, output sync
// instead of blank, taking that to be the default level.
if(is_vertical_sync_line) {
crt_.output_sync(2);
} else {
crt_.output_blank(2);
if(column_ < first_sync_column && ending_column >= first_sync_column) {
crt_.output_blank(first_sync_column*14 - 568);
}
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
crt_.output_sync(sync_length*14);
}
int second_blank_start;
// Colour burst is present on all lines of the display if graphics mode is enabled on the top
// portion; therefore use the graphics mode on line 0 rather than the current line, to avoid
// disabling it in mixed modes.
if(!is_text_mode(graphics_mode(0))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
// UGLY HACK AHOY!
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
// Supply the real phase value if this is an Apple build.
// TODO: eliminate UGLY HACK.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
constexpr uint8_t phase = 224;
#else
constexpr uint8_t phase = 192;
#endif
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
} else {
second_blank_start = std::max(first_sync_column + sync_length, column_);
}
if(ending_column > second_blank_start) {
crt_.output_blank((ending_column - second_blank_start) * 14);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
did_end_line();
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised. If this is a vertical sync line, output sync
// instead of blank, taking that to be the default level.
if(is_vertical_sync_line) {
crt_.output_sync(2);
} else {
crt_.output_blank(2);
}
}
}
}
BusHandler &bus_handler_;
BusHandler &bus_handler_;
};
}

View File

@ -29,327 +29,327 @@ constexpr bool is_text_mode(const GraphicsMode m) { return m <= GraphicsMode::Do
constexpr bool is_double_mode(const GraphicsMode m) { return int(m) & 1; }
template <typename TimeUnit> class VideoSwitches {
public:
/*!
Constructs a new instance of VideoSwitches in which changes to relevant switches
affect the video mode only after @c delay cycles.
public:
/*!
Constructs a new instance of VideoSwitches in which changes to relevant switches
affect the video mode only after @c delay cycles.
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
set of potential flashing characters and alternate video modes.
*/
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
character_zones_[0].xor_mask = 0xff;
character_zones_[0].address_mask = 0x3f;
character_zones_[1].xor_mask = 0;
character_zones_[1].address_mask = 0x3f;
character_zones_[2].xor_mask = 0;
character_zones_[2].address_mask = 0x3f;
character_zones_[3].xor_mask = 0;
character_zones_[3].address_mask = 0x3f;
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
set of potential flashing characters and alternate video modes.
*/
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
character_zones_[0].xor_mask = 0xff;
character_zones_[0].address_mask = 0x3f;
character_zones_[1].xor_mask = 0;
character_zones_[1].address_mask = 0x3f;
character_zones_[2].xor_mask = 0;
character_zones_[2].address_mask = 0x3f;
character_zones_[3].xor_mask = 0;
character_zones_[3].address_mask = 0x3f;
if(is_iie) {
character_zones_[1].xor_mask =
character_zones_[2].xor_mask =
character_zones_[3].xor_mask = 0xff;
character_zones_[2].address_mask =
character_zones_[3].address_mask = 0xff;
}
if(is_iie) {
character_zones_[1].xor_mask =
character_zones_[2].xor_mask =
character_zones_[3].xor_mask = 0xff;
character_zones_[2].address_mask =
character_zones_[3].address_mask = 0xff;
}
}
/*!
Advances @c cycles.
*/
void run_for(TimeUnit cycles) {
deferrer_.run_for(cycles);
}
/*!
Advances @c cycles.
*/
void run_for(TimeUnit cycles) {
deferrer_.run_for(cycles);
}
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool alternative_character_set) {
external_.alternative_character_set = alternative_character_set;
deferrer_.defer(delay_, [this, alternative_character_set] {
internal_.alternative_character_set = alternative_character_set;
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool alternative_character_set) {
external_.alternative_character_set = alternative_character_set;
deferrer_.defer(delay_, [this, alternative_character_set] {
internal_.alternative_character_set = alternative_character_set;
if(alternative_character_set) {
character_zones_[1].address_mask = 0xff;
character_zones_[1].xor_mask = 0xff;
} else {
character_zones_[1].address_mask = 0x3f;
character_zones_[1].xor_mask = flash_mask();
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
}
});
}
bool get_alternative_character_set() const {
return external_.alternative_character_set;
}
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool columns_80) {
external_.columns_80 = columns_80;
deferrer_.defer(delay_, [this, columns_80] {
internal_.columns_80 = columns_80;
});
}
bool get_80_columns() const {
return external_.columns_80;
}
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool store_80) {
external_.store_80 = internal_.store_80 = store_80;
}
bool get_80_store() const {
return external_.store_80;
}
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool page2) {
external_.page2 = internal_.page2 = page2;
}
bool get_page2() const {
return external_.page2;
}
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool text) {
external_.text = text;
deferrer_.defer(delay_, [this, text] {
internal_.text = text;
});
}
bool get_text() const {
return external_.text;
}
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool mixed) {
external_.mixed = mixed;
deferrer_.defer(delay_, [this, mixed] {
internal_.mixed = mixed;
});
}
bool get_mixed() const {
return external_.mixed;
}
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool high_resolution) {
external_.high_resolution = high_resolution;
deferrer_.defer(delay_, [this, high_resolution] {
internal_.high_resolution = high_resolution;
});
}
bool get_high_resolution() const {
return external_.high_resolution;
}
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool annunciator_3) {
external_.annunciator_3 = annunciator_3;
deferrer_.defer(delay_, [this, annunciator_3] {
internal_.annunciator_3 = annunciator_3;
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
});
}
bool get_annunciator_3() const {
return external_.annunciator_3;
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;
// There's some inconsistency in bit ordering amongst the common ROM dumps;
// detect that based arbitrarily on the second line of the $ graphic and
// ensure consistency.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
bool has_deferred_actions() const {
return !deferrer_.empty();
}
protected:
GraphicsMode graphics_mode(int row) const {
if(
internal_.text ||
(internal_.mixed && row >= 160 && row < 192)
) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(internal_.high_resolution) {
return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
if(alternative_character_set) {
character_zones_[1].address_mask = 0xff;
character_zones_[1].xor_mask = 0xff;
} else {
if(internal_.columns_80) return GraphicsMode::DoubleLowRes;
if(internal_.annunciator_3) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
int video_page() const {
return (internal_.store_80 || !internal_.page2) ? 0 : 1;
}
uint16_t get_row_address(int row) const {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
uint16_t(((video_page()+1) * 0x400) + row_address);
}
/*!
Should be called by subclasses at the end of each line of the display;
this gives the base class a peg on which to hang flashing-character updates.
*/
void did_end_line() {
// Update character set flashing; flashing is applied only when the alternative
// character set is not selected.
flash_ = (flash_ + 1) % (2 * flash_length);
if(!internal_.alternative_character_set) {
character_zones_[1].address_mask = 0x3f;
character_zones_[1].xor_mask = flash_mask();
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
}
});
}
bool get_alternative_character_set() const {
return external_.alternative_character_set;
}
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool columns_80) {
external_.columns_80 = columns_80;
deferrer_.defer(delay_, [this, columns_80] {
internal_.columns_80 = columns_80;
});
}
bool get_80_columns() const {
return external_.columns_80;
}
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool store_80) {
external_.store_80 = internal_.store_80 = store_80;
}
bool get_80_store() const {
return external_.store_80;
}
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool page2) {
external_.page2 = internal_.page2 = page2;
}
bool get_page2() const {
return external_.page2;
}
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool text) {
external_.text = text;
deferrer_.defer(delay_, [this, text] {
internal_.text = text;
});
}
bool get_text() const {
return external_.text;
}
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool mixed) {
external_.mixed = mixed;
deferrer_.defer(delay_, [this, mixed] {
internal_.mixed = mixed;
});
}
bool get_mixed() const {
return external_.mixed;
}
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool high_resolution) {
external_.high_resolution = high_resolution;
deferrer_.defer(delay_, [this, high_resolution] {
internal_.high_resolution = high_resolution;
});
}
bool get_high_resolution() const {
return external_.high_resolution;
}
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool annunciator_3) {
external_.annunciator_3 = annunciator_3;
deferrer_.defer(delay_, [this, annunciator_3] {
internal_.annunciator_3 = annunciator_3;
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
});
}
bool get_annunciator_3() const {
return external_.annunciator_3;
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;
// There's some inconsistency in bit ordering amongst the common ROM dumps;
// detect that based arbitrarily on the second line of the $ graphic and
// ensure consistency.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
private:
// Maintain a DeferredQueue for delayed mode switches.
const TimeUnit delay_;
DeferredQueuePerformer<TimeUnit> deferrer_;
bool has_deferred_actions() const {
return !deferrer_.empty();
}
struct Switches {
bool alternative_character_set = false;
bool columns_80 = false;
bool store_80 = false;
bool page2 = false;
bool text = true;
bool mixed = false;
bool high_resolution = false;
bool annunciator_3 = false;
} external_, internal_;
// 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line
// rate of around 15734.26 lines/second, flashing has a frequency of roughly
// 15734.26 / 8406, or roughly 1.83Hz.
//
// Internally that's modelled by a counter that goes to **twice** the value of the
// constant specified below. Hence the divide by two.
static constexpr int flash_length = 8406 / 2;
int flash_ = 0;
uint8_t flash_mask() const {
return uint8_t((flash_ / flash_length) * 0xff);
protected:
GraphicsMode graphics_mode(int row) const {
if(
internal_.text ||
(internal_.mixed && row >= 160 && row < 192)
) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(internal_.high_resolution) {
return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
if(internal_.columns_80) return GraphicsMode::DoubleLowRes;
if(internal_.annunciator_3) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
protected:
int video_page() const {
return (internal_.store_80 || !internal_.page2) ? 0 : 1;
}
// Describes the current text mode mapping from in-memory character index
// to output character; subclasses should:
//
// (i) use the top two-bits of the character code to index character_zones_;
// (ii) apply the address_mask to the character code in order to get a character
// offset into the character ROM; and
// (iii) apply the XOR mask to the output of the character ROM.
//
// By this means they will properly handle the limited character sets of Apple IIs
// prior to the IIe as well as the IIe and onward's alternative character set toggle.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones_[4];
uint16_t get_row_address(int row) const {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
// A mask that should be applied to high-resolution graphics bytes before output;
// it acts to retain or remove the top bit, affecting whether the half-pixel delay
// bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist.
uint8_t high_resolution_mask_ = 0xff;
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
uint16_t(((video_page()+1) * 0x400) + row_address);
}
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
/*!
Should be called by subclasses at the end of each line of the display;
this gives the base class a peg on which to hang flashing-character updates.
*/
void did_end_line() {
// Update character set flashing; flashing is applied only when the alternative
// character set is not selected.
flash_ = (flash_ + 1) % (2 * flash_length);
if(!internal_.alternative_character_set) {
character_zones_[1].xor_mask = flash_mask();
}
}
private:
// Maintain a DeferredQueue for delayed mode switches.
const TimeUnit delay_;
DeferredQueuePerformer<TimeUnit> deferrer_;
struct Switches {
bool alternative_character_set = false;
bool columns_80 = false;
bool store_80 = false;
bool page2 = false;
bool text = true;
bool mixed = false;
bool high_resolution = false;
bool annunciator_3 = false;
} external_, internal_;
// 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line
// rate of around 15734.26 lines/second, flashing has a frequency of roughly
// 15734.26 / 8406, or roughly 1.83Hz.
//
// Internally that's modelled by a counter that goes to **twice** the value of the
// constant specified below. Hence the divide by two.
static constexpr int flash_length = 8406 / 2;
int flash_ = 0;
uint8_t flash_mask() const {
return uint8_t((flash_ / flash_length) * 0xff);
}
protected:
// Describes the current text mode mapping from in-memory character index
// to output character; subclasses should:
//
// (i) use the top two-bits of the character code to index character_zones_;
// (ii) apply the address_mask to the character code in order to get a character
// offset into the character ROM; and
// (iii) apply the XOR mask to the output of the character ROM.
//
// By this means they will properly handle the limited character sets of Apple IIs
// prior to the IIe as well as the IIe and onward's alternative character set toggle.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones_[4];
// A mask that should be applied to high-resolution graphics bytes before output;
// it acts to retain or remove the top bit, affecting whether the half-pixel delay
// bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist.
uint8_t high_resolution_mask_ = 0xff;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
};
}

View File

@ -138,7 +138,7 @@ class ConcreteMachine:
}
}
bool get_switch_is_enabled(Atari2600Switch input) final {
bool get_switch_is_enabled(Atari2600Switch input) const final {
uint8_t port_input = bus_->mos6532_.get_port_input(1);
switch(input) {
case Atari2600SwitchReset: return !!(port_input & 0x01);

View File

@ -22,20 +22,20 @@ namespace Atari2600 {
Models an Atari 2600.
*/
class Machine {
public:
virtual ~Machine() = default;
public:
virtual ~Machine() = default;
/// Creates and returns an Atari 2600 on the heap.
static std::unique_ptr<Machine> Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
/// Creates and returns an Atari 2600 on the heap.
static std::unique_ptr<Machine> Atari2600(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch, bool) = 0;
/// Gets the state of switch @c input.
virtual bool get_switch_is_enabled(Atari2600Switch input) = 0;
/// Gets the state of switch @c input.
virtual bool get_switch_is_enabled(Atari2600Switch) const = 0;
// Presses or releases the reset button.
virtual void set_reset_switch(bool state) = 0;
// Presses or releases the reset button.
virtual void set_reset_switch(bool) = 0;
};
}

View File

@ -20,49 +20,49 @@
namespace Atari2600 {
class Bus {
public:
Bus() :
tia_sound_(audio_queue_),
speaker_(tia_sound_) {}
public:
Bus() :
tia_sound_(audio_queue_),
speaker_(tia_sound_) {}
virtual ~Bus() {
audio_queue_.flush();
}
virtual ~Bus() {
audio_queue_.flush();
}
virtual void run_for(const Cycles cycles) = 0;
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
virtual void set_reset_line(bool state) = 0;
virtual void flush() = 0;
virtual void run_for(const Cycles cycles) = 0;
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
virtual void set_reset_line(bool state) = 0;
virtual void flush() = 0;
// the RIOT, TIA and speaker
PIA mos6532_;
TIA tia_;
// the RIOT, TIA and speaker
PIA mos6532_;
TIA tia_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TIASound tia_sound_;
Outputs::Speaker::PullLowpass<TIASound> speaker_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TIASound tia_sound_;
Outputs::Speaker::PullLowpass<TIASound> speaker_;
// joystick state
uint8_t tia_input_value_[2] = {0xff, 0xff};
// joystick state
uint8_t tia_input_value_[2] = {0xff, 0xff};
protected:
// speaker backlog accumlation counter
Cycles cycles_since_speaker_update_;
inline void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
}
protected:
// speaker backlog accumlation counter
Cycles cycles_since_speaker_update_;
inline void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
}
// video backlog accumulation counter
Cycles cycles_since_video_update_;
inline void update_video() {
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// video backlog accumulation counter
Cycles cycles_since_video_update_;
inline void update_video() {
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// RIOT backlog accumulation counter
Cycles cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
}
// RIOT backlog accumulation counter
Cycles cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
}
};
}

View File

@ -11,36 +11,40 @@
namespace Atari2600::Cartridge {
class ActivisionStack: public BusExtender {
public:
ActivisionStack(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
last_opcode_(0x00) {}
public:
ActivisionStack(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
last_opcode_(0x00) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
if(!(address & 0x1000)) return;
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
// RST or JSR.
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(address & 0x2000) {
rom_ptr_ = rom_base_;
} else {
rom_ptr_ = rom_base_ + 4096;
}
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
// RST or JSR.
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(address & 0x2000) {
rom_ptr_ = rom_base_;
} else {
rom_ptr_ = rom_base_ + 4096;
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
}
private:
uint8_t *rom_ptr_;
uint8_t last_opcode_;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
}
private:
const uint8_t *rom_ptr_;
uint8_t last_opcode_;
};
}

View File

@ -13,49 +13,57 @@
namespace Atari2600::Cartridge {
class Atari16k: public BusExtender {
public:
Atari16k(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
public:
Atari16k(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
private:
const uint8_t *rom_ptr_;
};
class Atari16kSuperChip: public BusExtender {
public:
Atari16kSuperChip(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
public:
Atari16kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
const uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}

View File

@ -13,45 +13,55 @@
namespace Atari2600::Cartridge {
class Atari32k: public BusExtender {
public:
Atari32k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
public:
Atari32k(const uint8_t *const rom_base, const std::size_t rom_size)
: BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
private:
const uint8_t *rom_ptr_;
};
class Atari32kSuperChip: public BusExtender {
public:
Atari32kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
public:
Atari32kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size)
: BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
const uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}

View File

@ -13,47 +13,57 @@
namespace Atari2600::Cartridge {
class Atari8k: public BusExtender {
public:
Atari8k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
public:
Atari8k(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
private:
const uint8_t *rom_ptr_;
};
class Atari8kSuperChip: public BusExtender {
public:
Atari8kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
public:
Atari8kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
const uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}

View File

@ -13,26 +13,31 @@
namespace Atari2600::Cartridge {
class CBSRAMPlus: public BusExtender {
public:
CBSRAMPlus(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
public:
CBSRAMPlus(const uint8_t *const rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1100) ram_[address & 0xff] = *value;
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[256];
if(address < 0x1100) ram_[address & 0xff] = *value;
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
}
private:
const uint8_t *rom_ptr_;
uint8_t ram_[256];
};
}

View File

@ -14,202 +14,206 @@
namespace Atari2600::Cartridge {
class BusExtender: public CPU::MOS6502::BusHandler {
public:
BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
public:
BusExtender(const uint8_t *const rom_base, const std::size_t rom_size) :
rom_base_(rom_base), rom_size_(rom_size) {}
void advance_cycles(int) {}
void advance_cycles(int) {}
protected:
uint8_t *rom_base_;
std::size_t rom_size_;
protected:
const uint8_t *rom_base_;
std::size_t rom_size_;
};
template<class T> class Cartridge:
public CPU::MOS6502::BusHandler,
public Bus {
public:
Cartridge(const std::vector<uint8_t> &rom) :
m6502_(*this),
rom_(rom),
bus_extender_(rom_.data(), rom.size()) {
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
// consider doing something less fragile.
}
public:
Cartridge(const std::vector<uint8_t> &rom) :
m6502_(*this),
rom_(rom),
bus_extender_(rom_.data(), rom.size()) {
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
// consider doing something less fragile.
}
void run_for(const Cycles cycles) override {
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
// title. Random memory accesses are likely to trigger random counter resets.
horizontal_counter_resets_ = 0;
cycle_count_ = cycles;
m6502_.run_for(cycles);
}
void run_for(const Cycles cycles) override {
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
// title. Random memory accesses are likely to trigger random counter resets.
horizontal_counter_resets_ = 0;
cycle_count_ = cycles;
m6502_.run_for(cycles);
}
/*!
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
if(cycle_count_.as_integral() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}
/*!
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
if(cycle_count_.as_integral() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}
void set_reset_line(bool state) override { m6502_.set_reset_line(state); }
void set_reset_line(const bool state) override { m6502_.set_reset_line(state); }
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
uint8_t returnValue = 0xff;
int cycles_run_for = 3;
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
uint8_t returnValue = 0xff;
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 = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
// 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 = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
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);
bus_extender_.advance_cycles(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);
bus_extender_.advance_cycles(cycles_run_for / 3);
if(isAccessOperation(operation)) {
// give the cartridge a chance to respond to the bus access
bus_extender_.perform_bus_operation(operation, address, value);
// check for a RIOT RAM access
if((address&0x1280) == 0x80) {
if(isReadOperation(operation)) {
returnValue &= mos6532_.get_ram(address);
} else {
mos6532_.set_ram(address, *value);
}
}
// check for a TIA access
if(!(address&0x1080)) {
if(isReadOperation(operation)) {
const uint16_t decodedAddress = address & 0xf;
switch(decodedAddress) {
case 0x00: // missile 0 / player collisions
case 0x01: // missile 1 / player collisions
case 0x02: // player 0 / playfield / ball collisions
case 0x03: // player 1 / playfield / ball collisions
case 0x04: // missile 0 / playfield / ball collisions
case 0x05: // missile 1 / playfield / ball collisions
case 0x06: // ball / playfield collisions
case 0x07: // player / player, missile / missile collisions
returnValue &= tia_.get_collision_flags(decodedAddress);
break;
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
// TODO: pot ports
returnValue &= 0;
break;
case 0x0c:
case 0x0d:
returnValue &= tia_input_value_[decodedAddress - 0x0c];
break;
}
} else {
const uint16_t decodedAddress = address & 0x3f;
switch(decodedAddress) {
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03:
update_video();
tia_.reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation. Fix.
case 0x04:
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x06:
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_.set_background_colour(*value); break;
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
case 0x0b:
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0d:
case 0x0e:
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
case 0x10:
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
case 0x12:
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_.set_ball_position(); break;
case 0x1b:
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1d:
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
case 0x20:
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
case 0x22:
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
case 0x25:
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_.set_ball_delay((*value)&1); break;
case 0x28:
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_.move(); break;
case 0x2b: update_video(); tia_.clear_motion(); break;
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
case 0x17:
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
case 0x19:
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
}
}
}
// check for a PIA access
if((address&0x1280) == 0x280) {
update_6532();
if(isReadOperation(operation)) {
returnValue &= mos6532_.read(address);
} else {
mos6532_.write(address, *value);
}
}
if(isAccessOperation(operation)) {
// give the cartridge a chance to respond to the bus access
bus_extender_.perform_bus_operation(operation, address, value);
// check for a RIOT RAM access
if((address&0x1280) == 0x80) {
if(isReadOperation(operation)) {
*value &= returnValue;
returnValue &= mos6532_.get_ram(address);
} else {
mos6532_.set_ram(address, *value);
}
}
if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
// check for a TIA access
if(!(address&0x1080)) {
if(isReadOperation(operation)) {
const uint16_t decodedAddress = address & 0xf;
switch(decodedAddress) {
case 0x00: // missile 0 / player collisions
case 0x01: // missile 1 / player collisions
case 0x02: // player 0 / playfield / ball collisions
case 0x03: // player 1 / playfield / ball collisions
case 0x04: // missile 0 / playfield / ball collisions
case 0x05: // missile 1 / playfield / ball collisions
case 0x06: // ball / playfield collisions
case 0x07: // player / player, missile / missile collisions
returnValue &= tia_.get_collision_flags(decodedAddress);
break;
return Cycles(cycles_run_for / 3);
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
// TODO: pot ports
returnValue &= 0;
break;
case 0x0c:
case 0x0d:
returnValue &= tia_input_value_[decodedAddress - 0x0c];
break;
}
} else {
const uint16_t decodedAddress = address & 0x3f;
switch(decodedAddress) {
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03:
update_video();
tia_.reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation. Fix.
case 0x04:
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x06:
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_.set_background_colour(*value); break;
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
case 0x0b:
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0d:
case 0x0e:
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
case 0x10:
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
case 0x12:
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_.set_ball_position(); break;
case 0x1b:
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1d:
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
case 0x20:
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
case 0x22:
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
case 0x25:
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_.set_ball_delay((*value)&1); break;
case 0x28:
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_.move(); break;
case 0x2b: update_video(); tia_.clear_motion(); break;
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
case 0x17:
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
case 0x19:
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
}
}
}
// check for a PIA access
if((address&0x1280) == 0x280) {
update_6532();
if(isReadOperation(operation)) {
returnValue &= mos6532_.read(address);
} else {
mos6532_.write(address, *value);
}
}
if(isReadOperation(operation)) {
*value &= returnValue;
}
}
void flush() override {
update_audio();
update_video();
audio_queue_.perform();
}
if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
protected:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
return Cycles(cycles_run_for / 3);
}
private:
T bus_extender_;
int horizontal_counter_resets_ = 0;
Cycles cycle_count_;
void flush() override {
update_audio();
update_video();
audio_queue_.perform();
}
protected:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:
T bus_extender_;
int horizontal_counter_resets_ = 0;
Cycles cycle_count_;
};
}

View File

@ -13,28 +13,32 @@
namespace Atari2600::Cartridge {
class CommaVid: public BusExtender {
public:
CommaVid(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
public:
CommaVid(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
address &= 0x1fff;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
if(!(address & 0x1000)) return;
address &= 0x1fff;
if(address < 0x1400) {
if(isReadOperation(operation)) *value = ram_[address & 1023];
return;
}
if(address < 0x1800) {
ram_[address & 1023] = *value;
return;
}
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
if(address < 0x1400) {
if(isReadOperation(operation)) *value = ram_[address & 1023];
return;
}
private:
uint8_t ram_[1024];
if(address < 0x1800) {
ram_[address & 1023] = *value;
return;
}
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
}
private:
uint8_t ram_[1024];
};
}

View File

@ -13,52 +13,55 @@
namespace Atari2600::Cartridge {
class MNetwork: public BusExtender {
public:
MNetwork(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size_ - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
high_ram_ptr_ = high_ram_;
public:
MNetwork(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size_ - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
high_ram_ptr_ = high_ram_;
}
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address <= 0x1fe6) {
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
} else if(address == 0x1fe7) {
rom_ptr_[0] = nullptr;
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
int offset = (address - 0x1ff8) * 256;
high_ram_ptr_ = &high_ram_[offset];
}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address <= 0x1fe6) {
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
} else if(address == 0x1fe7) {
rom_ptr_[0] = nullptr;
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
int offset = (address - 0x1ff8) * 256;
high_ram_ptr_ = &high_ram_[offset];
}
if(address & 0x800) {
if(address < 0x1900) {
high_ram_ptr_[address & 255] = *value;
} else if(address < 0x1a00) {
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
} else {
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
}
if(address & 0x800) {
if(address < 0x1900) {
high_ram_ptr_[address & 255] = *value;
} else if(address < 0x1a00) {
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
} else {
if(rom_ptr_[0]) {
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
}
} else {
if(rom_ptr_[0]) {
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
} else {
if(address < 0x1400) {
low_ram_[address & 1023] = *value;
} else {
if(address < 0x1400) {
low_ram_[address & 1023] = *value;
} else {
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
}
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
}
}
}
}
private:
uint8_t *rom_ptr_[2];
uint8_t *high_ram_ptr_;
uint8_t low_ram_[1024], high_ram_[1024];
private:
const uint8_t *rom_ptr_[2];
uint8_t *high_ram_ptr_;
uint8_t low_ram_[1024], high_ram_[1024];
};
}

View File

@ -13,30 +13,31 @@
namespace Atari2600::Cartridge {
class MegaBoy: public BusExtender {
public:
MegaBoy(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
current_page_(0) {
public:
MegaBoy(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size), rom_ptr_(rom_base), current_page_(0) {}
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff0) {
current_page_ = (current_page_ + 1) & 15;
rom_ptr_ = rom_base_ + current_page_ * 4096;
}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff0) {
current_page_ = (current_page_ + 1) & 15;
rom_ptr_ = rom_base_ + current_page_ * 4096;
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
uint8_t current_page_;
private:
const uint8_t *rom_ptr_;
uint8_t current_page_;
};
}

View File

@ -13,31 +13,36 @@
namespace Atari2600::Cartridge {
class ParkerBros: public BusExtender {
public:
ParkerBros(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + 4096;
rom_ptr_[1] = rom_ptr_[0] + 1024;
rom_ptr_[2] = rom_ptr_[1] + 1024;
rom_ptr_[3] = rom_ptr_[2] + 1024;
public:
ParkerBros(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size)
{
rom_ptr_[0] = rom_base + 4096;
rom_ptr_[1] = rom_ptr_[0] + 1024;
rom_ptr_[2] = rom_ptr_[1] + 1024;
rom_ptr_[3] = rom_ptr_[2] + 1024;
}
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value
) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address < 0x1ff8) {
int slot = (address >> 3)&3;
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address < 0x1ff8) {
int slot = (address >> 3)&3;
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
}
if(isReadOperation(operation)) {
*value = rom_ptr_[(address >> 10)&3][address & 1023];
}
if(isReadOperation(operation)) {
*value = rom_ptr_[(address >> 10)&3][address & 1023];
}
}
private:
uint8_t *rom_ptr_[4];
private:
const uint8_t *rom_ptr_[4];
};
}

View File

@ -11,114 +11,118 @@
namespace Atari2600::Cartridge {
class Pitfall2: public BusExtender {
public:
Pitfall2(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
public:
Pitfall2(const uint8_t *const rom_base, const std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void advance_cycles(int cycles) {
cycles_since_audio_update_ += cycles;
}
void advance_cycles(const int cycles) {
cycles_since_audio_update_ += cycles;
}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
uint16_t address,
uint8_t *const value)
{
address &= 0x1fff;
if(!(address & 0x1000)) return;
switch(address) {
switch(address) {
// MARK: - Reads
// The random number generator
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
if(isReadOperation(operation)) {
*value = random_number_generator_;
}
random_number_generator_ = uint8_t(
(random_number_generator_ << 1) |
(~( (random_number_generator_ >> 7) ^
(random_number_generator_ >> 5) ^
(random_number_generator_ >> 4) ^
(random_number_generator_ >> 3)
) & 1));
break;
// The random number generator
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
if(isReadOperation(operation)) {
*value = random_number_generator_;
}
random_number_generator_ = uint8_t(
(random_number_generator_ << 1) |
(~( (random_number_generator_ >> 7) ^
(random_number_generator_ >> 5) ^
(random_number_generator_ >> 4) ^
(random_number_generator_ >> 3)
) & 1));
break;
case 0x1005: case 0x1006: case 0x1007:
*value = update_audio();
break;
case 0x1005: case 0x1006: case 0x1007:
*value = update_audio();
break;
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
*value = rom_base_[8192 + address_for_counter(address & 7)];
break;
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
*value = rom_base_[8192 + address_for_counter(address & 7)];
break;
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
break;
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
break;
// MARK: - Writes
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
top_[address & 7] = *value;
break;
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
bottom_[address & 7] = *value;
break;
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
mask_[address & 7] = 0x00;
break;
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8);
break;
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
random_number_generator_ = 0;
break;
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
top_[address & 7] = *value;
break;
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
bottom_[address & 7] = *value;
break;
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
mask_[address & 7] = 0x00;
break;
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8);
break;
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
random_number_generator_ = 0;
break;
// MARK: - Paging
case 0x1ff8: rom_ptr_ = rom_base_; break;
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
case 0x1ff8: rom_ptr_ = rom_base_; break;
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
// MARK: - Business as usual
default:
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
break;
}
}
private:
inline uint16_t address_for_counter(int counter) {
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
featcher_address_[counter]--;
return fetch_address;
}
inline uint8_t update_audio() {
const unsigned int clock_divisor = 57;
int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral());
int table_position = 0;
for(int c = 0; c < 3; c++) {
audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]));
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
table_position |= 0x4 >> c;
default:
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
break;
}
}
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
return level_table[table_position];
private:
inline uint16_t address_for_counter(const int counter) {
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
featcher_address_[counter]--;
return fetch_address;
}
inline uint8_t update_audio() {
const unsigned int clock_divisor = 57;
int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral());
int table_position = 0;
for(int c = 0; c < 3; c++) {
audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]));
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
table_position |= 0x4 >> c;
}
}
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t random_number_generator_ = 0;
uint8_t *rom_ptr_;
uint8_t audio_channel_[3];
Cycles cycles_since_audio_update_ = 0;
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
return level_table[table_position];
}
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t random_number_generator_ = 0;
const uint8_t *rom_ptr_;
uint8_t audio_channel_[3];
Cycles cycles_since_audio_update_ = 0;
};
}

View File

@ -13,25 +13,28 @@
namespace Atari2600::Cartridge {
class Tigervision: public BusExtender {
public:
Tigervision(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
}
public:
Tigervision(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if((address&0x1fff) == 0x3f) {
int offset = ((*value) * 2048) & (rom_size_ - 1);
rom_ptr_[0] = rom_base_ + offset;
return;
} else if((address&0x1000) && isReadOperation(operation)) {
*value = rom_ptr_[(address >> 11)&1][address & 2047];
}
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
if((address&0x1fff) == 0x3f) {
int offset = ((*value) * 2048) & (rom_size_ - 1);
rom_ptr_[0] = rom_base_ + offset;
return;
} else if((address&0x1000) && isReadOperation(operation)) {
*value = rom_ptr_[(address >> 11)&1][address & 2047];
}
}
private:
uint8_t *rom_ptr_[2];
private:
const uint8_t *rom_ptr_[2];
};
}

View File

@ -13,14 +13,18 @@
namespace Atari2600::Cartridge {
class Unpaged: public BusExtender {
public:
Unpaged(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
public:
Unpaged(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(isReadOperation(operation) && (address & 0x1000)) {
*value = rom_base_[address & (rom_size_ - 1)];
}
void perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
if(isReadOperation(operation) && (address & 0x1000)) {
*value = rom_base_[address & (rom_size_ - 1)];
}
}
};
}

View File

@ -15,23 +15,20 @@
namespace Atari2600 {
class PIA: public MOS::MOS6532<PIA> {
public:
inline uint8_t get_port_input(int port) {
return port_values_[port];
}
public:
inline uint8_t get_port_input(const int port) {
return port_values_[port];
}
inline void update_port_input(int port, uint8_t mask, bool set) {
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
set_port_did_change(port);
}
inline void update_port_input(const int port, const uint8_t mask, const bool set) {
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
set_port_did_change(port);
}
PIA() :
port_values_{0xff, 0xff}
{}
private:
uint8_t port_values_[2];
PIA() : port_values_{0xff, 0xff} {}
private:
uint8_t port_values_[2];
};
}

View File

@ -19,296 +19,295 @@
namespace Atari2600 {
class TIA {
public:
TIA();
public:
TIA();
enum class OutputMode {
NTSC, PAL
};
enum class OutputMode {
NTSC, PAL
};
/*!
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
*/
void run_for(const Cycles cycles);
void set_output_mode(OutputMode output_mode);
/*!
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
*/
void run_for(const Cycles cycles);
void set_output_mode(OutputMode output_mode);
void set_sync(bool sync);
void set_blank(bool blank);
void reset_horizontal_counter(); // Reset is delayed by four cycles.
void set_sync(bool sync);
void set_blank(bool blank);
void reset_horizontal_counter(); // Reset is delayed by four cycles.
/*!
@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(const Cycles from_offset);
/*!
@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(const Cycles from_offset);
void set_background_colour(uint8_t colour);
void set_background_colour(uint8_t colour);
void set_playfield(uint16_t offset, uint8_t value);
void set_playfield_control_and_ball_size(uint8_t value);
void set_playfield_ball_colour(uint8_t colour);
void set_playfield(uint16_t offset, uint8_t value);
void set_playfield_control_and_ball_size(uint8_t value);
void set_playfield_ball_colour(uint8_t colour);
void set_player_number_and_size(int player, uint8_t value);
void set_player_graphic(int player, uint8_t value);
void set_player_reflected(int player, bool reflected);
void set_player_delay(int player, bool delay);
void set_player_position(int player);
void set_player_motion(int player, uint8_t motion);
void set_player_missile_colour(int player, uint8_t colour);
void set_player_number_and_size(int player, uint8_t value);
void set_player_graphic(int player, uint8_t value);
void set_player_reflected(int player, bool reflected);
void set_player_delay(int player, bool delay);
void set_player_position(int player);
void set_player_motion(int player, uint8_t motion);
void set_player_missile_colour(int player, uint8_t colour);
void set_missile_enable(int missile, bool enabled);
void set_missile_position(int missile);
void set_missile_position_to_player(int missile, bool lock);
void set_missile_motion(int missile, uint8_t motion);
void set_missile_enable(int missile, bool enabled);
void set_missile_position(int missile);
void set_missile_position_to_player(int missile, bool lock);
void set_missile_motion(int missile, uint8_t motion);
void set_ball_enable(bool enabled);
void set_ball_delay(bool delay);
void set_ball_position();
void set_ball_motion(uint8_t motion);
void set_ball_enable(bool enabled);
void set_ball_delay(bool delay);
void set_ball_position();
void set_ball_motion(uint8_t motion);
void move();
void clear_motion();
void move();
void clear_motion();
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
void set_crt_delegate(Outputs::CRT::Delegate *);
void set_scan_target(Outputs::Display::ScanTarget *);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_crt_delegate(Outputs::CRT::Delegate *);
void set_scan_target(Outputs::Display::ScanTarget *);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
private:
Outputs::CRT::CRT crt_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_ = 0;
// contains flags to indicate whether sync or blank are currently active
int output_mode_ = 0;
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
enum class CollisionType : uint8_t {
Playfield = (1 << 0),
Ball = (1 << 1),
Player0 = (1 << 2),
Player1 = (1 << 3),
Missile0 = (1 << 4),
Missile1 = (1 << 5)
};
int collision_flags_ = 0;
int collision_flags_by_buffer_vaules_[64];
// colour mapping tables
enum class ColourMode {
Standard = 0,
ScoreLeft,
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
enum class ColourIndex {
Background = 0,
PlayfieldBall,
PlayerMissile0,
PlayerMissile1
};
struct Colour {
uint16_t luminance_phase;
uint8_t original;
};
std::array<Colour, 4> colour_palette_;
void set_colour_palette_entry(size_t index, uint8_t colour);
OutputMode tv_standard_;
// playfield state
int background_half_mask_ = 0;
enum class PlayfieldPriority {
Standard,
Score,
OnTop
} playfield_priority_;
uint32_t background_[2] = {0, 0};
// contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
// objects
template<class T> struct Object {
// the two programmer-set values
int position = 0;
int motion = 0;
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
int motion_step = 0;
int motion_time = 0;
// indicates whether this object is currently undergoing motion
bool is_moving = false;
};
// player state
struct Player: public Object<Player> {
int adder = 4;
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
int reverse_mask = false; // 7 for a reflected player, 0 for normal
int graphic_index = 0;
int pixel_position = 32, pixel_counter = 0;
int latched_pixel4_time = -1;
const bool enqueues = true;
inline void skip_pixels(const int count, int from_horizontal_counter) {
int old_pixel_counter = pixel_counter;
pixel_position = std::min(32, pixel_position + count * adder);
pixel_counter += count;
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
}
}
inline void reset_pixels(int copy) {
pixel_position = pixel_counter = 0;
copy_index_ = copy;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
skip_pixels(count, from_horizontal_counter);
}
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
while(queue_read_pointer_ != queue_write_pointer_) {
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
if(queue_[queue_read_pointer_].end > time_now) {
const int length = time_now - queue_[queue_read_pointer_].start;
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
queue_[queue_read_pointer_].start = time_now;
return;
} else {
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
}
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
}
}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
queue_[queue_write_pointer_].start = start;
queue_[queue_write_pointer_].end = end;
queue_[queue_write_pointer_].pixel_position = pixel_position;
queue_[queue_write_pointer_].adder = adder;
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
skip_pixels(end - start, from_horizontal_counter);
}
private:
Outputs::CRT::CRT crt_;
int copy_index_ = 0;
struct QueuedPixels {
int start = 0, end = 0;
int pixel_position = 0;
int adder = 0;
int reverse_mask = false;
} queue_[4];
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_ = 0;
// contains flags to indicate whether sync or blank are currently active
int output_mode_ = 0;
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
enum class CollisionType : uint8_t {
Playfield = (1 << 0),
Ball = (1 << 1),
Player0 = (1 << 2),
Player1 = (1 << 3),
Missile0 = (1 << 4),
Missile1 = (1 << 5)
};
int collision_flags_ = 0;
int collision_flags_by_buffer_vaules_[64];
// colour mapping tables
enum class ColourMode {
Standard = 0,
ScoreLeft,
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
enum class ColourIndex {
Background = 0,
PlayfieldBall,
PlayerMissile0,
PlayerMissile1
};
struct Colour {
uint16_t luminance_phase;
uint8_t original;
};
std::array<Colour, 4> colour_palette_;
void set_colour_palette_entry(size_t index, uint8_t colour);
OutputMode tv_standard_;
// playfield state
int background_half_mask_ = 0;
enum class PlayfieldPriority {
Standard,
Score,
OnTop
} playfield_priority_;
uint32_t background_[2] = {0, 0};
// contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
// objects
template<class T> struct Object {
// the two programmer-set values
int position = 0;
int motion = 0;
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
int motion_step = 0;
int motion_time = 0;
// indicates whether this object is currently undergoing motion
bool is_moving = false;
};
// player state
struct Player: public Object<Player> {
int adder = 4;
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
int reverse_mask = false; // 7 for a reflected player, 0 for normal
int graphic_index = 0;
int pixel_position = 32, pixel_counter = 0;
int latched_pixel4_time = -1;
const bool enqueues = true;
inline void skip_pixels(const int count, int from_horizontal_counter) {
int old_pixel_counter = pixel_counter;
pixel_position = std::min(32, pixel_position + count * adder);
pixel_counter += count;
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
int output_cursor = 0;
while(output_pixel_position < 32 && output_cursor < count) {
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
output_cursor++;
output_pixel_position += output_adder;
}
}
} player_[2];
inline void reset_pixels(int copy) {
pixel_position = pixel_counter = 0;
copy_index_ = copy;
// common actor for things that appear as a horizontal run of pixels
struct HorizontalRun: public Object<HorizontalRun> {
int pixel_position = 0;
int size = 1;
const bool enqueues = false;
inline void skip_pixels(const int count, int) {
pixel_position = std::max(0, pixel_position - count);
}
inline void reset_pixels(int) {
pixel_position = size;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) {
int output_cursor = 0;
while(pixel_position && output_cursor < count)
{
target[output_cursor] |= collision_identity;
output_cursor++;
pixel_position--;
}
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {}
void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {}
};
// missile state
struct Missile: public HorizontalRun {
bool enabled = false;
bool locked_to_player = false;
int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled && !locked_to_player) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
} missile_[2];
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
while(queue_read_pointer_ != queue_write_pointer_) {
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
if(queue_[queue_read_pointer_].end > time_now) {
const int length = time_now - queue_[queue_read_pointer_].start;
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
queue_[queue_read_pointer_].start = time_now;
return;
} else {
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
}
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
}
// ball state
struct Ball: public HorizontalRun {
bool enabled[2] = {false, false};
int enabled_index = 0;
const int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled[enabled_index]) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
} ball_;
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
queue_[queue_write_pointer_].start = start;
queue_[queue_write_pointer_].end = end;
queue_[queue_write_pointer_].pixel_position = pixel_position;
queue_[queue_write_pointer_].adder = adder;
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
skip_pixels(end - start, from_horizontal_counter);
}
// motion
bool horizontal_blank_extend_ = false;
template<class T> void perform_border_motion(T &object, int start, int end);
template<class T> void perform_motion_step(T &object);
private:
int copy_index_ = 0;
struct QueuedPixels {
int start = 0, end = 0;
int pixel_position = 0;
int adder = 0;
int reverse_mask = false;
} queue_[4];
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
// drawing methods and state
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
inline void draw_playfield(int start, int end);
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
int output_cursor = 0;
while(output_pixel_position < 32 && output_cursor < count) {
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
output_cursor++;
output_pixel_position += output_adder;
}
}
inline void output_for_cycles(int number_of_cycles);
inline void output_line();
} player_[2];
// common actor for things that appear as a horizontal run of pixels
struct HorizontalRun: public Object<HorizontalRun> {
int pixel_position = 0;
int size = 1;
const bool enqueues = false;
inline void skip_pixels(const int count, int) {
pixel_position = std::max(0, pixel_position - count);
}
inline void reset_pixels(int) {
pixel_position = size;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) {
int output_cursor = 0;
while(pixel_position && output_cursor < count)
{
target[output_cursor] |= collision_identity;
output_cursor++;
pixel_position--;
}
}
void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {}
void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {}
};
// missile state
struct Missile: public HorizontalRun {
bool enabled = false;
bool locked_to_player = false;
int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled && !locked_to_player) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
} missile_[2];
// ball state
struct Ball: public HorizontalRun {
bool enabled[2] = {false, false};
int enabled_index = 0;
const int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled[enabled_index]) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
} ball_;
// motion
bool horizontal_blank_extend_ = false;
template<class T> void perform_border_motion(T &object, int start, int end);
template<class T> void perform_motion_step(T &object);
// drawing methods and state
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
inline void draw_playfield(int start, int end);
inline void output_for_cycles(int number_of_cycles);
inline void output_line();
int pixels_start_location_ = 0;
uint16_t *pixel_target_ = nullptr;
inline void output_pixels(int start, int end);
int pixels_start_location_ = 0;
uint16_t *pixel_target_ = nullptr;
inline void output_pixels(int start, int end);
};
}

View File

@ -18,32 +18,32 @@ namespace Atari2600 {
constexpr int CPUTicksPerAudioTick = 2;
class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
public:
TIASound(Concurrency::AsyncTaskQueue<false> &audio_queue);
public:
TIASound(Concurrency::AsyncTaskQueue<false> &);
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
// To satisfy ::SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
uint8_t volume_[2];
uint8_t divider_[2];
uint8_t control_[2];
uint8_t volume_[2];
uint8_t divider_[2];
uint8_t control_[2];
int poly4_counter_[2];
int poly5_counter_[2];
int poly9_counter_[2];
int output_state_[2];
int poly4_counter_[2];
int poly5_counter_[2];
int poly9_counter_[2];
int output_state_[2];
int divider_counter_[2];
int16_t per_channel_volume_ = 0;
int divider_counter_[2];
int16_t per_channel_volume_ = 0;
};
}

View File

@ -34,75 +34,75 @@ namespace Coleco {
namespace Vision {
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input('0'), Input('1'), Input('2'),
Input('3'), Input('4'), Input('5'),
Input('6'), Input('7'), Input('8'),
Input('9'), Input('*'), Input('#'),
}) {}
Input('0'), Input('1'), Input('2'),
Input('3'), Input('4'), Input('5'),
Input('6'), Input('7'), Input('8'),
Input('9'), Input('*'), Input('#'),
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
case Input::Key:
if(!is_active) keypad_ |= 0xf;
else {
uint8_t mask = 0xf;
switch(digital_input.info.key.symbol) {
case '8': mask = 0x1; break;
case '4': mask = 0x2; break;
case '5': mask = 0x3; break;
case '7': mask = 0x5; break;
case '#': mask = 0x6; break;
case '2': mask = 0x7; break;
case '*': mask = 0x9; break;
case '0': mask = 0xa; break;
case '9': mask = 0xb; break;
case '3': mask = 0xc; break;
case '1': mask = 0xd; break;
case '6': mask = 0xe; break;
default: break;
}
keypad_ = (keypad_ & 0xf0) | mask;
}
break;
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case Input::Fire:
switch(digital_input.info.control.index) {
case Input::Key:
if(!is_active) keypad_ |= 0xf;
else {
uint8_t mask = 0xf;
switch(digital_input.info.key.symbol) {
case '8': mask = 0x1; break;
case '4': mask = 0x2; break;
case '5': mask = 0x3; break;
case '7': mask = 0x5; break;
case '#': mask = 0x6; break;
case '2': mask = 0x7; break;
case '*': mask = 0x9; break;
case '0': mask = 0xa; break;
case '9': mask = 0xb; break;
case '3': mask = 0xc; break;
case '1': mask = 0xd; break;
case '6': mask = 0xe; break;
default: break;
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
}
break;
}
}
keypad_ = (keypad_ & 0xf0) | mask;
}
break;
uint8_t get_direction_input() {
return direction_;
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case Input::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
}
break;
}
}
uint8_t get_keypad_input() {
return keypad_;
}
uint8_t get_direction_input() {
return direction_;
}
private:
uint8_t direction_ = 0xff;
uint8_t keypad_ = 0x7f;
uint8_t get_keypad_input() {
return keypad_;
}
private:
uint8_t direction_ = 0xff;
uint8_t keypad_ = 0x7f;
};
class ConcreteMachine:
@ -114,297 +114,297 @@ class ConcreteMachine:
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine {
public:
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
mixer_(sn76489_, ay_),
speaker_(mixer_) {
speaker_.set_input_rate(3579545.0f / float(sn76489_divider));
set_clock_rate(3579545);
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
public:
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
mixer_(sn76489_, ay_),
speaker_(mixer_) {
speaker_.set_input_rate(3579545.0f / float(sn76489_divider));
set_clock_rate(3579545);
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
bios_ = roms.find(rom_name)->second;
if(!target.media.cartridges.empty()) {
const auto &segment = target.media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
if(cartridge_.size() >= 32768)
cartridge_address_limit_ = 0xffff;
else
cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
// Ensure the cartrige is a multiple of 16kb in size, as that won't
// be checked when paging.
const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384;
cartridge_.resize(cartridge_.size() + extension);
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
// Ensure at least 32kb is allocated to the cartrige so that
// reads are never out of bounds.
cartridge_.resize(32768);
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
bios_ = roms.find(rom_name)->second;
if(!target.media.cartridges.empty()) {
const auto &segment = target.media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
if(cartridge_.size() >= 32768)
cartridge_address_limit_ = 0xffff;
else
cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
// Ensure the cartrige is a multiple of 16kb in size, as that won't
// be checked when paging.
const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384;
cartridge_.resize(cartridge_.size() + extension);
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
// Ensure at least 32kb is allocated to the cartrige so that
// reads are never out of bounds.
cartridge_.resize(32768);
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
}
// ColecoVisions have composite output only.
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
}
~ConcreteMachine() {
audio_queue_.flush();
// ColecoVisions have composite output only.
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
}
~ConcreteMachine() {
audio_queue_.flush();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return vdp_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// The SN76489 will use its ready line to trigger the Z80's wait, which will add
// thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle.
// This code works out the delay up front in order to simplify execution flow, though
// technically this is a little duplicative.
HalfCycles penalty(0);
if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) {
penalty = HalfCycles(62);
} else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) {
penalty = HalfCycles(2);
}
const HalfCycles length = cycle.length + penalty;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
if(vdp_ += length) {
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
}
time_since_sn76489_update_ += length;
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return vdp_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// The SN76489 will use its ready line to trigger the Z80's wait, which will add
// thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle.
// This code works out the delay up front in order to simplify execution flow, though
// technically this is a little duplicative.
HalfCycles penalty(0);
if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) {
penalty = HalfCycles(62);
} else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) {
penalty = HalfCycles(2);
}
const HalfCycles length = cycle.length + penalty;
if(vdp_ += length) {
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
}
time_since_sn76489_update_ += length;
// Act only if necessary.
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
*cycle.value = super_game_module_.ram[address];
} else {
*cycle.value = bios_[address];
}
} else if(super_game_module_.replace_ram && address < 0x8000) {
// Act only if necessary.
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else {
*cycle.value = 0xff;
*cycle.value = bios_[address];
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(super_game_module_.replace_bios && address < 0x2000) {
super_game_module_.ram[address] = *cycle.value;
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
} else if(super_game_module_.replace_ram && address < 0x8000) {
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
break;
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else {
*cycle.value = 0xff;
}
break;
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
*cycle.value = vdp_->read(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
break;
case CPU::Z80::PartialMachineCycle::Write:
if(super_game_module_.replace_bios && address < 0x2000) {
super_game_module_.ram[address] = *cycle.value;
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
break;
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
*cycle.value = vdp_->read(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
break;
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break;
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
default:
switch(address&0xff) {
default: *cycle.value = 0xff; break;
case 0x52:
// Read AY data.
update_audio();
*cycle.value = GI::AY38910::Utility::read(ay_);
break;
}
break;
}
break;
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
default:
switch(address&0xff) {
default: *cycle.value = 0xff; break;
case 0x52:
// Read AY data.
update_audio();
*cycle.value = GI::AY38910::Utility::read(ay_);
break;
}
break;
}
break;
case 5:
vdp_->write(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
case 7:
update_audio();
sn76489_.write(*cycle.value);
break;
case 5:
vdp_->write(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
break;
default:
// Catch Super Game Module accesses; it decodes more thoroughly.
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
break;
case 0x51:
// Set AY data.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
case 7:
update_audio();
sn76489_.write(*cycle.value);
break;
default: break;
}
}
default:
// Catch Super Game Module accesses; it decodes more thoroughly.
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
break;
case 0x51:
// Set AY data.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
return penalty;
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
}
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
default: break;
}
}
float get_confidence() final {
if(pc_zero_accesses_ > 1) return 0.0f;
return confidence_counter_.get_confidence();
return penalty;
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
float get_confidence() final {
if(pc_zero_accesses_ > 1) return 0.0f;
return confidence_counter_.get_confidence();
}
private:
inline void page_megacart(uint16_t address) {
const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size();
cartridge_pages_[1] = &cartridge_[selected_start];
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
private:
inline void page_megacart(uint16_t address) {
const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size();
cartridge_pages_[1] = &cartridge_[selected_start];
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
}
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;
uint8_t *cartridge_pages_[2];
uint8_t ram_[1024];
bool is_megacart_ = false;
uint16_t cartridge_address_limit_ = 0;
struct {
bool replace_bios = false;
bool replace_ram = false;
uint8_t ram[32768];
} super_game_module_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool joysticks_in_keypad_mode_ = false;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
HalfCycles time_since_sn76489_update_;
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;
uint8_t *cartridge_pages_[2];
uint8_t ram_[1024];
bool is_megacart_ = false;
uint16_t cartridge_address_limit_ = 0;
struct {
bool replace_bios = false;
bool replace_ram = false;
uint8_t ram[32768];
} super_game_module_;
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
int pc_zero_accesses_ = 0;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool joysticks_in_keypad_mode_ = false;
HalfCycles time_since_sn76489_update_;
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
int pc_zero_accesses_ = 0;
};
}

View File

@ -112,43 +112,43 @@ class KeyboardMachine: public KeyActions {
allowing automatic mapping from keyboard inputs to KeyActions.
*/
class MappedKeyboardMachine: public Inputs::Keyboard::Delegate, public KeyboardMachine {
public:
MappedKeyboardMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
public:
MappedKeyboardMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
/*!
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
See the character mapper for logical mapping.
*/
class KeyboardMapper {
public:
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0;
};
/*!
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
See the character mapper for logical mapping.
*/
class KeyboardMapper {
public:
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0;
};
/// Terminates a key sequence from the character mapper.
static constexpr uint16_t KeyEndSequence = 0xffff;
/// Terminates a key sequence from the character mapper.
static constexpr uint16_t KeyEndSequence = 0xffff;
/*!
Indicates that a key is not mapped (for the keyboard mapper) or that a
character cannot be typed (for the character mapper).
*/
static constexpr uint16_t KeyNotMapped = 0xfffe;
/*!
Indicates that a key is not mapped (for the keyboard mapper) or that a
character cannot be typed (for the character mapper).
*/
static constexpr uint16_t KeyNotMapped = 0xfffe;
/*!
Allows individual machines to provide the mapping between host keys
as per Inputs::Keyboard and their native scheme.
*/
virtual KeyboardMapper *get_keyboard_mapper();
/*!
Allows individual machines to provide the mapping between host keys
as per Inputs::Keyboard and their native scheme.
*/
virtual KeyboardMapper *get_keyboard_mapper();
/*!
Provides a keyboard that obtains this machine's keyboard mapper, maps
the key and supplies it via the KeyActions.
*/
virtual Inputs::Keyboard &get_keyboard() override;
/*!
Provides a keyboard that obtains this machine's keyboard mapper, maps
the key and supplies it via the KeyActions.
*/
virtual Inputs::Keyboard &get_keyboard() override;
private:
bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
void reset_all_keys(Inputs::Keyboard *keyboard) override;
Inputs::Keyboard keyboard_;
private:
bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
void reset_all_keys(Inputs::Keyboard *keyboard) override;
Inputs::Keyboard keyboard_;
};
}

View File

@ -23,77 +23,77 @@ namespace MachineTypes {
*/
class TimedMachine {
public:
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(int(cycles)));
public:
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(int(cycles)));
}
/*!
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
fiction: it will apply across the system, including to the CRT.
*/
virtual void set_speed_multiplier(double multiplier) {
if(speed_multiplier_ == multiplier) {
return;
}
/*!
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
fiction: it will apply across the system, including to the CRT.
*/
virtual void set_speed_multiplier(double multiplier) {
if(speed_multiplier_ == multiplier) {
return;
}
speed_multiplier_ = multiplier;
speed_multiplier_ = multiplier;
auto audio_producer = dynamic_cast<AudioProducer *>(this);
if(!audio_producer) return;
auto audio_producer = dynamic_cast<AudioProducer *>(this);
if(!audio_producer) return;
auto speaker = audio_producer->get_speaker();
if(speaker) {
speaker->set_input_rate_multiplier(float(multiplier));
}
auto speaker = audio_producer->get_speaker();
if(speaker) {
speaker->set_input_rate_multiplier(float(multiplier));
}
}
/*!
@returns The current speed multiplier.
*/
virtual double get_speed_multiplier() const {
return speed_multiplier_;
}
/*!
@returns The current speed multiplier.
*/
virtual double get_speed_multiplier() const {
return speed_multiplier_;
}
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual std::string debug_type() { return ""; }
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual std::string debug_type() { return ""; }
struct Output {
static constexpr int Video = 1 << 0;
static constexpr int Audio = 1 << 1;
struct Output {
static constexpr int Video = 1 << 0;
static constexpr int Audio = 1 << 1;
static constexpr int All = Video | Audio;
};
/// Ensures all locally-buffered output is posted onward for the types of output indicated
/// by the bitfield argument, which is comprised of flags from the namespace @c Output.
virtual void flush_output(int) {}
static constexpr int All = Video | Audio;
};
/// Ensures all locally-buffered output is posted onward for the types of output indicated
/// by the bitfield argument, which is comprised of flags from the namespace @c Output.
virtual void flush_output(int) {}
protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
/// Sets this machine's clock rate.
void set_clock_rate(double clock_rate) {
clock_rate_ = clock_rate;
}
/// Sets this machine's clock rate.
void set_clock_rate(double clock_rate) {
clock_rate_ = clock_rate;
}
/// Gets this machine's clock rate.
double get_clock_rate() const {
return clock_rate_;
}
/// Gets this machine's clock rate.
double get_clock_rate() const {
return clock_rate_;
}
private:
// Give the ScanProducer access to this machine's clock rate.
friend class ScanProducer;
private:
// Give the ScanProducer access to this machine's clock rate.
friend class ScanProducer;
double clock_rate_ = 1.0;
double clock_conversion_error_ = 0.0;
double speed_multiplier_ = 1.0;
double clock_rate_ = 1.0;
double clock_conversion_error_ = 0.0;
double speed_multiplier_ = 1.0;
};
}

View File

@ -112,8 +112,8 @@ struct TestProcessor: public CPU::MC68000::BusHandler {
}
}
private:
int instructions_remaining_;
private:
int instructions_remaining_;
};
}

View File

@ -34,377 +34,377 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
using Flags = InstructionSet::x86::Flags;
struct Registers {
public:
// static constexpr bool is_32bit = false;
public:
// static constexpr bool is_32bit = false;
uint8_t &al() { return ax_.halves.low; }
uint8_t &ah() { return ax_.halves.high; }
uint16_t &ax() { return ax_.full; }
uint8_t &al() { return ax_.halves.low; }
uint8_t &ah() { return ax_.halves.high; }
uint16_t &ax() { return ax_.full; }
CPU::RegisterPair16 &axp() { return ax_; }
CPU::RegisterPair16 &axp() { return ax_; }
uint8_t &cl() { return cx_.halves.low; }
uint8_t &ch() { return cx_.halves.high; }
uint16_t &cx() { return cx_.full; }
uint8_t &cl() { return cx_.halves.low; }
uint8_t &ch() { return cx_.halves.high; }
uint16_t &cx() { return cx_.full; }
uint8_t &dl() { return dx_.halves.low; }
uint8_t &dh() { return dx_.halves.high; }
uint16_t &dx() { return dx_.full; }
uint8_t &dl() { return dx_.halves.low; }
uint8_t &dh() { return dx_.halves.high; }
uint16_t &dx() { return dx_.full; }
uint8_t &bl() { return bx_.halves.low; }
uint8_t &bh() { return bx_.halves.high; }
uint16_t &bx() { return bx_.full; }
uint8_t &bl() { return bx_.halves.low; }
uint8_t &bh() { return bx_.halves.high; }
uint16_t &bx() { return bx_.full; }
uint16_t &sp() { return sp_; }
uint16_t &bp() { return bp_; }
uint16_t &si() { return si_; }
uint16_t &di() { return di_; }
uint16_t &sp() { return sp_; }
uint16_t &bp() { return bp_; }
uint16_t &si() { return si_; }
uint16_t &di() { return di_; }
uint16_t &ip() { return ip_; }
uint16_t &ip() { return ip_; }
uint16_t &es() { return es_; }
uint16_t &cs() { return cs_; }
uint16_t &ds() { return ds_; }
uint16_t &ss() { return ss_; }
uint16_t &es() { return es_; }
uint16_t &cs() { return cs_; }
uint16_t &ds() { return ds_; }
uint16_t &ss() { return ss_; }
const uint16_t es() const { return es_; }
const uint16_t cs() const { return cs_; }
const uint16_t ds() const { return ds_; }
const uint16_t ss() const { return ss_; }
const uint16_t es() const { return es_; }
const uint16_t cs() const { return cs_; }
const uint16_t ds() const { return ds_; }
const uint16_t ss() const { return ss_; }
bool operator ==(const Registers &rhs) const {
return
ax_.full == rhs.ax_.full &&
cx_.full == rhs.cx_.full &&
dx_.full == rhs.dx_.full &&
bx_.full == rhs.bx_.full &&
sp_ == rhs.sp_ &&
bp_ == rhs.bp_ &&
si_ == rhs.si_ &&
di_ == rhs.di_ &&
es_ == rhs.es_ &&
cs_ == rhs.cs_ &&
ds_ == rhs.ds_ &&
si_ == rhs.si_ &&
ip_ == rhs.ip_;
}
bool operator ==(const Registers &rhs) const {
return
ax_.full == rhs.ax_.full &&
cx_.full == rhs.cx_.full &&
dx_.full == rhs.dx_.full &&
bx_.full == rhs.bx_.full &&
sp_ == rhs.sp_ &&
bp_ == rhs.bp_ &&
si_ == rhs.si_ &&
di_ == rhs.di_ &&
es_ == rhs.es_ &&
cs_ == rhs.cs_ &&
ds_ == rhs.ds_ &&
si_ == rhs.si_ &&
ip_ == rhs.ip_;
}
private:
CPU::RegisterPair16 ax_;
CPU::RegisterPair16 cx_;
CPU::RegisterPair16 dx_;
CPU::RegisterPair16 bx_;
private:
CPU::RegisterPair16 ax_;
CPU::RegisterPair16 cx_;
CPU::RegisterPair16 dx_;
CPU::RegisterPair16 bx_;
uint16_t sp_;
uint16_t bp_;
uint16_t si_;
uint16_t di_;
uint16_t es_, cs_, ds_, ss_;
uint16_t ip_;
uint16_t sp_;
uint16_t bp_;
uint16_t si_;
uint16_t di_;
uint16_t es_, cs_, ds_, ss_;
uint16_t ip_;
};
class Segments {
public:
Segments(const Registers &registers) : registers_(registers) {}
public:
Segments(const Registers &registers) : registers_(registers) {}
using Source = InstructionSet::x86::Source;
using Source = InstructionSet::x86::Source;
/// Posted by @c perform after any operation which *might* have affected a segment register.
void did_update(Source segment) {
switch(segment) {
default: break;
case Source::ES: es_base_ = registers_.es() << 4; break;
case Source::CS: cs_base_ = registers_.cs() << 4; break;
case Source::DS: ds_base_ = registers_.ds() << 4; break;
case Source::SS: ss_base_ = registers_.ss() << 4; break;
}
/// Posted by @c perform after any operation which *might* have affected a segment register.
void did_update(Source segment) {
switch(segment) {
default: break;
case Source::ES: es_base_ = registers_.es() << 4; break;
case Source::CS: cs_base_ = registers_.cs() << 4; break;
case Source::DS: ds_base_ = registers_.ds() << 4; break;
case Source::SS: ss_base_ = registers_.ss() << 4; break;
}
}
void reset() {
did_update(Source::ES);
did_update(Source::CS);
did_update(Source::DS);
did_update(Source::SS);
}
void reset() {
did_update(Source::ES);
did_update(Source::CS);
did_update(Source::DS);
did_update(Source::SS);
}
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
bool operator ==(const Segments &rhs) const {
return
es_base_ == rhs.es_base_ &&
cs_base_ == rhs.cs_base_ &&
ds_base_ == rhs.ds_base_ &&
ss_base_ == rhs.ss_base_;
}
bool operator ==(const Segments &rhs) const {
return
es_base_ == rhs.es_base_ &&
cs_base_ == rhs.cs_base_ &&
ds_base_ == rhs.ds_base_ &&
ss_base_ == rhs.ss_base_;
}
private:
const Registers &registers_;
private:
const Registers &registers_;
};
struct Memory {
public:
using AccessType = InstructionSet::x86::AccessType;
public:
using AccessType = InstructionSet::x86::AccessType;
// Constructor.
Memory(Registers &registers, const Segments &segments) : registers_(registers), segments_(segments) {
memory.resize(1024*1024);
// Constructor.
Memory(Registers &registers, const Segments &segments) : registers_(registers), segments_(segments) {
memory.resize(1024*1024);
}
// Initialisation.
void clear() {
tags.clear();
}
void seed(uint32_t address, uint8_t value) {
memory[address] = value;
tags[address] = Tag::Seeded;
}
void touch(uint32_t address) {
tags[address] = Tag::AccessExpected;
}
//
// Preauthorisation call-ins.
//
void preauthorise_stack_write(uint32_t length) {
uint16_t sp = registers_.sp();
while(length--) {
--sp;
preauthorise(InstructionSet::x86::Source::SS, sp);
}
// Initialisation.
void clear() {
tags.clear();
}
void preauthorise_stack_read(uint32_t length) {
uint16_t sp = registers_.sp();
while(length--) {
preauthorise(InstructionSet::x86::Source::SS, sp);
++sp;
}
void seed(uint32_t address, uint8_t value) {
memory[address] = value;
tags[address] = Tag::Seeded;
}
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
while(length--) {
preauthorise(segment, start);
++start;
}
void touch(uint32_t address) {
tags[address] = Tag::AccessExpected;
}
void preauthorise_read(uint32_t start, uint32_t length) {
while(length--) {
preauthorise(start);
++start;
}
}
//
// Preauthorisation call-ins.
//
void preauthorise_stack_write(uint32_t length) {
uint16_t sp = registers_.sp();
while(length--) {
--sp;
preauthorise(InstructionSet::x86::Source::SS, sp);
//
// Access call-ins.
//
// Accesses an address based on segment:offset.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
return access<IntT, type>(segment, offset, Tag::Accessed);
}
// Accesses an address based on physical location.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
return access<IntT, type>(address, Tag::Accessed);
}
template <typename IntT>
void write_back() {
if constexpr (std::is_same_v<IntT, uint16_t>) {
if(write_back_address_[0] != NoWriteBack) {
memory[write_back_address_[0]] = write_back_value_ & 0xff;
memory[write_back_address_[1]] = write_back_value_ >> 8;
write_back_address_[0] = 0;
}
}
void preauthorise_stack_read(uint32_t length) {
uint16_t sp = registers_.sp();
while(length--) {
preauthorise(InstructionSet::x86::Source::SS, sp);
++sp;
}
}
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
while(length--) {
preauthorise(segment, start);
++start;
}
}
void preauthorise_read(uint32_t start, uint32_t length) {
while(length--) {
preauthorise(start);
++start;
}
}
//
// Direct write.
//
template <typename IntT>
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
if(!test_preauthorisation(address(segment, offset))) {
printf("Non-preauthorised access\n");
}
//
// Access call-ins.
//
// Accesses an address based on segment:offset.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
return access<IntT, type>(segment, offset, Tag::Accessed);
// Bytes can be written without further ado.
if constexpr (std::is_same_v<IntT, uint8_t>) {
memory[address(segment, offset) & 0xf'ffff] = value;
return;
}
// Accesses an address based on physical location.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
return access<IntT, type>(address, Tag::Accessed);
// Words that straddle the segment end must be split in two.
if(offset == 0xffff) {
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
return;
}
template <typename IntT>
void write_back() {
if constexpr (std::is_same_v<IntT, uint16_t>) {
if(write_back_address_[0] != NoWriteBack) {
memory[write_back_address_[0]] = write_back_value_ & 0xff;
memory[write_back_address_[1]] = write_back_value_ >> 8;
write_back_address_[0] = 0;
}
}
const uint32_t target = address(segment, offset) & 0xf'ffff;
// Words that straddle the end of physical RAM must also be split in two.
if(target == 0xf'ffff) {
memory[0xf'ffff] = value & 0xff;
memory[0x0'0000] = value >> 8;
return;
}
//
// Direct write.
//
template <typename IntT>
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
if(!test_preauthorisation(address(segment, offset))) {
printf("Non-preauthorised access\n");
}
// It's safe just to write then.
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
}
// Bytes can be written without further ado.
if constexpr (std::is_same_v<IntT, uint8_t>) {
memory[address(segment, offset) & 0xf'ffff] = value;
return;
}
private:
enum class Tag {
Seeded,
AccessExpected,
Accessed,
};
// Words that straddle the segment end must be split in two.
std::unordered_set<uint32_t> preauthorisations;
std::unordered_map<uint32_t, Tag> tags;
std::vector<uint8_t> memory;
Registers &registers_;
const Segments &segments_;
void preauthorise(uint32_t address) {
preauthorisations.insert(address);
}
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
preauthorise((segment_base(segment) + address) & 0xf'ffff);
}
bool test_preauthorisation(uint32_t address) {
auto authorisation = preauthorisations.find(address);
if(authorisation == preauthorisations.end()) {
return false;
}
preauthorisations.erase(authorisation);
return true;
}
uint32_t segment_base(InstructionSet::x86::Source segment) {
using Source = InstructionSet::x86::Source;
switch(segment) {
default: return segments_.ds_base_;
case Source::ES: return segments_.es_base_;
case Source::CS: return segments_.cs_base_;
case Source::SS: return segments_.ss_base_;
}
}
uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) {
return (segment_base(segment) + offset) & 0xf'ffff;
}
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
// so that defined-flag-only masks can be applied while verifying RAM contents.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
const uint32_t physical_address = address(segment, offset);
if constexpr (std::is_same_v<IntT, uint16_t>) {
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
// to the start. So the 16-bit value will need to be a local cache.
if(offset == 0xffff) {
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
return;
}
const uint32_t target = address(segment, offset) & 0xf'ffff;
// Words that straddle the end of physical RAM must also be split in two.
if(target == 0xf'ffff) {
memory[0xf'ffff] = value & 0xff;
memory[0x0'0000] = value >> 8;
return;
}
// It's safe just to write then.
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
}
private:
enum class Tag {
Seeded,
AccessExpected,
Accessed,
};
std::unordered_set<uint32_t> preauthorisations;
std::unordered_map<uint32_t, Tag> tags;
std::vector<uint8_t> memory;
Registers &registers_;
const Segments &segments_;
void preauthorise(uint32_t address) {
preauthorisations.insert(address);
}
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
preauthorise((segment_base(segment) + address) & 0xf'ffff);
}
bool test_preauthorisation(uint32_t address) {
auto authorisation = preauthorisations.find(address);
if(authorisation == preauthorisations.end()) {
return false;
}
preauthorisations.erase(authorisation);
return true;
}
uint32_t segment_base(InstructionSet::x86::Source segment) {
using Source = InstructionSet::x86::Source;
switch(segment) {
default: return segments_.ds_base_;
case Source::ES: return segments_.es_base_;
case Source::CS: return segments_.cs_base_;
case Source::SS: return segments_.ss_base_;
return split_word<type>(physical_address, address(segment, 0), tag);
}
}
uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) {
return (segment_base(segment) + offset) & 0xf'ffff;
}
return access<IntT, type>(physical_address, tag);
}
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
// so that defined-flag-only masks can be applied while verifying RAM contents.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
const uint32_t physical_address = address(segment, offset);
if constexpr (std::is_same_v<IntT, uint16_t>) {
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
// to the start. So the 16-bit value will need to be a local cache.
if(offset == 0xffff) {
return split_word<type>(physical_address, address(segment, 0), tag);
}
}
return access<IntT, type>(physical_address, tag);
}
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
// to a segment, they're just at an absolute location.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
if constexpr (type == AccessType::PreauthorisedRead) {
if(!test_preauthorisation(address)) {
printf("Non preauthorised access\n");
}
}
for(size_t c = 0; c < sizeof(IntT); c++) {
tags[(address + c) & 0xf'ffff] = tag;
}
// Dispense with the single-byte case trivially.
if constexpr (std::is_same_v<IntT, uint8_t>) {
return memory[address];
} else if(address != 0xf'ffff) {
return *reinterpret_cast<IntT *>(&memory[address]);
} else {
return split_word<type>(address, 0, tag);
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
// to a segment, they're just at an absolute location.
template <typename IntT, AccessType type>
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
if constexpr (type == AccessType::PreauthorisedRead) {
if(!test_preauthorisation(address)) {
printf("Non preauthorised access\n");
}
}
template <AccessType type>
typename InstructionSet::x86::Accessor<uint16_t, type>::type
split_word(uint32_t low_address, uint32_t high_address, Tag tag) {
if constexpr (is_writeable(type)) {
write_back_address_[0] = low_address;
write_back_address_[1] = high_address;
tags[low_address] = tag;
tags[high_address] = tag;
// Prepopulate only if this is a modify.
if constexpr (type == AccessType::ReadModifyWrite) {
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
}
return write_back_value_;
} else {
return memory[low_address] | (memory[high_address] << 8);
}
for(size_t c = 0; c < sizeof(IntT); c++) {
tags[(address + c) & 0xf'ffff] = tag;
}
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
uint16_t write_back_value_;
// Dispense with the single-byte case trivially.
if constexpr (std::is_same_v<IntT, uint8_t>) {
return memory[address];
} else if(address != 0xf'ffff) {
return *reinterpret_cast<IntT *>(&memory[address]);
} else {
return split_word<type>(address, 0, tag);
}
}
template <AccessType type>
typename InstructionSet::x86::Accessor<uint16_t, type>::type
split_word(uint32_t low_address, uint32_t high_address, Tag tag) {
if constexpr (is_writeable(type)) {
write_back_address_[0] = low_address;
write_back_address_[1] = high_address;
tags[low_address] = tag;
tags[high_address] = tag;
// Prepopulate only if this is a modify.
if constexpr (type == AccessType::ReadModifyWrite) {
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
}
return write_back_value_;
} else {
return memory[low_address] | (memory[high_address] << 8);
}
}
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
uint16_t write_back_value_;
};
struct IO {
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); }
};
class FlowController {
public:
FlowController(Registers &registers, Segments &segments) :
registers_(registers), segments_(segments) {}
public:
FlowController(Registers &registers, Segments &segments) :
registers_(registers), segments_(segments) {}
// Requirements for perform.
template <typename AddressT>
void jump(AddressT address) {
static_assert(std::is_same_v<AddressT, uint16_t>);
registers_.ip() = address;
}
// Requirements for perform.
template <typename AddressT>
void jump(AddressT address) {
static_assert(std::is_same_v<AddressT, uint16_t>);
registers_.ip() = address;
}
template <typename AddressT>
void jump(uint16_t segment, AddressT address) {
static_assert(std::is_same_v<AddressT, uint16_t>);
registers_.cs() = segment;
segments_.did_update(Segments::Source::CS);
registers_.ip() = address;
}
template <typename AddressT>
void jump(uint16_t segment, AddressT address) {
static_assert(std::is_same_v<AddressT, uint16_t>);
registers_.cs() = segment;
segments_.did_update(Segments::Source::CS);
registers_.ip() = address;
}
void halt() {}
void wait() {}
void halt() {}
void wait() {}
void repeat_last() {
should_repeat_ = true;
}
void repeat_last() {
should_repeat_ = true;
}
// Other actions.
void begin_instruction() {
should_repeat_ = false;
}
bool should_repeat() const {
return should_repeat_;
}
// Other actions.
void begin_instruction() {
should_repeat_ = false;
}
bool should_repeat() const {
return should_repeat_;
}
private:
Registers &registers_;
Segments &segments_;
bool should_repeat_ = false;
private:
Registers &registers_;
Segments &segments_;
bool should_repeat_ = false;
};
struct ExecutionSupport {

View File

@ -17,15 +17,15 @@
#pragma mark - C++ delegate handlers
class MachineTrapHandler: public CPU::AllRAMProcessor::TrapHandler {
public:
MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {}
public:
MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {}
void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) {
[target_ testMachineDidTrapAtAddress:address];
}
void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) {
[target_ testMachineDidTrapAtAddress:address];
}
private:
CSTestMachine *target_;
private:
CSTestMachine *target_;
};
#pragma mark - The test machine

View File

@ -20,15 +20,15 @@
#pragma mark - C++ delegate handlers
class BusOperationHandler: public CPU::Z80::AllRAMProcessor::MemoryAccessDelegate {
public:
BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {}
public:
BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {}
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) {
[target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp];
}
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) {
[target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp];
}
private:
CSTestMachineZ80 *target_;
private:
CSTestMachineZ80 *target_;
};
#pragma mark - Register enum map

View File

@ -13,44 +13,44 @@
#include "68000.hpp"
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
public:
ComparativeBusHandler(const char *trace_name) {
trace = gzopen(trace_name, "rt");
public:
ComparativeBusHandler(const char *trace_name) {
trace = gzopen(trace_name, "rt");
}
~ComparativeBusHandler() {
gzclose(trace);
}
void will_perform(uint32_t address, uint16_t) {
// Obtain the next line from the trace file.
char correct_state[300] = "\n";
gzgets(trace, correct_state, sizeof(correct_state));
++line_count;
// Generate state locally.
const auto state = get_state().registers;
char local_state[300];
snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
address,
state.status,
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
state.stack_pointer()
);
// Check that the two coincide.
if(strcmp(correct_state, local_state)) {
fprintf(stderr, "Diverges at line %d\n", line_count);
fprintf(stderr, "Good: %s", correct_state);
fprintf(stderr, "Bad: %s", local_state);
throw std::exception();
}
}
~ComparativeBusHandler() {
gzclose(trace);
}
virtual CPU::MC68000::State get_state() = 0;
void will_perform(uint32_t address, uint16_t) {
// Obtain the next line from the trace file.
char correct_state[300] = "\n";
gzgets(trace, correct_state, sizeof(correct_state));
++line_count;
// Generate state locally.
const auto state = get_state().registers;
char local_state[300];
snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
address,
state.status,
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
state.stack_pointer()
);
// Check that the two coincide.
if(strcmp(correct_state, local_state)) {
fprintf(stderr, "Diverges at line %d\n", line_count);
fprintf(stderr, "Good: %s", correct_state);
fprintf(stderr, "Bad: %s", local_state);
throw std::exception();
}
}
virtual CPU::MC68000::State get_state() = 0;
private:
int line_count = 0;
gzFile trace;
private:
int line_count = 0;
gzFile trace;
};

View File

@ -18,82 +18,82 @@
#include "CSROMFetcher.hpp"
class EmuTOS: public ComparativeBusHandler {
public:
EmuTOS(const std::vector<uint8_t> &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
assert(!(emuTOS.size() & 1));
emuTOS_.resize(emuTOS.size() / 2);
public:
EmuTOS(const std::vector<uint8_t> &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
assert(!(emuTOS.size() & 1));
emuTOS_.resize(emuTOS.size() / 2);
for(size_t c = 0; c < emuTOS_.size(); ++c) {
emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1];
}
for(size_t c = 0; c < emuTOS_.size(); ++c) {
emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1];
}
}
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
CPU::MC68000::State get_state() final {
return m68000_.get_state();
}
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
// As much about the Atari ST's memory map as is relevant here: the ROM begins
// at 0xfc0000, and the first eight bytes are mirrored to the first four memory
// addresses in order for /RESET to work properly. RAM otherwise fills the first
// 512kb of the address space. Trying to write to ROM raises a bus error.
const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4;
const bool is_ram = word_address < ram_.size();
const bool is_peripheral = !is_rom && !is_ram;
uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data();
if(is_rom) {
word_address %= emuTOS_.size();
} else {
word_address %= ram_.size();
}
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
CPU::MC68000::State get_state() final {
return m68000_.get_state();
}
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
// As much about the Atari ST's memory map as is relevant here: the ROM begins
// at 0xfc0000, and the first eight bytes are mirrored to the first four memory
// addresses in order for /RESET to work properly. RAM otherwise fills the first
// 512kb of the address space. Trying to write to ROM raises a bus error.
const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4;
const bool is_ram = word_address < ram_.size();
const bool is_peripheral = !is_rom && !is_ram;
uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data();
if(is_rom) {
word_address %= emuTOS_.size();
} else {
word_address %= ram_.size();
}
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
if(is_peripheral) {
switch(address & 0x7ff) {
// A hard-coded value for TIMER B.
case (0xa21 >> 1):
peripheral_result = 0x00000001;
break;
}
}
using namespace CPU::MC68000;
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
default: break;
case Operation::SelectWord | Operation::Read:
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Operation::SelectByte | Operation::Read:
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Operation::SelectWord:
base[word_address] = cycle.value->w;
break;
case Operation::SelectByte:
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
if(is_peripheral) {
switch(address & 0x7ff) {
// A hard-coded value for TIMER B.
case (0xa21 >> 1):
peripheral_result = 0x00000001;
break;
}
}
return HalfCycles(0);
using namespace CPU::MC68000;
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
default: break;
case Operation::SelectWord | Operation::Read:
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Operation::SelectByte | Operation::Read:
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Operation::SelectWord:
base[word_address] = cycle.value->w;
break;
case Operation::SelectByte:
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
break;
}
}
private:
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
return HalfCycles(0);
}
std::vector<uint16_t> emuTOS_;
std::array<uint16_t, 256*1024> ram_;
private:
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
std::vector<uint16_t> emuTOS_;
std::array<uint16_t, 256*1024> ram_;
};
@interface EmuTOSTests : XCTestCase

View File

@ -21,73 +21,73 @@
#include "CSROMFetcher.hpp"
class QL: public ComparativeBusHandler {
public:
QL(const std::vector<uint8_t> &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
assert(!(rom.size() & 1));
rom_.resize(rom.size() / 2);
public:
QL(const std::vector<uint8_t> &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
assert(!(rom.size() & 1));
rom_.resize(rom.size() / 2);
for(size_t c = 0; c < rom_.size(); ++c) {
rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1];
for(size_t c = 0; c < rom_.size(); ++c) {
rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1];
}
}
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
CPU::MC68000::State get_state() final {
return m68000_.get_state();
}
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
// QL memory map: ROM is in the lowest area; RAM is from 0x20000.
const bool is_rom = word_address < rom_.size();
const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size();
const bool is_peripheral = !is_ram && !is_rom;
uint16_t *const base = is_rom ? rom_.data() : ram_.data();
if(is_rom) {
word_address %= rom_.size();
}
if(is_ram) {
word_address %= ram_.size();
}
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
using namespace CPU::MC68000;
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
default: break;
case Operation::SelectWord | Operation::Read:
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Operation::SelectByte | Operation::Read:
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Operation::SelectWord:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = cycle.value->w;
break;
case Operation::SelectByte:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
break;
}
}
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
return HalfCycles(0);
}
CPU::MC68000::State get_state() final {
return m68000_.get_state();
}
private:
CPU::MC68000::Processor<QL, true, false, true> m68000_;
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
// QL memory map: ROM is in the lowest area; RAM is from 0x20000.
const bool is_rom = word_address < rom_.size();
const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size();
const bool is_peripheral = !is_ram && !is_rom;
uint16_t *const base = is_rom ? rom_.data() : ram_.data();
if(is_rom) {
word_address %= rom_.size();
}
if(is_ram) {
word_address %= ram_.size();
}
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
using namespace CPU::MC68000;
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
default: break;
case Operation::SelectWord | Operation::Read:
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Operation::SelectByte | Operation::Read:
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Operation::SelectWord:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = cycle.value->w;
break;
case Operation::SelectByte:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
break;
}
}
return HalfCycles(0);
}
private:
CPU::MC68000::Processor<QL, true, false, true> m68000_;
std::vector<uint16_t> rom_;
std::array<uint16_t, 64*1024> ram_;
std::vector<uint16_t> rom_;
std::array<uint16_t, 64*1024> ram_;
};
@interface QLTests : XCTestCase

View File

@ -22,115 +22,115 @@ using namespace InstructionSet::M68k;
begin execution at 0x0400.
*/
class RAM68000: public CPU::MC68000::BusHandler {
public:
RAM68000() : m68000_(*this) {}
public:
RAM68000() : m68000_(*this) {}
uint32_t initial_pc() const {
return 0x1000;
uint32_t initial_pc() const {
return 0x1000;
}
void set_program(
const std::vector<uint16_t> &program,
uint32_t stack_pointer = 0x206
) {
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
// Ensure the condition codes start unset and set the initial program counter
// and supervisor stack pointer, as well as starting in supervisor mode.
auto registers = m68000_.get_state().registers;
registers.status = 0x2700;
registers.program_counter = initial_pc();
registers.supervisor_stack_pointer = stack_pointer;
m68000_.decode_from_state(registers);
}
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
auto state = m68000_.get_state();
func(state.registers);
m68000_.set_state(state);
}
void will_perform(uint32_t, uint16_t) {
--instructions_remaining_;
if(instructions_remaining_ < 0) {
throw StopException();
}
}
void set_program(
const std::vector<uint16_t> &program,
uint32_t stack_pointer = 0x206
) {
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
void run_for_instructions(int count) {
duration_ = HalfCycles(0);
instructions_remaining_ = count;
if(!instructions_remaining_) return;
// Ensure the condition codes start unset and set the initial program counter
// and supervisor stack pointer, as well as starting in supervisor mode.
auto registers = m68000_.get_state().registers;
registers.status = 0x2700;
registers.program_counter = initial_pc();
registers.supervisor_stack_pointer = stack_pointer;
m68000_.decode_from_state(registers);
}
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
auto state = m68000_.get_state();
func(state.registers);
m68000_.set_state(state);
}
void will_perform(uint32_t, uint16_t) {
--instructions_remaining_;
if(instructions_remaining_ < 0) {
throw StopException();
try {
while(true) {
run_for(HalfCycles(2000));
}
}
} catch (const StopException &) {}
}
void run_for_instructions(int count) {
duration_ = HalfCycles(0);
instructions_remaining_ = count;
if(!instructions_remaining_) return;
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
try {
while(true) {
run_for(HalfCycles(2000));
}
} catch (const StopException &) {}
}
uint16_t *ram_at(uint32_t address) {
return &ram_[(address >> 1) % ram_.size()];
}
void run_for(HalfCycles cycles) {
m68000_.run_for(cycles);
}
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t word_address = cycle.word_address();
duration_ += cycle.length;
uint16_t *ram_at(uint32_t address) {
return &ram_[(address >> 1) % ram_.size()];
}
if(cycle.data_select_active()) {
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
cycle.value->b = 10;
} else {
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
default: break;
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t word_address = cycle.word_address();
duration_ += cycle.length;
if(cycle.data_select_active()) {
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
cycle.value->b = 10;
} else {
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
default: break;
case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read:
cycle.value->w = ram_[word_address % ram_.size()];
break;
case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read:
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
break;
case CPU::MC68000::Operation::SelectWord:
ram_[word_address % ram_.size()] = cycle.value->w;
break;
case CPU::MC68000::Operation::SelectByte:
ram_[word_address % ram_.size()] = uint16_t(
(cycle.value->b << cycle.byte_shift()) |
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
);
break;
}
case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read:
cycle.value->w = ram_[word_address % ram_.size()];
break;
case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read:
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
break;
case CPU::MC68000::Operation::SelectWord:
ram_[word_address % ram_.size()] = cycle.value->w;
break;
case CPU::MC68000::Operation::SelectByte:
ram_[word_address % ram_.size()] = uint16_t(
(cycle.value->b << cycle.byte_shift()) |
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
);
break;
}
}
return HalfCycles(0);
}
CPU::MC68000::State get_processor_state() {
return m68000_.get_state();
}
return HalfCycles(0);
}
auto &processor() {
return m68000_;
}
CPU::MC68000::State get_processor_state() {
return m68000_.get_state();
}
int get_cycle_count() {
return int(duration_.as_integral()) >> 1;
}
auto &processor() {
return m68000_;
}
void reset_cycle_count() {
duration_ = HalfCycles(0);
}
int get_cycle_count() {
return int(duration_.as_integral()) >> 1;
}
private:
struct StopException {};
void reset_cycle_count() {
duration_ = HalfCycles(0);
}
CPU::MC68000::Processor<RAM68000, true, true, true> m68000_;
std::array<uint16_t, 256*1024> ram_{};
int instructions_remaining_;
HalfCycles duration_;
private:
struct StopException {};
CPU::MC68000::Processor<RAM68000, true, true, true> m68000_;
std::array<uint16_t, 256*1024> ram_{};
int instructions_remaining_;
HalfCycles duration_;
};

View File

@ -179,13 +179,13 @@ struct CapturingZ80: public CPU::Z80::BusHandler {
return contentions_48k_;
}
private:
CPU::Z80::Processor<CapturingZ80, false, false> z80_;
uint8_t ram_[65536];
uint16_t code_length_ = 0;
private:
CPU::Z80::Processor<CapturingZ80, false, false> z80_;
uint8_t ram_[65536];
uint16_t code_length_ = 0;
std::vector<BusRecord> bus_records_;
std::vector<ContentionCheck> contentions_48k_;
std::vector<BusRecord> bus_records_;
std::vector<ContentionCheck> contentions_48k_;
};
}

View File

@ -93,85 +93,85 @@ struct MachineRunner {
std::mutex *machine_mutex;
Machine::DynamicMachine *machine;
private:
SDL_TimerID timer_ = 0;
Time::Nanos last_time_ = 0;
std::atomic<Time::Nanos> vsync_time_;
std::atomic_flag frame_lock_;
private:
SDL_TimerID timer_ = 0;
Time::Nanos last_time_ = 0;
std::atomic<Time::Nanos> vsync_time_;
std::atomic_flag frame_lock_;
enum class State {
Running,
Stopping,
Stopped
};
std::atomic<State> state_{State::Running};
enum class State {
Running,
Stopping,
Stopped
};
std::atomic<State> state_{State::Running};
Time::ScanSynchroniser scan_synchroniser_;
Time::ScanSynchroniser scan_synchroniser_;
// A slightly clumsy means of trying to derive frame rate from calls to
// signal_vsync(); SDL_DisplayMode provides only an integral quantity
// whereas, empirically, it's fairly common for monitors to run at the
// NTSC-esque frame rates of 59.94Hz.
std::array<Time::Nanos, 32> frame_times_;
Time::Nanos frame_time_average_ = 0;
size_t frame_time_pointer_ = 0;
std::atomic<double> _frame_period;
// A slightly clumsy means of trying to derive frame rate from calls to
// signal_vsync(); SDL_DisplayMode provides only an integral quantity
// whereas, empirically, it's fairly common for monitors to run at the
// NTSC-esque frame rates of 59.94Hz.
std::array<Time::Nanos, 32> frame_times_;
Time::Nanos frame_time_average_ = 0;
size_t frame_time_pointer_ = 0;
std::atomic<double> _frame_period;
static constexpr Uint32 timer_period = 4;
static Uint32 sdl_callback(Uint32, void *param) {
reinterpret_cast<MachineRunner *>(param)->update();
return timer_period;
static constexpr Uint32 timer_period = 4;
static Uint32 sdl_callback(Uint32, void *param) {
reinterpret_cast<MachineRunner *>(param)->update();
return timer_period;
}
void update() {
// If a shutdown is in progress, signal stoppage and do nothing.
if(state_ != State::Running) {
state_ = State::Stopped;
return;
}
void update() {
// If a shutdown is in progress, signal stoppage and do nothing.
if(state_ != State::Running) {
state_ = State::Stopped;
return;
}
// Get time now and determine how long it has been since the last time this
// function was called. If it's more than half a second then forego any activity
// now, as there's obviously been some sort of substantial time glitch.
const auto time_now = Time::nanos_now();
if(time_now - last_time_ > Time::Nanos(500'000'000)) {
last_time_ = time_now - Time::Nanos(500'000'000);
}
const auto vsync_time = vsync_time_.load();
std::unique_lock lock_guard(*machine_mutex);
const auto scan_producer = machine->scan_producer();
const auto timed_machine = machine->timed_machine();
bool split_and_sync = false;
if(last_time_ < vsync_time && time_now >= vsync_time) {
split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period);
}
if(split_and_sync) {
timed_machine->run_for(double(vsync_time - last_time_) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
timed_machine->set_speed_multiplier(
scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status())
);
// This is a bit of an SDL ugliness; wait here until the next frame is drawn.
// That is, unless and until I can think of a good way of running background
// updates via a share group — possibly an extra intermediate buffer is needed?
lock_guard.unlock();
while(frame_lock_.test_and_set());
lock_guard.lock();
timed_machine->run_for(double(time_now - vsync_time) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
} else {
timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier());
timed_machine->run_for(double(time_now - last_time_) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
}
last_time_ = time_now;
// Get time now and determine how long it has been since the last time this
// function was called. If it's more than half a second then forego any activity
// now, as there's obviously been some sort of substantial time glitch.
const auto time_now = Time::nanos_now();
if(time_now - last_time_ > Time::Nanos(500'000'000)) {
last_time_ = time_now - Time::Nanos(500'000'000);
}
const auto vsync_time = vsync_time_.load();
std::unique_lock lock_guard(*machine_mutex);
const auto scan_producer = machine->scan_producer();
const auto timed_machine = machine->timed_machine();
bool split_and_sync = false;
if(last_time_ < vsync_time && time_now >= vsync_time) {
split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period);
}
if(split_and_sync) {
timed_machine->run_for(double(vsync_time - last_time_) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
timed_machine->set_speed_multiplier(
scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status())
);
// This is a bit of an SDL ugliness; wait here until the next frame is drawn.
// That is, unless and until I can think of a good way of running background
// updates via a share group — possibly an extra intermediate buffer is needed?
lock_guard.unlock();
while(frame_lock_.test_and_set());
lock_guard.lock();
timed_machine->run_for(double(time_now - vsync_time) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
} else {
timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier());
timed_machine->run_for(double(time_now - last_time_) / 1e9);
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
}
last_time_ = time_now;
}
};
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
@ -214,92 +214,92 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
};
class ActivityObserver: public Activity::Observer {
public:
ActivityObserver(Activity::Source *source, float aspect_ratio) {
// Get the suorce to supply all LEDs and drives.
source->set_activity_observer(this);
public:
ActivityObserver(Activity::Source *source, float aspect_ratio) {
// Get the suorce to supply all LEDs and drives.
source->set_activity_observer(this);
// The objective is to display drives on one side of the screen, other LEDs on the other. Drives
// may or may not have LEDs and this code intends to display only those which do; so a quick
// comparative processing of the two lists is called for.
// The objective is to display drives on one side of the screen, other LEDs on the other. Drives
// may or may not have LEDs and this code intends to display only those which do; so a quick
// comparative processing of the two lists is called for.
// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed.
drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) {
return std::find(leds_.begin(), leds_.end(), string) == leds_.end();
}) - drives_.begin());
// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed.
drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) {
return std::find(leds_.begin(), leds_.end(), string) == leds_.end();
}) - drives_.begin());
// Remove from the list of LEDs any which are drives. Those will be represented separately.
leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) {
return std::find(drives_.begin(), drives_.end(), string) != drives_.end();
}) - leds_.begin());
// Remove from the list of LEDs any which are drives. Those will be represented separately.
leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) {
return std::find(drives_.begin(), drives_.end(), string) != drives_.end();
}) - leds_.begin());
set_aspect_ratio(aspect_ratio);
set_aspect_ratio(aspect_ratio);
}
void set_aspect_ratio(float aspect_ratio) {
std::lock_guard lock_guard(mutex);
lights_.clear();
// Generate a bunch of LEDs for connected drives.
constexpr float height = 0.05f;
const float width = height / aspect_ratio;
const float right_x = 1.0f - 2.0f * width;
float y = 1.0f - 2.0f * height;
for(const auto &drive: drives_) {
lights_.emplace(drive, std::make_unique<Outputs::Display::OpenGL::Rectangle>(right_x, y, width, height));
y -= height * 2.0f;
}
void set_aspect_ratio(float aspect_ratio) {
std::lock_guard lock_guard(mutex);
lights_.clear();
/*
This would generate LEDs for things other than drives; I'm declining for now
due to the inexpressiveness of just painting a rectangle.
// Generate a bunch of LEDs for connected drives.
constexpr float height = 0.05f;
const float width = height / aspect_ratio;
const float right_x = 1.0f - 2.0f * width;
float y = 1.0f - 2.0f * height;
for(const auto &drive: drives_) {
lights_.emplace(drive, std::make_unique<Outputs::Display::OpenGL::Rectangle>(right_x, y, width, height));
const float left_x = -1.0f + 2.0f * width;
y = 1.0f - 2.0f * height;
for(const auto &led: leds_) {
lights_.emplace(led, std::make_unique<OpenGL::Rectangle>(left_x, y, width, height));
y -= height * 2.0f;
}
*/
}
/*
This would generate LEDs for things other than drives; I'm declining for now
due to the inexpressiveness of just painting a rectangle.
const float left_x = -1.0f + 2.0f * width;
y = 1.0f - 2.0f * height;
for(const auto &led: leds_) {
lights_.emplace(led, std::make_unique<OpenGL::Rectangle>(left_x, y, width, height));
y -= height * 2.0f;
}
*/
void draw() {
std::lock_guard lock_guard(mutex);
for(const auto &lit_led: lit_leds_) {
if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end())
lights_[lit_led]->draw(0.0, 0.8, 0.0);
}
blinking_leds_.clear();
}
void draw() {
std::lock_guard lock_guard(mutex);
for(const auto &lit_led: lit_leds_) {
if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end())
lights_[lit_led]->draw(0.0, 0.8, 0.0);
}
blinking_leds_.clear();
}
private:
std::vector<std::string> leds_;
void register_led(const std::string &name, uint8_t) final {
std::lock_guard lock_guard(mutex);
leds_.push_back(name);
}
private:
std::vector<std::string> leds_;
void register_led(const std::string &name, uint8_t) final {
std::lock_guard lock_guard(mutex);
leds_.push_back(name);
}
std::vector<std::string> drives_;
void register_drive(const std::string &name) final {
std::lock_guard lock_guard(mutex);
drives_.push_back(name);
}
std::vector<std::string> drives_;
void register_drive(const std::string &name) final {
std::lock_guard lock_guard(mutex);
drives_.push_back(name);
}
void set_led_status(const std::string &name, bool lit) final {
std::lock_guard lock_guard(mutex);
if(lit) lit_leds_.insert(name);
else lit_leds_.erase(name);
}
void set_led_status(const std::string &name, bool lit) final {
std::lock_guard lock_guard(mutex);
if(lit) lit_leds_.insert(name);
else lit_leds_.erase(name);
}
void announce_drive_event(const std::string &name, DriveEvent) final {
std::lock_guard lock_guard(mutex);
blinking_leds_.insert(name);
}
void announce_drive_event(const std::string &name, DriveEvent) final {
std::lock_guard lock_guard(mutex);
blinking_leds_.insert(name);
}
std::map<std::string, std::unique_ptr<Outputs::Display::OpenGL::Rectangle>> lights_;
std::set<std::string> lit_leds_;
std::set<std::string> blinking_leds_;
std::mutex mutex;
std::map<std::string, std::unique_ptr<Outputs::Display::OpenGL::Rectangle>> lights_;
std::set<std::string> lit_leds_;
std::set<std::string> blinking_leds_;
std::mutex mutex;
};
bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key) {
@ -465,31 +465,31 @@ std::string system_get(const char *command) {
Maintains a communicative window title.
*/
class DynamicWindowTitler {
public:
DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {}
public:
DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {}
std::string window_title() {
if(!mouse_is_captured_) return file_name_;
return file_name_ + " (press control+escape to release mouse)";
}
std::string window_title() {
if(!mouse_is_captured_) return file_name_;
return file_name_ + " (press control+escape to release mouse)";
}
void set_mouse_is_captured(bool is_captured) {
mouse_is_captured_ = is_captured;
update_window_title();
}
void set_mouse_is_captured(bool is_captured) {
mouse_is_captured_ = is_captured;
update_window_title();
}
void set_file_name(const std::string &name) {
file_name_ = name;
update_window_title();
}
void set_file_name(const std::string &name) {
file_name_ = name;
update_window_title();
}
private:
void update_window_title() {
SDL_SetWindowTitle(window_, window_title().c_str());
}
bool mouse_is_captured_ = false;
SDL_Window *window_ = nullptr;
std::string file_name_;
private:
void update_window_title() {
SDL_SetWindowTitle(window_, window_title().c_str());
}
bool mouse_is_captured_ = false;
SDL_Window *window_ = nullptr;
std::string file_name_;
};
/*!
@ -497,37 +497,37 @@ class DynamicWindowTitler {
of historic hat values.
*/
class SDLJoystick {
public:
SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) {
hat_values_.resize(SDL_JoystickNumHats(joystick));
}
public:
SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) {
hat_values_.resize(SDL_JoystickNumHats(joystick));
}
~SDLJoystick() {
SDL_JoystickClose(joystick_);
}
~SDLJoystick() {
SDL_JoystickClose(joystick_);
}
/// @returns The underlying SDL_Joystick.
SDL_Joystick *get() {
return joystick_;
}
/// @returns The underlying SDL_Joystick.
SDL_Joystick *get() {
return joystick_;
}
/// @returns A reference to the storage for the previous state of hat @c c.
Uint8 &last_hat_value(int c) {
return hat_values_[c];
}
/// @returns A reference to the storage for the previous state of hat @c c.
Uint8 &last_hat_value(int c) {
return hat_values_[c];
}
/// @returns The logic OR of all stored hat states.
Uint8 hat_values() {
Uint8 value = 0;
for(const auto hat_value: hat_values_) {
value |= hat_value;
}
return value;
/// @returns The logic OR of all stored hat states.
Uint8 hat_values() {
Uint8 value = 0;
for(const auto hat_value: hat_values_) {
value |= hat_value;
}
return value;
}
private:
SDL_Joystick *joystick_;
std::vector<Uint8> hat_values_;
private:
SDL_Joystick *joystick_;
std::vector<Uint8> hat_values_;
};
}

View File

@ -13,106 +13,106 @@ using namespace CPU::Z80;
namespace {
class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
public:
ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {}
inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) {
timestamp_ += cycle.length;
if(!cycle.is_terminal()) {
return HalfCycles(0);
}
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case PartialMachineCycle::ReadOpcode:
check_address_for_trap(address);
case PartialMachineCycle::Read:
*cycle.value = memory_[address];
break;
case PartialMachineCycle::Write:
memory_[address] = *cycle.value;
break;
case PartialMachineCycle::Output:
break;
case PartialMachineCycle::Input:
*cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff;
break;
case PartialMachineCycle::Internal:
case PartialMachineCycle::Refresh:
break;
case PartialMachineCycle::Interrupt:
// A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise
// arbitrary.
*cycle.value = 0x21;
break;
default:
break;
}
if(memory_delegate_ != nullptr) {
memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
}
public:
ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {}
inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) {
timestamp_ += cycle.length;
if(!cycle.is_terminal()) {
return HalfCycles(0);
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case PartialMachineCycle::ReadOpcode:
check_address_for_trap(address);
case PartialMachineCycle::Read:
*cycle.value = memory_[address];
break;
case PartialMachineCycle::Write:
memory_[address] = *cycle.value;
break;
case PartialMachineCycle::Output:
break;
case PartialMachineCycle::Input:
*cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff;
break;
case PartialMachineCycle::Internal:
case PartialMachineCycle::Refresh:
break;
case PartialMachineCycle::Interrupt:
// A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise
// arbitrary.
*cycle.value = 0x21;
break;
default:
break;
}
void run_for_instruction() final {
int toggles = 0;
int cycles = 0;
if(memory_delegate_ != nullptr) {
memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
}
// Run:
// (1) until is_starting_new_instruction is true;
// (2) until it is false again; and
// (3) until it is true again.
while(true) {
if(z80_.is_starting_new_instruction() != (toggles&1)) {
++toggles;
if(toggles == 3) break;
}
z80_.run_for(Cycles(1));
++cycles;
return HalfCycles(0);
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
void run_for_instruction() final {
int toggles = 0;
int cycles = 0;
// Run:
// (1) until is_starting_new_instruction is true;
// (2) until it is false again; and
// (3) until it is true again.
while(true) {
if(z80_.is_starting_new_instruction() != (toggles&1)) {
++toggles;
if(toggles == 3) break;
}
z80_.run_for(Cycles(1));
++cycles;
}
}
uint16_t value_of(Register r) final {
return z80_.value_of(r);
}
uint16_t value_of(Register r) final {
return z80_.value_of(r);
}
void set_value_of(Register r, uint16_t value) final {
z80_.set_value_of(r, value);
}
void set_value_of(Register r, uint16_t value) final {
z80_.set_value_of(r, value);
}
bool get_halt_line() final {
return z80_.get_halt_line();
}
bool get_halt_line() final {
return z80_.get_halt_line();
}
void reset_power_on() final {
return z80_.reset_power_on();
}
void reset_power_on() final {
return z80_.reset_power_on();
}
void set_interrupt_line(bool value) final {
z80_.set_interrupt_line(value);
}
void set_interrupt_line(bool value) final {
z80_.set_interrupt_line(value);
}
void set_non_maskable_interrupt_line(bool value) final {
z80_.set_non_maskable_interrupt_line(value);
}
void set_non_maskable_interrupt_line(bool value) final {
z80_.set_non_maskable_interrupt_line(value);
}
void set_wait_line(bool value) final {
z80_.set_wait_line(value);
}
void set_wait_line(bool value) final {
z80_.set_wait_line(value);
}
private:
CPU::Z80::Processor<ConcreteAllRAMProcessor, false, true> z80_;
bool was_m1_ = false;
private:
CPU::Z80::Processor<ConcreteAllRAMProcessor, false, true> z80_;
bool was_m1_ = false;
};
}

View File

@ -80,8 +80,8 @@ struct RangeDispatcher {
}
}
private:
template <bool use_end, typename... Args> static void dispatch(SequencerT &target, int begin, int end, Args&&... args) {
private:
template <bool use_end, typename... Args> static void dispatch(SequencerT &target, int begin, int end, Args&&... args) {
#define index(n) \
case n: \
if constexpr (n <= SequencerT::max) { \
@ -140,18 +140,18 @@ struct SubrangeDispatcher {
#undef index
}
private:
static constexpr int find_begin(int n) {
const auto type = ClassifierT::region(n);
while(n && ClassifierT::region(n - 1) == type) --n;
return n;
}
private:
static constexpr int find_begin(int n) {
const auto type = ClassifierT::region(n);
while(n && ClassifierT::region(n - 1) == type) --n;
return n;
}
static constexpr int find_end(int n) {
const auto type = ClassifierT::region(n);
while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n;
return n;
}
static constexpr int find_end(int n) {
const auto type = ClassifierT::region(n);
while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n;
return n;
}
};
#undef switch_indices

View File

@ -39,132 +39,132 @@ namespace Reflection {
No guarantees of speed or any other kind of efficiency are offered.
*/
class Enum {
public:
/*!
Registers @c name and the entries within @c declaration for the enum type @c Type.
public:
/*!
Registers @c name and the entries within @c declaration for the enum type @c Type.
Assuming the caller used the macros above, a standard pattern where both things can be placed in
the same namespace might look like:
Assuming the caller used the macros above, a standard pattern where both things can be placed in
the same namespace might look like:
ReflectableEnum(MyEnum, int, A, B, C);
ReflectableEnum(MyEnum, int, A, B, C);
...
...
AnnounceEnum(MyEnum)
AnnounceEnum(MyEnum)
If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the
EnumDeclaration macro.
*/
template <typename Type> static void declare(const char *name, const char *declaration) {
const char *d_ptr = declaration;
If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the
EnumDeclaration macro.
*/
template <typename Type> static void declare(const char *name, const char *declaration) {
const char *d_ptr = declaration;
std::vector<std::string> result;
while(true) {
// Skip non-alphas, and exit if the terminator is found.
while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr;
if(!*d_ptr) break;
std::vector<std::string> result;
while(true) {
// Skip non-alphas, and exit if the terminator is found.
while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr;
if(!*d_ptr) break;
// Note the current location and proceed for all alphas and digits.
const auto start = d_ptr;
while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr;
// Note the current location and proceed for all alphas and digits.
const auto start = d_ptr;
while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr;
// Add a string view.
result.emplace_back(std::string(start, size_t(d_ptr - start)));
}
members_by_type_.emplace(std::type_index(typeid(Type)), result);
names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name));
// Add a string view.
result.emplace_back(std::string(start, size_t(d_ptr - start)));
}
/*!
@returns the declared name of the enum @c Type if it has been registered; the empty string otherwise.
*/
template <typename Type> static const std::string &name() {
return name(typeid(Type));
}
members_by_type_.emplace(std::type_index(typeid(Type)), result);
names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name));
}
/*!
@returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise.
*/
static const std::string &name(std::type_index type) {
const auto entry = names_by_type_.find(type);
if(entry == names_by_type_.end()) return empty_string_;
return entry->second;
}
/*!
@returns the declared name of the enum @c Type if it has been registered; the empty string otherwise.
*/
template <typename Type> static const std::string &name() {
return name(typeid(Type));
}
/*!
@returns the number of members of the enum @c Type if it has been registered; 0 otherwise.
*/
template <typename Type> static size_t size() {
return size(typeid(Type));
}
/*!
@returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise.
*/
static const std::string &name(std::type_index type) {
const auto entry = names_by_type_.find(type);
if(entry == names_by_type_.end()) return empty_string_;
return entry->second;
}
/*!
@returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise.
*/
static size_t size(std::type_index type) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return std::string::npos;
return entry->second.size();
}
/*!
@returns the number of members of the enum @c Type if it has been registered; 0 otherwise.
*/
template <typename Type> static size_t size() {
return size(typeid(Type));
}
/*!
@returns A @c std::string name for the enum value @c e.
*/
template <typename Type> static const std::string &to_string(Type e) {
return to_string(typeid(Type), int(e));
}
/*!
@returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise.
*/
static size_t size(std::type_index type) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return std::string::npos;
return entry->second.size();
}
/*!
@returns A @c std::string name for the enum value @c e from the enum with type_info @c type.
*/
static const std::string &to_string(std::type_index type, int e) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return empty_string_;
return entry->second[size_t(e)];
}
/*!
@returns A @c std::string name for the enum value @c e.
*/
template <typename Type> static const std::string &to_string(Type e) {
return to_string(typeid(Type), int(e));
}
/*!
@returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise.
*/
static const std::vector<std::string> &all_values(std::type_index type) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return empty_vector_;
return entry->second;
}
/*!
@returns A @c std::string name for the enum value @c e from the enum with type_info @c type.
*/
static const std::string &to_string(std::type_index type, int e) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return empty_string_;
return entry->second[size_t(e)];
}
/*!
@returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise.
*/
template <typename Type> static const std::vector<std::string> &all_values() {
return all_values(typeid(Type));
}
/*!
@returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise.
*/
static const std::vector<std::string> &all_values(std::type_index type) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return empty_vector_;
return entry->second;
}
/*!
@returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if
the name is not found.
*/
template <typename Type> static Type from_string(const std::string &str) {
return Type(from_string(typeid(Type), str));
}
/*!
@returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise.
*/
template <typename Type> static const std::vector<std::string> &all_values() {
return all_values(typeid(Type));
}
/*!
@returns A value for the name @c str in the enum with type_info @c type , or @c -1 if
the name is not found.
*/
static int from_string(std::type_index type, const std::string &str) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return -1;
const auto iterator = std::find(entry->second.begin(), entry->second.end(), str);
if(iterator == entry->second.end()) return -1;
return int(iterator - entry->second.begin());
}
/*!
@returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if
the name is not found.
*/
template <typename Type> static Type from_string(const std::string &str) {
return Type(from_string(typeid(Type), str));
}
private:
static inline std::unordered_map<std::type_index, std::vector<std::string>> members_by_type_;
static inline std::unordered_map<std::type_index, std::string> names_by_type_;
static inline const std::string empty_string_;
static inline const std::vector<std::string> empty_vector_;
/*!
@returns A value for the name @c str in the enum with type_info @c type , or @c -1 if
the name is not found.
*/
static int from_string(std::type_index type, const std::string &str) {
const auto entry = members_by_type_.find(type);
if(entry == members_by_type_.end()) return -1;
const auto iterator = std::find(entry->second.begin(), entry->second.end(), str);
if(iterator == entry->second.end()) return -1;
return int(iterator - entry->second.begin());
}
private:
static inline std::unordered_map<std::type_index, std::vector<std::string>> members_by_type_;
static inline std::unordered_map<std::type_index, std::string> names_by_type_;
static inline const std::string empty_string_;
static inline const std::vector<std::string> empty_vector_;
};
}

View File

@ -435,11 +435,11 @@ struct ArrayReceiver: public Reflection::Struct {
return nullptr;
}
private:
Reflection::Struct *target_;
const std::type_info *type_;
std::string key_;
size_t count_;
private:
Reflection::Struct *target_;
const std::type_info *type_;
std::string key_;
size_t count_;
};
}

View File

@ -75,9 +75,9 @@ struct Struct {
*/
virtual bool should_serialise([[maybe_unused]] const std::string &key) const { return true; }
private:
void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const;
bool deserialise(const uint8_t *bson, size_t size);
private:
void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const;
bool deserialise(const uint8_t *bson, size_t size);
};
/*!
@ -149,209 +149,209 @@ template <typename Type> bool get(const Struct &target, const std::string &name,
template <typename Type> Type get(const Struct &target, const std::string &name, size_t offset = 0);
template <typename Owner> class StructImpl: public Struct {
public:
/*!
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
It is the caller's responsibility to provide an appropriate type of data.
*/
void *get(const std::string &name) final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
}
/*!
Stores the @c value of type @c Type to the offset registered for the field @c name.
public:
/*!
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
It is the caller's responsibility to provide an appropriate type of data.
*/
void set(const std::string &name, const void *value, size_t offset) final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return;
assert(offset < iterator->second.count);
memcpy(reinterpret_cast<uint8_t *>(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size);
}
*/
void *get(const std::string &name) final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
}
/*!
@returns @c type_info for the field @c name.
*/
const std::type_info *type_of(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return iterator->second.type;
}
/*!
Stores the @c value of type @c Type to the offset registered for the field @c name.
/*!
@returns The number of instances of objects of the same type as @c name that sit consecutively in memory.
*/
size_t count_of(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return 0;
return iterator->second.count;
}
It is the caller's responsibility to provide an appropriate type of data.
*/
void set(const std::string &name, const void *value, size_t offset) final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return;
assert(offset < iterator->second.count);
memcpy(reinterpret_cast<uint8_t *>(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size);
}
/*!
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
the empty list otherwise.
*/
std::vector<std::string> values_for(const std::string &name) const final {
std::vector<std::string> result;
/*!
@returns @c type_info for the field @c name.
*/
const std::type_info *type_of(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return iterator->second.type;
}
// Return an empty vector if this field isn't declared.
const auto type = type_of(name);
if(!type) return result;
/*!
@returns The number of instances of objects of the same type as @c name that sit consecutively in memory.
*/
size_t count_of(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return 0;
return iterator->second.count;
}
// Also return an empty vector if this field isn't a registered enum.
const auto all_values = Enum::all_values(*type);
if(all_values.empty()) return result;
/*!
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
the empty list otherwise.
*/
std::vector<std::string> values_for(const std::string &name) const final {
std::vector<std::string> result;
// If no restriction is stored, return all values.
const auto permitted_values = permitted_enum_values_.find(name);
if(permitted_values == permitted_enum_values_.end()) return all_values;
// Return an empty vector if this field isn't declared.
const auto type = type_of(name);
if(!type) return result;
// Compile a vector of only those values the stored set indicates.
auto value = all_values.begin();
auto flag = permitted_values->second.begin();
while(value != all_values.end() && flag != permitted_values->second.end()) {
if(*flag) {
result.push_back(*value);
}
++flag;
++value;
// Also return an empty vector if this field isn't a registered enum.
const auto all_values = Enum::all_values(*type);
if(all_values.empty()) return result;
// If no restriction is stored, return all values.
const auto permitted_values = permitted_enum_values_.find(name);
if(permitted_values == permitted_enum_values_.end()) return all_values;
// Compile a vector of only those values the stored set indicates.
auto value = all_values.begin();
auto flag = permitted_values->second.begin();
while(value != all_values.end() && flag != permitted_values->second.end()) {
if(*flag) {
result.push_back(*value);
}
return result;
++flag;
++value;
}
/*!
@returns A vector of all declared fields for this struct.
*/
std::vector<std::string> all_keys() const final {
std::vector<std::string> keys;
for(const auto &pair: contents_) {
keys.push_back(pair.first);
return result;
}
/*!
@returns A vector of all declared fields for this struct.
*/
std::vector<std::string> all_keys() const final {
std::vector<std::string> keys;
for(const auto &pair: contents_) {
keys.push_back(pair.first);
}
return keys;
}
protected:
/*
This interface requires reflective structs to declare all fields;
specifically they should call:
declare_field(&field1, "field1");
declare_field(&field2, "field2");
Fields are registered in class storage. So callers can use needs_declare()
to determine whether a class of this type has already established the
reflective fields.
*/
/*!
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
it'll be the struct that's exposed.
*/
template <typename Type> void declare(Type *t, const std::string &name) {
// If the declared item is a class, see whether it can be dynamically cast
// to a reflectable for emplacement. If so, exit early.
if constexpr (std::is_class<Type>()) {
if(declare_reflectable(t, name)) return;
}
// If the declared item is an array, record it as a pointer to the
// first element plus a size.
if constexpr (std::is_array<Type>()) {
declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0]));
return;
}
declare_emplace(t, name);
}
/*!
If @c t is a previously-declared field that links to a declared enum then the variable
arguments provide a list of the acceptable values for that field. The list should be terminated
with a value of -1.
*/
template <typename Type> void limit_enum(Type *t, ...) {
const auto name = name_of(t);
if(name.empty()) return;
// The default vector size of '8' isn't especially scientific,
// but I feel like it's a good choice.
std::vector<bool> permitted_values(8);
va_list list;
va_start(list, t);
while(true) {
const int next = va_arg(list, int);
if(next < 0) break;
if(permitted_values.size() <= size_t(next)) {
permitted_values.resize(permitted_values.size() << 1);
}
return keys;
permitted_values[size_t(next)] = true;
}
va_end(list);
permitted_enum_values_.emplace(name, permitted_values);
}
/*!
@returns @c true if this subclass of @c Struct has not yet declared any fields.
*/
bool needs_declare() {
return contents_.empty();
}
/*!
Performs a reverse lookup from field to name.
*/
std::string name_of(void *field) {
const ssize_t offset = reinterpret_cast<uint8_t *>(field) - reinterpret_cast<uint8_t *>(this);
auto iterator = contents_.begin();
while(iterator != contents_.end()) {
if(iterator->second.offset == offset) break;
++iterator;
}
protected:
/*
This interface requires reflective structs to declare all fields;
specifically they should call:
if(iterator != contents_.end()) {
return iterator->first;
} else {
return std::string();
}
}
declare_field(&field1, "field1");
declare_field(&field2, "field2");
Fields are registered in class storage. So callers can use needs_declare()
to determine whether a class of this type has already established the
reflective fields.
*/
/*!
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
it'll be the struct that's exposed.
*/
template <typename Type> void declare(Type *t, const std::string &name) {
// If the declared item is a class, see whether it can be dynamically cast
// to a reflectable for emplacement. If so, exit early.
if constexpr (std::is_class<Type>()) {
if(declare_reflectable(t, name)) return;
}
// If the declared item is an array, record it as a pointer to the
// first element plus a size.
if constexpr (std::is_array<Type>()) {
declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0]));
return;
}
declare_emplace(t, name);
private:
template <typename Type> bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) {
if constexpr (std::is_base_of<Reflection::Struct, Type>::value) {
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
declare_emplace(str, name);
return true;
}
/*!
If @c t is a previously-declared field that links to a declared enum then the variable
arguments provide a list of the acceptable values for that field. The list should be terminated
with a value of -1.
*/
template <typename Type> void limit_enum(Type *t, ...) {
const auto name = name_of(t);
if(name.empty()) return;
return false;
}
// The default vector size of '8' isn't especially scientific,
// but I feel like it's a good choice.
std::vector<bool> permitted_values(8);
template <typename Type> void declare_emplace(Type *t, const std::string &name, size_t count = 1) {
contents_.emplace(
std::make_pair(
name,
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type), count)
));
}
va_list list;
va_start(list, t);
while(true) {
const int next = va_arg(list, int);
if(next < 0) break;
if(permitted_values.size() <= size_t(next)) {
permitted_values.resize(permitted_values.size() << 1);
}
permitted_values[size_t(next)] = true;
}
va_end(list);
permitted_enum_values_.emplace(name, permitted_values);
}
/*!
@returns @c true if this subclass of @c Struct has not yet declared any fields.
*/
bool needs_declare() {
return contents_.empty();
}
/*!
Performs a reverse lookup from field to name.
*/
std::string name_of(void *field) {
const ssize_t offset = reinterpret_cast<uint8_t *>(field) - reinterpret_cast<uint8_t *>(this);
auto iterator = contents_.begin();
while(iterator != contents_.end()) {
if(iterator->second.offset == offset) break;
++iterator;
}
if(iterator != contents_.end()) {
return iterator->first;
} else {
return std::string();
}
}
private:
template <typename Type> bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) {
if constexpr (std::is_base_of<Reflection::Struct, Type>::value) {
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
declare_emplace(str, name);
return true;
}
return false;
}
template <typename Type> void declare_emplace(Type *t, const std::string &name, size_t count = 1) {
contents_.emplace(
std::make_pair(
name,
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type), count)
));
}
struct Field {
const std::type_info *type;
ssize_t offset;
size_t size;
size_t count;
Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) :
type(&type), offset(offset), size(size), count(count) {}
};
static inline std::unordered_map<std::string, Field> contents_;
static inline std::unordered_map<std::string, std::vector<bool>> permitted_enum_values_;
struct Field {
const std::type_info *type;
ssize_t offset;
size_t size;
size_t count;
Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) :
type(&type), offset(offset), size(size), count(count) {}
};
static inline std::unordered_map<std::string, Field> contents_;
static inline std::unordered_map<std::string, std::vector<bool>> permitted_enum_values_;
};

View File

@ -28,75 +28,87 @@ namespace SignalProcessing {
smaller numbers permit a filter that operates more quickly and with less lag but less effectively.
*/
class FIRFilter {
private:
static constexpr float FixedMultiplier = 32767.0f;
static constexpr int FixedShift = 15;
private:
static constexpr float FixedMultiplier = 32767.0f;
static constexpr int FixedShift = 15;
public:
/*! A suggested default attenuation value. */
constexpr static float DefaultAttenuation = 60.0f;
/*!
Creates an instance of @c FIRFilter.
public:
/*! A suggested default attenuation value. */
constexpr static float DefaultAttenuation = 60.0f;
/*!
Creates an instance of @c FIRFilter.
@param number_of_taps The size of window for input data.
@param input_sample_rate The sampling rate of the input signal.
@param low_frequency The lowest frequency of signal to retain in the output.
@param high_frequency The highest frequency of signal to retain in the output.
@param attenuation The attenuation of the discarded frequencies.
*/
FIRFilter(std::size_t number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation = DefaultAttenuation);
FIRFilter(const std::vector<float> &coefficients);
@param number_of_taps The size of window for input data.
@param input_sample_rate The sampling rate of the input signal.
@param low_frequency The lowest frequency of signal to retain in the output.
@param high_frequency The highest frequency of signal to retain in the output.
@param attenuation The attenuation of the discarded frequencies.
*/
FIRFilter(
std::size_t number_of_taps,
float input_sample_rate,
float low_frequency,
float high_frequency,
float attenuation = DefaultAttenuation
);
FIRFilter(const std::vector<float> &coefficients);
/*!
Applies the filter to one batch of input samples, returning the net result.
/*!
Applies the filter to one batch of input samples, returning the net result.
@param src The source buffer to apply the filter to.
@returns The result of applying the filter.
*/
inline short apply(const short *src, size_t stride = 1) const {
#ifdef USE_ACCELERATE
short result;
vDSP_dotpr_s1_15(filter_coefficients_.data(), 1, src, vDSP_Stride(stride), &result, filter_coefficients_.size());
return result;
#else
int outputValue = 0;
for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) {
outputValue += filter_coefficients_[c] * src[c * stride];
}
return short(outputValue >> FixedShift);
#endif
}
@param src The source buffer to apply the filter to.
@returns The result of applying the filter.
*/
inline short apply(const short *src, size_t stride = 1) const {
#ifdef USE_ACCELERATE
short result;
vDSP_dotpr_s1_15(
filter_coefficients_.data(),
1,
src,
vDSP_Stride(stride), &result, filter_coefficients_.size()
);
return result;
#else
int outputValue = 0;
for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) {
outputValue += filter_coefficients_[c] * src[c * stride];
}
return short(outputValue >> FixedShift);
#endif
}
/*! @returns The number of taps used by this filter. */
inline std::size_t get_number_of_taps() const {
return filter_coefficients_.size();
}
/*! @returns The number of taps used by this filter. */
inline std::size_t get_number_of_taps() const {
return filter_coefficients_.size();
}
/*! @returns The weighted coefficients that describe this filter. */
std::vector<float> get_coefficients() const;
/*! @returns The weighted coefficients that describe this filter. */
std::vector<float> get_coefficients() const;
/*!
@returns A filter that would have the effect of adding (and scaling) the outputs of the two filters.
Defined only if both have the same number of taps.
*/
FIRFilter operator+(const FIRFilter &) const;
/*!
@returns A filter that would have the effect of adding (and scaling) the outputs of the two filters.
Defined only if both have the same number of taps.
*/
FIRFilter operator+(const FIRFilter &) const;
/*!
@returns A filter that would have the effect of applying the two filters in succession.
Defined only if both have the same number of taps.
*/
FIRFilter operator*(const FIRFilter &) const;
/*!
@returns A filter that would have the effect of applying the two filters in succession.
Defined only if both have the same number of taps.
*/
FIRFilter operator*(const FIRFilter &) const;
/*!
@returns A filter that would have the opposite effect of this filter.
*/
FIRFilter operator-() const;
/*!
@returns A filter that would have the opposite effect of this filter.
*/
FIRFilter operator-() const;
private:
std::vector<short> filter_coefficients_;
private:
std::vector<short> filter_coefficients_;
static void coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps);
static float ino(float a);
static void coefficients_for_idealised_filter_response(
short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps);
static float ino(float a);
};
}

View File

@ -23,73 +23,73 @@ namespace SignalProcessing {
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
*/
class Stepper {
public:
/*!
Establishes a stepper with a one-to-one conversion rate.
*/
Stepper() : Stepper(1,1) {}
public:
/*!
Establishes a stepper with a one-to-one conversion rate.
*/
Stepper() : Stepper(1,1) {}
/*!
Establishes a stepper that will receive steps at the @c input_rate and dictate the number
of steps that should be taken at the @c output_rate.
*/
Stepper(uint64_t output_rate, uint64_t input_rate) :
accumulated_error_(-(int64_t(input_rate) << 1)),
input_rate_(input_rate),
output_rate_(output_rate),
whole_step_(output_rate / input_rate),
adjustment_up_(int64_t(output_rate % input_rate) << 1),
adjustment_down_(int64_t(input_rate) << 1) {}
/*!
Establishes a stepper that will receive steps at the @c input_rate and dictate the number
of steps that should be taken at the @c output_rate.
*/
Stepper(uint64_t output_rate, uint64_t input_rate) :
accumulated_error_(-(int64_t(input_rate) << 1)),
input_rate_(input_rate),
output_rate_(output_rate),
whole_step_(output_rate / input_rate),
adjustment_up_(int64_t(output_rate % input_rate) << 1),
adjustment_down_(int64_t(input_rate) << 1) {}
/*!
Advances one step at the input rate.
/*!
Advances one step at the input rate.
@returns the number of output steps.
*/
inline uint64_t step() {
uint64_t update = whole_step_;
accumulated_error_ += adjustment_up_;
if(accumulated_error_ > 0) {
update++;
accumulated_error_ -= adjustment_down_;
}
return update;
@returns the number of output steps.
*/
inline uint64_t step() {
uint64_t update = whole_step_;
accumulated_error_ += adjustment_up_;
if(accumulated_error_ > 0) {
update++;
accumulated_error_ -= adjustment_down_;
}
return update;
}
/*!
Advances by @c number_of_steps steps at the input rate.
/*!
Advances by @c number_of_steps steps at the input rate.
@returns the number of output steps.
*/
inline uint64_t step(uint64_t number_of_steps) {
uint64_t update = whole_step_ * number_of_steps;
accumulated_error_ += adjustment_up_ * int64_t(number_of_steps);
if(accumulated_error_ > 0) {
update += 1 + uint64_t(accumulated_error_ / adjustment_down_);
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
}
return update;
@returns the number of output steps.
*/
inline uint64_t step(uint64_t number_of_steps) {
uint64_t update = whole_step_ * number_of_steps;
accumulated_error_ += adjustment_up_ * int64_t(number_of_steps);
if(accumulated_error_ > 0) {
update += 1 + uint64_t(accumulated_error_ / adjustment_down_);
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
}
return update;
}
/*!
@returns the output rate.
*/
inline uint64_t get_output_rate() {
return output_rate_;
}
/*!
@returns the output rate.
*/
inline uint64_t get_output_rate() const {
return output_rate_;
}
/*!
@returns the input rate.
*/
inline uint64_t get_input_rate() {
return input_rate_;
}
/*!
@returns the input rate.
*/
inline uint64_t get_input_rate() const {
return input_rate_;
}
private:
int64_t accumulated_error_;
uint64_t input_rate_, output_rate_;
uint64_t whole_step_;
int64_t adjustment_up_, adjustment_down_;
private:
int64_t accumulated_error_;
uint64_t input_rate_, output_rate_;
uint64_t whole_step_;
int64_t adjustment_up_, adjustment_down_;
};
}

View File

@ -24,59 +24,59 @@ namespace Storage::Cartridge {
making the base class 100% descriptive.
*/
class Cartridge {
public:
struct Segment {
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> &&data) :
start_address(start_address), end_address(end_address), data(data) {}
public:
struct Segment {
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> &&data) :
start_address(start_address), end_address(end_address), data(data) {}
Segment(size_t start_address, size_t end_address, const std::vector<uint8_t> &data) :
start_address(start_address), end_address(end_address), data(data) {}
Segment(size_t start_address, size_t end_address, const std::vector<uint8_t> &data) :
start_address(start_address), end_address(end_address), data(data) {}
Segment(size_t start_address, std::vector<uint8_t> &&data) :
Segment(start_address, start_address + data.size(), data) {}
Segment(size_t start_address, std::vector<uint8_t> &&data) :
Segment(start_address, start_address + data.size(), data) {}
Segment(size_t start_address, const std::vector<uint8_t> &data) :
Segment(start_address, start_address + data.size(), data) {}
Segment(size_t start_address, const std::vector<uint8_t> &data) :
Segment(start_address, start_address + data.size(), data) {}
Segment(Segment &&segment) :
start_address(segment.start_address),
end_address(segment.end_address),
data(std::move(segment.data)) {}
Segment(Segment &&segment) :
start_address(segment.start_address),
end_address(segment.end_address),
data(std::move(segment.data)) {}
Segment(const Segment &segment) :
start_address(segment.start_address),
end_address(segment.end_address),
data(segment.data) {}
Segment(const Segment &segment) :
start_address(segment.start_address),
end_address(segment.end_address),
data(segment.data) {}
/// Indicates that an address is unknown.
static const size_t UnknownAddress;
/// Indicates that an address is unknown.
static const size_t UnknownAddress;
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
size_t start_address;
/*!
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
to start_address + data_length due to potential paging.
*/
size_t end_address;
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
size_t start_address;
/*!
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
to start_address + data_length due to potential paging.
*/
size_t end_address;
/*!
The data contents for this segment. If @c start_address and @c end_address are suppled then
the first end_address - start_address bytes will be those initially visible. The size will
not necessarily be the same as @c end_address - @c start_address due to potential paging.
*/
std::vector<uint8_t> data;
};
/*!
The data contents for this segment. If @c start_address and @c end_address are suppled then
the first end_address - start_address bytes will be those initially visible. The size will
not necessarily be the same as @c end_address - @c start_address due to potential paging.
*/
std::vector<uint8_t> data;
};
const std::vector<Segment> &get_segments() const {
return segments_;
}
virtual ~Cartridge() = default;
const std::vector<Segment> &get_segments() const {
return segments_;
}
virtual ~Cartridge() = default;
Cartridge() = default;
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
Cartridge() = default;
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
protected:
std::vector<Segment> segments_;
protected:
std::vector<Segment> segments_;
};
}

View File

@ -15,12 +15,12 @@
namespace Storage::Cartridge {
class BinaryDump : public Cartridge {
public:
BinaryDump(const std::string &file_name);
public:
BinaryDump(const std::string &file_name);
enum {
ErrorNotAccessible
};
enum {
ErrorNotAccessible
};
};
}

View File

@ -15,12 +15,12 @@
namespace Storage::Cartridge {
class PRG : public Cartridge {
public:
PRG(const std::string &file_name);
public:
PRG(const std::string &file_name);
enum {
ErrorNotROM
};
enum {
ErrorNotROM
};
};
}

View File

@ -30,139 +30,139 @@ class Controller:
public ClockingHint::Source,
private Drive::EventDelegate,
private ClockingHint::Observer {
protected:
/*!
Constructs a @c Controller that will be run at @c clock_rate.
*/
Controller(Cycles clock_rate);
protected:
/*!
Constructs a @c Controller that will be run at @c clock_rate.
*/
Controller(Cycles clock_rate);
/*!
Communicates to the PLL the expected length of a bit as a fraction of a second.
*/
void set_expected_bit_length(Time bit_length);
/*!
Communicates to the PLL the expected length of a bit as a fraction of a second.
*/
void set_expected_bit_length(Time bit_length);
/*!
Advances the drive by @c number_of_cycles cycles.
*/
void run_for(const Cycles cycles);
/*!
Advances the drive by @c number_of_cycles cycles.
*/
void run_for(const Cycles cycles);
/*!
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
machines allow zero or multiple drives to be attached, with useless results.
/*!
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
machines allow zero or multiple drives to be attached, with useless results.
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
*/
void set_drive(int index_mask);
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
*/
void set_drive(int index_mask);
/*!
Adds a new drive to the drive list, returning its index.
*/
template<typename... Args> size_t emplace_drive(Args&&... args) {
drives_.emplace_back(new Drive(std::forward<Args>(args)...));
drives_.back()->set_clocking_hint_observer(this);
return drives_.size() - 1;
/*!
Adds a new drive to the drive list, returning its index.
*/
template<typename... Args> size_t emplace_drive(Args&&... args) {
drives_.emplace_back(new Drive(std::forward<Args>(args)...));
drives_.back()->set_clocking_hint_observer(this);
return drives_.size() - 1;
}
/*!
Adds @c count new drives to the drive list, returning the index of the final one added.
*/
template<typename... Args> size_t emplace_drives(size_t count, Args&&... args) {
while(count--) {
emplace_drive(std::forward<Args>(args)...);
}
return drives_.size() - 1;
}
/*!
Adds @c count new drives to the drive list, returning the index of the final one added.
*/
template<typename... Args> size_t emplace_drives(size_t count, Args&&... args) {
while(count--) {
emplace_drive(std::forward<Args>(args)...);
}
return drives_.size() - 1;
/*!
Should be implemented by subclasses; communicates each bit that the PLL recognises.
*/
virtual void process_input_bit(int value) = 0;
/*!
Should be implemented by subclasses; communicates that the index hole has been reached.
*/
virtual void process_index_hole() = 0;
/*!
Should be implemented by subclasses if they implement writing; communicates that
all bits supplied to write_bit have now been written.
*/
virtual void process_write_completed() override;
/*!
Puts the controller and the drive returned by get_drive() into write mode, supplying to
the drive the current bit length.
While the controller is in write mode it disconnects the PLL. So subclasses will not
receive any calls to @c process_input_bit.
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
the index hole. Writing will continue over the index hole otherwise.
*/
void begin_writing(bool clamp_to_index_hole);
/*!
Puts the drive returned by get_drive() out of write mode, and marks the controller
as no longer being in write mode.
*/
void end_writing();
/*!
@returns @c true if the controller is in reading mode; @c false otherwise.
*/
bool is_reading();
/*!
Returns the connected drive or, if none is connected, an invented one. No guarantees are
made about the lifetime or the exclusivity of the invented drive.
*/
Drive &get_drive();
const Drive &get_drive() const;
Drive &get_drive(size_t index) {
return *drives_[index];
}
const Drive &get_drive(size_t index) const {
return *drives_[index];
}
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
size_t index = 0;
for(auto &drive: drives_) {
func(*drive, index);
++index;
}
}
/*!
Should be implemented by subclasses; communicates each bit that the PLL recognises.
*/
virtual void process_input_bit(int value) = 0;
/*!
As per ClockingHint::Source.
*/
ClockingHint::Preference preferred_clocking() const override;
/*!
Should be implemented by subclasses; communicates that the index hole has been reached.
*/
virtual void process_index_hole() = 0;
private:
Time bit_length_;
Cycles::IntType clock_rate_multiplier_ = 1;
Cycles::IntType clock_rate_ = 1;
/*!
Should be implemented by subclasses if they implement writing; communicates that
all bits supplied to write_bit have now been written.
*/
virtual void process_write_completed() override;
bool is_reading_ = true;
/*!
Puts the controller and the drive returned by get_drive() into write mode, supplying to
the drive the current bit length.
DigitalPhaseLockedLoop<Controller> pll_;
friend DigitalPhaseLockedLoop<Controller>;
While the controller is in write mode it disconnects the PLL. So subclasses will not
receive any calls to @c process_input_bit.
Drive empty_drive_;
std::vector<std::unique_ptr<Drive>> drives_;
Drive *drive_;
int drive_selection_mask_ = 0xff;
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
the index hole. Writing will continue over the index hole otherwise.
*/
void begin_writing(bool clamp_to_index_hole);
// ClockingHint::Observer.
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
/*!
Puts the drive returned by get_drive() out of write mode, and marks the controller
as no longer being in write mode.
*/
void end_writing();
// for Drive::EventDelegate
void process_event(const Drive::Event &event) final;
void advance(const Cycles cycles) final;
/*!
@returns @c true if the controller is in reading mode; @c false otherwise.
*/
bool is_reading();
/*!
Returns the connected drive or, if none is connected, an invented one. No guarantees are
made about the lifetime or the exclusivity of the invented drive.
*/
Drive &get_drive();
const Drive &get_drive() const;
Drive &get_drive(size_t index) {
return *drives_[index];
}
const Drive &get_drive(size_t index) const {
return *drives_[index];
}
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
size_t index = 0;
for(auto &drive: drives_) {
func(*drive, index);
++index;
}
}
/*!
As per ClockingHint::Source.
*/
ClockingHint::Preference preferred_clocking() const override;
private:
Time bit_length_;
Cycles::IntType clock_rate_multiplier_ = 1;
Cycles::IntType clock_rate_ = 1;
bool is_reading_ = true;
DigitalPhaseLockedLoop<Controller> pll_;
friend DigitalPhaseLockedLoop<Controller>;
Drive empty_drive_;
std::vector<std::unique_ptr<Drive>> drives_;
Drive *drive_;
int drive_selection_mask_ = 0xff;
// ClockingHint::Observer.
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
// for Drive::EventDelegate
void process_event(const Drive::Event &event) final;
void advance(const Cycles cycles) final;
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
};
}

View File

@ -20,148 +20,148 @@ namespace Storage::Disk {
being able to post event messages to subclasses.
*/
class MFMController: public Controller {
public:
MFMController(Cycles clock_rate);
public:
MFMController(Cycles clock_rate);
protected:
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
void set_is_double_density(bool);
protected:
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
void set_is_double_density(bool);
/// @returns @c true if currently decoding MFM content; @c false otherwise.
bool get_is_double_density();
/// @returns @c true if currently decoding MFM content; @c false otherwise.
bool get_is_double_density();
enum DataMode {
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
Scanning,
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
Reading,
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
Writing
};
/// Sets the current data mode.
void set_data_mode(DataMode);
enum DataMode {
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
Scanning,
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
Reading,
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
Writing
};
/// Sets the current data mode.
void set_data_mode(DataMode);
/*!
Describes a token found in the incoming PLL bit stream. Tokens can be one of:
/*!
Describes a token found in the incoming PLL bit stream. Tokens can be one of:
Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole;
ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc.
Data: the pattern that begins a data section, i.e. sector contents.
DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents.
Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole;
ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc.
Data: the pattern that begins a data section, i.e. sector contents.
DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents.
Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types
of token listed above; this class combines notification of that mark and the distinct index sync mark.
Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an
announcement of sync followed by an announcement of one of the above four types of token.
Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types
of token listed above; this class combines notification of that mark and the distinct index sync mark.
Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an
announcement of sync followed by an announcement of one of the above four types of token.
Byte: reports reading of an ordinary byte, with expected timing bits.
Byte: reports reading of an ordinary byte, with expected timing bits.
When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token
is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as
spurious sync signals can otherwise be found in ordinary data, causing framing errors.
*/
struct Token {
enum Type {
Index, ID, Data, DeletedData, Sync, Byte
} type;
uint8_t byte_value;
};
/// @returns The most-recently read token from the surface of the disk.
Token get_latest_token();
When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token
is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as
spurious sync signals can otherwise be found in ordinary data, causing framing errors.
*/
struct Token {
enum Type {
Index, ID, Data, DeletedData, Sync, Byte
} type;
uint8_t byte_value;
};
/// @returns The most-recently read token from the surface of the disk.
Token get_latest_token();
/// @returns The controller's CRC generator. This is automatically fed during reading.
CRC::CCITT &get_crc_generator();
/// @returns The controller's CRC generator. This is automatically fed during reading.
CRC::CCITT &get_crc_generator();
// Events
enum class Event: int {
Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
IndexHole = (1 << 1), // Indicates the passing of a physical index hole.
DataWritten = (1 << 2), // Indicates that all queued bits have been written
};
// Events
enum class Event: int {
Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
IndexHole = (1 << 1), // Indicates the passing of a physical index hole.
DataWritten = (1 << 2), // Indicates that all queued bits have been written
};
/*!
Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream.
Therefore it is called to announce when:
/*!
Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream.
Therefore it is called to announce when:
(i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data.
(ii) the index hole passes; or
(iii) the queue of data to be written has been exhausted.
*/
virtual void posit_event(int type) = 0;
(i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data.
(ii) the index hole passes; or
(iii) the queue of data to be written has been exhausted.
*/
virtual void posit_event(int type) = 0;
/*!
Encodes @c bit according to the current single/double density mode and adds it
to the controller's write buffer.
*/
void write_bit(int bit);
/*!
Encodes @c bit according to the current single/double density mode and adds it
to the controller's write buffer.
*/
void write_bit(int bit);
/*!
Encodes @c byte according to the current single/double density mode and adds it
to the controller's write buffer.
*/
void write_byte(uint8_t byte);
/*!
Encodes @c byte according to the current single/double density mode and adds it
to the controller's write buffer.
*/
void write_byte(uint8_t byte);
/*!
Serialises @c value into the controller's write buffer without adjustment.
*/
void write_raw_short(uint16_t value);
/*!
Serialises @c value into the controller's write buffer without adjustment.
*/
void write_raw_short(uint16_t value);
/*!
Gets the current value of the CRC generator and makes two calls to @c write_byte, to
write first its higher-value byte and then its lower.
*/
void write_crc();
/*!
Gets the current value of the CRC generator and makes two calls to @c write_byte, to
write first its higher-value byte and then its lower.
*/
void write_crc();
/*!
Calls @c write_byte with @c value, @c quantity times.
*/
void write_n_bytes(int quantity, uint8_t value);
/*!
Calls @c write_byte with @c value, @c quantity times.
*/
void write_n_bytes(int quantity, uint8_t value);
/*!
Writes everything that should per the spec appear prior to the address contained
in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator.
*/
void write_id_joiner();
/*!
Writes everything that should per the spec appear prior to the address contained
in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator.
*/
void write_id_joiner();
/*!
Writes at most what should, per the spec, appear after the ID's CRC, up to and
including the mark that indicates the beginning of data, appropriately seeding
the CRC generator; if @c skip_first_gap is set then the initial gap after the
CRC isn't written.
*/
void write_id_data_joiner(bool is_deleted, bool skip_first_gap);
/*!
Writes at most what should, per the spec, appear after the ID's CRC, up to and
including the mark that indicates the beginning of data, appropriately seeding
the CRC generator; if @c skip_first_gap is set then the initial gap after the
CRC isn't written.
*/
void write_id_data_joiner(bool is_deleted, bool skip_first_gap);
/*!
Writes the gap expected after a sector's data CRC and before the beginning of the
next ID joiner.
*/
void write_post_data_gap();
/*!
Writes the gap expected after a sector's data CRC and before the beginning of the
next ID joiner.
*/
void write_post_data_gap();
/*!
Writes everything that should, per the spec, following the index hole and prior
to any sectors.
*/
void write_start_of_track();
/*!
Writes everything that should, per the spec, following the index hole and prior
to any sectors.
*/
void write_start_of_track();
private:
// Storage::Disk::Controller
virtual void process_input_bit(int value);
virtual void process_index_hole();
virtual void process_write_completed();
private:
// Storage::Disk::Controller
virtual void process_input_bit(int value);
virtual void process_index_hole();
virtual void process_write_completed();
// Reading state.
Token latest_token_;
Encodings::MFM::Shifter shifter_;
// Reading state.
Token latest_token_;
Encodings::MFM::Shifter shifter_;
// input configuration
bool is_double_density_;
DataMode data_mode_ = DataMode::Scanning;
// input configuration
bool is_double_density_;
DataMode data_mode_ = DataMode::Scanning;
// writing
int last_bit_;
// writing
int last_bit_;
// CRC generator
CRC::CCITT crc_generator_;
// CRC generator
CRC::CCITT crc_generator_;
};
}

View File

@ -24,110 +24,110 @@ namespace Storage {
@c length_of_history The number of historic pulses to consider in locking to phase.
*/
template <typename BitHandler, size_t length_of_history = 3> class DigitalPhaseLockedLoop {
public:
/*!
Instantiates a @c DigitalPhaseLockedLoop.
public:
/*!
Instantiates a @c DigitalPhaseLockedLoop.
@param clocks_per_bit The expected number of cycles between each bit of input.
*/
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
@param clocks_per_bit The expected number of cycles between each bit of input.
*/
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
/*!
Changes the expected window length.
*/
void set_clocks_per_bit(int clocks_per_bit) {
window_length_ = clocks_per_bit_ = clocks_per_bit;
/*!
Changes the expected window length.
*/
void set_clocks_per_bit(int clocks_per_bit) {
window_length_ = clocks_per_bit_ = clocks_per_bit;
}
/*!
Runs the loop, impliedly posting no pulses during that period.
@c number_of_cycles The time to run the loop for.
*/
void run_for(const Cycles cycles) {
offset_ += cycles.as_integral();
phase_ += cycles.as_integral();
if(phase_ >= window_length_) {
auto windows_crossed = phase_ / window_length_;
// Check whether this triggers any 0s.
if(window_was_filled_) --windows_crossed;
for(int c = 0; c < windows_crossed; c++)
bit_handler_.digital_phase_locked_loop_output_bit(0);
window_was_filled_ = false;
phase_ %= window_length_;
}
}
/*!
Runs the loop, impliedly posting no pulses during that period.
@c number_of_cycles The time to run the loop for.
*/
void run_for(const Cycles cycles) {
offset_ += cycles.as_integral();
phase_ += cycles.as_integral();
if(phase_ >= window_length_) {
auto windows_crossed = phase_ / window_length_;
// Check whether this triggers any 0s.
if(window_was_filled_) --windows_crossed;
for(int c = 0; c < windows_crossed; c++)
bit_handler_.digital_phase_locked_loop_output_bit(0);
window_was_filled_ = false;
phase_ %= window_length_;
}
/*!
Announces a pulse at the current time.
*/
void add_pulse() {
if(!window_was_filled_) {
bit_handler_.digital_phase_locked_loop_output_bit(1);
window_was_filled_ = true;
post_phase_offset(phase_, offset_);
offset_ = 0;
}
}
/*!
Announces a pulse at the current time.
*/
void add_pulse() {
if(!window_was_filled_) {
bit_handler_.digital_phase_locked_loop_output_bit(1);
window_was_filled_ = true;
post_phase_offset(phase_, offset_);
offset_ = 0;
}
}
private:
BitHandler &bit_handler_;
private:
BitHandler &bit_handler_;
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
// Erase the effect of whatever is currently in this slot.
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
// Erase the effect of whatever is currently in this slot.
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
// Fill in the new fields.
const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1));
offset_history_[offset_history_pointer_].divisor = multiple;
offset_history_[offset_history_pointer_].spacing = new_offset;
// Fill in the new fields.
const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1));
offset_history_[offset_history_pointer_].divisor = multiple;
offset_history_[offset_history_pointer_].spacing = new_offset;
// Add in the new values;
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
// Add in the new values;
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
// Advance the write slot.
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
// Advance the write slot.
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
#ifndef NDEBUG
Cycles::IntType td = 0, ts = 0;
for(auto offset: offset_history_) {
td += offset.divisor;
ts += offset.spacing;
}
assert(ts == total_spacing_);
assert(td == total_divisor_);
Cycles::IntType td = 0, ts = 0;
for(auto offset: offset_history_) {
td += offset.divisor;
ts += offset.spacing;
}
assert(ts == total_spacing_);
assert(td == total_divisor_);
#endif
// In net: use an unweighted average of the stored offsets to compute current window size,
// bucketing them by rounding to the nearest multiple of the base clocks per bit
window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1));
// In net: use an unweighted average of the stored offsets to compute current window size,
// bucketing them by rounding to the nearest multiple of the base clocks per bit
window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1));
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
const auto error = new_phase - (window_length_ >> 1);
phase_ -= (error + 1) >> 1;
}
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
const auto error = new_phase - (window_length_ >> 1);
phase_ -= (error + 1) >> 1;
}
struct LoggedOffset {
Cycles::IntType divisor = 1, spacing = 1;
};
std::array<LoggedOffset, length_of_history> offset_history_;
std::size_t offset_history_pointer_ = 0;
struct LoggedOffset {
Cycles::IntType divisor = 1, spacing = 1;
};
std::array<LoggedOffset, length_of_history> offset_history_;
std::size_t offset_history_pointer_ = 0;
Cycles::IntType total_spacing_ = length_of_history;
Cycles::IntType total_divisor_ = length_of_history;
Cycles::IntType total_spacing_ = length_of_history;
Cycles::IntType total_divisor_ = length_of_history;
Cycles::IntType phase_ = 0;
Cycles::IntType window_length_ = 0;
Cycles::IntType phase_ = 0;
Cycles::IntType window_length_ = 0;
Cycles::IntType offset_ = 0;
bool window_was_filled_ = false;
Cycles::IntType offset_ = 0;
bool window_was_filled_ = false;
int clocks_per_bit_ = 0;
int clocks_per_bit_ = 0;
};
}

View File

@ -23,48 +23,48 @@ namespace Storage::Disk {
Models a flopy disk.
*/
class Disk {
public:
virtual ~Disk() = default;
public:
virtual ~Disk() = default;
/*!
@returns the number of discrete positions that this disk uses to model its complete surface area.
/*!
@returns the number of discrete positions that this disk uses to model its complete surface area.
This is not necessarily a track count. There is no implicit guarantee that every position will
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
*/
virtual HeadPosition get_maximum_head_position() = 0;
This is not necessarily a track count. There is no implicit guarantee that every position will
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
*/
virtual HeadPosition get_maximum_head_position() = 0;
/*!
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
*/
virtual int get_head_count() = 0;
/*!
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
*/
virtual int get_head_count() = 0;
/*!
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
/*!
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
/*!
Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only.
*/
virtual void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track) = 0;
/*!
Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only.
*/
virtual void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track) = 0;
/*!
Provides a hint that no further tracks are likely to be written for a while.
*/
virtual void flush_tracks() = 0;
/*!
Provides a hint that no further tracks are likely to be written for a while.
*/
virtual void flush_tracks() = 0;
/*!
@returns whether the disk image is read only. Defaults to @c true if not overridden.
*/
virtual bool get_is_read_only() = 0;
/*!
@returns whether the disk image is read only. Defaults to @c true if not overridden.
*/
virtual bool get_is_read_only() = 0;
/*!
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
This can avoid some degree of work when disk images offer sub-head-position precision.
*/
virtual bool tracks_differ(Track::Address, Track::Address) = 0;
/*!
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
This can avoid some degree of work when disk images offer sub-head-position precision.
*/
virtual bool tracks_differ(Track::Address, Track::Address) = 0;
};
}

View File

@ -30,48 +30,48 @@ enum class Error {
if it matches the media.
*/
class DiskImage {
public:
virtual ~DiskImage() = default;
public:
virtual ~DiskImage() = default;
/*!
@returns the distance at which there stops being any further content.
/*!
@returns the distance at which there stops being any further content.
This is not necessarily a track count. There is no implicit guarantee that every position will
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
*/
virtual HeadPosition get_maximum_head_position() = 0;
This is not necessarily a track count. There is no implicit guarantee that every position will
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
*/
virtual HeadPosition get_maximum_head_position() = 0;
/*!
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
*/
virtual int get_head_count() { return 1; }
/*!
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
*/
virtual int get_head_count() { return 1; }
/*!
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
/*!
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
/*!
Replaces the Tracks indicated by the map, that maps from physical address to track content.
*/
virtual void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) {}
/*!
Replaces the Tracks indicated by the map, that maps from physical address to track content.
*/
virtual void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) {}
/*!
Communicates that it is likely to be a while before any more tracks are written.
*/
virtual void flush_tracks() {}
/*!
Communicates that it is likely to be a while before any more tracks are written.
*/
virtual void flush_tracks() {}
/*!
@returns whether the disk image is read only. Defaults to @c true if not overridden.
*/
virtual bool get_is_read_only() { return true; }
/*!
@returns whether the disk image is read only. Defaults to @c true if not overridden.
*/
virtual bool get_is_read_only() { return true; }
/*!
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
This can avoid some degree of work when disk images offer sub-head-position precision.
*/
virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; }
/*!
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
This can avoid some degree of work when disk images offer sub-head-position precision.
*/
virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; }
};
class DiskImageHolderBase: public Disk {
@ -90,29 +90,29 @@ class DiskImageHolderBase: public Disk {
the underlying image doesn't implement TypeDistinguisher, or else to pass the call along.
*/
template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher {
public:
template <typename... Ts> DiskImageHolder(Ts&&... args) :
disk_image_(args...) {}
~DiskImageHolder();
public:
template <typename... Ts> DiskImageHolder(Ts&&... args) :
disk_image_(args...) {}
~DiskImageHolder();
HeadPosition get_maximum_head_position();
int get_head_count();
std::shared_ptr<Track> get_track_at_position(Track::Address address);
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
void flush_tracks();
bool get_is_read_only();
bool tracks_differ(Track::Address lhs, Track::Address rhs);
HeadPosition get_maximum_head_position();
int get_head_count();
std::shared_ptr<Track> get_track_at_position(Track::Address address);
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
void flush_tracks();
bool get_is_read_only();
bool tracks_differ(Track::Address lhs, Track::Address rhs);
private:
T disk_image_;
private:
T disk_image_;
TargetPlatform::Type target_platform_type() final {
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) {
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type();
} else {
return TargetPlatform::Type(~0);
}
TargetPlatform::Type target_platform_type() final {
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) {
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type();
} else {
return TargetPlatform::Type(~0);
}
}
};
#include "DiskImageImplementation.hpp"

View File

@ -28,9 +28,10 @@ namespace Storage::Disk {
*/
class Disk2MG {
public:
using DiskOrMassStorageDevice = std::variant<std::nullptr_t, DiskImageHolderBase *, Storage::MassStorage::MassStorageDevice *>;
static DiskOrMassStorageDevice open(const std::string &file_name);
public:
using DiskOrMassStorageDevice =
std::variant<std::nullptr_t, DiskImageHolderBase *, Storage::MassStorage::MassStorageDevice *>;
static DiskOrMassStorageDevice open(const std::string &file_name);
};
}

View File

@ -18,23 +18,23 @@ namespace Storage::Disk {
Provides a @c Disk containing an ADF disk image: a decoded sector dump of an Acorn ADFS disk.
*/
class AcornADF: public MFMSectorDump {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const std::string &file_name);
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const std::string &file_name);
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
private:
long get_file_offset_for_position(Track::Address address) final;
int head_count_ = 1;
uint8_t sector_size_ = 1;
int sectors_per_track_ = 16;
private:
long get_file_offset_for_position(Track::Address) final;
int head_count_ = 1;
uint8_t sector_size_ = 1;
int sectors_per_track_ = 16;
};
}

View File

@ -19,23 +19,23 @@ namespace Storage::Disk {
but the Amiga doesn't use IBM-style sector demarcation.
*/
class AmigaADF: public DiskImage {
public:
/*!
Construct an @c AmigaADF containing content from the file with name @c file_name.
public:
/*!
Construct an @c AmigaADF containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image.
*/
AmigaADF(const std::string &file_name);
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image.
*/
AmigaADF(const std::string &file_name);
// implemented to satisfy @c Disk
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
// implemented to satisfy @c Disk
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
std::shared_ptr<Track> get_track_at_position(Track::Address) final;
private:
Storage::FileHolder file_;
long get_file_offset_for_position(Track::Address address);
private:
Storage::FileHolder file_;
long get_file_offset_for_position(Track::Address);
};

View File

@ -20,28 +20,28 @@ namespace Storage::Disk {
implicitly numbered and located.
*/
class AppleDSK: public DiskImage {
public:
/*!
Construct an @c AppleDSK containing content from the file with name @c file_name.
public:
/*!
Construct an @c AppleDSK containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image.
*/
AppleDSK(const std::string &file_name);
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image.
*/
AppleDSK(const std::string &file_name);
// Implemented to satisfy @c DiskImage.
HeadPosition get_maximum_head_position() final;
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) final;
bool get_is_read_only() final;
// Implemented to satisfy @c DiskImage.
HeadPosition get_maximum_head_position() final;
std::shared_ptr<Track> get_track_at_position(Track::Address) final;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) final;
bool get_is_read_only() final;
private:
Storage::FileHolder file_;
int sectors_per_track_ = 16;
bool is_prodos_ = false;
private:
Storage::FileHolder file_;
int sectors_per_track_ = 16;
bool is_prodos_ = false;
long file_offset(Track::Address address);
size_t logical_sector_for_physical_sector(size_t physical);
long file_offset(Track::Address);
size_t logical_sector_for_physical_sector(size_t physical);
};
}

View File

@ -21,51 +21,51 @@ namespace Storage::Disk {
Provides a @c Disk containing an Amstrad CPC-type disk image: some arrangement of sectors with status bits.
*/
class CPCDSK: public DiskImage {
public:
/*!
Construct a @c CPCDSK containing content from the file with name @c file_name.
public:
/*!
Construct a @c CPCDSK containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
CPCDSK(const std::string &file_name);
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
CPCDSK(const std::string &file_name);
// DiskImage interface.
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
bool get_is_read_only() final;
void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
// DiskImage interface.
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
bool get_is_read_only() final;
void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
private:
struct Track {
uint8_t track;
uint8_t side;
enum class DataRate {
Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity
} data_rate;
enum class DataEncoding {
Unknown, FM, MFM
} data_encoding;
uint8_t sector_length;
uint8_t gap3_length;
uint8_t filler_byte;
private:
struct Track {
uint8_t track;
uint8_t side;
enum class DataRate {
Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity
} data_rate;
enum class DataEncoding {
Unknown, FM, MFM
} data_encoding;
uint8_t sector_length;
uint8_t gap3_length;
uint8_t filler_byte;
struct Sector: public ::Storage::Encodings::MFM::Sector {
uint8_t fdc_status1;
uint8_t fdc_status2;
};
std::vector<Sector> sectors;
struct Sector: public ::Storage::Encodings::MFM::Sector {
uint8_t fdc_status1;
uint8_t fdc_status2;
};
std::string file_name_;
std::vector<std::unique_ptr<Track>> tracks_;
std::size_t index_for_track(::Storage::Disk::Track::Address address);
int head_count_;
int head_position_count_;
bool is_extended_;
bool is_read_only_;
std::vector<Sector> sectors;
};
std::string file_name_;
std::vector<std::unique_ptr<Track>> tracks_;
std::size_t index_for_track(::Storage::Disk::Track::Address address);
int head_count_;
int head_position_count_;
bool is_extended_;
bool is_read_only_;
};
}

View File

@ -17,24 +17,24 @@ namespace Storage::Disk {
Provides a @c Disk containing a D64 disk image: a decoded sector dump of a C1540-format disk.
*/
class D64: public DiskImage {
public:
/*!
Construct a @c D64 containing content from the file with name @c file_name.
public:
/*!
Construct a @c D64 containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image.
*/
D64(const std::string &file_name);
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image.
*/
D64(const std::string &file_name);
// implemented to satisfy @c Disk
HeadPosition get_maximum_head_position() final;
using DiskImage::get_is_read_only;
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
// implemented to satisfy @c Disk
HeadPosition get_maximum_head_position() final;
using DiskImage::get_is_read_only;
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
private:
Storage::FileHolder file_;
int number_of_tracks_;
uint16_t disk_id_;
private:
Storage::FileHolder file_;
int number_of_tracks_;
uint16_t disk_id_;
};
}

Some files were not shown because too many files have changed in this diff Show More