mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-18 16:30:29 +00:00
Take another big swing at indentation, some const
s.
This commit is contained in:
parent
31c878b654
commit
d3ed485e7a
@ -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_;
|
||||
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 ®(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 ®(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_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||