mirror of
https://github.com/TomHarte/CLK.git
synced 2026-03-17 14:16:45 +00:00
Take another big swing at indentation, some consts.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
inline static PerformerLookup performer_lookup_;
|
||||
|
||||
|
||||
@@ -85,79 +85,79 @@ public:
|
||||
|
||||
private:
|
||||
class State: public NullFlowController {
|
||||
public:
|
||||
State(BusHandler &handler) : bus_handler_(handler) {}
|
||||
public:
|
||||
State(BusHandler &handler) : bus_handler_(handler) {}
|
||||
|
||||
void run(int &);
|
||||
bool stopped = false;
|
||||
void run(int &);
|
||||
bool stopped = false;
|
||||
|
||||
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
|
||||
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
|
||||
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
|
||||
template <typename IntT> void write(uint32_t address, IntT value);
|
||||
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
|
||||
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
|
||||
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
|
||||
template <typename IntT> void write(uint32_t address, IntT value);
|
||||
|
||||
template <typename IntT> IntT read_pc();
|
||||
template <typename IntT> IntT read_pc();
|
||||
|
||||
// Processor state.
|
||||
Status status;
|
||||
CPU::SlicedInt32 program_counter;
|
||||
CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7.
|
||||
CPU::SlicedInt32 stack_pointers[2];
|
||||
uint32_t instruction_address;
|
||||
uint16_t instruction_opcode;
|
||||
// Processor state.
|
||||
Status status;
|
||||
CPU::SlicedInt32 program_counter;
|
||||
CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7.
|
||||
CPU::SlicedInt32 stack_pointers[2];
|
||||
uint32_t instruction_address;
|
||||
uint16_t instruction_opcode;
|
||||
|
||||
// Things that are ephemerally duplicative of Status.
|
||||
int active_stack_pointer = 0;
|
||||
Status::FlagT should_trace = 0;
|
||||
// Things that are ephemerally duplicative of Status.
|
||||
int active_stack_pointer = 0;
|
||||
Status::FlagT should_trace = 0;
|
||||
|
||||
// Bus state.
|
||||
int interrupt_input = 0;
|
||||
// Bus state.
|
||||
int interrupt_input = 0;
|
||||
|
||||
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
|
||||
// postincrement and predecrement mode.
|
||||
static constexpr uint32_t byte_increments[] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 2
|
||||
};
|
||||
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
|
||||
// postincrement and predecrement mode.
|
||||
static constexpr uint32_t byte_increments[] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 2
|
||||
};
|
||||
|
||||
// Flow control; Cf. Perform.hpp.
|
||||
template <bool use_current_instruction_pc = true> void raise_exception(int);
|
||||
// Flow control; Cf. Perform.hpp.
|
||||
template <bool use_current_instruction_pc = true> void raise_exception(int);
|
||||
|
||||
void did_update_status();
|
||||
void did_update_status();
|
||||
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
void bsr(uint32_t offset);
|
||||
void jmp(uint32_t);
|
||||
void jsr(uint32_t offset);
|
||||
void rtr();
|
||||
void rts();
|
||||
void rte();
|
||||
void stop();
|
||||
void reset();
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
void bsr(uint32_t offset);
|
||||
void jmp(uint32_t);
|
||||
void jsr(uint32_t offset);
|
||||
void rtr();
|
||||
void rts();
|
||||
void rte();
|
||||
void stop();
|
||||
void reset();
|
||||
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
void unlink(uint32_t &address);
|
||||
void pea(uint32_t address);
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
void unlink(uint32_t &address);
|
||||
void pea(uint32_t address);
|
||||
|
||||
void move_to_usp(uint32_t address);
|
||||
void move_from_usp(uint32_t &address);
|
||||
void move_to_usp(uint32_t address);
|
||||
void move_from_usp(uint32_t &address);
|
||||
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
|
||||
private:
|
||||
BusHandler &bus_handler_;
|
||||
Predecoder<model> decoder_;
|
||||
private:
|
||||
BusHandler &bus_handler_;
|
||||
Predecoder<model> decoder_;
|
||||
|
||||
struct EffectiveAddress {
|
||||
CPU::SlicedInt32 value;
|
||||
bool requires_fetch;
|
||||
};
|
||||
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
|
||||
uint32_t index_8bitdisplacement(uint32_t);
|
||||
struct EffectiveAddress {
|
||||
CPU::SlicedInt32 value;
|
||||
bool requires_fetch;
|
||||
};
|
||||
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
|
||||
uint32_t index_8bitdisplacement(uint32_t);
|
||||
} state_;
|
||||
};
|
||||
|
||||
|
||||
@@ -233,11 +233,11 @@ private:
|
||||
//
|
||||
// So here's a thin non-templated shim to unblock initial PC Compatible development.
|
||||
class Decoder8086 {
|
||||
public:
|
||||
std::pair<int, Instruction<false>> decode(const uint8_t *source, std::size_t length);
|
||||
public:
|
||||
std::pair<int, Instruction<false>> decode(const uint8_t *source, std::size_t length);
|
||||
|
||||
private:
|
||||
Decoder<Model::i8086> decoder;
|
||||
private:
|
||||
Decoder<Model::i8086> decoder;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -563,60 +563,60 @@ constexpr Operation rep_operation(Operation operation, Repetition repetition) {
|
||||
///
|
||||
/// It cannot natively describe a base of ::None.
|
||||
class ScaleIndexBase {
|
||||
public:
|
||||
constexpr ScaleIndexBase() noexcept = default;
|
||||
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
|
||||
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
|
||||
sib_(uint8_t(
|
||||
scale << 6 |
|
||||
(int(index != Source::None ? index : Source::eSP) << 3) |
|
||||
int(base)
|
||||
)) {}
|
||||
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
||||
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
||||
public:
|
||||
constexpr ScaleIndexBase() noexcept = default;
|
||||
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
|
||||
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
|
||||
sib_(uint8_t(
|
||||
scale << 6 |
|
||||
(int(index != Source::None ? index : Source::eSP) << 3) |
|
||||
int(base)
|
||||
)) {}
|
||||
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
||||
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
||||
|
||||
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
||||
constexpr int scale() const {
|
||||
return sib_ >> 6;
|
||||
}
|
||||
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
||||
constexpr int scale() const {
|
||||
return sib_ >> 6;
|
||||
}
|
||||
|
||||
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
|
||||
constexpr Source index() const {
|
||||
constexpr Source sources[] = {
|
||||
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
|
||||
};
|
||||
static_assert(sizeof(sources) == 8);
|
||||
return sources[(sib_ >> 3) & 0x7];
|
||||
}
|
||||
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
|
||||
constexpr Source index() const {
|
||||
constexpr Source sources[] = {
|
||||
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
|
||||
};
|
||||
static_assert(sizeof(sources) == 8);
|
||||
return sources[(sib_ >> 3) & 0x7];
|
||||
}
|
||||
|
||||
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
|
||||
constexpr Source base() const {
|
||||
return Source(sib_ & 0x7);
|
||||
}
|
||||
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
|
||||
constexpr Source base() const {
|
||||
return Source(sib_ & 0x7);
|
||||
}
|
||||
|
||||
constexpr uint8_t without_base() const {
|
||||
return sib_ & ~0x3;
|
||||
}
|
||||
constexpr uint8_t without_base() const {
|
||||
return sib_ & ~0x3;
|
||||
}
|
||||
|
||||
bool operator ==(const ScaleIndexBase &rhs) const {
|
||||
// Permit either exact equality or index and base being equal
|
||||
// but transposed with a scale of 1.
|
||||
return
|
||||
(sib_ == rhs.sib_) ||
|
||||
(
|
||||
!scale() && !rhs.scale() &&
|
||||
rhs.index() == base() &&
|
||||
rhs.base() == index()
|
||||
);
|
||||
}
|
||||
bool operator ==(const ScaleIndexBase &rhs) const {
|
||||
// Permit either exact equality or index and base being equal
|
||||
// but transposed with a scale of 1.
|
||||
return
|
||||
(sib_ == rhs.sib_) ||
|
||||
(
|
||||
!scale() && !rhs.scale() &&
|
||||
rhs.index() == base() &&
|
||||
rhs.base() == index()
|
||||
);
|
||||
}
|
||||
|
||||
operator uint8_t() const {
|
||||
return sib_;
|
||||
}
|
||||
operator uint8_t() const {
|
||||
return sib_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Data is stored directly as an 80386 SIB byte.
|
||||
uint8_t sib_ = 0;
|
||||
private:
|
||||
// Data is stored directly as an 80386 SIB byte.
|
||||
uint8_t sib_ = 0;
|
||||
};
|
||||
static_assert(sizeof(ScaleIndexBase) == 1);
|
||||
static_assert(alignof(ScaleIndexBase) == 1);
|
||||
@@ -632,69 +632,69 @@ static_assert(alignof(ScaleIndexBase) == 1);
|
||||
///
|
||||
/// In all cases, the applicable segment is indicated by the instruction.
|
||||
class DataPointer {
|
||||
public:
|
||||
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
||||
constexpr DataPointer(Source source) noexcept : source_(source) {}
|
||||
public:
|
||||
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
||||
constexpr DataPointer(Source source) noexcept : source_(source) {}
|
||||
|
||||
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
||||
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
|
||||
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
||||
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
|
||||
|
||||
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
||||
/// whether the base field of the SIB is effective.
|
||||
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
||||
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
||||
/// whether the base field of the SIB is effective.
|
||||
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
||||
|
||||
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
||||
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
||||
constexpr DataPointer(Source base, Source index, int scale) noexcept :
|
||||
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
||||
sib_(scale, index, base) {}
|
||||
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
||||
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
||||
constexpr DataPointer(Source base, Source index, int scale) noexcept :
|
||||
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
||||
sib_(scale, index, base) {}
|
||||
|
||||
constexpr bool operator ==(const DataPointer &rhs) const {
|
||||
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
|
||||
return
|
||||
source_ == rhs.source_ && (
|
||||
source_ < Source::IndirectNoBase ||
|
||||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
|
||||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
|
||||
);
|
||||
constexpr bool operator ==(const DataPointer &rhs) const {
|
||||
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
|
||||
return
|
||||
source_ == rhs.source_ && (
|
||||
source_ < Source::IndirectNoBase ||
|
||||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
|
||||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
|
||||
);
|
||||
}
|
||||
|
||||
constexpr Source source() const {
|
||||
return source_;
|
||||
}
|
||||
|
||||
constexpr int scale() const {
|
||||
return sib_.scale();
|
||||
}
|
||||
|
||||
constexpr Source index() const {
|
||||
return sib_.index();
|
||||
}
|
||||
|
||||
/// @returns The default segment to use for this access.
|
||||
constexpr Source default_segment() const {
|
||||
switch(source_) {
|
||||
default:
|
||||
case Source::IndirectNoBase:
|
||||
return Source::None;
|
||||
|
||||
case Source::Indirect:
|
||||
switch(base()) {
|
||||
default: return Source::DS;
|
||||
case Source::eBP:
|
||||
case Source::eSP: return Source::SS;
|
||||
case Source::eDI: return Source::ES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Source source() const {
|
||||
return source_;
|
||||
}
|
||||
constexpr Source base() const {
|
||||
return sib_.base();
|
||||
}
|
||||
|
||||
constexpr int scale() const {
|
||||
return sib_.scale();
|
||||
}
|
||||
|
||||
constexpr Source index() const {
|
||||
return sib_.index();
|
||||
}
|
||||
|
||||
/// @returns The default segment to use for this access.
|
||||
constexpr Source default_segment() const {
|
||||
switch(source_) {
|
||||
default:
|
||||
case Source::IndirectNoBase:
|
||||
return Source::None;
|
||||
|
||||
case Source::Indirect:
|
||||
switch(base()) {
|
||||
default: return Source::DS;
|
||||
case Source::eBP:
|
||||
case Source::eSP: return Source::SS;
|
||||
case Source::eDI: return Source::ES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Source base() const {
|
||||
return sib_.base();
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_ = Source::Indirect;
|
||||
ScaleIndexBase sib_;
|
||||
private:
|
||||
Source source_ = Source::Indirect;
|
||||
ScaleIndexBase sib_;
|
||||
};
|
||||
|
||||
template<bool is_32bit> class Instruction {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -236,275 +236,275 @@ struct MemoryController {
|
||||
ioc_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
private:
|
||||
Log::Logger<Log::Source::ARMIOC> logger;
|
||||
private:
|
||||
Log::Logger<Log::Source::ARMIOC> logger;
|
||||
|
||||
enum class ReadZone {
|
||||
LogicallyMappedRAM,
|
||||
PhysicallyMappedRAM,
|
||||
IOControllers,
|
||||
LowROM,
|
||||
HighROM,
|
||||
};
|
||||
enum class WriteZone {
|
||||
LogicallyMappedRAM,
|
||||
PhysicallyMappedRAM,
|
||||
IOControllers,
|
||||
VideoController,
|
||||
DMAAndMEMC,
|
||||
AddressTranslator,
|
||||
};
|
||||
template <bool is_read>
|
||||
using Zone = std::conditional_t<is_read, ReadZone, WriteZone>;
|
||||
enum class ReadZone {
|
||||
LogicallyMappedRAM,
|
||||
PhysicallyMappedRAM,
|
||||
IOControllers,
|
||||
LowROM,
|
||||
HighROM,
|
||||
};
|
||||
enum class WriteZone {
|
||||
LogicallyMappedRAM,
|
||||
PhysicallyMappedRAM,
|
||||
IOControllers,
|
||||
VideoController,
|
||||
DMAAndMEMC,
|
||||
AddressTranslator,
|
||||
};
|
||||
template <bool is_read>
|
||||
using Zone = std::conditional_t<is_read, ReadZone, WriteZone>;
|
||||
|
||||
template <bool is_read>
|
||||
static std::array<Zone<is_read>, 0x20> zones() {
|
||||
std::array<Zone<is_read>, 0x20> zones{};
|
||||
for(size_t c = 0; c < zones.size(); c++) {
|
||||
const auto address = c << 21;
|
||||
if(address < 0x200'0000) {
|
||||
zones[c] = Zone<is_read>::LogicallyMappedRAM;
|
||||
} else if(address < 0x300'0000) {
|
||||
zones[c] = Zone<is_read>::PhysicallyMappedRAM;
|
||||
} else if(address < 0x340'0000) {
|
||||
zones[c] = Zone<is_read>::IOControllers;
|
||||
} else if(address < 0x360'0000) {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::LowROM;
|
||||
} else {
|
||||
zones[c] = Zone<is_read>::VideoController;
|
||||
}
|
||||
} else if(address < 0x380'0000) {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::LowROM;
|
||||
} else {
|
||||
zones[c] = Zone<is_read>::DMAAndMEMC;
|
||||
}
|
||||
template <bool is_read>
|
||||
static std::array<Zone<is_read>, 0x20> zones() {
|
||||
std::array<Zone<is_read>, 0x20> zones{};
|
||||
for(size_t c = 0; c < zones.size(); c++) {
|
||||
const auto address = c << 21;
|
||||
if(address < 0x200'0000) {
|
||||
zones[c] = Zone<is_read>::LogicallyMappedRAM;
|
||||
} else if(address < 0x300'0000) {
|
||||
zones[c] = Zone<is_read>::PhysicallyMappedRAM;
|
||||
} else if(address < 0x340'0000) {
|
||||
zones[c] = Zone<is_read>::IOControllers;
|
||||
} else if(address < 0x360'0000) {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::LowROM;
|
||||
} else {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::HighROM;
|
||||
} else {
|
||||
zones[c] = Zone<is_read>::AddressTranslator;
|
||||
}
|
||||
zones[c] = Zone<is_read>::VideoController;
|
||||
}
|
||||
} else if(address < 0x380'0000) {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::LowROM;
|
||||
} else {
|
||||
zones[c] = Zone<is_read>::DMAAndMEMC;
|
||||
}
|
||||
} else {
|
||||
if constexpr (is_read) {
|
||||
zones[c] = Zone<is_read>::HighROM;
|
||||
} else {
|
||||
zones[c] = Zone<is_read>::AddressTranslator;
|
||||
}
|
||||
}
|
||||
return zones;
|
||||
}
|
||||
return zones;
|
||||
}
|
||||
|
||||
bool has_moved_rom_ = false;
|
||||
std::array<uint8_t, 2*1024*1024> rom_;
|
||||
std::array<uint8_t, 4*1024*1024> ram_{};
|
||||
InputOutputController<InterruptObserverT, ClockRateObserverT> ioc_;
|
||||
|
||||
template <typename IntT>
|
||||
IntT &physical_ram(uint32_t address) {
|
||||
address = aligned<IntT>(address);
|
||||
address &= (ram_.size() - 1);
|
||||
return *reinterpret_cast<IntT *>(&ram_[address]);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
IntT &high_rom(uint32_t address) {
|
||||
address = aligned<IntT>(address);
|
||||
return *reinterpret_cast<IntT *>(&rom_[address & (rom_.size() - 1)]);
|
||||
}
|
||||
|
||||
std::array<ReadZone, 0x20> read_zones_ = zones<true>();
|
||||
const std::array<WriteZone, 0x20> write_zones_ = zones<false>();
|
||||
|
||||
// Control register values.
|
||||
bool os_mode_ = false;
|
||||
bool sound_dma_enable_ = false;
|
||||
bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily.
|
||||
|
||||
enum class DynamicRAMRefresh {
|
||||
None = 0b00,
|
||||
DuringFlyback = 0b01,
|
||||
Continuous = 0b11,
|
||||
} dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value.
|
||||
|
||||
enum class ROMAccessTime {
|
||||
ns450 = 0b00,
|
||||
ns325 = 0b01,
|
||||
ns200 = 0b10,
|
||||
ns200with60nsNibble = 0b11,
|
||||
} high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450;
|
||||
|
||||
enum class PageSize {
|
||||
kb4 = 0b00,
|
||||
kb8 = 0b01,
|
||||
kb16 = 0b10,
|
||||
kb32 = 0b11,
|
||||
} page_size_ = PageSize::kb4;
|
||||
int page_address_shift_ = 12;
|
||||
uint32_t page_adddress_mask_ = 0xffff;
|
||||
|
||||
// Address translator.
|
||||
//
|
||||
// MEMC contains one entry per a physical page number, indicating where it goes logically.
|
||||
// Any logical access is tested against all 128 mappings. So that's backwards compared to
|
||||
// the ideal for an emulator, which would map from logical to physical, even if a lot more
|
||||
// compact — there are always 128 physical pages; there are up to 8192 logical pages.
|
||||
//
|
||||
// So captured here are both the physical -> logical map as representative of the real
|
||||
// hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt)
|
||||
// from the other.
|
||||
|
||||
// Physical to logical mapping.
|
||||
std::array<uint32_t, 128> pages_{};
|
||||
|
||||
// Logical to physical mapping; this is divided by 'access mode'
|
||||
// (i.e. the combination of read/write, trans and OS mode flags,
|
||||
// as multipliexed by the @c mapping() function) because mapping
|
||||
// varies by mode — not just in terms of restricting access, but
|
||||
// actually presenting different memory.
|
||||
using MapTarget = std::array<uint8_t *, 8192>;
|
||||
std::array<MapTarget, 6> mapping_;
|
||||
|
||||
template <bool is_read>
|
||||
MapTarget &mapping(bool trans, bool os_mode) {
|
||||
const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0);
|
||||
return mapping_[index];
|
||||
}
|
||||
|
||||
bool map_dirty_ = true;
|
||||
|
||||
/// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise.
|
||||
template <typename IntT, bool is_read>
|
||||
IntT *logical_ram(uint32_t address, bool trans) {
|
||||
// Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive
|
||||
// mapping process. It can be eliminated when the process is improved.
|
||||
if(map_dirty_) {
|
||||
update_mapping();
|
||||
map_dirty_ = false;
|
||||
}
|
||||
address = aligned<IntT>(address);
|
||||
address &= 0x1ff'ffff;
|
||||
const size_t page = address >> page_address_shift_;
|
||||
|
||||
const auto &map = mapping<is_read>(trans, os_mode_);
|
||||
address &= page_adddress_mask_;
|
||||
return reinterpret_cast<IntT *>(&map[page][address]);
|
||||
}
|
||||
|
||||
void update_mapping() {
|
||||
// For each physical page, project it into logical space.
|
||||
switch(page_size_) {
|
||||
default:
|
||||
case PageSize::kb4: update_mapping<PageSize::kb4>(); break;
|
||||
case PageSize::kb8: update_mapping<PageSize::kb8>(); break;
|
||||
case PageSize::kb16: update_mapping<PageSize::kb16>(); break;
|
||||
case PageSize::kb32: update_mapping<PageSize::kb32>(); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <PageSize size>
|
||||
void update_mapping() {
|
||||
// Clear all logical mappings.
|
||||
for(auto &map: mapping_) {
|
||||
// Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark
|
||||
// the entire page as unmapped no matter what offset is added.
|
||||
std::fill(map.begin(), map.end(), ram_.data() - 32768);
|
||||
}
|
||||
|
||||
bool has_moved_rom_ = false;
|
||||
std::array<uint8_t, 2*1024*1024> rom_;
|
||||
std::array<uint8_t, 4*1024*1024> ram_{};
|
||||
InputOutputController<InterruptObserverT, ClockRateObserverT> ioc_;
|
||||
// For each physical page, project it into logical space
|
||||
// and store it.
|
||||
for(const auto page: pages_) {
|
||||
uint32_t physical, logical;
|
||||
|
||||
template <typename IntT>
|
||||
IntT &physical_ram(uint32_t address) {
|
||||
address = aligned<IntT>(address);
|
||||
address &= (ram_.size() - 1);
|
||||
return *reinterpret_cast<IntT *>(&ram_[address]);
|
||||
}
|
||||
switch(size) {
|
||||
case PageSize::kb4:
|
||||
// 4kb:
|
||||
// A[6:0] -> PPN[6:0]
|
||||
// A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages
|
||||
physical = page & BitMask<6, 0>::value;
|
||||
|
||||
template <typename IntT>
|
||||
IntT &high_rom(uint32_t address) {
|
||||
address = aligned<IntT>(address);
|
||||
return *reinterpret_cast<IntT *>(&rom_[address & (rom_.size() - 1)]);
|
||||
}
|
||||
physical <<= 12;
|
||||
|
||||
std::array<ReadZone, 0x20> read_zones_ = zones<true>();
|
||||
const std::array<WriteZone, 0x20> write_zones_ = zones<false>();
|
||||
logical = (page & BitMask<11, 10>::value) << 1;
|
||||
logical |= (page & BitMask<22, 12>::value) >> 12;
|
||||
break;
|
||||
|
||||
// Control register values.
|
||||
bool os_mode_ = false;
|
||||
bool sound_dma_enable_ = false;
|
||||
bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily.
|
||||
case PageSize::kb8:
|
||||
// 8kb:
|
||||
// A[0] -> PPN[6]; A[6:1] -> PPN[5:0]
|
||||
// A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages
|
||||
physical = (page & BitMask<0, 0>::value) << 6;
|
||||
physical |= (page & BitMask<6, 1>::value) >> 1;
|
||||
|
||||
enum class DynamicRAMRefresh {
|
||||
None = 0b00,
|
||||
DuringFlyback = 0b01,
|
||||
Continuous = 0b11,
|
||||
} dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value.
|
||||
physical <<= 13;
|
||||
|
||||
enum class ROMAccessTime {
|
||||
ns450 = 0b00,
|
||||
ns325 = 0b01,
|
||||
ns200 = 0b10,
|
||||
ns200with60nsNibble = 0b11,
|
||||
} high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450;
|
||||
logical = page & BitMask<11, 10>::value;
|
||||
logical |= (page & BitMask<22, 13>::value) >> 13;
|
||||
break;
|
||||
|
||||
enum class PageSize {
|
||||
kb4 = 0b00,
|
||||
kb8 = 0b01,
|
||||
kb16 = 0b10,
|
||||
kb32 = 0b11,
|
||||
} page_size_ = PageSize::kb4;
|
||||
int page_address_shift_ = 12;
|
||||
uint32_t page_adddress_mask_ = 0xffff;
|
||||
case PageSize::kb16:
|
||||
// 16kb:
|
||||
// A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0]
|
||||
// A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages
|
||||
physical = (page & BitMask<1, 0>::value) << 5;
|
||||
physical |= (page & BitMask<6, 2>::value) >> 2;
|
||||
|
||||
// Address translator.
|
||||
//
|
||||
// MEMC contains one entry per a physical page number, indicating where it goes logically.
|
||||
// Any logical access is tested against all 128 mappings. So that's backwards compared to
|
||||
// the ideal for an emulator, which would map from logical to physical, even if a lot more
|
||||
// compact — there are always 128 physical pages; there are up to 8192 logical pages.
|
||||
//
|
||||
// So captured here are both the physical -> logical map as representative of the real
|
||||
// hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt)
|
||||
// from the other.
|
||||
physical <<= 14;
|
||||
|
||||
// Physical to logical mapping.
|
||||
std::array<uint32_t, 128> pages_{};
|
||||
logical = (page & BitMask<11, 10>::value) >> 1;
|
||||
logical |= (page & BitMask<22, 14>::value) >> 14;
|
||||
break;
|
||||
|
||||
// Logical to physical mapping; this is divided by 'access mode'
|
||||
// (i.e. the combination of read/write, trans and OS mode flags,
|
||||
// as multipliexed by the @c mapping() function) because mapping
|
||||
// varies by mode — not just in terms of restricting access, but
|
||||
// actually presenting different memory.
|
||||
using MapTarget = std::array<uint8_t *, 8192>;
|
||||
std::array<MapTarget, 6> mapping_;
|
||||
case PageSize::kb32:
|
||||
// 32kb:
|
||||
// A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0]
|
||||
// A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages
|
||||
physical = (page & BitMask<1, 1>::value) << 5;
|
||||
physical |= (page & BitMask<2, 2>::value) << 3;
|
||||
physical |= (page & BitMask<0, 0>::value) << 4;
|
||||
physical |= (page & BitMask<6, 3>::value) >> 3;
|
||||
|
||||
template <bool is_read>
|
||||
MapTarget &mapping(bool trans, bool os_mode) {
|
||||
const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0);
|
||||
return mapping_[index];
|
||||
}
|
||||
physical <<= 15;
|
||||
|
||||
bool map_dirty_ = true;
|
||||
|
||||
/// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise.
|
||||
template <typename IntT, bool is_read>
|
||||
IntT *logical_ram(uint32_t address, bool trans) {
|
||||
// Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive
|
||||
// mapping process. It can be eliminated when the process is improved.
|
||||
if(map_dirty_) {
|
||||
update_mapping();
|
||||
map_dirty_ = false;
|
||||
logical = (page & BitMask<11, 10>::value) >> 2;
|
||||
logical |= (page & BitMask<22, 15>::value) >> 15;
|
||||
break;
|
||||
}
|
||||
address = aligned<IntT>(address);
|
||||
address &= 0x1ff'ffff;
|
||||
const size_t page = address >> page_address_shift_;
|
||||
|
||||
const auto &map = mapping<is_read>(trans, os_mode_);
|
||||
address &= page_adddress_mask_;
|
||||
return reinterpret_cast<IntT *>(&map[page][address]);
|
||||
}
|
||||
|
||||
void update_mapping() {
|
||||
// For each physical page, project it into logical space.
|
||||
switch(page_size_) {
|
||||
default:
|
||||
case PageSize::kb4: update_mapping<PageSize::kb4>(); break;
|
||||
case PageSize::kb8: update_mapping<PageSize::kb8>(); break;
|
||||
case PageSize::kb16: update_mapping<PageSize::kb16>(); break;
|
||||
case PageSize::kb32: update_mapping<PageSize::kb32>(); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <PageSize size>
|
||||
void update_mapping() {
|
||||
// Clear all logical mappings.
|
||||
for(auto &map: mapping_) {
|
||||
// Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark
|
||||
// the entire page as unmapped no matter what offset is added.
|
||||
std::fill(map.begin(), map.end(), ram_.data() - 32768);
|
||||
}
|
||||
|
||||
// For each physical page, project it into logical space
|
||||
// and store it.
|
||||
for(const auto page: pages_) {
|
||||
uint32_t physical, logical;
|
||||
|
||||
switch(size) {
|
||||
case PageSize::kb4:
|
||||
// 4kb:
|
||||
// A[6:0] -> PPN[6:0]
|
||||
// A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages
|
||||
physical = page & BitMask<6, 0>::value;
|
||||
|
||||
physical <<= 12;
|
||||
|
||||
logical = (page & BitMask<11, 10>::value) << 1;
|
||||
logical |= (page & BitMask<22, 12>::value) >> 12;
|
||||
break;
|
||||
|
||||
case PageSize::kb8:
|
||||
// 8kb:
|
||||
// A[0] -> PPN[6]; A[6:1] -> PPN[5:0]
|
||||
// A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages
|
||||
physical = (page & BitMask<0, 0>::value) << 6;
|
||||
physical |= (page & BitMask<6, 1>::value) >> 1;
|
||||
|
||||
physical <<= 13;
|
||||
|
||||
logical = page & BitMask<11, 10>::value;
|
||||
logical |= (page & BitMask<22, 13>::value) >> 13;
|
||||
break;
|
||||
|
||||
case PageSize::kb16:
|
||||
// 16kb:
|
||||
// A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0]
|
||||
// A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages
|
||||
physical = (page & BitMask<1, 0>::value) << 5;
|
||||
physical |= (page & BitMask<6, 2>::value) >> 2;
|
||||
|
||||
physical <<= 14;
|
||||
|
||||
logical = (page & BitMask<11, 10>::value) >> 1;
|
||||
logical |= (page & BitMask<22, 14>::value) >> 14;
|
||||
break;
|
||||
|
||||
case PageSize::kb32:
|
||||
// 32kb:
|
||||
// A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0]
|
||||
// A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages
|
||||
physical = (page & BitMask<1, 1>::value) << 5;
|
||||
physical |= (page & BitMask<2, 2>::value) << 3;
|
||||
physical |= (page & BitMask<0, 0>::value) << 4;
|
||||
physical |= (page & BitMask<6, 3>::value) >> 3;
|
||||
|
||||
physical <<= 15;
|
||||
|
||||
logical = (page & BitMask<11, 10>::value) >> 2;
|
||||
logical |= (page & BitMask<22, 15>::value) >> 15;
|
||||
break;
|
||||
}
|
||||
|
||||
// printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical);
|
||||
|
||||
// TODO: consider clashes.
|
||||
// TODO: what if there's less than 4mb present?
|
||||
const auto target = &ram_[physical];
|
||||
// TODO: consider clashes.
|
||||
// TODO: what if there's less than 4mb present?
|
||||
const auto target = &ram_[physical];
|
||||
|
||||
const auto set_supervisor = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(false, false)[logical] = target;
|
||||
if(write) mapping<false>(false, false)[logical] = target;
|
||||
};
|
||||
const auto set_supervisor = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(false, false)[logical] = target;
|
||||
if(write) mapping<false>(false, false)[logical] = target;
|
||||
};
|
||||
|
||||
const auto set_os = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(true, true)[logical] = target;
|
||||
if(write) mapping<false>(true, true)[logical] = target;
|
||||
};
|
||||
const auto set_os = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(true, true)[logical] = target;
|
||||
if(write) mapping<false>(true, true)[logical] = target;
|
||||
};
|
||||
|
||||
const auto set_user = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(true, false)[logical] = target;
|
||||
if(write) mapping<false>(true, false)[logical] = target;
|
||||
};
|
||||
const auto set_user = [&](bool read, bool write) {
|
||||
if(read) mapping<true>(true, false)[logical] = target;
|
||||
if(write) mapping<false>(true, false)[logical] = target;
|
||||
};
|
||||
|
||||
set_supervisor(true, true);
|
||||
switch((page >> 8) & 3) {
|
||||
case 0b00:
|
||||
set_os(true, true);
|
||||
set_user(true, true);
|
||||
break;
|
||||
case 0b01:
|
||||
set_os(true, true);
|
||||
set_user(true, false);
|
||||
break;
|
||||
default:
|
||||
set_os(true, false);
|
||||
set_user(false, false);
|
||||
break;
|
||||
}
|
||||
set_supervisor(true, true);
|
||||
switch((page >> 8) & 3) {
|
||||
case 0b00:
|
||||
set_os(true, true);
|
||||
set_user(true, true);
|
||||
break;
|
||||
case 0b01:
|
||||
set_os(true, true);
|
||||
set_user(true, false);
|
||||
break;
|
||||
default:
|
||||
set_os(true, false);
|
||||
set_user(false, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,19 +14,19 @@
|
||||
namespace Electron {
|
||||
|
||||
class Plus3 final : public WD::WD1770 {
|
||||
public:
|
||||
Plus3();
|
||||
public:
|
||||
Plus3();
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
|
||||
void set_control_register(uint8_t control);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk>, size_t drive);
|
||||
void set_control_register(uint8_t control);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
uint8_t last_control_ = 0;
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
uint8_t last_control_ = 0;
|
||||
|
||||
void set_motor_on(bool on) override;
|
||||
std::string drive_name(size_t drive);
|
||||
void set_motor_on(bool) override;
|
||||
std::string drive_name(size_t);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,26 +14,25 @@
|
||||
namespace Electron {
|
||||
|
||||
class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, false> {
|
||||
public:
|
||||
SoundGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
public:
|
||||
SoundGenerator(Concurrency::AsyncTaskQueue<false> &);
|
||||
|
||||
void set_divider(uint8_t divider);
|
||||
void set_divider(uint8_t);
|
||||
void set_is_enabled(bool);
|
||||
|
||||
void set_is_enabled(bool is_enabled);
|
||||
static constexpr unsigned int clock_rate_divider = 8;
|
||||
|
||||
static constexpr unsigned int clock_rate_divider = 8;
|
||||
// For BufferSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
// For BufferSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
unsigned int volume_ = 0;
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
unsigned int volume_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,56 +20,56 @@ namespace Electron {
|
||||
class Tape:
|
||||
public Storage::Tape::TapePlayer,
|
||||
public Storage::Tape::Acorn::Shifter::Delegate {
|
||||
public:
|
||||
Tape();
|
||||
public:
|
||||
Tape();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Tape::TapePlayer::run_for;
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Tape::TapePlayer::run_for;
|
||||
|
||||
uint8_t get_data_register();
|
||||
void set_data_register(uint8_t value);
|
||||
void set_counter(uint8_t value);
|
||||
uint8_t get_data_register();
|
||||
void set_data_register(uint8_t value);
|
||||
void set_counter(uint8_t value);
|
||||
|
||||
inline uint8_t get_interrupt_status() { return interrupt_status_; }
|
||||
void clear_interrupts(uint8_t interrupts);
|
||||
inline uint8_t get_interrupt_status() { return interrupt_status_; }
|
||||
void clear_interrupts(uint8_t interrupts);
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
inline void set_is_running(bool is_running) { is_running_ = is_running; }
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
inline void set_is_running(bool is_running) { is_running_ = is_running; }
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
void acorn_shifter_output_bit(int value);
|
||||
|
||||
private:
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
private:
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
struct {
|
||||
int minimum_bits_until_full = 0;
|
||||
} input_;
|
||||
struct {
|
||||
unsigned int cycles_into_pulse = 0;
|
||||
unsigned int bits_remaining_until_empty = 0;
|
||||
} output_;
|
||||
struct {
|
||||
int minimum_bits_until_full = 0;
|
||||
} input_;
|
||||
struct {
|
||||
unsigned int cycles_into_pulse = 0;
|
||||
unsigned int bits_remaining_until_empty = 0;
|
||||
} output_;
|
||||
|
||||
bool is_running_ = false;
|
||||
bool is_enabled_ = false;
|
||||
bool is_in_input_mode_ = false;
|
||||
bool is_running_ = false;
|
||||
bool is_enabled_ = false;
|
||||
bool is_in_input_mode_ = false;
|
||||
|
||||
inline void evaluate_interrupts();
|
||||
uint16_t data_register_ = 0;
|
||||
inline void evaluate_interrupts();
|
||||
uint16_t data_register_ = 0;
|
||||
|
||||
uint8_t interrupt_status_ = 0;
|
||||
uint8_t last_posted_interrupt_status_ = 0;
|
||||
Delegate *delegate_ = nullptr;
|
||||
uint8_t interrupt_status_ = 0;
|
||||
uint8_t last_posted_interrupt_status_ = 0;
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
||||
::Storage::Tape::Acorn::Shifter shifter_;
|
||||
::Storage::Tape::Acorn::Shifter shifter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,160 +24,160 @@ namespace Electron {
|
||||
is accessing it the CPU may not.
|
||||
*/
|
||||
class VideoOutput {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a VideoOutput that will read its pixels from @c memory.
|
||||
public:
|
||||
/*!
|
||||
Instantiates a VideoOutput that will read its pixels from @c memory.
|
||||
|
||||
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
|
||||
*/
|
||||
VideoOutput(const uint8_t *memory);
|
||||
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
|
||||
*/
|
||||
VideoOutput(const uint8_t *memory);
|
||||
|
||||
/// Sets the destination for output.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
/// Sets the destination for output.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Produces the next @c cycles of video output.
|
||||
///
|
||||
/// @returns a bit mask of all interrupts triggered.
|
||||
uint8_t run_for(const Cycles cycles);
|
||||
/// Produces the next @c cycles of video output.
|
||||
///
|
||||
/// @returns a bit mask of all interrupts triggered.
|
||||
uint8_t run_for(const Cycles cycles);
|
||||
|
||||
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
||||
/// IO [/1Mhz] access that is first signalled in the upcoming cycle.
|
||||
Cycles io_delay() {
|
||||
return 2 + ((h_count_ >> 3)&1);
|
||||
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
||||
/// IO [/1Mhz] access that is first signalled in the upcoming cycle.
|
||||
Cycles io_delay() {
|
||||
return 2 + ((h_count_ >> 3)&1);
|
||||
}
|
||||
|
||||
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
||||
/// RAM access that is first signalled in the upcoming cycle.
|
||||
Cycles ram_delay() {
|
||||
if(!mode_40_ && !in_blank()) {
|
||||
return 2 + ((h_active - h_count_) >> 3);
|
||||
}
|
||||
return io_delay();
|
||||
}
|
||||
|
||||
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
||||
/// RAM access that is first signalled in the upcoming cycle.
|
||||
Cycles ram_delay() {
|
||||
if(!mode_40_ && !in_blank()) {
|
||||
return 2 + ((h_active - h_count_) >> 3);
|
||||
}
|
||||
return io_delay();
|
||||
/*!
|
||||
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
|
||||
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
|
||||
*/
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/*!
|
||||
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
|
||||
before the video circuits will allow the CPU to access RAM.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
||||
|
||||
private:
|
||||
const uint8_t *ram_ = nullptr;
|
||||
|
||||
// CRT output
|
||||
enum class OutputStage {
|
||||
Sync, Blank, Pixels, ColourBurst,
|
||||
};
|
||||
OutputStage output_ = OutputStage::Blank;
|
||||
int output_length_ = 0;
|
||||
int screen_pitch_ = 0;
|
||||
|
||||
uint8_t *current_output_target_ = nullptr;
|
||||
uint8_t *initial_output_target_ = nullptr;
|
||||
int current_output_divider_ = 1;
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
// Palettes.
|
||||
uint8_t palette_[8]{};
|
||||
uint8_t palette1bpp_[2]{};
|
||||
uint8_t palette2bpp_[4]{};
|
||||
uint8_t palette4bpp_[16]{};
|
||||
|
||||
template <int index, int source_bit, int target_bit>
|
||||
uint8_t channel() {
|
||||
if constexpr (source_bit < target_bit) {
|
||||
return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit);
|
||||
} else {
|
||||
return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
|
||||
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
|
||||
*/
|
||||
void write(int address, uint8_t value);
|
||||
template <int r_index, int r_bit, int g_index, int g_bit, int b_index, int b_bit>
|
||||
uint8_t palette_entry() {
|
||||
return channel<r_index, r_bit, 2>() | channel<g_index, g_bit, 1>() | channel<b_index, b_bit, 0>();
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
|
||||
before the video circuits will allow the CPU to access RAM.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
||||
// User-selected base address; constrained to a 64-byte boundary by the setter.
|
||||
uint16_t screen_base_ = 0;
|
||||
|
||||
private:
|
||||
const uint8_t *ram_ = nullptr;
|
||||
// Parameters implied by mode selection.
|
||||
uint16_t mode_base_ = 0;
|
||||
bool mode_40_ = true;
|
||||
bool mode_text_ = false;
|
||||
enum class Bpp {
|
||||
One = 1, Two = 2, Four = 4
|
||||
} mode_bpp_ = Bpp::One;
|
||||
|
||||
// CRT output
|
||||
enum class OutputStage {
|
||||
Sync, Blank, Pixels, ColourBurst,
|
||||
};
|
||||
OutputStage output_ = OutputStage::Blank;
|
||||
int output_length_ = 0;
|
||||
int screen_pitch_ = 0;
|
||||
// Frame position.
|
||||
int v_count_ = 0;
|
||||
int h_count_ = 0;
|
||||
bool field_ = true;
|
||||
|
||||
uint8_t *current_output_target_ = nullptr;
|
||||
uint8_t *initial_output_target_ = nullptr;
|
||||
int current_output_divider_ = 1;
|
||||
Outputs::CRT::CRT crt_;
|
||||
// Current working address.
|
||||
uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row.
|
||||
uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line.
|
||||
int char_row_ = 0; // Character row; 0–9 in text mode, 0–7 in graphics.
|
||||
|
||||
// Palettes.
|
||||
uint8_t palette_[8]{};
|
||||
uint8_t palette1bpp_[2]{};
|
||||
uint8_t palette2bpp_[4]{};
|
||||
uint8_t palette4bpp_[16]{};
|
||||
// Sync states.
|
||||
bool vsync_int_ = false; // True => vsync active.
|
||||
bool hsync_int_ = false; // True => hsync active.
|
||||
|
||||
template <int index, int source_bit, int target_bit>
|
||||
uint8_t channel() {
|
||||
if constexpr (source_bit < target_bit) {
|
||||
return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit);
|
||||
} else {
|
||||
return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit);
|
||||
}
|
||||
}
|
||||
// Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all
|
||||
// divisible by 8, so it's safe to count time with a 2Mhz input.
|
||||
static constexpr int h_active = 640;
|
||||
static constexpr int hsync_start = 768;
|
||||
static constexpr int hsync_end = 832;
|
||||
static constexpr int h_reset_addr = 1016;
|
||||
static constexpr int h_total = 1024; // Minor digression from the FPGA original here;
|
||||
// in this implementation the value is tested
|
||||
// _after_ position increment rather than before/instead.
|
||||
// So it needs to be one higher. Which is baked into
|
||||
// the constant to emphasise the all-divisible-by-8 property.
|
||||
|
||||
template <int r_index, int r_bit, int g_index, int g_bit, int b_index, int b_bit>
|
||||
uint8_t palette_entry() {
|
||||
return channel<r_index, r_bit, 2>() | channel<g_index, g_bit, 1>() | channel<b_index, b_bit, 0>();
|
||||
}
|
||||
static constexpr int h_half = h_total / 2;
|
||||
static constexpr int hburst_start = 856;
|
||||
static constexpr int hburst_end = 896;
|
||||
|
||||
// User-selected base address; constrained to a 64-byte boundary by the setter.
|
||||
uint16_t screen_base_ = 0;
|
||||
// Vertical timing parameters; all in terms of lines. As per the horizontal parameters above,
|
||||
// lines begin with their first visible pixel (or the equivalent position).
|
||||
static constexpr int v_active_gph = 256;
|
||||
static constexpr int v_active_txt = 250;
|
||||
static constexpr int v_disp_gph = v_active_gph - 1;
|
||||
static constexpr int v_disp_txt = v_active_txt - 1;
|
||||
static constexpr int vsync_start = 274;
|
||||
static constexpr int vsync_end = 276;
|
||||
static constexpr int v_rtc = 99;
|
||||
|
||||
// Parameters implied by mode selection.
|
||||
uint16_t mode_base_ = 0;
|
||||
bool mode_40_ = true;
|
||||
bool mode_text_ = false;
|
||||
enum class Bpp {
|
||||
One = 1, Two = 2, Four = 4
|
||||
} mode_bpp_ = Bpp::One;
|
||||
// Various signals that it was convenient to factor out.
|
||||
int v_total() const {
|
||||
return field_ ? 312 : 311;
|
||||
}
|
||||
|
||||
// Frame position.
|
||||
int v_count_ = 0;
|
||||
int h_count_ = 0;
|
||||
bool field_ = true;
|
||||
bool last_line() const {
|
||||
return char_row_ == (mode_text_ ? 9 : 7);
|
||||
}
|
||||
|
||||
// Current working address.
|
||||
uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row.
|
||||
uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line.
|
||||
int char_row_ = 0; // Character row; 0–9 in text mode, 0–7 in graphics.
|
||||
bool in_blank() const {
|
||||
return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8;
|
||||
}
|
||||
|
||||
// Sync states.
|
||||
bool vsync_int_ = false; // True => vsync active.
|
||||
bool hsync_int_ = false; // True => hsync active.
|
||||
|
||||
// Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all
|
||||
// divisible by 8, so it's safe to count time with a 2Mhz input.
|
||||
static constexpr int h_active = 640;
|
||||
static constexpr int hsync_start = 768;
|
||||
static constexpr int hsync_end = 832;
|
||||
static constexpr int h_reset_addr = 1016;
|
||||
static constexpr int h_total = 1024; // Minor digression from the FPGA original here;
|
||||
// in this implementation the value is tested
|
||||
// _after_ position increment rather than before/instead.
|
||||
// So it needs to be one higher. Which is baked into
|
||||
// the constant to emphasise the all-divisible-by-8 property.
|
||||
|
||||
static constexpr int h_half = h_total / 2;
|
||||
static constexpr int hburst_start = 856;
|
||||
static constexpr int hburst_end = 896;
|
||||
|
||||
// Vertical timing parameters; all in terms of lines. As per the horizontal parameters above,
|
||||
// lines begin with their first visible pixel (or the equivalent position).
|
||||
static constexpr int v_active_gph = 256;
|
||||
static constexpr int v_active_txt = 250;
|
||||
static constexpr int v_disp_gph = v_active_gph - 1;
|
||||
static constexpr int v_disp_txt = v_active_txt - 1;
|
||||
static constexpr int vsync_start = 274;
|
||||
static constexpr int vsync_end = 276;
|
||||
static constexpr int v_rtc = 99;
|
||||
|
||||
// Various signals that it was convenient to factor out.
|
||||
int v_total() const {
|
||||
return field_ ? 312 : 311;
|
||||
}
|
||||
|
||||
bool last_line() const {
|
||||
return char_row_ == (mode_text_ ? 9 : 7);
|
||||
}
|
||||
|
||||
bool in_blank() const {
|
||||
return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8;
|
||||
}
|
||||
|
||||
bool is_v_end() const {
|
||||
return v_count_ == v_total();
|
||||
}
|
||||
bool is_v_end() const {
|
||||
return v_count_ == v_total();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,201 +50,201 @@ class ConcreteMachine:
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::TimedMachine,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
memory_(target.chip_ram, target.fast_ram),
|
||||
chipset_(memory_, PALClockRate)
|
||||
{
|
||||
// Temporary: use a hard-coded Kickstart selection.
|
||||
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
memory_(target.chip_ram, target.fast_ram),
|
||||
chipset_(memory_, PALClockRate)
|
||||
{
|
||||
// Temporary: use a hard-coded Kickstart selection.
|
||||
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
|
||||
|
||||
// For now, also hard-code assumption of PAL.
|
||||
// (Assumption is both here and in the video timing of the Chipset).
|
||||
set_clock_rate(PALClockRate);
|
||||
// For now, also hard-code assumption of PAL.
|
||||
// (Assumption is both here and in the video timing of the Chipset).
|
||||
set_clock_rate(PALClockRate);
|
||||
|
||||
// Insert supplied media.
|
||||
insert_media(target.media);
|
||||
// Insert supplied media.
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
// MARK: - MediaTarget.
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
return chipset_.insert(media.disks);
|
||||
}
|
||||
|
||||
// MARK: - MC68000::BusHandler.
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
|
||||
HalfCycles total_length;
|
||||
if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
|
||||
total_length = chipset_.run_until_after_cpu_slot().duration;
|
||||
assert(total_length >= cycle.length);
|
||||
} else {
|
||||
total_length = cycle.length;
|
||||
chipset_.run_for(total_length);
|
||||
}
|
||||
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
|
||||
|
||||
// Check for assertion of reset.
|
||||
if(cycle.operation & CPU::MC68000::Operation::Reset) {
|
||||
memory_.reset();
|
||||
logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter);
|
||||
}
|
||||
|
||||
// MARK: - MediaTarget.
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
return chipset_.insert(media.disks);
|
||||
}
|
||||
|
||||
// MARK: - MC68000::BusHandler.
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
|
||||
HalfCycles total_length;
|
||||
if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
|
||||
total_length = chipset_.run_until_after_cpu_slot().duration;
|
||||
assert(total_length >= cycle.length);
|
||||
} else {
|
||||
total_length = cycle.length;
|
||||
chipset_.run_for(total_length);
|
||||
}
|
||||
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
|
||||
|
||||
// Check for assertion of reset.
|
||||
if(cycle.operation & CPU::MC68000::Operation::Reset) {
|
||||
memory_.reset();
|
||||
logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter);
|
||||
}
|
||||
|
||||
// Autovector interrupts.
|
||||
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
// Do nothing if no address is exposed.
|
||||
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
|
||||
|
||||
// Grab the target address to pick a memory source.
|
||||
const uint32_t address = cycle.host_endian_byte_address();
|
||||
|
||||
// Set VPA if this is [going to be] a CIA access.
|
||||
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
|
||||
|
||||
if(!memory_.regions[address >> 18].read_write_mask) {
|
||||
if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
|
||||
// Check for various potential chip accesses.
|
||||
|
||||
// Per the manual:
|
||||
//
|
||||
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
|
||||
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
|
||||
//
|
||||
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
|
||||
// these might be listed the wrong way around.
|
||||
//
|
||||
// Additional assumption: the relevant CIA select lines are connected
|
||||
// directly to the chip enables.
|
||||
if((address & 0xe0'0000) == 0xa0'0000) {
|
||||
const int reg = address >> 8;
|
||||
const bool select_a = !(address & 0x1000);
|
||||
const bool select_b = !(address & 0x2000);
|
||||
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
uint16_t result = 0xffff;
|
||||
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
|
||||
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
|
||||
cycle.set_value16(result);
|
||||
} else {
|
||||
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
|
||||
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
|
||||
}
|
||||
|
||||
// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16());
|
||||
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
|
||||
chipset_.perform(cycle);
|
||||
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
|
||||
// This is the Autoconf space; right now the only
|
||||
// Autoconf device this emulator implements is fast RAM,
|
||||
// which if present is provided as part of the memory map.
|
||||
//
|
||||
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
|
||||
memory_.perform(cycle);
|
||||
} else {
|
||||
// This'll do for open bus, for now.
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
cycle.set_value16(0xffff);
|
||||
}
|
||||
|
||||
// Log only for the region that is definitely not just ROM this machine doesn't have.
|
||||
if(address < 0xf0'0000) {
|
||||
logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A regular memory access.
|
||||
cycle.apply(
|
||||
&memory_.regions[address >> 18].contents[address],
|
||||
memory_.regions[address >> 18].read_write_mask
|
||||
);
|
||||
}
|
||||
|
||||
// Autovector interrupts.
|
||||
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_;
|
||||
// Do nothing if no address is exposed.
|
||||
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
|
||||
|
||||
// MARK: - Memory map.
|
||||
// Grab the target address to pick a memory source.
|
||||
const uint32_t address = cycle.host_endian_byte_address();
|
||||
|
||||
MemoryMap memory_;
|
||||
// Set VPA if this is [going to be] a CIA access.
|
||||
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
|
||||
|
||||
// MARK: - Chipset.
|
||||
if(!memory_.regions[address >> 18].read_write_mask) {
|
||||
if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
|
||||
// Check for various potential chip accesses.
|
||||
|
||||
Chipset chipset_;
|
||||
// Per the manual:
|
||||
//
|
||||
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
|
||||
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
|
||||
//
|
||||
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
|
||||
// these might be listed the wrong way around.
|
||||
//
|
||||
// Additional assumption: the relevant CIA select lines are connected
|
||||
// directly to the chip enables.
|
||||
if((address & 0xe0'0000) == 0xa0'0000) {
|
||||
const int reg = address >> 8;
|
||||
const bool select_a = !(address & 0x1000);
|
||||
const bool select_b = !(address & 0x2000);
|
||||
|
||||
// MARK: - Activity Source
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
uint16_t result = 0xffff;
|
||||
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
|
||||
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
|
||||
cycle.set_value16(result);
|
||||
} else {
|
||||
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
|
||||
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
chipset_.set_activity_observer(observer);
|
||||
// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16());
|
||||
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
|
||||
chipset_.perform(cycle);
|
||||
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
|
||||
// This is the Autoconf space; right now the only
|
||||
// Autoconf device this emulator implements is fast RAM,
|
||||
// which if present is provided as part of the memory map.
|
||||
//
|
||||
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
|
||||
memory_.perform(cycle);
|
||||
} else {
|
||||
// This'll do for open bus, for now.
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
cycle.set_value16(0xffff);
|
||||
}
|
||||
|
||||
// Log only for the region that is definitely not just ROM this machine doesn't have.
|
||||
if(address < 0xf0'0000) {
|
||||
logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A regular memory access.
|
||||
cycle.apply(
|
||||
&memory_.regions[address >> 18].contents[address],
|
||||
memory_.regions[address >> 18].read_write_mask
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::AudioProducer.
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return chipset_.get_speaker();
|
||||
}
|
||||
private:
|
||||
CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_;
|
||||
|
||||
// MARK: - MachineTypes::ScanProducer.
|
||||
// MARK: - Memory map.
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
chipset_.set_scan_target(scan_target);
|
||||
}
|
||||
MemoryMap memory_;
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return chipset_.get_scaled_scan_status();
|
||||
}
|
||||
// MARK: - Chipset.
|
||||
|
||||
// MARK: - MachineTypes::TimedMachine.
|
||||
Chipset chipset_;
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
// MARK: - Activity Source
|
||||
|
||||
void flush_output(int) final {
|
||||
chipset_.flush();
|
||||
}
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
chipset_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::MouseMachine.
|
||||
// MARK: - MachineTypes::AudioProducer.
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return chipset_.get_mouse();
|
||||
}
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return chipset_.get_speaker();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::JoystickMachine.
|
||||
// MARK: - MachineTypes::ScanProducer.
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return chipset_.get_joysticks();
|
||||
}
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
chipset_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
// MARK: - Keyboard.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return chipset_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
Amiga::KeyboardMapper keyboard_mapper_;
|
||||
KeyboardMapper *get_keyboard_mapper() {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
// MARK: - MachineTypes::TimedMachine.
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
chipset_.get_keyboard().set_key_state(key, is_pressed);
|
||||
}
|
||||
void run_for(const Cycles cycles) final {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
void clear_all_keys() {
|
||||
chipset_.get_keyboard().clear_all_keys();
|
||||
}
|
||||
};
|
||||
void flush_output(int) final {
|
||||
chipset_.flush();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::MouseMachine.
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return chipset_.get_mouse();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::JoystickMachine.
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return chipset_.get_joysticks();
|
||||
}
|
||||
|
||||
// MARK: - Keyboard.
|
||||
|
||||
Amiga::KeyboardMapper keyboard_mapper_;
|
||||
KeyboardMapper *get_keyboard_mapper() {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
chipset_.get_keyboard().set_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() {
|
||||
chipset_.get_keyboard().clear_all_keys();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -19,142 +19,142 @@
|
||||
namespace Amiga {
|
||||
|
||||
class Audio: public DMADevice<4> {
|
||||
public:
|
||||
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
|
||||
public:
|
||||
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
|
||||
|
||||
/// Idiomatic call-in for DMA scheduling; indicates that this class may
|
||||
/// perform a DMA access for the stated channel now.
|
||||
bool advance_dma(int channel);
|
||||
/// Idiomatic call-in for DMA scheduling; indicates that this class may
|
||||
/// perform a DMA access for the stated channel now.
|
||||
bool advance_dma(int channel);
|
||||
|
||||
/// Advances output by one DMA window, which is implicitly two cycles
|
||||
/// at the output rate that was specified to the constructor.
|
||||
void output();
|
||||
/// Advances output by one DMA window, which is implicitly two cycles
|
||||
/// at the output rate that was specified to the constructor.
|
||||
void output();
|
||||
|
||||
/// Sets the total number of words to fetch for the given channel.
|
||||
void set_length(int channel, uint16_t);
|
||||
/// Sets the total number of words to fetch for the given channel.
|
||||
void set_length(int channel, uint16_t);
|
||||
|
||||
/// Sets the number of DMA windows between each 8-bit output,
|
||||
/// in the same time base as @c ticks_per_line.
|
||||
void set_period(int channel, uint16_t);
|
||||
/// Sets the number of DMA windows between each 8-bit output,
|
||||
/// in the same time base as @c ticks_per_line.
|
||||
void set_period(int channel, uint16_t);
|
||||
|
||||
/// Sets the output volume for the given channel; if bit 6 is set
|
||||
/// then output is maximal; otherwise bits 0–5 select
|
||||
/// a volume of [0–63]/64, on a logarithmic scale.
|
||||
void set_volume(int channel, uint16_t);
|
||||
/// Sets the output volume for the given channel; if bit 6 is set
|
||||
/// then output is maximal; otherwise bits 0–5 select
|
||||
/// a volume of [0–63]/64, on a logarithmic scale.
|
||||
void set_volume(int channel, uint16_t);
|
||||
|
||||
/// Sets the next two samples of audio to output.
|
||||
template <bool is_external = true> void set_data(int channel, uint16_t);
|
||||
/// Sets the next two samples of audio to output.
|
||||
template <bool is_external = true> void set_data(int channel, uint16_t);
|
||||
|
||||
/// Provides a copy of the DMA enable flags, for the purpose of
|
||||
/// determining which channels are enabled for DMA.
|
||||
void set_channel_enables(uint16_t);
|
||||
/// Provides a copy of the DMA enable flags, for the purpose of
|
||||
/// determining which channels are enabled for DMA.
|
||||
void set_channel_enables(uint16_t);
|
||||
|
||||
/// Sets which channels, if any, modulate period or volume of
|
||||
/// their neighbours.
|
||||
void set_modulation_flags(uint16_t);
|
||||
/// Sets which channels, if any, modulate period or volume of
|
||||
/// their neighbours.
|
||||
void set_modulation_flags(uint16_t);
|
||||
|
||||
/// Sets which interrupt requests are currently active.
|
||||
void set_interrupt_requests(uint16_t);
|
||||
/// Sets which interrupt requests are currently active.
|
||||
void set_interrupt_requests(uint16_t);
|
||||
|
||||
/// Obtains the output source.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
/// Obtains the output source.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
// The data latch plus a count of unused samples
|
||||
// in the latch, which will always be 0, 1 or 2.
|
||||
uint16_t data = 0x0000;
|
||||
bool wants_data = false;
|
||||
uint16_t data_latch = 0x0000;
|
||||
|
||||
// The DMA address; unlike most of the Amiga Chipset,
|
||||
// the user posts a value to feed a pointer, rather
|
||||
// than having access to the pointer itself.
|
||||
bool should_reload_address = false;
|
||||
uint32_t data_address = 0x0000'0000;
|
||||
|
||||
// Number of words remaining in DMA data.
|
||||
uint16_t length = 0;
|
||||
uint16_t length_counter = 0;
|
||||
|
||||
// Number of ticks between each sample, plus the
|
||||
// current counter, which counts downward.
|
||||
uint16_t period = 0;
|
||||
uint16_t period_counter = 0;
|
||||
|
||||
// Modulation / attach flags.
|
||||
bool attach_period = false;
|
||||
bool attach_volume = false;
|
||||
|
||||
// Output volume, [0, 64].
|
||||
uint8_t volume = 0;
|
||||
uint8_t volume_latch = 0;
|
||||
|
||||
// Indicates whether DMA is enabled for this channel.
|
||||
bool dma_enabled = false;
|
||||
|
||||
// Records whether this audio interrupt is pending.
|
||||
bool interrupt_pending = false;
|
||||
bool will_request_interrupt = false;
|
||||
|
||||
// Replicates the Hardware Reference Manual state machine;
|
||||
// comments indicate which of the documented states each
|
||||
// label refers to.
|
||||
enum class State {
|
||||
Disabled, // 000
|
||||
WaitingForDummyDMA, // 001
|
||||
WaitingForDMA, // 101
|
||||
PlayingHigh, // 010
|
||||
PlayingLow, // 011
|
||||
} state = State::Disabled;
|
||||
|
||||
/// Dispatches to the appropriate templatised output for the current state.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
bool output(Channel *moduland);
|
||||
|
||||
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State state> bool output(Channel *moduland);
|
||||
|
||||
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
|
||||
/// and taking any steps specific to that particular transition.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State begin, State end> bool transit(Channel *moduland);
|
||||
|
||||
/// Begins @c state, performing all fixed logic that would otherwise have to be
|
||||
/// repeated endlessly in the relevant @c output.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
template <State state> void begin_state(Channel *moduland);
|
||||
|
||||
/// Provides the common length-decrementing logic used when transitioning
|
||||
/// between PlayingHigh and PlayingLow in either direction.
|
||||
void decrement_length();
|
||||
|
||||
// Output state.
|
||||
int8_t output_level = 0;
|
||||
uint8_t output_phase = 0;
|
||||
bool output_enabled = false;
|
||||
|
||||
void reset_output_phase() {
|
||||
output_phase = 0;
|
||||
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
|
||||
}
|
||||
} channels_[4];
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
// The data latch plus a count of unused samples
|
||||
// in the latch, which will always be 0, 1 or 2.
|
||||
uint16_t data = 0x0000;
|
||||
bool wants_data = false;
|
||||
uint16_t data_latch = 0x0000;
|
||||
// Transient output state, and its destination.
|
||||
Outputs::Speaker::PushLowpass<true> speaker_;
|
||||
Concurrency::AsyncTaskQueue<true> queue_;
|
||||
|
||||
// The DMA address; unlike most of the Amiga Chipset,
|
||||
// the user posts a value to feed a pointer, rather
|
||||
// than having access to the pointer itself.
|
||||
bool should_reload_address = false;
|
||||
uint32_t data_address = 0x0000'0000;
|
||||
|
||||
// Number of words remaining in DMA data.
|
||||
uint16_t length = 0;
|
||||
uint16_t length_counter = 0;
|
||||
|
||||
// Number of ticks between each sample, plus the
|
||||
// current counter, which counts downward.
|
||||
uint16_t period = 0;
|
||||
uint16_t period_counter = 0;
|
||||
|
||||
// Modulation / attach flags.
|
||||
bool attach_period = false;
|
||||
bool attach_volume = false;
|
||||
|
||||
// Output volume, [0, 64].
|
||||
uint8_t volume = 0;
|
||||
uint8_t volume_latch = 0;
|
||||
|
||||
// Indicates whether DMA is enabled for this channel.
|
||||
bool dma_enabled = false;
|
||||
|
||||
// Records whether this audio interrupt is pending.
|
||||
bool interrupt_pending = false;
|
||||
bool will_request_interrupt = false;
|
||||
|
||||
// Replicates the Hardware Reference Manual state machine;
|
||||
// comments indicate which of the documented states each
|
||||
// label refers to.
|
||||
enum class State {
|
||||
Disabled, // 000
|
||||
WaitingForDummyDMA, // 001
|
||||
WaitingForDMA, // 101
|
||||
PlayingHigh, // 010
|
||||
PlayingLow, // 011
|
||||
} state = State::Disabled;
|
||||
|
||||
/// Dispatches to the appropriate templatised output for the current state.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
bool output(Channel *moduland);
|
||||
|
||||
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State state> bool output(Channel *moduland);
|
||||
|
||||
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
|
||||
/// and taking any steps specific to that particular transition.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State begin, State end> bool transit(Channel *moduland);
|
||||
|
||||
/// Begins @c state, performing all fixed logic that would otherwise have to be
|
||||
/// repeated endlessly in the relevant @c output.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
template <State state> void begin_state(Channel *moduland);
|
||||
|
||||
/// Provides the common length-decrementing logic used when transitioning
|
||||
/// between PlayingHigh and PlayingLow in either direction.
|
||||
void decrement_length();
|
||||
|
||||
// Output state.
|
||||
int8_t output_level = 0;
|
||||
uint8_t output_phase = 0;
|
||||
bool output_enabled = false;
|
||||
|
||||
void reset_output_phase() {
|
||||
output_phase = 0;
|
||||
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
|
||||
}
|
||||
} channels_[4];
|
||||
|
||||
// Transient output state, and its destination.
|
||||
Outputs::Speaker::PushLowpass<true> speaker_;
|
||||
Concurrency::AsyncTaskQueue<true> queue_;
|
||||
|
||||
using AudioBuffer = std::array<int16_t, 4096>;
|
||||
static constexpr int BufferCount = 3;
|
||||
AudioBuffer buffer_[BufferCount];
|
||||
std::atomic<bool> buffer_available_[BufferCount];
|
||||
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
|
||||
using AudioBuffer = std::array<int16_t, 4096>;
|
||||
static constexpr int BufferCount = 3;
|
||||
AudioBuffer buffer_[BufferCount];
|
||||
std::atomic<bool> buffer_available_[BufferCount];
|
||||
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,18 +31,18 @@ struct BitplaneData: public std::array<uint16_t, 6> {
|
||||
};
|
||||
|
||||
class Bitplanes: public DMADevice<6, 2> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
bool advance_dma(int cycle);
|
||||
void do_end_of_line();
|
||||
void set_control(uint16_t);
|
||||
bool advance_dma(int cycle);
|
||||
void do_end_of_line();
|
||||
void set_control(uint16_t);
|
||||
|
||||
private:
|
||||
bool is_high_res_ = false;
|
||||
int plane_count_ = 0;
|
||||
private:
|
||||
bool is_high_res_ = false;
|
||||
int plane_count_ = 0;
|
||||
|
||||
BitplaneData next;
|
||||
BitplaneData next;
|
||||
};
|
||||
|
||||
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
|
||||
@@ -55,44 +55,44 @@ template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
|
||||
}
|
||||
|
||||
class BitplaneShifter {
|
||||
public:
|
||||
/// Installs a new set of output pixels.
|
||||
void set(
|
||||
const BitplaneData &previous,
|
||||
const BitplaneData &next,
|
||||
int odd_delay,
|
||||
int even_delay);
|
||||
public:
|
||||
/// Installs a new set of output pixels.
|
||||
void set(
|
||||
const BitplaneData &previous,
|
||||
const BitplaneData &next,
|
||||
int odd_delay,
|
||||
int even_delay);
|
||||
|
||||
/// Shifts either two pixels (in low-res mode) or four pixels (in high-res).
|
||||
void shift(bool high_res) {
|
||||
constexpr int shifts[] = {16, 32};
|
||||
/// Shifts either two pixels (in low-res mode) or four pixels (in high-res).
|
||||
void shift(bool high_res) {
|
||||
constexpr int shifts[] = {16, 32};
|
||||
|
||||
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
|
||||
data_[0] <<= shifts[high_res];
|
||||
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
|
||||
data_[0] <<= shifts[high_res];
|
||||
}
|
||||
|
||||
/// @returns The next four pixels to output; in low-resolution mode only two
|
||||
/// of them will be unique.
|
||||
///
|
||||
/// The value is arranges so that MSB = first pixel to output, LSB = last.
|
||||
///
|
||||
/// Each byte is swizzled to provide easier playfield separation, being in the form:
|
||||
/// b6, b7 = 0;
|
||||
/// b3–b5: planes 1, 3 and 5;
|
||||
/// b0–b2: planes 0, 2 and 4.
|
||||
uint32_t get(bool high_res) {
|
||||
if(high_res) {
|
||||
return uint32_t(data_[1] >> 32);
|
||||
} else {
|
||||
uint32_t result = uint16_t(data_[1] >> 48);
|
||||
result = ((result & 0xff00) << 8) | (result & 0x00ff);
|
||||
result |= result << 8;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The next four pixels to output; in low-resolution mode only two
|
||||
/// of them will be unique.
|
||||
///
|
||||
/// The value is arranges so that MSB = first pixel to output, LSB = last.
|
||||
///
|
||||
/// Each byte is swizzled to provide easier playfield separation, being in the form:
|
||||
/// b6, b7 = 0;
|
||||
/// b3–b5: planes 1, 3 and 5;
|
||||
/// b0–b2: planes 0, 2 and 4.
|
||||
uint32_t get(bool high_res) {
|
||||
if(high_res) {
|
||||
return uint32_t(data_[1] >> 32);
|
||||
} else {
|
||||
uint32_t result = uint16_t(data_[1] >> 48);
|
||||
result = ((result & 0xff00) << 8) | (result & 0x00ff);
|
||||
result |= result << 8;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint64_t, 2> data_{};
|
||||
private:
|
||||
std::array<uint64_t, 2> data_{};
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -24,104 +24,104 @@ namespace Amiga {
|
||||
and can subsequently be retrieved. This is included for testing purposes.
|
||||
*/
|
||||
template <bool record_bus = false> class Blitter: public DMADevice<4, 4> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
DMADevice<4, 4>::set_pointer<id, shift>(value);
|
||||
}
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
DMADevice<4, 4>::set_pointer<id, shift>(value);
|
||||
}
|
||||
|
||||
// Various setters; it's assumed that address decoding is handled externally.
|
||||
//
|
||||
// In all cases where a channel is identified numerically, it's taken that
|
||||
// 0 = A, 1 = B, 2 = C, 3 = D.
|
||||
void set_control(int index, uint16_t value);
|
||||
void set_first_word_mask(uint16_t value);
|
||||
void set_last_word_mask(uint16_t value);
|
||||
// Various setters; it's assumed that address decoding is handled externally.
|
||||
//
|
||||
// In all cases where a channel is identified numerically, it's taken that
|
||||
// 0 = A, 1 = B, 2 = C, 3 = D.
|
||||
void set_control(int index, uint16_t value);
|
||||
void set_first_word_mask(uint16_t value);
|
||||
void set_last_word_mask(uint16_t value);
|
||||
|
||||
void set_size(uint16_t value);
|
||||
void set_minterms(uint16_t value);
|
||||
// void set_vertical_size(uint16_t value);
|
||||
// void set_horizontal_size(uint16_t value);
|
||||
void set_data(int channel, uint16_t value);
|
||||
void set_size(uint16_t value);
|
||||
void set_minterms(uint16_t value);
|
||||
// void set_vertical_size(uint16_t value);
|
||||
// void set_horizontal_size(uint16_t value);
|
||||
void set_data(int channel, uint16_t value);
|
||||
|
||||
uint16_t get_status();
|
||||
uint16_t get_status();
|
||||
|
||||
template <bool complete_immediately> bool advance_dma();
|
||||
template <bool complete_immediately> bool advance_dma();
|
||||
|
||||
struct Transaction {
|
||||
enum class Type {
|
||||
SkippedSlot,
|
||||
ReadA,
|
||||
ReadB,
|
||||
ReadC,
|
||||
AddToPipeline,
|
||||
WriteFromPipeline
|
||||
} type = Type::SkippedSlot;
|
||||
struct Transaction {
|
||||
enum class Type {
|
||||
SkippedSlot,
|
||||
ReadA,
|
||||
ReadB,
|
||||
ReadC,
|
||||
AddToPipeline,
|
||||
WriteFromPipeline
|
||||
} type = Type::SkippedSlot;
|
||||
|
||||
uint32_t address = 0;
|
||||
uint16_t value = 0;
|
||||
uint32_t address = 0;
|
||||
uint16_t value = 0;
|
||||
|
||||
Transaction() = default;
|
||||
Transaction(Type type) : type(type) {}
|
||||
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
|
||||
Transaction() = default;
|
||||
Transaction(Type type) : type(type) {}
|
||||
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
|
||||
|
||||
std::string to_string() const {
|
||||
std::string result;
|
||||
std::string to_string() const {
|
||||
std::string result;
|
||||
|
||||
switch(type) {
|
||||
case Type::SkippedSlot: result = "SkippedSlot"; break;
|
||||
case Type::ReadA: result = "ReadA"; break;
|
||||
case Type::ReadB: result = "ReadB"; break;
|
||||
case Type::ReadC: result = "ReadC"; break;
|
||||
case Type::AddToPipeline: result = "AddToPipeline"; break;
|
||||
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
|
||||
}
|
||||
|
||||
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
|
||||
return result;
|
||||
switch(type) {
|
||||
case Type::SkippedSlot: result = "SkippedSlot"; break;
|
||||
case Type::ReadA: result = "ReadA"; break;
|
||||
case Type::ReadB: result = "ReadB"; break;
|
||||
case Type::ReadC: result = "ReadC"; break;
|
||||
case Type::AddToPipeline: result = "AddToPipeline"; break;
|
||||
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
|
||||
}
|
||||
};
|
||||
std::vector<Transaction> get_and_reset_transactions();
|
||||
|
||||
private:
|
||||
int width_ = 0, height_ = 0;
|
||||
int shifts_[2]{};
|
||||
uint16_t a_mask_[2] = {0xffff, 0xffff};
|
||||
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
std::vector<Transaction> get_and_reset_transactions();
|
||||
|
||||
bool line_mode_ = false;
|
||||
bool one_dot_ = false;
|
||||
int line_direction_ = 0;
|
||||
int line_sign_ = 1;
|
||||
private:
|
||||
int width_ = 0, height_ = 0;
|
||||
int shifts_[2]{};
|
||||
uint16_t a_mask_[2] = {0xffff, 0xffff};
|
||||
|
||||
uint32_t direction_ = 1;
|
||||
bool inclusive_fill_ = false;
|
||||
bool exclusive_fill_ = false;
|
||||
bool fill_carry_ = false;
|
||||
bool line_mode_ = false;
|
||||
bool one_dot_ = false;
|
||||
int line_direction_ = 0;
|
||||
int line_sign_ = 1;
|
||||
|
||||
uint8_t minterms_ = 0;
|
||||
uint32_t a32_ = 0, b32_ = 0;
|
||||
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
|
||||
uint32_t direction_ = 1;
|
||||
bool inclusive_fill_ = false;
|
||||
bool exclusive_fill_ = false;
|
||||
bool fill_carry_ = false;
|
||||
|
||||
bool not_zero_flag_ = false;
|
||||
uint8_t minterms_ = 0;
|
||||
uint32_t a32_ = 0, b32_ = 0;
|
||||
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
|
||||
|
||||
BlitterSequencer sequencer_;
|
||||
uint32_t write_address_ = 0xffff'ffff;
|
||||
uint16_t write_value_ = 0;
|
||||
enum WritePhase {
|
||||
Starting, Full
|
||||
} write_phase_;
|
||||
int y_, x_;
|
||||
uint16_t transient_a_mask_;
|
||||
bool busy_ = false;
|
||||
int loop_index_ = -1;
|
||||
bool not_zero_flag_ = false;
|
||||
|
||||
int error_ = 0;
|
||||
bool draw_ = false;
|
||||
bool has_c_data_ = false;
|
||||
BlitterSequencer sequencer_;
|
||||
uint32_t write_address_ = 0xffff'ffff;
|
||||
uint16_t write_value_ = 0;
|
||||
enum WritePhase {
|
||||
Starting, Full
|
||||
} write_phase_;
|
||||
int y_, x_;
|
||||
uint16_t transient_a_mask_;
|
||||
bool busy_ = false;
|
||||
int loop_index_ = -1;
|
||||
|
||||
void add_modulos();
|
||||
std::vector<Transaction> transactions_;
|
||||
int error_ = 0;
|
||||
bool draw_ = false;
|
||||
bool has_c_data_ = false;
|
||||
|
||||
void add_modulos();
|
||||
std::vector<Transaction> transactions_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -19,146 +19,146 @@ namespace Amiga {
|
||||
relying on tables.
|
||||
*/
|
||||
class BlitterSequencer {
|
||||
public:
|
||||
enum class Channel {
|
||||
/// Tells the caller to calculate and load a new piece of output
|
||||
/// into the output pipeline.
|
||||
///
|
||||
/// If any inputs are enabled then a one-slot output pipeline applies:
|
||||
/// output will rest in the pipeline for one write phase before being written.
|
||||
Write,
|
||||
/// Indicates that a write should occur if anything is in the pipeline, otherwise
|
||||
/// no activity should occur.
|
||||
FlushPipeline,
|
||||
/// The caller should read from channel C.
|
||||
C,
|
||||
/// The caller should read from channel B.
|
||||
B,
|
||||
/// The caller should read from channel A.
|
||||
A,
|
||||
/// Indicates an unused DMA slot.
|
||||
None
|
||||
};
|
||||
public:
|
||||
enum class Channel {
|
||||
/// Tells the caller to calculate and load a new piece of output
|
||||
/// into the output pipeline.
|
||||
///
|
||||
/// If any inputs are enabled then a one-slot output pipeline applies:
|
||||
/// output will rest in the pipeline for one write phase before being written.
|
||||
Write,
|
||||
/// Indicates that a write should occur if anything is in the pipeline, otherwise
|
||||
/// no activity should occur.
|
||||
FlushPipeline,
|
||||
/// The caller should read from channel C.
|
||||
C,
|
||||
/// The caller should read from channel B.
|
||||
B,
|
||||
/// The caller should read from channel A.
|
||||
A,
|
||||
/// Indicates an unused DMA slot.
|
||||
None
|
||||
};
|
||||
|
||||
/// Sets the current control value, which indicates which
|
||||
/// channels are enabled.
|
||||
void set_control(int control) {
|
||||
control_ = control & 0xf;
|
||||
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
|
||||
// of control values during a blit.
|
||||
/// Sets the current control value, which indicates which
|
||||
/// channels are enabled.
|
||||
void set_control(int control) {
|
||||
control_ = control & 0xf;
|
||||
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
|
||||
// of control values during a blit.
|
||||
}
|
||||
|
||||
/// Indicates that blitting should conclude after this step, i.e.
|
||||
/// whatever is being fetched now is part of the final set of input data;
|
||||
/// this is safe to call following a fetch request on any channel.
|
||||
void complete() {
|
||||
next_phase_ =
|
||||
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
|
||||
Phase::PauseAndComplete : Phase::Complete;
|
||||
}
|
||||
|
||||
/// Begins a blit operation.
|
||||
void begin() {
|
||||
phase_ = next_phase_ = Phase::Ongoing;
|
||||
index_ = loop_ = 0;
|
||||
}
|
||||
|
||||
/// Provides the next channel to fetch from, or that a write is required,
|
||||
/// along with a count of complete channel iterations so far completed.
|
||||
std::pair<Channel, int> next() {
|
||||
switch(phase_) {
|
||||
default: break;
|
||||
|
||||
case Phase::Complete:
|
||||
return std::make_pair(Channel::FlushPipeline, loop_);
|
||||
|
||||
case Phase::PauseAndComplete:
|
||||
phase_ = Phase::Complete;
|
||||
return std::make_pair(Channel::None, loop_);
|
||||
}
|
||||
|
||||
/// Indicates that blitting should conclude after this step, i.e.
|
||||
/// whatever is being fetched now is part of the final set of input data;
|
||||
/// this is safe to call following a fetch request on any channel.
|
||||
void complete() {
|
||||
next_phase_ =
|
||||
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
|
||||
Phase::PauseAndComplete : Phase::Complete;
|
||||
Channel next = Channel::None;
|
||||
|
||||
switch(control_) {
|
||||
default: break;
|
||||
|
||||
case 0: next = next_channel(pattern0); break;
|
||||
case 1: next = next_channel(pattern1); break;
|
||||
case 2: next = next_channel(pattern2); break;
|
||||
case 3: next = next_channel(pattern3); break;
|
||||
case 4: next = next_channel(pattern4); break;
|
||||
case 5: next = next_channel(pattern5); break;
|
||||
case 6: next = next_channel(pattern6); break;
|
||||
case 7: next = next_channel(pattern7); break;
|
||||
case 8: next = next_channel(pattern8); break;
|
||||
case 9: next = next_channel(pattern9); break;
|
||||
case 10: next = next_channel(patternA); break;
|
||||
case 11: next = next_channel(patternB); break;
|
||||
case 12: next = next_channel(patternC); break;
|
||||
case 13: next = next_channel(patternD); break;
|
||||
case 14: next = next_channel(patternE); break;
|
||||
case 15: next = next_channel(patternF); break;
|
||||
}
|
||||
|
||||
/// Begins a blit operation.
|
||||
void begin() {
|
||||
phase_ = next_phase_ = Phase::Ongoing;
|
||||
index_ = loop_ = 0;
|
||||
return std::make_pair(next, loop_);
|
||||
}
|
||||
|
||||
template <int channel> bool channel_enabled() {
|
||||
return control_ & (8 >> channel);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
|
||||
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
|
||||
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
|
||||
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
|
||||
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
|
||||
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
|
||||
loop_ += index_ / list.size();
|
||||
index_ %= list.size();
|
||||
const Channel result = list[index_];
|
||||
++index_;
|
||||
if(index_ == list.size()) {
|
||||
phase_ = next_phase_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Provides the next channel to fetch from, or that a write is required,
|
||||
/// along with a count of complete channel iterations so far completed.
|
||||
std::pair<Channel, int> next() {
|
||||
switch(phase_) {
|
||||
default: break;
|
||||
// Current control flags, i.e. which channels are enabled.
|
||||
int control_ = 0;
|
||||
|
||||
case Phase::Complete:
|
||||
return std::make_pair(Channel::FlushPipeline, loop_);
|
||||
// Index into the pattern table for this blit.
|
||||
size_t index_ = 0;
|
||||
|
||||
case Phase::PauseAndComplete:
|
||||
phase_ = Phase::Complete;
|
||||
return std::make_pair(Channel::None, loop_);
|
||||
}
|
||||
// Number of times the entire pattern table has been completed.
|
||||
int loop_ = 0;
|
||||
|
||||
Channel next = Channel::None;
|
||||
enum class Phase {
|
||||
/// Return the next thing in the pattern table and advance.
|
||||
/// If looping from the end of the pattern table to the start,
|
||||
/// set phase_ to next_phase_.
|
||||
Ongoing,
|
||||
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
|
||||
PauseAndComplete,
|
||||
/// Return Channel::Write indefinitely.
|
||||
Complete
|
||||
};
|
||||
|
||||
switch(control_) {
|
||||
default: break;
|
||||
|
||||
case 0: next = next_channel(pattern0); break;
|
||||
case 1: next = next_channel(pattern1); break;
|
||||
case 2: next = next_channel(pattern2); break;
|
||||
case 3: next = next_channel(pattern3); break;
|
||||
case 4: next = next_channel(pattern4); break;
|
||||
case 5: next = next_channel(pattern5); break;
|
||||
case 6: next = next_channel(pattern6); break;
|
||||
case 7: next = next_channel(pattern7); break;
|
||||
case 8: next = next_channel(pattern8); break;
|
||||
case 9: next = next_channel(pattern9); break;
|
||||
case 10: next = next_channel(patternA); break;
|
||||
case 11: next = next_channel(patternB); break;
|
||||
case 12: next = next_channel(patternC); break;
|
||||
case 13: next = next_channel(patternD); break;
|
||||
case 14: next = next_channel(patternE); break;
|
||||
case 15: next = next_channel(patternF); break;
|
||||
}
|
||||
|
||||
return std::make_pair(next, loop_);
|
||||
}
|
||||
|
||||
template <int channel> bool channel_enabled() {
|
||||
return control_ & (8 >> channel);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
|
||||
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
|
||||
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
|
||||
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
|
||||
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
|
||||
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
|
||||
loop_ += index_ / list.size();
|
||||
index_ %= list.size();
|
||||
const Channel result = list[index_];
|
||||
++index_;
|
||||
if(index_ == list.size()) {
|
||||
phase_ = next_phase_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Current control flags, i.e. which channels are enabled.
|
||||
int control_ = 0;
|
||||
|
||||
// Index into the pattern table for this blit.
|
||||
size_t index_ = 0;
|
||||
|
||||
// Number of times the entire pattern table has been completed.
|
||||
int loop_ = 0;
|
||||
|
||||
enum class Phase {
|
||||
/// Return the next thing in the pattern table and advance.
|
||||
/// If looping from the end of the pattern table to the start,
|
||||
/// set phase_ to next_phase_.
|
||||
Ongoing,
|
||||
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
|
||||
PauseAndComplete,
|
||||
/// Return Channel::Write indefinitely.
|
||||
Complete
|
||||
};
|
||||
|
||||
// Current sequencer pahse.
|
||||
Phase phase_ = Phase::Complete;
|
||||
// Phase to assume at the end of this iteration of the sequence table.
|
||||
Phase next_phase_ = Phase::Complete;
|
||||
// Current sequencer pahse.
|
||||
Phase phase_ = Phase::Complete;
|
||||
// Phase to assume at the end of this iteration of the sequence table.
|
||||
Phase next_phase_ = Phase::Complete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,341 +37,340 @@
|
||||
namespace Amiga {
|
||||
|
||||
class Chipset: private ClockingHint::Observer {
|
||||
public:
|
||||
Chipset(MemoryMap &memory_map, int input_clock_rate);
|
||||
|
||||
struct Changes {
|
||||
int interrupt_level = 0;
|
||||
HalfCycles duration;
|
||||
|
||||
Changes &operator += (const Changes &rhs) {
|
||||
duration += rhs.duration;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Advances the stated amount of time.
|
||||
Changes run_for(HalfCycles);
|
||||
|
||||
/// Advances to the end of the next available CPU slot.
|
||||
Changes run_until_after_cpu_slot();
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
|
||||
template <typename Microcycle>
|
||||
void perform(const Microcycle &cycle) {
|
||||
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
cycle.set_value16(read(register_address));
|
||||
} else {
|
||||
write(register_address, cycle.value16());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the current state of the CIA interrupt lines.
|
||||
void set_cia_interrupts(bool cia_a, bool cia_b);
|
||||
|
||||
/// Provides the chipset's current interrupt level.
|
||||
int get_interrupt_level() {
|
||||
return interrupt_level_;
|
||||
}
|
||||
|
||||
/// Inserts the disks provided.
|
||||
/// @returns @c true if anything was inserted; @c false otherwise.
|
||||
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
|
||||
|
||||
// The standard CRT set.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
// Activity observation.
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
cia_a_handler_.set_activity_observer(observer);
|
||||
disk_controller_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// Keyboard and mouse exposure.
|
||||
Keyboard &get_keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// Synchronisation.
|
||||
void flush();
|
||||
|
||||
// Input for receiving collected bitplanes.
|
||||
void post_bitplanes(const BitplaneData &data);
|
||||
|
||||
// Obtains the source of audio output.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return audio_.get_speaker();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class DMADeviceBase;
|
||||
|
||||
// MARK: - Register read/write functions.
|
||||
uint16_t read(uint32_t address, bool allow_conversion = true);
|
||||
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
|
||||
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
|
||||
friend class Copper;
|
||||
|
||||
// MARK: - E Clock and keyboard dividers.
|
||||
|
||||
HalfCycles cia_divider_;
|
||||
HalfCycles keyboard_divider_;
|
||||
|
||||
// MARK: - Interrupts.
|
||||
|
||||
uint16_t interrupt_enable_ = 0;
|
||||
uint16_t interrupt_requests_ = 0;
|
||||
int interrupt_level_ = 0;
|
||||
|
||||
void update_interrupts();
|
||||
void posit_interrupt(InterruptFlag::FlagT);
|
||||
|
||||
// MARK: - Scheduler.
|
||||
|
||||
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
|
||||
template <bool stop_on_cpu> int advance_slots(int, int);
|
||||
template <int cycle, bool stop_if_cpu> bool perform_cycle();
|
||||
template <int cycle> void output();
|
||||
void output_pixels(int cycles_until_sync);
|
||||
void apply_ham(uint8_t);
|
||||
|
||||
// MARK: - DMA Control, Scheduler and Blitter.
|
||||
|
||||
uint16_t dma_control_ = 0;
|
||||
Blitter<false> blitter_;
|
||||
|
||||
// MARK: - Sprites and collision flags.
|
||||
|
||||
std::array<Sprite, 8> sprites_;
|
||||
std::array<TwoSpriteShifter, 4> sprite_shifters_;
|
||||
uint16_t collisions_ = 0, collisions_flags_= 0;
|
||||
|
||||
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
|
||||
|
||||
// MARK: - Raster position and state.
|
||||
|
||||
// Definitions related to PAL/NTSC.
|
||||
// (Default values are PAL).
|
||||
int line_length_ = 227;
|
||||
int short_field_height_ = 312;
|
||||
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
|
||||
|
||||
// Current raster position.
|
||||
int line_cycle_ = 0, y_ = 0;
|
||||
|
||||
// Parameters affecting bitplane collection and output.
|
||||
uint16_t display_window_start_[2] = {0, 0};
|
||||
uint16_t display_window_stop_[2] = {0, 0};
|
||||
uint16_t fetch_window_[2] = {0, 0};
|
||||
|
||||
// Ephemeral bitplane collection state.
|
||||
bool fetch_vertical_ = false;
|
||||
bool display_horizontal_ = false;
|
||||
bool did_fetch_ = false;
|
||||
|
||||
int horizontal_offset_ = 0;
|
||||
enum HorizontalFetch {
|
||||
Started, WillRequestStop, StopRequested, Stopped
|
||||
} horizontal_fetch_ = HorizontalFetch::Stopped;
|
||||
|
||||
// Output state.
|
||||
uint16_t border_colour_ = 0;
|
||||
bool is_border_ = true;
|
||||
int zone_duration_ = 0;
|
||||
uint16_t *pixels_ = nullptr;
|
||||
uint16_t last_colour_ = 0; // Retained for HAM mode.
|
||||
void flush_output();
|
||||
|
||||
Bitplanes bitplanes_;
|
||||
|
||||
BitplaneData next_bitplanes_, previous_bitplanes_;
|
||||
bool has_next_bitplanes_ = false;
|
||||
|
||||
int odd_priority_ = 0, even_priority_ = 0;
|
||||
bool even_over_odd_ = false;
|
||||
bool hold_and_modify_ = false;
|
||||
bool dual_playfields_ = false;
|
||||
bool interlace_ = false;
|
||||
bool is_long_field_ = false;
|
||||
|
||||
BitplaneShifter bitplane_pixels_;
|
||||
|
||||
int odd_delay_ = 0, even_delay_ = 0;
|
||||
bool is_high_res_ = false;
|
||||
|
||||
// MARK: - Copper.
|
||||
|
||||
Copper copper_;
|
||||
|
||||
// MARK: - Audio.
|
||||
|
||||
Audio audio_;
|
||||
|
||||
// MARK: - Serial port.
|
||||
|
||||
class SerialPort {
|
||||
public:
|
||||
Chipset(MemoryMap &memory_map, int input_clock_rate);
|
||||
|
||||
struct Changes {
|
||||
int interrupt_level = 0;
|
||||
HalfCycles duration;
|
||||
|
||||
Changes &operator += (const Changes &rhs) {
|
||||
duration += rhs.duration;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Advances the stated amount of time.
|
||||
Changes run_for(HalfCycles);
|
||||
|
||||
/// Advances to the end of the next available CPU slot.
|
||||
Changes run_until_after_cpu_slot();
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
|
||||
template <typename Microcycle>
|
||||
void perform(const Microcycle &cycle) {
|
||||
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
cycle.set_value16(read(register_address));
|
||||
} else {
|
||||
write(register_address, cycle.value16());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the current state of the CIA interrupt lines.
|
||||
void set_cia_interrupts(bool cia_a, bool cia_b);
|
||||
|
||||
/// Provides the chipset's current interrupt level.
|
||||
int get_interrupt_level() {
|
||||
return interrupt_level_;
|
||||
}
|
||||
|
||||
/// Inserts the disks provided.
|
||||
/// @returns @c true if anything was inserted; @c false otherwise.
|
||||
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
|
||||
|
||||
// The standard CRT set.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
// Activity observation.
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
cia_a_handler_.set_activity_observer(observer);
|
||||
disk_controller_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// Keyboard and mouse exposure.
|
||||
Keyboard &get_keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// Synchronisation.
|
||||
void flush();
|
||||
|
||||
// Input for receiving collected bitplanes.
|
||||
void post_bitplanes(const BitplaneData &data);
|
||||
|
||||
// Obtains the source of audio output.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return audio_.get_speaker();
|
||||
}
|
||||
void set_control(uint16_t);
|
||||
void set_data(uint16_t);
|
||||
uint16_t get_status();
|
||||
|
||||
private:
|
||||
friend class DMADeviceBase;
|
||||
// uint16_t value = 0, reload = 0;
|
||||
// uint16_t shift = 0, receive_shift = 0;
|
||||
// uint16_t status;
|
||||
} serial_;
|
||||
|
||||
// MARK: - Register read/write functions.
|
||||
uint16_t read(uint32_t address, bool allow_conversion = true);
|
||||
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
|
||||
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
|
||||
friend class Copper;
|
||||
// MARK: - Pixel output.
|
||||
|
||||
// MARK: - E Clock and keyboard dividers.
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t palette_[32]{};
|
||||
uint16_t swizzled_palette_[64]{};
|
||||
|
||||
HalfCycles cia_divider_;
|
||||
HalfCycles keyboard_divider_;
|
||||
// MARK: - Mouse.
|
||||
private:
|
||||
Mouse mouse_;
|
||||
|
||||
// MARK: - Interrupts.
|
||||
public:
|
||||
Inputs::Mouse &get_mouse() {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
uint16_t interrupt_enable_ = 0;
|
||||
uint16_t interrupt_requests_ = 0;
|
||||
int interrupt_level_ = 0;
|
||||
// MARK: - Joystick.
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
Joystick &joystick(size_t index) const {
|
||||
return *static_cast<Joystick *>(joysticks_[index].get());
|
||||
}
|
||||
|
||||
void update_interrupts();
|
||||
void posit_interrupt(InterruptFlag::FlagT);
|
||||
|
||||
// MARK: - Scheduler.
|
||||
|
||||
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
|
||||
template <bool stop_on_cpu> int advance_slots(int, int);
|
||||
template <int cycle, bool stop_if_cpu> bool perform_cycle();
|
||||
template <int cycle> void output();
|
||||
void output_pixels(int cycles_until_sync);
|
||||
void apply_ham(uint8_t);
|
||||
|
||||
// MARK: - DMA Control, Scheduler and Blitter.
|
||||
|
||||
uint16_t dma_control_ = 0;
|
||||
Blitter<false> blitter_;
|
||||
|
||||
// MARK: - Sprites and collision flags.
|
||||
|
||||
std::array<Sprite, 8> sprites_;
|
||||
std::array<TwoSpriteShifter, 4> sprite_shifters_;
|
||||
uint16_t collisions_ = 0, collisions_flags_= 0;
|
||||
|
||||
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
|
||||
|
||||
// MARK: - Raster position and state.
|
||||
|
||||
// Definitions related to PAL/NTSC.
|
||||
// (Default values are PAL).
|
||||
int line_length_ = 227;
|
||||
int short_field_height_ = 312;
|
||||
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
|
||||
|
||||
// Current raster position.
|
||||
int line_cycle_ = 0, y_ = 0;
|
||||
|
||||
// Parameters affecting bitplane collection and output.
|
||||
uint16_t display_window_start_[2] = {0, 0};
|
||||
uint16_t display_window_stop_[2] = {0, 0};
|
||||
uint16_t fetch_window_[2] = {0, 0};
|
||||
|
||||
// Ephemeral bitplane collection state.
|
||||
bool fetch_vertical_ = false;
|
||||
bool display_horizontal_ = false;
|
||||
bool did_fetch_ = false;
|
||||
|
||||
int horizontal_offset_ = 0;
|
||||
enum HorizontalFetch {
|
||||
Started, WillRequestStop, StopRequested, Stopped
|
||||
} horizontal_fetch_ = HorizontalFetch::Stopped;
|
||||
|
||||
// Output state.
|
||||
uint16_t border_colour_ = 0;
|
||||
bool is_border_ = true;
|
||||
int zone_duration_ = 0;
|
||||
uint16_t *pixels_ = nullptr;
|
||||
uint16_t last_colour_ = 0; // Retained for HAM mode.
|
||||
void flush_output();
|
||||
|
||||
Bitplanes bitplanes_;
|
||||
|
||||
BitplaneData next_bitplanes_, previous_bitplanes_;
|
||||
bool has_next_bitplanes_ = false;
|
||||
|
||||
int odd_priority_ = 0, even_priority_ = 0;
|
||||
bool even_over_odd_ = false;
|
||||
bool hold_and_modify_ = false;
|
||||
bool dual_playfields_ = false;
|
||||
bool interlace_ = false;
|
||||
bool is_long_field_ = false;
|
||||
|
||||
BitplaneShifter bitplane_pixels_;
|
||||
|
||||
int odd_delay_ = 0, even_delay_ = 0;
|
||||
bool is_high_res_ = false;
|
||||
|
||||
// MARK: - Copper.
|
||||
|
||||
Copper copper_;
|
||||
|
||||
// MARK: - Audio.
|
||||
|
||||
Audio audio_;
|
||||
|
||||
// MARK: - Serial port.
|
||||
|
||||
class SerialPort {
|
||||
public:
|
||||
void set_control(uint16_t);
|
||||
void set_data(uint16_t);
|
||||
uint16_t get_status();
|
||||
|
||||
private:
|
||||
// uint16_t value = 0, reload = 0;
|
||||
// uint16_t shift = 0, receive_shift = 0;
|
||||
// uint16_t status;
|
||||
} serial_;
|
||||
|
||||
// MARK: - Pixel output.
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t palette_[32]{};
|
||||
uint16_t swizzled_palette_[64]{};
|
||||
|
||||
// MARK: - Mouse.
|
||||
private:
|
||||
Mouse mouse_;
|
||||
// MARK: - CIAs.
|
||||
private:
|
||||
class DiskController;
|
||||
|
||||
class CIAAHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
Inputs::Mouse &get_mouse() {
|
||||
return mouse_;
|
||||
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port port);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// TEMPORARY.
|
||||
// TODO: generalise mice and joysticks.
|
||||
// This is a hack. A TEMPORARY HACK.
|
||||
void set_joystick(Joystick *joystick) {
|
||||
joystick_ = joystick;
|
||||
}
|
||||
|
||||
// MARK: - Joystick.
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
Joystick &joystick(size_t index) const {
|
||||
return *static_cast<Joystick *>(joysticks_[index].get());
|
||||
}
|
||||
|
||||
// MARK: - CIAs.
|
||||
private:
|
||||
class DiskController;
|
||||
|
||||
class CIAAHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port port);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// TEMPORARY.
|
||||
// TODO: generalise mice and joysticks.
|
||||
// This is a hack. A TEMPORARY HACK.
|
||||
void set_joystick(Joystick *joystick) {
|
||||
joystick_ = joystick;
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryMap &map_;
|
||||
DiskController &controller_;
|
||||
Mouse &mouse_;
|
||||
Joystick *joystick_ = nullptr;
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
inline static const std::string led_name = "Power";
|
||||
} cia_a_handler_;
|
||||
|
||||
class CIABHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
CIABHandler(DiskController &controller);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port);
|
||||
|
||||
private:
|
||||
DiskController &controller_;
|
||||
} cia_b_handler_;
|
||||
MemoryMap &map_;
|
||||
DiskController &controller_;
|
||||
Mouse &mouse_;
|
||||
Joystick *joystick_ = nullptr;
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
inline static const std::string led_name = "Power";
|
||||
} cia_a_handler_;
|
||||
|
||||
class CIABHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
|
||||
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
|
||||
|
||||
// CIAs are provided for direct access; it's up to the caller properly
|
||||
// to distinguish relevant accesses.
|
||||
CIAA cia_a;
|
||||
CIAB cia_b;
|
||||
CIABHandler(DiskController &controller);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port);
|
||||
|
||||
private:
|
||||
// MARK: - Disk drives.
|
||||
DiskController &controller_;
|
||||
} cia_b_handler_;
|
||||
|
||||
class DiskDMA: public DMADevice<1> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
public:
|
||||
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
|
||||
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
|
||||
|
||||
void set_length(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
bool advance_dma();
|
||||
// CIAs are provided for direct access; it's up to the caller properly
|
||||
// to distinguish relevant accesses.
|
||||
CIAA cia_a;
|
||||
CIAB cia_b;
|
||||
|
||||
void enqueue(uint16_t value, bool matches_sync);
|
||||
private:
|
||||
// MARK: - Disk drives.
|
||||
|
||||
private:
|
||||
uint16_t length_;
|
||||
bool dma_enable_ = false;
|
||||
bool write_ = false;
|
||||
uint16_t last_set_length_ = 0;
|
||||
bool sync_with_word_ = false;
|
||||
class DiskDMA: public DMADevice<1> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
std::array<uint16_t, 4> buffer_;
|
||||
size_t buffer_read_ = 0, buffer_write_ = 0;
|
||||
void set_length(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
bool advance_dma();
|
||||
|
||||
enum class State {
|
||||
Inactive,
|
||||
WaitingForSync,
|
||||
Reading,
|
||||
} state_ = State::Inactive;
|
||||
} disk_;
|
||||
void enqueue(uint16_t value, bool matches_sync);
|
||||
|
||||
class DiskController: public Storage::Disk::Controller {
|
||||
public:
|
||||
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
|
||||
private:
|
||||
uint16_t length_;
|
||||
bool dma_enable_ = false;
|
||||
bool write_ = false;
|
||||
uint16_t last_set_length_ = 0;
|
||||
bool sync_with_word_ = false;
|
||||
|
||||
void set_mtr_sel_side_dir_step(uint8_t);
|
||||
uint8_t get_rdy_trk0_wpro_chng();
|
||||
std::array<uint16_t, 4> buffer_;
|
||||
size_t buffer_read_ = 0, buffer_write_ = 0;
|
||||
|
||||
void run_for(Cycles duration) {
|
||||
Storage::Disk::Controller::run_for(duration);
|
||||
}
|
||||
enum class State {
|
||||
Inactive,
|
||||
WaitingForSync,
|
||||
Reading,
|
||||
} state_ = State::Inactive;
|
||||
} disk_;
|
||||
|
||||
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
class DiskController: public Storage::Disk::Controller {
|
||||
public:
|
||||
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
|
||||
|
||||
void set_sync_word(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
void set_mtr_sel_side_dir_step(uint8_t);
|
||||
uint8_t get_rdy_trk0_wpro_chng();
|
||||
|
||||
private:
|
||||
void process_input_bit(int value) final;
|
||||
void process_index_hole() final;
|
||||
void run_for(Cycles duration) {
|
||||
Storage::Disk::Controller::run_for(duration);
|
||||
}
|
||||
|
||||
// Implement the Amiga's drive ID shift registers
|
||||
// directly in the controller for now.
|
||||
uint32_t drive_ids_[4]{};
|
||||
uint32_t previous_select_ = 0;
|
||||
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
uint16_t data_ = 0;
|
||||
int bit_count_ = 0;
|
||||
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
|
||||
bool sync_with_word_ = false;
|
||||
void set_sync_word(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
|
||||
Chipset &chipset_;
|
||||
DiskDMA &disk_dma_;
|
||||
CIAB &cia_;
|
||||
private:
|
||||
void process_input_bit(int value) final;
|
||||
void process_index_hole() final;
|
||||
|
||||
} disk_controller_;
|
||||
friend DiskController;
|
||||
// Implement the Amiga's drive ID shift registers
|
||||
// directly in the controller for now.
|
||||
uint32_t drive_ids_[4]{};
|
||||
uint32_t previous_select_ = 0;
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
|
||||
bool disk_controller_is_sleeping_ = false;
|
||||
uint16_t paula_disk_control_ = 0;
|
||||
uint16_t data_ = 0;
|
||||
int bit_count_ = 0;
|
||||
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
|
||||
bool sync_with_word_ = false;
|
||||
|
||||
// MARK: - Keyboard.
|
||||
Chipset &chipset_;
|
||||
DiskDMA &disk_dma_;
|
||||
CIAB &cia_;
|
||||
} disk_controller_;
|
||||
friend DiskController;
|
||||
|
||||
Keyboard keyboard_;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
|
||||
bool disk_controller_is_sleeping_ = false;
|
||||
uint16_t paula_disk_control_ = 0;
|
||||
|
||||
// MARK: - Keyboard.
|
||||
|
||||
Keyboard keyboard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,39 +13,39 @@
|
||||
namespace Amiga {
|
||||
|
||||
class Copper: public DMADevice<2> {
|
||||
public:
|
||||
using DMADevice<2>::DMADevice;
|
||||
public:
|
||||
using DMADevice<2>::DMADevice;
|
||||
|
||||
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
|
||||
///
|
||||
/// @returns @c true if the slot was used; @c false otherwise.
|
||||
bool advance_dma(uint16_t position, uint16_t blitter_status);
|
||||
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
|
||||
///
|
||||
/// @returns @c true if the slot was used; @c false otherwise.
|
||||
bool advance_dma(uint16_t position, uint16_t blitter_status);
|
||||
|
||||
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
|
||||
template <int id> void reload() {
|
||||
address_ = pointer_[id];
|
||||
state_ = State::FetchFirstWord;
|
||||
}
|
||||
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
|
||||
template <int id> void reload() {
|
||||
address_ = pointer_[id];
|
||||
state_ = State::FetchFirstWord;
|
||||
}
|
||||
|
||||
/// Sets the Copper control word.
|
||||
void set_control(uint16_t c) {
|
||||
control_ = c;
|
||||
}
|
||||
/// Sets the Copper control word.
|
||||
void set_control(uint16_t c) {
|
||||
control_ = c;
|
||||
}
|
||||
|
||||
/// Forces the Copper into the stopped state.
|
||||
void stop() {
|
||||
state_ = State::Stopped;
|
||||
}
|
||||
/// Forces the Copper into the stopped state.
|
||||
void stop() {
|
||||
state_ = State::Stopped;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t address_ = 0;
|
||||
uint16_t control_ = 0;
|
||||
private:
|
||||
uint32_t address_ = 0;
|
||||
uint16_t control_ = 0;
|
||||
|
||||
enum class State {
|
||||
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
|
||||
} state_ = State::Stopped;
|
||||
bool skip_next_ = false;
|
||||
uint16_t instruction_[2]{};
|
||||
enum class State {
|
||||
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
|
||||
} state_ = State::Stopped;
|
||||
bool skip_next_ = false;
|
||||
uint16_t instruction_[2]{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,40 +32,40 @@ class DMADeviceBase {
|
||||
};
|
||||
|
||||
template <size_t num_addresses, size_t num_modulos = 0> class DMADevice: public DMADeviceBase {
|
||||
public:
|
||||
using DMADeviceBase::DMADeviceBase;
|
||||
public:
|
||||
using DMADeviceBase::DMADeviceBase;
|
||||
|
||||
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
static_assert(id < num_addresses);
|
||||
static_assert(shift == 0 || shift == 16);
|
||||
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
static_assert(id < num_addresses);
|
||||
static_assert(shift == 0 || shift == 16);
|
||||
|
||||
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
|
||||
pointer_[id] = byte_pointer_[id] >> 1;
|
||||
}
|
||||
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
|
||||
pointer_[id] = byte_pointer_[id] >> 1;
|
||||
}
|
||||
|
||||
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id> void set_modulo(uint16_t value) {
|
||||
static_assert(id < num_modulos);
|
||||
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id> void set_modulo(uint16_t value) {
|
||||
static_assert(id < num_modulos);
|
||||
|
||||
// Convert by sign extension.
|
||||
modulos_[id] = uint32_t(int16_t(value) >> 1);
|
||||
}
|
||||
// Convert by sign extension.
|
||||
modulos_[id] = uint32_t(int16_t(value) >> 1);
|
||||
}
|
||||
|
||||
template <int id, int shift> uint16_t get_pointer() {
|
||||
// Restore the original least-significant bit.
|
||||
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
|
||||
return uint16_t(source >> shift);
|
||||
}
|
||||
template <int id, int shift> uint16_t get_pointer() {
|
||||
// Restore the original least-significant bit.
|
||||
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
|
||||
return uint16_t(source >> shift);
|
||||
}
|
||||
|
||||
protected:
|
||||
// These are shifted right one to provide word-indexing pointers;
|
||||
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
|
||||
std::array<uint32_t, num_addresses> pointer_{};
|
||||
std::array<uint32_t, num_modulos> modulos_{};
|
||||
protected:
|
||||
// These are shifted right one to provide word-indexing pointers;
|
||||
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
|
||||
std::array<uint32_t, num_addresses> pointer_{};
|
||||
std::array<uint32_t, num_modulos> modulos_{};
|
||||
|
||||
private:
|
||||
std::array<uint32_t, num_addresses> byte_pointer_{};
|
||||
private:
|
||||
std::array<uint32_t, num_addresses> byte_pointer_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -77,42 +77,42 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe
|
||||
};
|
||||
|
||||
class Keyboard {
|
||||
public:
|
||||
Keyboard(Serial::Line<true> &output);
|
||||
public:
|
||||
Keyboard(Serial::Line<true> &output);
|
||||
|
||||
// enum Lines: uint8_t {
|
||||
// Data = (1 << 0),
|
||||
// Clock = (1 << 1),
|
||||
// };
|
||||
// enum Lines: uint8_t {
|
||||
// Data = (1 << 0),
|
||||
// Clock = (1 << 1),
|
||||
// };
|
||||
//
|
||||
// uint8_t update(uint8_t);
|
||||
// uint8_t update(uint8_t);
|
||||
|
||||
void set_key_state(uint16_t, bool);
|
||||
void clear_all_keys();
|
||||
void set_key_state(uint16_t, bool);
|
||||
void clear_all_keys();
|
||||
|
||||
void run_for(HalfCycles duration) {
|
||||
output_.advance_writer(duration);
|
||||
}
|
||||
void run_for(HalfCycles duration) {
|
||||
output_.advance_writer(duration);
|
||||
}
|
||||
|
||||
private:
|
||||
// enum class ShiftState {
|
||||
// Shifting,
|
||||
// AwaitingHandshake,
|
||||
// Idle,
|
||||
// } shift_state_ = ShiftState::Idle;
|
||||
private:
|
||||
// enum class ShiftState {
|
||||
// Shifting,
|
||||
// AwaitingHandshake,
|
||||
// Idle,
|
||||
// } shift_state_ = ShiftState::Idle;
|
||||
//
|
||||
// enum class State {
|
||||
// Startup,
|
||||
// } state_ = State::Startup;
|
||||
//
|
||||
// int bit_phase_ = 0;
|
||||
// uint32_t shift_sequence_ = 0;
|
||||
// int bits_remaining_ = 0;
|
||||
//
|
||||
// uint8_t lines_ = 0;
|
||||
|
||||
// enum class State {
|
||||
// Startup,
|
||||
// } state_ = State::Startup;
|
||||
|
||||
// int bit_phase_ = 0;
|
||||
// uint32_t shift_sequence_ = 0;
|
||||
// int bits_remaining_ = 0;
|
||||
|
||||
// uint8_t lines_ = 0;
|
||||
|
||||
Serial::Line<true> &output_;
|
||||
std::array<bool, 128> pressed_{};
|
||||
Serial::Line<true> &output_;
|
||||
std::array<bool, 128> pressed_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,178 +17,178 @@
|
||||
namespace Amiga {
|
||||
|
||||
class MemoryMap {
|
||||
private:
|
||||
static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead;
|
||||
static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite;
|
||||
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
|
||||
private:
|
||||
static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead;
|
||||
static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite;
|
||||
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
|
||||
|
||||
public:
|
||||
std::array<uint8_t, 512*1024> kickstart{0xff};
|
||||
std::vector<uint8_t> chip_ram{};
|
||||
public:
|
||||
std::array<uint8_t, 512*1024> kickstart{0xff};
|
||||
std::vector<uint8_t> chip_ram{};
|
||||
|
||||
struct MemoryRegion {
|
||||
uint8_t *contents = nullptr;
|
||||
unsigned int read_write_mask = 0;
|
||||
} regions[64]; // i.e. top six bits are used as an index.
|
||||
struct MemoryRegion {
|
||||
uint8_t *contents = nullptr;
|
||||
unsigned int read_write_mask = 0;
|
||||
} regions[64]; // i.e. top six bits are used as an index.
|
||||
|
||||
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
|
||||
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
|
||||
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
|
||||
// Address spaces that matter:
|
||||
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
|
||||
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
|
||||
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
|
||||
// Address spaces that matter:
|
||||
//
|
||||
// 00'0000 – 08'0000: chip RAM. [or overlayed KickStart]
|
||||
// – 10'0000: extended chip ram for ECS.
|
||||
// – 20'0000: slow RAM and further chip RAM.
|
||||
// – a0'0000: auto-config space (/fast RAM).
|
||||
// ...
|
||||
// bf'd000 – c0'0000: 8250s.
|
||||
// c0'0000 – d8'0000: pseudo-fast RAM.
|
||||
// ...
|
||||
// dc'0000 – dd'0000: optional real-time clock.
|
||||
// df'f000 - e0'0000: custom chip registers.
|
||||
// ...
|
||||
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
|
||||
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
|
||||
// fc'0000 – : 256kb Kickstart otherwise.
|
||||
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
|
||||
|
||||
switch(chip_ram_size) {
|
||||
default:
|
||||
case ChipRAM::FiveHundredAndTwelveKilobytes:
|
||||
chip_ram.resize(512 * 1024);
|
||||
break;
|
||||
case ChipRAM::OneMegabyte:
|
||||
chip_ram.resize(1 * 1024 * 1024);
|
||||
break;
|
||||
case ChipRAM::TwoMegabytes:
|
||||
chip_ram.resize(2 * 1024 * 1024);
|
||||
break;
|
||||
}
|
||||
|
||||
switch(fast_ram_size) {
|
||||
default:
|
||||
fast_autoconf_visible_ = false;
|
||||
break;
|
||||
case FastRAM::OneMegabyte:
|
||||
fast_ram_.resize(1 * 1024 * 1024);
|
||||
fast_ram_size_ = 5;
|
||||
break;
|
||||
case FastRAM::TwoMegabytes:
|
||||
fast_ram_.resize(2 * 1024 * 1024);
|
||||
fast_ram_size_ = 6;
|
||||
break;
|
||||
case FastRAM::FourMegabytes:
|
||||
fast_ram_.resize(4 * 1024 * 1024);
|
||||
fast_ram_size_ = 7;
|
||||
break;
|
||||
case FastRAM::EightMegabytes:
|
||||
fast_ram_.resize(8 * 1024 * 1024);
|
||||
fast_ram_size_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
set_overlay(true);
|
||||
}
|
||||
|
||||
void set_overlay(bool enabled) {
|
||||
if(overlay_ == enabled) {
|
||||
return;
|
||||
}
|
||||
overlay_ = enabled;
|
||||
|
||||
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
|
||||
if(enabled) {
|
||||
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
|
||||
/// and in the Zorro register range.
|
||||
template <typename Microcycle>
|
||||
bool perform(const Microcycle &cycle) {
|
||||
if(!fast_autoconf_visible_) return false;
|
||||
|
||||
const uint32_t register_address = *cycle.address & 0xfe;
|
||||
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
// Re: Autoconf:
|
||||
//
|
||||
// 00'0000 – 08'0000: chip RAM. [or overlayed KickStart]
|
||||
// – 10'0000: extended chip ram for ECS.
|
||||
// – 20'0000: slow RAM and further chip RAM.
|
||||
// – a0'0000: auto-config space (/fast RAM).
|
||||
// ...
|
||||
// bf'd000 – c0'0000: 8250s.
|
||||
// c0'0000 – d8'0000: pseudo-fast RAM.
|
||||
// ...
|
||||
// dc'0000 – dd'0000: optional real-time clock.
|
||||
// df'f000 - e0'0000: custom chip registers.
|
||||
// ...
|
||||
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
|
||||
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
|
||||
// fc'0000 – : 256kb Kickstart otherwise.
|
||||
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
|
||||
// "All read registers physically return only the top 4 bits of data, on D31-D28";
|
||||
// (this is from Zorro III documentation; I'm assuming it to be D15–D11 for the
|
||||
// 68000's 16-bit bus);
|
||||
//
|
||||
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
|
||||
// 8 bits actually being nybbles from two paired addresses."
|
||||
|
||||
switch(chip_ram_size) {
|
||||
default:
|
||||
case ChipRAM::FiveHundredAndTwelveKilobytes:
|
||||
chip_ram.resize(512 * 1024);
|
||||
uint8_t value = 0xf;
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
|
||||
case 0x00: // er_Type (high)
|
||||
value =
|
||||
0xc | // Zoro II-style PIC.
|
||||
0x2; // Memory will be linked into the free pool
|
||||
break;
|
||||
case ChipRAM::OneMegabyte:
|
||||
chip_ram.resize(1 * 1024 * 1024);
|
||||
case 0x02: // er_Type (low)
|
||||
value = fast_ram_size_;
|
||||
break;
|
||||
case ChipRAM::TwoMegabytes:
|
||||
chip_ram.resize(2 * 1024 * 1024);
|
||||
|
||||
// er_Manufacturer
|
||||
//
|
||||
// On the manufacturer number: this is supposed to be assigned
|
||||
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
|
||||
//
|
||||
// (0xffff seems to be invalid, so _something_ needs to be supplied)
|
||||
case 0x10: case 0x12:
|
||||
value = 0xa; // Manufacturer's number, high byte.
|
||||
break;
|
||||
case 0x14: case 0x16:
|
||||
value = 0xb; // Manufacturer's number, low byte.
|
||||
break;
|
||||
}
|
||||
|
||||
switch(fast_ram_size) {
|
||||
default:
|
||||
// Shove the value into the top of the data bus.
|
||||
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
|
||||
} else {
|
||||
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
|
||||
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
|
||||
case 0x48: { // ec_BaseAddress (A23–A16)
|
||||
const auto address = uint32_t(cycle.value8_high()) << 16;
|
||||
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
|
||||
fast_autoconf_visible_ = false;
|
||||
break;
|
||||
case FastRAM::OneMegabyte:
|
||||
fast_ram_.resize(1 * 1024 * 1024);
|
||||
fast_ram_size_ = 5;
|
||||
break;
|
||||
case FastRAM::TwoMegabytes:
|
||||
fast_ram_.resize(2 * 1024 * 1024);
|
||||
fast_ram_size_ = 6;
|
||||
break;
|
||||
case FastRAM::FourMegabytes:
|
||||
fast_ram_.resize(4 * 1024 * 1024);
|
||||
fast_ram_size_ = 7;
|
||||
break;
|
||||
case FastRAM::EightMegabytes:
|
||||
fast_ram_.resize(8 * 1024 * 1024);
|
||||
fast_ram_size_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
set_overlay(true);
|
||||
}
|
||||
|
||||
void set_overlay(bool enabled) {
|
||||
if(overlay_ == enabled) {
|
||||
return;
|
||||
}
|
||||
overlay_ = enabled;
|
||||
|
||||
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
|
||||
if(enabled) {
|
||||
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
|
||||
/// and in the Zorro register range.
|
||||
template <typename Microcycle>
|
||||
bool perform(const Microcycle &cycle) {
|
||||
if(!fast_autoconf_visible_) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t register_address = *cycle.address & 0xfe;
|
||||
private:
|
||||
std::vector<uint8_t> fast_ram_{};
|
||||
uint8_t fast_ram_size_ = 0;
|
||||
|
||||
if(cycle.operation & CPU::MC68000::Operation::Read) {
|
||||
// Re: Autoconf:
|
||||
//
|
||||
// "All read registers physically return only the top 4 bits of data, on D31-D28";
|
||||
// (this is from Zorro III documentation; I'm assuming it to be D15–D11 for the
|
||||
// 68000's 16-bit bus);
|
||||
//
|
||||
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
|
||||
// 8 bits actually being nybbles from two paired addresses."
|
||||
bool fast_autoconf_visible_ = true;
|
||||
bool overlay_ = false;
|
||||
|
||||
uint8_t value = 0xf;
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
|
||||
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
|
||||
assert(!(start & precision_loss_mask));
|
||||
assert(!((end - (1 << 18)) & precision_loss_mask));
|
||||
assert(end > start);
|
||||
|
||||
case 0x00: // er_Type (high)
|
||||
value =
|
||||
0xc | // Zoro II-style PIC.
|
||||
0x2; // Memory will be linked into the free pool
|
||||
break;
|
||||
case 0x02: // er_Type (low)
|
||||
value = fast_ram_size_;
|
||||
break;
|
||||
|
||||
// er_Manufacturer
|
||||
//
|
||||
// On the manufacturer number: this is supposed to be assigned
|
||||
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
|
||||
//
|
||||
// (0xffff seems to be invalid, so _something_ needs to be supplied)
|
||||
case 0x10: case 0x12:
|
||||
value = 0xa; // Manufacturer's number, high byte.
|
||||
break;
|
||||
case 0x14: case 0x16:
|
||||
value = 0xb; // Manufacturer's number, low byte.
|
||||
break;
|
||||
}
|
||||
|
||||
// Shove the value into the top of the data bus.
|
||||
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
|
||||
} else {
|
||||
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
|
||||
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
|
||||
case 0x48: { // ec_BaseAddress (A23–A16)
|
||||
const auto address = uint32_t(cycle.value8_high()) << 16;
|
||||
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
|
||||
fast_autoconf_visible_ = false;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> fast_ram_{};
|
||||
uint8_t fast_ram_size_ = 0;
|
||||
|
||||
bool fast_autoconf_visible_ = true;
|
||||
bool overlay_ = false;
|
||||
|
||||
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
|
||||
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
|
||||
assert(!(start & precision_loss_mask));
|
||||
assert(!((end - (1 << 18)) & precision_loss_mask));
|
||||
assert(end > start);
|
||||
|
||||
if(base) base -= start;
|
||||
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
|
||||
regions[c].contents = base;
|
||||
regions[c].read_write_mask = read_write_mask;
|
||||
}
|
||||
if(base) base -= start;
|
||||
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
|
||||
regions[c].contents = base;
|
||||
regions[c].read_write_mask = read_write_mask;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,33 +22,33 @@ struct MouseJoystickInput {
|
||||
};
|
||||
|
||||
class Mouse: public Inputs::Mouse, public MouseJoystickInput {
|
||||
public:
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
public:
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
|
||||
private:
|
||||
int get_number_of_buttons() const final;
|
||||
void set_button_pressed(int, bool) final;
|
||||
void reset_all_buttons() final;
|
||||
void move(int, int) final;
|
||||
private:
|
||||
int get_number_of_buttons() const final;
|
||||
void set_button_pressed(int, bool) final;
|
||||
void reset_all_buttons() final;
|
||||
void move(int, int) final;
|
||||
|
||||
uint8_t declared_position_[2]{};
|
||||
uint8_t cia_state_ = 0xff;
|
||||
std::array<std::atomic<int>, 2> position_{};
|
||||
uint8_t declared_position_[2]{};
|
||||
uint8_t cia_state_ = 0xff;
|
||||
std::array<std::atomic<int>, 2> position_{};
|
||||
};
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
|
||||
public:
|
||||
Joystick();
|
||||
public:
|
||||
Joystick();
|
||||
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
|
||||
private:
|
||||
void did_set_input(const Input &input, bool is_active) final;
|
||||
private:
|
||||
void did_set_input(const Input &input, bool is_active) final;
|
||||
|
||||
bool inputs_[Joystick::Input::Type::Max]{};
|
||||
uint16_t position_ = 0;
|
||||
bool inputs_[Joystick::Input::Type::Max]{};
|
||||
uint16_t position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,52 +15,52 @@
|
||||
namespace Amiga {
|
||||
|
||||
class Sprite: public DMADevice<1> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
void set_start_position(uint16_t value);
|
||||
void set_stop_and_control(uint16_t value);
|
||||
void set_image_data(int slot, uint16_t value);
|
||||
void set_start_position(uint16_t value);
|
||||
void set_stop_and_control(uint16_t value);
|
||||
void set_image_data(int slot, uint16_t value);
|
||||
|
||||
bool advance_dma(int offset, int y, bool is_first_line);
|
||||
bool advance_dma(int offset, int y, bool is_first_line);
|
||||
|
||||
uint16_t data[2]{};
|
||||
bool attached = false;
|
||||
bool visible = false;
|
||||
uint16_t h_start = 0;
|
||||
uint16_t data[2]{};
|
||||
bool attached = false;
|
||||
bool visible = false;
|
||||
uint16_t h_start = 0;
|
||||
|
||||
private:
|
||||
uint16_t v_start_ = 0, v_stop_ = 0;
|
||||
private:
|
||||
uint16_t v_start_ = 0, v_stop_ = 0;
|
||||
};
|
||||
|
||||
class TwoSpriteShifter {
|
||||
public:
|
||||
/// Installs new pixel data for @c sprite (either 0 or 1),
|
||||
/// with @c delay being either 0 or 1 to indicate whether
|
||||
/// output should begin now or in one pixel's time.
|
||||
template <int sprite> void load(
|
||||
uint16_t lsb,
|
||||
uint16_t msb,
|
||||
int delay);
|
||||
public:
|
||||
/// Installs new pixel data for @c sprite (either 0 or 1),
|
||||
/// with @c delay being either 0 or 1 to indicate whether
|
||||
/// output should begin now or in one pixel's time.
|
||||
template <int sprite> void load(
|
||||
uint16_t lsb,
|
||||
uint16_t msb,
|
||||
int delay);
|
||||
|
||||
/// Shifts two pixels.
|
||||
void shift() {
|
||||
data_ <<= 8;
|
||||
data_ |= overflow_;
|
||||
overflow_ = 0;
|
||||
}
|
||||
/// Shifts two pixels.
|
||||
void shift() {
|
||||
data_ <<= 8;
|
||||
data_ |= overflow_;
|
||||
overflow_ = 0;
|
||||
}
|
||||
|
||||
/// @returns The next two pixels to output, formulated as
|
||||
/// abcd efgh where ab and ef are two pixels of the first sprite
|
||||
/// and cd and gh are two pixels of the second. In each case the
|
||||
/// more significant two are output first.
|
||||
uint8_t get() {
|
||||
return uint8_t(data_ >> 56);
|
||||
}
|
||||
/// @returns The next two pixels to output, formulated as
|
||||
/// abcd efgh where ab and ef are two pixels of the first sprite
|
||||
/// and cd and gh are two pixels of the second. In each case the
|
||||
/// more significant two are output first.
|
||||
uint8_t get() {
|
||||
return uint8_t(data_ >> 56);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t data_;
|
||||
uint8_t overflow_;
|
||||
private:
|
||||
uint64_t data_;
|
||||
uint8_t overflow_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,32 +17,32 @@ namespace Amstrad {
|
||||
exposes motor control, applying the same value to all drives.
|
||||
*/
|
||||
class FDC: public Intel::i8272::i8272 {
|
||||
private:
|
||||
Intel::i8272::BusHandler bus_handler_;
|
||||
private:
|
||||
Intel::i8272::BusHandler bus_handler_;
|
||||
|
||||
public:
|
||||
FDC(Cycles clock_rate = Cycles(8000000)) :
|
||||
i8272(bus_handler_, clock_rate)
|
||||
{
|
||||
emplace_drive(clock_rate.as<int>(), 300, 1);
|
||||
set_drive(1);
|
||||
}
|
||||
public:
|
||||
FDC(Cycles clock_rate = Cycles(8000000)) :
|
||||
i8272(bus_handler_, clock_rate)
|
||||
{
|
||||
emplace_drive(clock_rate.as<int>(), 300, 1);
|
||||
set_drive(1);
|
||||
}
|
||||
|
||||
void set_motor_on(bool on) {
|
||||
get_drive().set_motor_on(on);
|
||||
}
|
||||
void set_motor_on(bool on) {
|
||||
get_drive().set_motor_on(on);
|
||||
}
|
||||
|
||||
void select_drive(int) {
|
||||
// TODO: support more than one drive. (and in set_disk)
|
||||
}
|
||||
void select_drive(int) {
|
||||
// TODO: support more than one drive. (and in set_disk)
|
||||
}
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
get_drive().set_activity_observer(observer, "Drive 1", true);
|
||||
}
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
get_drive().set_activity_observer(observer, "Drive 1", true);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -93,75 +93,75 @@ inline Command decode_command(uint8_t code) {
|
||||
update @c set_device_output.
|
||||
*/
|
||||
class Bus {
|
||||
public:
|
||||
Bus(HalfCycles clock_speed);
|
||||
public:
|
||||
Bus(HalfCycles clock_speed);
|
||||
|
||||
/*!
|
||||
Advances time; ADB is a clocked serial signal.
|
||||
*/
|
||||
void run_for(HalfCycles);
|
||||
/*!
|
||||
Advances time; ADB is a clocked serial signal.
|
||||
*/
|
||||
void run_for(HalfCycles);
|
||||
|
||||
/*!
|
||||
Adds a device to the bus, returning the index it should use
|
||||
to refer to itself in subsequent calls to set_device_output.
|
||||
*/
|
||||
size_t add_device();
|
||||
/*!
|
||||
Adds a device to the bus, returning the index it should use
|
||||
to refer to itself in subsequent calls to set_device_output.
|
||||
*/
|
||||
size_t add_device();
|
||||
|
||||
/*!
|
||||
Sets the current data line output for @c device.
|
||||
*/
|
||||
void set_device_output(size_t device_id, bool output);
|
||||
/*!
|
||||
Sets the current data line output for @c device.
|
||||
*/
|
||||
void set_device_output(size_t device_id, bool output);
|
||||
|
||||
/*!
|
||||
@returns The current state of the ADB data line.
|
||||
*/
|
||||
bool get_state() const;
|
||||
/*!
|
||||
@returns The current state of the ADB data line.
|
||||
*/
|
||||
bool get_state() const;
|
||||
|
||||
enum class Event {
|
||||
Reset,
|
||||
Attention,
|
||||
Byte,
|
||||
ServiceRequest,
|
||||
enum class Event {
|
||||
Reset,
|
||||
Attention,
|
||||
Byte,
|
||||
ServiceRequest,
|
||||
|
||||
Unrecognised
|
||||
};
|
||||
Unrecognised
|
||||
};
|
||||
|
||||
struct Device {
|
||||
/// Reports to an observer that @c event was observed in the activity
|
||||
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
|
||||
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
|
||||
struct Device {
|
||||
/// Reports to an observer that @c event was observed in the activity
|
||||
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
|
||||
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
|
||||
|
||||
/// Requests that the device update itself @c microseconds and, if necessary, post a
|
||||
/// new value ot @c set_device_output. This will be called only when the bus needs
|
||||
/// to reevaluate its current level. It cannot reliably be used to track the timing between
|
||||
/// observed events.
|
||||
virtual void advance_state(double microseconds, bool current_level) = 0;
|
||||
};
|
||||
/*!
|
||||
Adds a device.
|
||||
*/
|
||||
size_t add_device(Device *);
|
||||
/// Requests that the device update itself @c microseconds and, if necessary, post a
|
||||
/// new value ot @c set_device_output. This will be called only when the bus needs
|
||||
/// to reevaluate its current level. It cannot reliably be used to track the timing between
|
||||
/// observed events.
|
||||
virtual void advance_state(double microseconds, bool current_level) = 0;
|
||||
};
|
||||
/*!
|
||||
Adds a device.
|
||||
*/
|
||||
size_t add_device(Device *);
|
||||
|
||||
private:
|
||||
HalfCycles time_in_state_;
|
||||
mutable HalfCycles time_since_get_state_;
|
||||
private:
|
||||
HalfCycles time_in_state_;
|
||||
mutable HalfCycles time_since_get_state_;
|
||||
|
||||
double half_cycles_to_microseconds_ = 1.0;
|
||||
std::vector<Device *> devices_;
|
||||
unsigned int shift_register_ = 0;
|
||||
unsigned int start_target_ = 8;
|
||||
bool data_level_ = true;
|
||||
double half_cycles_to_microseconds_ = 1.0;
|
||||
std::vector<Device *> devices_;
|
||||
unsigned int shift_register_ = 0;
|
||||
unsigned int start_target_ = 8;
|
||||
bool data_level_ = true;
|
||||
|
||||
// ADB addressing supports at most 16 devices but that doesn't include
|
||||
// the controller. So assume a maximum of 17 connected devices.
|
||||
std::bitset<17> bus_state_{0xffffffff};
|
||||
size_t next_device_id_ = 0;
|
||||
// ADB addressing supports at most 16 devices but that doesn't include
|
||||
// the controller. So assume a maximum of 17 connected devices.
|
||||
std::bitset<17> bus_state_{0xffffffff};
|
||||
size_t next_device_id_ = 0;
|
||||
|
||||
inline void shift(unsigned int);
|
||||
enum class Phase {
|
||||
PacketCapture,
|
||||
AttentionCapture
|
||||
} phase_ = Phase::AttentionCapture;
|
||||
inline void shift(unsigned int);
|
||||
enum class Phase {
|
||||
PacketCapture,
|
||||
AttentionCapture
|
||||
} phase_ = Phase::AttentionCapture;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -94,20 +94,20 @@ enum class Key: uint16_t {
|
||||
};
|
||||
|
||||
class Keyboard: public ReactiveDevice {
|
||||
public:
|
||||
Keyboard(Bus &);
|
||||
public:
|
||||
Keyboard(Bus &);
|
||||
|
||||
bool set_key_pressed(Key key, bool is_pressed);
|
||||
void clear_all_keys();
|
||||
bool set_key_pressed(Key key, bool is_pressed);
|
||||
void clear_all_keys();
|
||||
|
||||
private:
|
||||
void perform_command(const Command &command) override;
|
||||
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
|
||||
private:
|
||||
void perform_command(const Command &command) override;
|
||||
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
|
||||
|
||||
std::mutex keys_mutex_;
|
||||
std::array<bool, 128> pressed_keys_{};
|
||||
std::vector<uint8_t> pending_events_;
|
||||
uint16_t modifiers_ = 0xffff;
|
||||
std::mutex keys_mutex_;
|
||||
std::array<bool, 128> pressed_keys_{};
|
||||
std::vector<uint8_t> pending_events_;
|
||||
uint16_t modifiers_ = 0xffff;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
namespace Apple::ADB {
|
||||
|
||||
class Mouse: public ReactiveDevice, public Inputs::Mouse {
|
||||
public:
|
||||
Mouse(Bus &);
|
||||
public:
|
||||
Mouse(Bus &);
|
||||
|
||||
private:
|
||||
void perform_command(const Command &command) override;
|
||||
private:
|
||||
void perform_command(const Command &command) override;
|
||||
|
||||
void move(int x, int y) override;
|
||||
int get_number_of_buttons() const override;
|
||||
void set_button_pressed(int index, bool is_pressed) override;
|
||||
void reset_all_buttons() override;
|
||||
void move(int x, int y) override;
|
||||
int get_number_of_buttons() const override;
|
||||
void set_button_pressed(int index, bool is_pressed) override;
|
||||
void reset_all_buttons() override;
|
||||
|
||||
std::atomic<int16_t> delta_x_, delta_y_;
|
||||
std::atomic<int> button_flags_ = 0;
|
||||
uint16_t last_posted_reg0_ = 0;
|
||||
std::atomic<int16_t> delta_x_, delta_y_;
|
||||
std::atomic<int> button_flags_ = 0;
|
||||
uint16_t last_posted_reg0_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,45 +17,44 @@
|
||||
namespace Apple::ADB {
|
||||
|
||||
class ReactiveDevice: public Bus::Device {
|
||||
protected:
|
||||
ReactiveDevice(Bus &bus, uint8_t adb_device_id);
|
||||
protected:
|
||||
ReactiveDevice(Bus &bus, uint8_t adb_device_id);
|
||||
|
||||
void post_response(const std::vector<uint8_t> &&response);
|
||||
void post_service_request();
|
||||
void receive_bytes(size_t count);
|
||||
void post_response(const std::vector<uint8_t> &&response);
|
||||
void post_service_request();
|
||||
void receive_bytes(size_t count);
|
||||
|
||||
virtual void perform_command(const Command &command) = 0;
|
||||
virtual void did_receive_data(const Command &, const std::vector<uint8_t> &) {}
|
||||
virtual void perform_command(const Command &command) = 0;
|
||||
virtual void did_receive_data(const Command &, const std::vector<uint8_t> &) {}
|
||||
|
||||
private:
|
||||
void advance_state(double microseconds, bool current_level) override;
|
||||
void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override;
|
||||
private:
|
||||
void advance_state(double microseconds, bool current_level) override;
|
||||
void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override;
|
||||
|
||||
private:
|
||||
Bus &bus_;
|
||||
const size_t device_id_;
|
||||
Bus &bus_;
|
||||
const size_t device_id_;
|
||||
|
||||
std::vector<uint8_t> response_;
|
||||
int bit_offset_ = 0;
|
||||
double microseconds_at_bit_ = 0;
|
||||
std::vector<uint8_t> response_;
|
||||
int bit_offset_ = 0;
|
||||
double microseconds_at_bit_ = 0;
|
||||
|
||||
enum class Phase {
|
||||
AwaitingAttention,
|
||||
AwaitingCommand,
|
||||
AwaitingContent,
|
||||
ServiceRequestPending,
|
||||
} phase_ = Phase::AwaitingAttention;
|
||||
std::vector<uint8_t> content_;
|
||||
size_t expected_content_size_ = 0;
|
||||
Command command_;
|
||||
bool stop_has_begin_ = false;
|
||||
enum class Phase {
|
||||
AwaitingAttention,
|
||||
AwaitingCommand,
|
||||
AwaitingContent,
|
||||
ServiceRequestPending,
|
||||
} phase_ = Phase::AwaitingAttention;
|
||||
std::vector<uint8_t> content_;
|
||||
size_t expected_content_size_ = 0;
|
||||
Command command_;
|
||||
bool stop_has_begin_ = false;
|
||||
|
||||
uint16_t register3_;
|
||||
const uint8_t default_adb_device_id_;
|
||||
uint16_t register3_;
|
||||
const uint8_t default_adb_device_id_;
|
||||
|
||||
std::atomic<bool> service_desired_ = false;
|
||||
std::atomic<bool> service_desired_ = false;
|
||||
|
||||
void reset();
|
||||
void reset();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,23 +22,23 @@
|
||||
namespace Apple::II {
|
||||
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
static ROM::Request rom_request(bool is_16_sector);
|
||||
DiskIICard(ROM::Map &, bool is_16_sector);
|
||||
public:
|
||||
static ROM::Request rom_request(bool is_16_sector);
|
||||
DiskIICard(ROM::Map &, bool is_16_sector);
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void run_for(Cycles cycles, int stretches) final;
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void run_for(Cycles cycles, int stretches) final;
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) final;
|
||||
void set_activity_observer(Activity::Observer *observer) final;
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
Storage::Disk::Drive &get_drive(int drive);
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
Storage::Disk::Drive &get_drive(int drive);
|
||||
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
std::vector<uint8_t> boot_;
|
||||
Apple::DiskII diskii_;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
std::vector<uint8_t> boot_;
|
||||
Apple::DiskII diskii_;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,92 +16,92 @@
|
||||
namespace Apple::II {
|
||||
|
||||
class JoystickPair {
|
||||
public:
|
||||
JoystickPair() {
|
||||
// Add a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
public:
|
||||
JoystickPair() {
|
||||
// Add a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Horizontal),
|
||||
Input(Input::Vertical),
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Horizontal),
|
||||
Input(Input::Vertical),
|
||||
|
||||
// The Apple II offers three buttons between two joysticks;
|
||||
// this emulator puts three buttons on each joystick and
|
||||
// combines them.
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
Input(Input::Fire, 2),
|
||||
}) {}
|
||||
// The Apple II offers three buttons between two joysticks;
|
||||
// this emulator puts three buttons on each joystick and
|
||||
// combines them.
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
Input(Input::Fire, 2),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, float value) final {
|
||||
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
|
||||
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
|
||||
}
|
||||
|
||||
void did_set_input(const Input &input, bool value) final {
|
||||
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
|
||||
buttons[input.info.control.index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool buttons[3] = {false, false, false};
|
||||
float axes[2] = {0.5f, 0.5f};
|
||||
};
|
||||
|
||||
inline bool button(size_t index) {
|
||||
return joystick(0)->buttons[index] || joystick(1)->buttons[2-index];
|
||||
}
|
||||
|
||||
inline bool analogue_channel_is_discharged(size_t channel) {
|
||||
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
|
||||
}
|
||||
|
||||
inline void update_charge(float one_mhz_cycles = 1.0f) {
|
||||
analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f);
|
||||
}
|
||||
|
||||
inline void access_c070() {
|
||||
// Permit analogue inputs that are currently discharged to begin a charge cycle.
|
||||
// Ensure those that were still charging retain that state.
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
if(analogue_channel_is_discharged(c)) {
|
||||
analogue_biases_[c] = 0.0f;
|
||||
} else {
|
||||
analogue_biases_[c] += analogue_charge_;
|
||||
void did_set_input(const Input &input, float value) final {
|
||||
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
|
||||
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
|
||||
}
|
||||
|
||||
void did_set_input(const Input &input, bool value) final {
|
||||
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
|
||||
buttons[input.info.control.index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool buttons[3] = {false, false, false};
|
||||
float axes[2] = {0.5f, 0.5f};
|
||||
};
|
||||
|
||||
inline bool button(size_t index) {
|
||||
return joystick(0)->buttons[index] || joystick(1)->buttons[2-index];
|
||||
}
|
||||
|
||||
inline bool analogue_channel_is_discharged(size_t channel) {
|
||||
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
|
||||
}
|
||||
|
||||
inline void update_charge(float one_mhz_cycles = 1.0f) {
|
||||
analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f);
|
||||
}
|
||||
|
||||
inline void access_c070() {
|
||||
// Permit analogue inputs that are currently discharged to begin a charge cycle.
|
||||
// Ensure those that were still charging retain that state.
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
if(analogue_channel_is_discharged(c)) {
|
||||
analogue_biases_[c] = 0.0f;
|
||||
} else {
|
||||
analogue_biases_[c] += analogue_charge_;
|
||||
}
|
||||
analogue_charge_ = 0.0f;
|
||||
}
|
||||
analogue_charge_ = 0.0f;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
private:
|
||||
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
|
||||
// to begin a charge and discharge cycle **if they are not already charging**.
|
||||
// The greater the analogue input, the faster they will charge and therefore the sooner
|
||||
// they will discharge.
|
||||
//
|
||||
// This emulator models that with analogue_charge_ being essentially the amount of time,
|
||||
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
|
||||
// inputs were already partially charged then they gain a bias in analogue_biases_.
|
||||
//
|
||||
// It's a little indirect, but it means only having to increment the one value in the
|
||||
// main loop.
|
||||
float analogue_charge_ = 0.0f;
|
||||
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
private:
|
||||
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
|
||||
// to begin a charge and discharge cycle **if they are not already charging**.
|
||||
// The greater the analogue input, the faster they will charge and therefore the sooner
|
||||
// they will discharge.
|
||||
//
|
||||
// This emulator models that with analogue_charge_ being essentially the amount of time,
|
||||
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
|
||||
// inputs were already partially charged then they gain a bias in analogue_biases_.
|
||||
//
|
||||
// It's a little indirect, but it means only having to increment the one value in the
|
||||
// main loop.
|
||||
float analogue_charge_ = 0.0f;
|
||||
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
inline Joystick *joystick(size_t index) {
|
||||
return static_cast<Joystick *>(joysticks_[index].get());
|
||||
}
|
||||
inline Joystick *joystick(size_t index) {
|
||||
return static_cast<Joystick *>(joysticks_[index].get());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -19,95 +19,95 @@ namespace Apple::II {
|
||||
* machine.set_language_card_paging() if the proper mapped state changes.
|
||||
*/
|
||||
template <typename Machine> class LanguageCardSwitches {
|
||||
public:
|
||||
struct State {
|
||||
/// When RAM is visible in the range $D000–$FFFF:
|
||||
/// @c true indicates that bank 2 should be used between $D000 and $DFFF;
|
||||
/// @c false indicates bank 1.
|
||||
bool bank2 = true;
|
||||
public:
|
||||
struct State {
|
||||
/// When RAM is visible in the range $D000–$FFFF:
|
||||
/// @c true indicates that bank 2 should be used between $D000 and $DFFF;
|
||||
/// @c false indicates bank 1.
|
||||
bool bank2 = true;
|
||||
|
||||
/// @c true indicates that RAM should be readable in the range $D000–$FFFF;
|
||||
/// @c false indicates ROM should be readable.
|
||||
bool read = false;
|
||||
/// @c true indicates that RAM should be readable in the range $D000–$FFFF;
|
||||
/// @c false indicates ROM should be readable.
|
||||
bool read = false;
|
||||
|
||||
/// @c true indicates that ROM is selected for 'writing' in the range $D000–$FFFF (i.e. writes are a no-op);
|
||||
/// @c false indicates that RAM is selected for writing.
|
||||
bool write = false;
|
||||
/// @c true indicates that ROM is selected for 'writing' in the range $D000–$FFFF (i.e. writes are a no-op);
|
||||
/// @c false indicates that RAM is selected for writing.
|
||||
bool write = false;
|
||||
|
||||
bool operator != (const State &rhs) const {
|
||||
return
|
||||
bank2 != rhs.bank2 ||
|
||||
read != rhs.read ||
|
||||
write != rhs.write;
|
||||
}
|
||||
};
|
||||
|
||||
LanguageCardSwitches(Machine &machine) : machine_(machine) {}
|
||||
|
||||
/// Used by an owner to forward any access to $c08x.
|
||||
void access(uint16_t address, bool is_read) {
|
||||
const auto previous_state = state_;
|
||||
|
||||
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
|
||||
|
||||
// "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1.
|
||||
state_.bank2 = !(address & 8);
|
||||
|
||||
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
|
||||
// (other accesses reset it)
|
||||
state_.read = !(((address&2) >> 1) ^ (address&1));
|
||||
|
||||
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
|
||||
if(pre_write_ && is_read && (address&1)) state_.write = false;
|
||||
|
||||
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
|
||||
if(!(address&1)) state_.write = true;
|
||||
|
||||
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
|
||||
|
||||
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
|
||||
pre_write_ = is_read ? (address&1) : false;
|
||||
|
||||
// Apply whatever the net effect of all that is to the memory map.
|
||||
if(previous_state != state_) {
|
||||
machine_.template set_paging<PagingType::LanguageCard>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides read-only access to the current language card switch state.
|
||||
const State &state() const {
|
||||
return state_;
|
||||
}
|
||||
|
||||
/// Provides relevant parts of the IIgs interface.
|
||||
void set_state(uint8_t value) {
|
||||
const auto previous_state = state_;
|
||||
|
||||
// Bit 3: 1 => enable ROM, 0 => enable RAM.
|
||||
state_.read = !(value & 0x08);
|
||||
// Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference
|
||||
// correcting the original, which lists them the other way around]
|
||||
state_.bank2 = value & 0x04;
|
||||
|
||||
if(previous_state != state_) {
|
||||
machine_.template set_paging<PagingType::LanguageCard>();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_state() const {
|
||||
bool operator != (const State &rhs) const {
|
||||
return
|
||||
(state_.read ? 0x00 : 0x08) |
|
||||
(state_.bank2 ? 0x04 : 0x00);
|
||||
bank2 != rhs.bank2 ||
|
||||
read != rhs.read ||
|
||||
write != rhs.write;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Machine &machine_;
|
||||
State state_;
|
||||
LanguageCardSwitches(Machine &machine) : machine_(machine) {}
|
||||
|
||||
// This is an additional flip flop contained on the language card, but
|
||||
// it is one step removed from current banking state, so I've excluded it
|
||||
// from the State struct.
|
||||
bool pre_write_ = false;
|
||||
/// Used by an owner to forward any access to $c08x.
|
||||
void access(uint16_t address, bool is_read) {
|
||||
const auto previous_state = state_;
|
||||
|
||||
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
|
||||
|
||||
// "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1.
|
||||
state_.bank2 = !(address & 8);
|
||||
|
||||
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
|
||||
// (other accesses reset it)
|
||||
state_.read = !(((address&2) >> 1) ^ (address&1));
|
||||
|
||||
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
|
||||
if(pre_write_ && is_read && (address&1)) state_.write = false;
|
||||
|
||||
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
|
||||
if(!(address&1)) state_.write = true;
|
||||
|
||||
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
|
||||
|
||||
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
|
||||
pre_write_ = is_read ? (address&1) : false;
|
||||
|
||||
// Apply whatever the net effect of all that is to the memory map.
|
||||
if(previous_state != state_) {
|
||||
machine_.template set_paging<PagingType::LanguageCard>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides read-only access to the current language card switch state.
|
||||
const State &state() const {
|
||||
return state_;
|
||||
}
|
||||
|
||||
/// Provides relevant parts of the IIgs interface.
|
||||
void set_state(uint8_t value) {
|
||||
const auto previous_state = state_;
|
||||
|
||||
// Bit 3: 1 => enable ROM, 0 => enable RAM.
|
||||
state_.read = !(value & 0x08);
|
||||
// Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference
|
||||
// correcting the original, which lists them the other way around]
|
||||
state_.bank2 = value & 0x04;
|
||||
|
||||
if(previous_state != state_) {
|
||||
machine_.template set_paging<PagingType::LanguageCard>();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_state() const {
|
||||
return
|
||||
(state_.read ? 0x00 : 0x08) |
|
||||
(state_.bank2 ? 0x04 : 0x00);
|
||||
}
|
||||
|
||||
private:
|
||||
Machine &machine_;
|
||||
State state_;
|
||||
|
||||
// This is an additional flip flop contained on the language card, but
|
||||
// it is one step removed from current banking state, so I've excluded it
|
||||
// from the State struct.
|
||||
bool pre_write_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,121 +15,121 @@
|
||||
namespace Apple::II {
|
||||
|
||||
class AYPair {
|
||||
public:
|
||||
AYPair(Concurrency::AsyncTaskQueue<false> &queue) :
|
||||
ays_{
|
||||
{GI::AY38910::Personality::AY38910, queue},
|
||||
{GI::AY38910::Personality::AY38910, queue},
|
||||
} {}
|
||||
public:
|
||||
AYPair(Concurrency::AsyncTaskQueue<false> &queue) :
|
||||
ays_{
|
||||
{GI::AY38910::Personality::AY38910, queue},
|
||||
{GI::AY38910::Personality::AY38910, queue},
|
||||
} {}
|
||||
|
||||
void advance() {
|
||||
ays_[0].advance();
|
||||
ays_[1].advance();
|
||||
}
|
||||
void advance() {
|
||||
ays_[0].advance();
|
||||
ays_[1].advance();
|
||||
}
|
||||
|
||||
void set_sample_volume_range(std::int16_t range) {
|
||||
ays_[0].set_sample_volume_range(range);
|
||||
ays_[1].set_sample_volume_range(range);
|
||||
}
|
||||
void set_sample_volume_range(std::int16_t range) {
|
||||
ays_[0].set_sample_volume_range(range);
|
||||
ays_[1].set_sample_volume_range(range);
|
||||
}
|
||||
|
||||
bool is_zero_level() const {
|
||||
return ays_[0].is_zero_level() && ays_[1].is_zero_level();
|
||||
}
|
||||
bool is_zero_level() const {
|
||||
return ays_[0].is_zero_level() && ays_[1].is_zero_level();
|
||||
}
|
||||
|
||||
Outputs::Speaker::StereoSample level() const {
|
||||
Outputs::Speaker::StereoSample level;
|
||||
level.left = ays_[0].level();
|
||||
level.right = ays_[1].level();
|
||||
return level;
|
||||
}
|
||||
Outputs::Speaker::StereoSample level() const {
|
||||
Outputs::Speaker::StereoSample level;
|
||||
level.left = ays_[0].level();
|
||||
level.right = ays_[1].level();
|
||||
return level;
|
||||
}
|
||||
|
||||
GI::AY38910::AY38910SampleSource<false> &get(int index) {
|
||||
return ays_[index];
|
||||
}
|
||||
GI::AY38910::AY38910SampleSource<false> &get(int index) {
|
||||
return ays_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
GI::AY38910::AY38910SampleSource<false> ays_[2];
|
||||
private:
|
||||
GI::AY38910::AY38910SampleSource<false> ays_[2];
|
||||
};
|
||||
|
||||
class Mockingboard: public Card {
|
||||
public:
|
||||
Mockingboard(AYPair &ays) :
|
||||
vias_{ {handlers_[0]}, {handlers_[1]} },
|
||||
handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} {
|
||||
set_select_constraints(0);
|
||||
public:
|
||||
Mockingboard(AYPair &ays) :
|
||||
vias_{ {handlers_[0]}, {handlers_[1]} },
|
||||
handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} {
|
||||
set_select_constraints(0);
|
||||
}
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final {
|
||||
if(!(select & IO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final {
|
||||
if(!(select & IO)) {
|
||||
return;
|
||||
}
|
||||
int index = (address >> 7) & 1;
|
||||
if(is_read) {
|
||||
*value = vias_[index].read(address);
|
||||
} else {
|
||||
vias_[index].write(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
int index = (address >> 7) & 1;
|
||||
if(is_read) {
|
||||
*value = vias_[index].read(address);
|
||||
void run_for(Cycles cycles, int) final {
|
||||
vias_[0].run_for(cycles);
|
||||
vias_[1].run_for(cycles);
|
||||
}
|
||||
|
||||
bool nmi() final {
|
||||
return handlers_[1].interrupt;
|
||||
}
|
||||
|
||||
bool irq() final {
|
||||
return handlers_[0].interrupt;
|
||||
}
|
||||
|
||||
void did_change_interrupt_flags() {
|
||||
delegate_->card_did_change_interrupt_flags(this);
|
||||
}
|
||||
|
||||
private:
|
||||
struct AYVIA: public MOS::MOS6522::PortHandler {
|
||||
AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource<false> &ay) :
|
||||
card(card), ay(ay) {}
|
||||
|
||||
void set_interrupt_status(bool status) {
|
||||
interrupt = status;
|
||||
card.did_change_interrupt_flags();
|
||||
}
|
||||
|
||||
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
|
||||
if(port) {
|
||||
using ControlLines = GI::AY38910::ControlLines;
|
||||
ay.set_control_lines(
|
||||
ControlLines(
|
||||
((value & 1) ? ControlLines::BC1 : 0) |
|
||||
((value & 2) ? ControlLines::BDIR : 0) |
|
||||
ControlLines::BC2
|
||||
)
|
||||
);
|
||||
|
||||
ay.set_reset(!(value & 4));
|
||||
} else {
|
||||
vias_[index].write(address, *value);
|
||||
ay.set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(Cycles cycles, int) final {
|
||||
vias_[0].run_for(cycles);
|
||||
vias_[1].run_for(cycles);
|
||||
}
|
||||
|
||||
bool nmi() final {
|
||||
return handlers_[1].interrupt;
|
||||
}
|
||||
|
||||
bool irq() final {
|
||||
return handlers_[0].interrupt;
|
||||
}
|
||||
|
||||
void did_change_interrupt_flags() {
|
||||
delegate_->card_did_change_interrupt_flags(this);
|
||||
}
|
||||
|
||||
private:
|
||||
struct AYVIA: public MOS::MOS6522::PortHandler {
|
||||
AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource<false> &ay) :
|
||||
card(card), ay(ay) {}
|
||||
|
||||
void set_interrupt_status(bool status) {
|
||||
interrupt = status;
|
||||
card.did_change_interrupt_flags();
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
if(!port) {
|
||||
return ay.get_data_output();
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
|
||||
if(port) {
|
||||
using ControlLines = GI::AY38910::ControlLines;
|
||||
ay.set_control_lines(
|
||||
ControlLines(
|
||||
((value & 1) ? ControlLines::BC1 : 0) |
|
||||
((value & 2) ? ControlLines::BDIR : 0) |
|
||||
ControlLines::BC2
|
||||
)
|
||||
);
|
||||
bool interrupt;
|
||||
Mockingboard &card;
|
||||
GI::AY38910::AY38910SampleSource<false> &ay;
|
||||
};
|
||||
|
||||
ay.set_reset(!(value & 4));
|
||||
} else {
|
||||
ay.set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
if(!port) {
|
||||
return ay.get_data_output();
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
bool interrupt;
|
||||
Mockingboard &card;
|
||||
GI::AY38910::AY38910SampleSource<false> &ay;
|
||||
};
|
||||
|
||||
MOS::MOS6522::MOS6522<AYVIA> vias_[2];
|
||||
AYVIA handlers_[2];
|
||||
MOS::MOS6522::MOS6522<AYVIA> vias_[2];
|
||||
AYVIA handlers_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,31 +25,31 @@
|
||||
namespace Apple::II {
|
||||
|
||||
class SCSICard: public Card {
|
||||
public:
|
||||
static ROM::Request rom_request();
|
||||
SCSICard(ROM::Map &, int clock_rate);
|
||||
public:
|
||||
static ROM::Request rom_request();
|
||||
SCSICard(ROM::Map &, int clock_rate);
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
|
||||
void set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
|
||||
void set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
|
||||
|
||||
void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final {
|
||||
scsi_bus_.run_for(cycles);
|
||||
}
|
||||
void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final {
|
||||
scsi_bus_.run_for(cycles);
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) final;
|
||||
void set_activity_observer(Activity::Observer *observer) final;
|
||||
|
||||
private:
|
||||
uint8_t *ram_pointer_ = nullptr;
|
||||
uint8_t *rom_pointer_ = nullptr;
|
||||
private:
|
||||
uint8_t *ram_pointer_ = nullptr;
|
||||
uint8_t *rom_pointer_ = nullptr;
|
||||
|
||||
std::array<uint8_t, 8*1024> ram_;
|
||||
std::array<uint8_t, 16*1024> rom_;
|
||||
std::array<uint8_t, 8*1024> ram_;
|
||||
std::array<uint8_t, 16*1024> rom_;
|
||||
|
||||
SCSI::Bus scsi_bus_;
|
||||
NCR::NCR5380::NCR5380 ncr5380_;
|
||||
SCSI::Target::Target<SCSI::DirectAccessDevice> storage_;
|
||||
Log::Logger<Log::Source::AppleIISCSICard> logger_;
|
||||
SCSI::Bus scsi_bus_;
|
||||
NCR::NCR5380::NCR5380 ncr5380_;
|
||||
SCSI::Target::Target<SCSI::DirectAccessDevice> storage_;
|
||||
Log::Logger<Log::Source::AppleIISCSICard> logger_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -116,398 +116,398 @@ class VideoBase: public VideoSwitches<Cycles> {
|
||||
};
|
||||
|
||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
|
||||
bus_handler_(bus_handler) {}
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
|
||||
bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
Obtains the last value the video read prior to time now+offset, according to the *current*
|
||||
video mode, i.e. not allowing for any changes still enqueued.
|
||||
*/
|
||||
uint8_t get_last_read_value(Cycles offset) {
|
||||
// Rules of generation:
|
||||
/*!
|
||||
Obtains the last value the video read prior to time now+offset, according to the *current*
|
||||
video mode, i.e. not allowing for any changes still enqueued.
|
||||
*/
|
||||
uint8_t get_last_read_value(Cycles offset) {
|
||||
// Rules of generation:
|
||||
|
||||
// FOR ALL MODELS IN ALL MODES:
|
||||
//
|
||||
// - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the
|
||||
// SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*)
|
||||
//
|
||||
// - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte
|
||||
// wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*)
|
||||
//
|
||||
// - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the
|
||||
// displayed area, using 128-byte wraparound addressing." (5-13*)
|
||||
//
|
||||
// - "The first address of HBL is always addressed twice consecutively" (5-11*)
|
||||
//
|
||||
// - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255,
|
||||
// so those six 64-byte sections are scanned twice" (5-13*)
|
||||
// FOR ALL MODELS IN ALL MODES:
|
||||
//
|
||||
// - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the
|
||||
// SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*)
|
||||
//
|
||||
// - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte
|
||||
// wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*)
|
||||
//
|
||||
// - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the
|
||||
// displayed area, using 128-byte wraparound addressing." (5-13*)
|
||||
//
|
||||
// - "The first address of HBL is always addressed twice consecutively" (5-11*)
|
||||
//
|
||||
// - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255,
|
||||
// so those six 64-byte sections are scanned twice" (5-13*)
|
||||
|
||||
// FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES):
|
||||
//
|
||||
// - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*)
|
||||
//
|
||||
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
|
||||
// before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is
|
||||
// $147F." (5-11*)
|
||||
//
|
||||
// - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*)
|
||||
//
|
||||
// - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC
|
||||
// variables" (5-37*)
|
||||
//
|
||||
// - Figure 5.17 Screen Memory Scanning (5-37*)
|
||||
// FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES):
|
||||
//
|
||||
// - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*)
|
||||
//
|
||||
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
|
||||
// before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is
|
||||
// $147F." (5-11*)
|
||||
//
|
||||
// - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*)
|
||||
//
|
||||
// - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC
|
||||
// variables" (5-37*)
|
||||
//
|
||||
// - Figure 5.17 Screen Memory Scanning (5-37*)
|
||||
|
||||
// FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE:
|
||||
//
|
||||
// - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**)
|
||||
//
|
||||
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
|
||||
// before address $400 is $47F." (5-11**)
|
||||
//
|
||||
// - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*)
|
||||
//
|
||||
// - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of
|
||||
// differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other
|
||||
// display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned
|
||||
// memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**)
|
||||
//
|
||||
// - Figure 5.17 Display Memory Scanning (5-41**)
|
||||
// FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE:
|
||||
//
|
||||
// - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**)
|
||||
//
|
||||
// - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned
|
||||
// before address $400 is $47F." (5-11**)
|
||||
//
|
||||
// - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*)
|
||||
//
|
||||
// - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of
|
||||
// differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other
|
||||
// display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned
|
||||
// memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**)
|
||||
//
|
||||
// - Figure 5.17 Display Memory Scanning (5-41**)
|
||||
|
||||
// Source: * Understanding the Apple II by Jim Sather
|
||||
// Source: ** Understanding the Apple IIe by Jim Sather
|
||||
// Source: * Understanding the Apple II by Jim Sather
|
||||
// Source: ** Understanding the Apple IIe by Jim Sather
|
||||
|
||||
// Determine column at offset.
|
||||
int mapped_column = column_ + int(offset.as_integral());
|
||||
// Determine column at offset.
|
||||
int mapped_column = column_ + int(offset.as_integral());
|
||||
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
|
||||
// Apply carry into the row counter.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
mapped_row %= 262;
|
||||
mapped_column %= 65;
|
||||
// Apply carry into the row counter.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
mapped_row %= 262;
|
||||
mapped_column %= 65;
|
||||
|
||||
// Remember if we're in a horizontal blanking interval.
|
||||
int hbl = mapped_column < 25;
|
||||
// Remember if we're in a horizontal blanking interval.
|
||||
int hbl = mapped_column < 25;
|
||||
|
||||
// The first column is read twice.
|
||||
if(mapped_column == 0) {
|
||||
mapped_column = 1;
|
||||
}
|
||||
|
||||
// Vertical blanking rows read eight bytes earlier.
|
||||
if(mapped_row >= 192) {
|
||||
mapped_column -= 8;
|
||||
}
|
||||
|
||||
// Apple out-of-bounds row logic.
|
||||
if(mapped_row >= 256) {
|
||||
mapped_row = 0x3a + (mapped_row&255);
|
||||
} else {
|
||||
mapped_row %= 192;
|
||||
}
|
||||
|
||||
// Calculate the address.
|
||||
uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25);
|
||||
|
||||
// Wraparound addressing within 128 byte sections.
|
||||
if(mapped_row < 64 && mapped_column < 25) {
|
||||
read_address += 128;
|
||||
}
|
||||
|
||||
if(hbl && !is_iie_) {
|
||||
// On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal
|
||||
// blanking bytes read from $1000 higher.
|
||||
const GraphicsMode pixel_mode = graphics_mode(mapped_row);
|
||||
if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) {
|
||||
read_address += 0x1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the address and return the value.
|
||||
uint8_t value, aux_value;
|
||||
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
|
||||
return value;
|
||||
// The first column is read twice.
|
||||
if(mapped_column == 0) {
|
||||
mapped_column = 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
|
||||
*/
|
||||
bool get_is_vertical_blank(Cycles offset) {
|
||||
// Determine column at offset.
|
||||
int mapped_column = column_ + int(offset.as_integral());
|
||||
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
|
||||
// Apply carry into the row counter.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
mapped_row %= 262;
|
||||
|
||||
// Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html
|
||||
// "on the IIe, the screen is blanked when the bit is low".
|
||||
return mapped_row < 192;
|
||||
// Vertical blanking rows read eight bytes earlier.
|
||||
if(mapped_row >= 192) {
|
||||
mapped_column -= 8;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of
|
||||
line.
|
||||
// Apple out-of-bounds row logic.
|
||||
if(mapped_row >= 256) {
|
||||
mapped_row = 0x3a + (mapped_row&255);
|
||||
} else {
|
||||
mapped_row %= 192;
|
||||
}
|
||||
|
||||
// Calculate the address.
|
||||
uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25);
|
||||
|
||||
// Wraparound addressing within 128 byte sections.
|
||||
if(mapped_row < 64 && mapped_column < 25) {
|
||||
read_address += 128;
|
||||
}
|
||||
|
||||
if(hbl && !is_iie_) {
|
||||
// On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal
|
||||
// blanking bytes read from $1000 higher.
|
||||
const GraphicsMode pixel_mode = graphics_mode(mapped_row);
|
||||
if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) {
|
||||
read_address += 0x1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the address and return the value.
|
||||
uint8_t value, aux_value;
|
||||
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
|
||||
*/
|
||||
bool get_is_vertical_blank(Cycles offset) {
|
||||
// Determine column at offset.
|
||||
int mapped_column = column_ + int(offset.as_integral());
|
||||
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
|
||||
// Apply carry into the row counter.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
mapped_row %= 262;
|
||||
|
||||
// Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html
|
||||
// "on the IIe, the screen is blanked when the bit is low".
|
||||
return mapped_row < 192;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of
|
||||
line.
|
||||
*/
|
||||
void advance(Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
void advance(Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
constexpr int first_sync_line = 220; // A complete guess. Information needed.
|
||||
constexpr int first_sync_column = 49; // Also a guess.
|
||||
constexpr int sync_length = 4; // One of the two likely candidates.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
constexpr int first_sync_line = 220; // A complete guess. Information needed.
|
||||
constexpr int first_sync_column = 49; // Also a guess.
|
||||
constexpr int sync_length = 4; // One of the two likely candidates.
|
||||
int int_cycles = int(cycles.as_integral());
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
|
||||
|
||||
int int_cycles = int(cycles.as_integral());
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
|
||||
|
||||
if(is_vertical_sync_line) {
|
||||
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
|
||||
// pulses (and hence keep hsync approximately where it should be during vsync).
|
||||
const int blank_start = std::max(first_sync_column - sync_length, column_);
|
||||
const int blank_end = std::min(first_sync_column, ending_column);
|
||||
if(blank_end > blank_start) {
|
||||
if(blank_start > column_) {
|
||||
crt_.output_sync((blank_start - column_) * 14);
|
||||
}
|
||||
crt_.output_blank((blank_end - blank_start) * 14);
|
||||
if(blank_end < ending_column) {
|
||||
crt_.output_sync((ending_column - blank_end) * 14);
|
||||
}
|
||||
} else {
|
||||
crt_.output_sync(cycles_this_line * 14);
|
||||
if(is_vertical_sync_line) {
|
||||
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
|
||||
// pulses (and hence keep hsync approximately where it should be during vsync).
|
||||
const int blank_start = std::max(first_sync_column - sync_length, column_);
|
||||
const int blank_end = std::min(first_sync_column, ending_column);
|
||||
if(blank_end > blank_start) {
|
||||
if(blank_start > column_) {
|
||||
crt_.output_sync((blank_start - column_) * 14);
|
||||
}
|
||||
crt_.output_blank((blank_end - blank_start) * 14);
|
||||
if(blank_end < ending_column) {
|
||||
crt_.output_sync((ending_column - blank_end) * 14);
|
||||
}
|
||||
} else {
|
||||
const GraphicsMode line_mode = graphics_mode(row_);
|
||||
crt_.output_sync(cycles_this_line * 14);
|
||||
}
|
||||
} else {
|
||||
const GraphicsMode line_mode = graphics_mode(row_);
|
||||
|
||||
// Determine whether there's any fetching to do. Fetching occurs during the first
|
||||
// 40 columns of rows prior to 192.
|
||||
if(row_ < 192 && column_ < 40) {
|
||||
const int character_row = row_ >> 3;
|
||||
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
// Determine whether there's any fetching to do. Fetching occurs during the first
|
||||
// 40 columns of rows prior to 192.
|
||||
if(row_ < 192 && column_ < 40) {
|
||||
const int character_row = row_ >> 3;
|
||||
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
// Grab the memory contents that'll be needed momentarily.
|
||||
const int fetch_end = std::min(40, ending_column);
|
||||
uint16_t fetch_address;
|
||||
switch(line_mode) {
|
||||
default:
|
||||
case GraphicsMode::Text:
|
||||
case GraphicsMode::DoubleText:
|
||||
case GraphicsMode::LowRes:
|
||||
case GraphicsMode::FatLowRes:
|
||||
case GraphicsMode::DoubleLowRes: {
|
||||
const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address);
|
||||
fetch_address = uint16_t(text_address + column_);
|
||||
} break;
|
||||
// Grab the memory contents that'll be needed momentarily.
|
||||
const int fetch_end = std::min(40, ending_column);
|
||||
uint16_t fetch_address;
|
||||
switch(line_mode) {
|
||||
default:
|
||||
case GraphicsMode::Text:
|
||||
case GraphicsMode::DoubleText:
|
||||
case GraphicsMode::LowRes:
|
||||
case GraphicsMode::FatLowRes:
|
||||
case GraphicsMode::DoubleLowRes: {
|
||||
const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address);
|
||||
fetch_address = uint16_t(text_address + column_);
|
||||
} break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
|
||||
break;
|
||||
}
|
||||
|
||||
bus_handler_.perform_read(
|
||||
fetch_address,
|
||||
size_t(fetch_end - column_),
|
||||
&base_stream_[size_t(column_)],
|
||||
&auxiliary_stream_[size_t(column_)]);
|
||||
case GraphicsMode::HighRes:
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
|
||||
break;
|
||||
}
|
||||
|
||||
if(row_ < 192) {
|
||||
// The pixel area is the first 40.5 columns; base contents
|
||||
// remain where they would naturally be but auxiliary
|
||||
// graphics appear to the left of that.
|
||||
if(!column_) {
|
||||
pixel_pointer_ = crt_.begin_data(568);
|
||||
graphics_carry_ = 0;
|
||||
was_double_ = true;
|
||||
bus_handler_.perform_read(
|
||||
fetch_address,
|
||||
size_t(fetch_end - column_),
|
||||
&base_stream_[size_t(column_)],
|
||||
&auxiliary_stream_[size_t(column_)]);
|
||||
}
|
||||
|
||||
if(row_ < 192) {
|
||||
// The pixel area is the first 40.5 columns; base contents
|
||||
// remain where they would naturally be but auxiliary
|
||||
// graphics appear to the left of that.
|
||||
if(!column_) {
|
||||
pixel_pointer_ = crt_.begin_data(568);
|
||||
graphics_carry_ = 0;
|
||||
was_double_ = true;
|
||||
}
|
||||
|
||||
if(column_ < 40) {
|
||||
const int pixel_start = std::max(0, column_);
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int pixel_row = row_ & 7;
|
||||
|
||||
const bool is_double = is_double_mode(line_mode);
|
||||
if(!is_double && was_double_ && pixel_pointer_) {
|
||||
pixel_pointer_[pixel_start*14 + 0] =
|
||||
pixel_pointer_[pixel_start*14 + 1] =
|
||||
pixel_pointer_[pixel_start*14 + 2] =
|
||||
pixel_pointer_[pixel_start*14 + 3] =
|
||||
pixel_pointer_[pixel_start*14 + 4] =
|
||||
pixel_pointer_[pixel_start*14 + 5] =
|
||||
pixel_pointer_[pixel_start*14 + 6] = 0;
|
||||
}
|
||||
was_double_ = is_double;
|
||||
|
||||
if(pixel_pointer_) {
|
||||
switch(line_mode) {
|
||||
case GraphicsMode::Text:
|
||||
output_text(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
size_t(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleText:
|
||||
output_double_text(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
size_t(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
output_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::FatLowRes:
|
||||
output_fat_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleLowRes:
|
||||
output_double_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
output_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
output_double_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(column_ < 40) {
|
||||
const int pixel_start = std::max(0, column_);
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int pixel_row = row_ & 7;
|
||||
|
||||
const bool is_double = is_double_mode(line_mode);
|
||||
if(!is_double && was_double_ && pixel_pointer_) {
|
||||
pixel_pointer_[pixel_start*14 + 0] =
|
||||
pixel_pointer_[pixel_start*14 + 1] =
|
||||
pixel_pointer_[pixel_start*14 + 2] =
|
||||
pixel_pointer_[pixel_start*14 + 3] =
|
||||
pixel_pointer_[pixel_start*14 + 4] =
|
||||
pixel_pointer_[pixel_start*14 + 5] =
|
||||
pixel_pointer_[pixel_start*14 + 6] = 0;
|
||||
}
|
||||
was_double_ = is_double;
|
||||
|
||||
if(pixel_end == 40) {
|
||||
if(pixel_pointer_) {
|
||||
switch(line_mode) {
|
||||
case GraphicsMode::Text:
|
||||
output_text(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
size_t(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleText:
|
||||
output_double_text(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
size_t(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
output_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::FatLowRes:
|
||||
output_fat_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleLowRes:
|
||||
output_double_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
output_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
output_double_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[size_t(pixel_start)],
|
||||
&auxiliary_stream_[size_t(pixel_start)],
|
||||
size_t(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
if(was_double_) {
|
||||
pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
|
||||
pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
|
||||
} else {
|
||||
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
|
||||
pixel_pointer_[567] = graphics_carry_;
|
||||
else
|
||||
pixel_pointer_[567] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(pixel_end == 40) {
|
||||
if(pixel_pointer_) {
|
||||
if(was_double_) {
|
||||
pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
|
||||
pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
|
||||
} else {
|
||||
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
|
||||
pixel_pointer_[567] = graphics_carry_;
|
||||
else
|
||||
pixel_pointer_[567] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
crt_.output_data(568, 568);
|
||||
pixel_pointer_ = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(column_ < 40 && ending_column >= 40) {
|
||||
crt_.output_blank(568);
|
||||
crt_.output_data(568, 568);
|
||||
pixel_pointer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
if(column_ < first_sync_column && ending_column >= first_sync_column) {
|
||||
crt_.output_blank(first_sync_column*14 - 568);
|
||||
}
|
||||
|
||||
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
|
||||
crt_.output_sync(sync_length*14);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
// Colour burst is present on all lines of the display if graphics mode is enabled on the top
|
||||
// portion; therefore use the graphics mode on line 0 rather than the current line, to avoid
|
||||
// disabling it in mixed modes.
|
||||
if(!is_text_mode(graphics_mode(0))) {
|
||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
// UGLY HACK AHOY!
|
||||
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
|
||||
// Supply the real phase value if this is an Apple build.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
constexpr uint8_t phase = 224;
|
||||
#else
|
||||
constexpr uint8_t phase = 192;
|
||||
#endif
|
||||
|
||||
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + sync_length, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_.output_blank((ending_column - second_blank_start) * 14);
|
||||
} else {
|
||||
if(column_ < 40 && ending_column >= 40) {
|
||||
crt_.output_blank(568);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
did_end_line();
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised. If this is a vertical sync line, output sync
|
||||
// instead of blank, taking that to be the default level.
|
||||
if(is_vertical_sync_line) {
|
||||
crt_.output_sync(2);
|
||||
} else {
|
||||
crt_.output_blank(2);
|
||||
if(column_ < first_sync_column && ending_column >= first_sync_column) {
|
||||
crt_.output_blank(first_sync_column*14 - 568);
|
||||
}
|
||||
|
||||
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
|
||||
crt_.output_sync(sync_length*14);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
// Colour burst is present on all lines of the display if graphics mode is enabled on the top
|
||||
// portion; therefore use the graphics mode on line 0 rather than the current line, to avoid
|
||||
// disabling it in mixed modes.
|
||||
if(!is_text_mode(graphics_mode(0))) {
|
||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
// UGLY HACK AHOY!
|
||||
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
|
||||
// Supply the real phase value if this is an Apple build.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
constexpr uint8_t phase = 224;
|
||||
#else
|
||||
constexpr uint8_t phase = 192;
|
||||
#endif
|
||||
|
||||
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + sync_length, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_.output_blank((ending_column - second_blank_start) * 14);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
did_end_line();
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised. If this is a vertical sync line, output sync
|
||||
// instead of blank, taking that to be the default level.
|
||||
if(is_vertical_sync_line) {
|
||||
crt_.output_sync(2);
|
||||
} else {
|
||||
crt_.output_blank(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusHandler &bus_handler_;
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -29,327 +29,327 @@ constexpr bool is_text_mode(const GraphicsMode m) { return m <= GraphicsMode::Do
|
||||
constexpr bool is_double_mode(const GraphicsMode m) { return int(m) & 1; }
|
||||
|
||||
template <typename TimeUnit> class VideoSwitches {
|
||||
public:
|
||||
/*!
|
||||
Constructs a new instance of VideoSwitches in which changes to relevant switches
|
||||
affect the video mode only after @c delay cycles.
|
||||
public:
|
||||
/*!
|
||||
Constructs a new instance of VideoSwitches in which changes to relevant switches
|
||||
affect the video mode only after @c delay cycles.
|
||||
|
||||
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
|
||||
set of potential flashing characters and alternate video modes.
|
||||
*/
|
||||
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
|
||||
character_zones_[0].xor_mask = 0xff;
|
||||
character_zones_[0].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = 0;
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
character_zones_[2].xor_mask = 0;
|
||||
character_zones_[2].address_mask = 0x3f;
|
||||
character_zones_[3].xor_mask = 0;
|
||||
character_zones_[3].address_mask = 0x3f;
|
||||
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
|
||||
set of potential flashing characters and alternate video modes.
|
||||
*/
|
||||
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
|
||||
character_zones_[0].xor_mask = 0xff;
|
||||
character_zones_[0].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = 0;
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
character_zones_[2].xor_mask = 0;
|
||||
character_zones_[2].address_mask = 0x3f;
|
||||
character_zones_[3].xor_mask = 0;
|
||||
character_zones_[3].address_mask = 0x3f;
|
||||
|
||||
if(is_iie) {
|
||||
character_zones_[1].xor_mask =
|
||||
character_zones_[2].xor_mask =
|
||||
character_zones_[3].xor_mask = 0xff;
|
||||
character_zones_[2].address_mask =
|
||||
character_zones_[3].address_mask = 0xff;
|
||||
}
|
||||
if(is_iie) {
|
||||
character_zones_[1].xor_mask =
|
||||
character_zones_[2].xor_mask =
|
||||
character_zones_[3].xor_mask = 0xff;
|
||||
character_zones_[2].address_mask =
|
||||
character_zones_[3].address_mask = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances @c cycles.
|
||||
*/
|
||||
void run_for(TimeUnit cycles) {
|
||||
deferrer_.run_for(cycles);
|
||||
}
|
||||
/*!
|
||||
Advances @c cycles.
|
||||