1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-18 16:30:29 +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);