mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-21 02:37:44 +00:00
Take another big swing at indentation, some const
s.
This commit is contained in:
parent
31c878b654
commit
d3ed485e7a
@ -27,18 +27,18 @@ private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
|
||||
|
@ -285,14 +285,14 @@ private:
|
||||
automatically to gain run_for(HalfCycles).
|
||||
*/
|
||||
template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
}
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
}
|
||||
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
};
|
||||
|
@ -98,28 +98,28 @@ private:
|
||||
This list is efficient only for short queues.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
|
||||
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
|
||||
target_(time_to_next);
|
||||
length -= time_to_next;
|
||||
DeferredQueue<TimeUnit>::advance(time_to_next);
|
||||
}
|
||||
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
|
||||
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
|
||||
target_(time_to_next);
|
||||
length -= time_to_next;
|
||||
DeferredQueue<TimeUnit>::advance(time_to_next);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
};
|
||||
|
@ -104,40 +104,40 @@ public:
|
||||
|
||||
private:
|
||||
class VarianceCollector {
|
||||
public:
|
||||
VarianceCollector(Time::Nanos default_value) {
|
||||
sum_ = default_value * 128;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
history_[c] = default_value;
|
||||
}
|
||||
public:
|
||||
VarianceCollector(Time::Nanos default_value) {
|
||||
sum_ = default_value * 128;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
history_[c] = default_value;
|
||||
}
|
||||
}
|
||||
|
||||
void post(Time::Nanos value) {
|
||||
sum_ -= history_[write_pointer_];
|
||||
sum_ += value;
|
||||
history_[write_pointer_] = value;
|
||||
write_pointer_ = (write_pointer_ + 1) & 127;
|
||||
void post(Time::Nanos value) {
|
||||
sum_ -= history_[write_pointer_];
|
||||
sum_ += value;
|
||||
history_[write_pointer_] = value;
|
||||
write_pointer_ = (write_pointer_ + 1) & 127;
|
||||
}
|
||||
|
||||
Time::Nanos mean() {
|
||||
return sum_ / 128;
|
||||
}
|
||||
|
||||
Time::Nanos variance() {
|
||||
// I haven't yet come up with a better solution that calculating this
|
||||
// in whole every time, given the way that the mean mutates.
|
||||
Time::Nanos variance = 0;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const auto difference = ((history_[c] * 128) - sum_) / 128;
|
||||
variance += (difference * difference);
|
||||
}
|
||||
return variance / 128;
|
||||
}
|
||||
|
||||
Time::Nanos mean() {
|
||||
return sum_ / 128;
|
||||
}
|
||||
|
||||
Time::Nanos variance() {
|
||||
// I haven't yet come up with a better solution that calculating this
|
||||
// in whole every time, given the way that the mean mutates.
|
||||
Time::Nanos variance = 0;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const auto difference = ((history_[c] * 128) - sum_) / 128;
|
||||
variance += (difference * difference);
|
||||
}
|
||||
return variance / 128;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Nanos sum_;
|
||||
Time::Nanos history_[128];
|
||||
size_t write_pointer_ = 0;
|
||||
private:
|
||||
Time::Nanos sum_;
|
||||
Time::Nanos history_[128];
|
||||
size_t write_pointer_ = 0;
|
||||
};
|
||||
|
||||
Nanos redraw_begin_time_ = 0;
|
||||
|
@ -64,36 +64,36 @@ public:
|
||||
|
||||
private:
|
||||
class Channel {
|
||||
public:
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line() const;
|
||||
public:
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line() const;
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
|
||||
enum class Parity {
|
||||
Even, Odd, Off
|
||||
} parity_ = Parity::Off;
|
||||
enum class Parity {
|
||||
Even, Odd, Off
|
||||
} parity_ = Parity::Off;
|
||||
|
||||
enum class StopBits {
|
||||
Synchronous, OneBit, OneAndAHalfBits, TwoBits
|
||||
} stop_bits_ = StopBits::Synchronous;
|
||||
enum class StopBits {
|
||||
Synchronous, OneBit, OneAndAHalfBits, TwoBits
|
||||
} stop_bits_ = StopBits::Synchronous;
|
||||
|
||||
enum class Sync {
|
||||
Monosync, Bisync, SDLC, External
|
||||
} sync_mode_ = Sync::Monosync;
|
||||
enum class Sync {
|
||||
Monosync, Bisync, SDLC, External
|
||||
} sync_mode_ = Sync::Monosync;
|
||||
|
||||
int clock_rate_multiplier_ = 1;
|
||||
int clock_rate_multiplier_ = 1;
|
||||
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
|
||||
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
bool external_status_interrupt_ = false;
|
||||
uint8_t external_interrupt_status_ = 0;
|
||||
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
bool external_status_interrupt_ = false;
|
||||
uint8_t external_interrupt_status_ = 0;
|
||||
|
||||
bool dcd_ = false;
|
||||
bool dcd_ = false;
|
||||
} channels_[2];
|
||||
|
||||
uint8_t pointer_ = 0;
|
||||
|
@ -192,111 +192,111 @@ constexpr SpriteMode sprite_mode(ScreenMode screen_mode) {
|
||||
// TODO: should this be extended to include Master System sprites?
|
||||
template <Personality personality, SpriteMode mode>
|
||||
class SpriteFetcher {
|
||||
public:
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
public:
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
|
||||
// table, which is intended to fill the 512 bytes before the programmer-located sprite
|
||||
// attribute table.
|
||||
//
|
||||
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
|
||||
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
|
||||
//
|
||||
// AttributeAddressMask is used to enable or disable that behaviour.
|
||||
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
|
||||
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
|
||||
// table, which is intended to fill the 512 bytes before the programmer-located sprite
|
||||
// attribute table.
|
||||
//
|
||||
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
|
||||
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
|
||||
//
|
||||
// AttributeAddressMask is used to enable or disable that behaviour.
|
||||
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
|
||||
|
||||
SpriteFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y) {}
|
||||
SpriteFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y) {}
|
||||
|
||||
void fetch_location(int slot) {
|
||||
fetch_xy(slot);
|
||||
void fetch_location(int slot) {
|
||||
fetch_xy(slot);
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
fetch_xy(slot + 1);
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
fetch_xy(slot + 1);
|
||||
|
||||
base->name_[0] = name(slot);
|
||||
base->name_[1] = name(slot + 1);
|
||||
}
|
||||
base->name_[0] = name(slot);
|
||||
base->name_[1] = name(slot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_pattern(int slot) {
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
fetch_image(slot, name(slot));
|
||||
break;
|
||||
void fetch_pattern(int slot) {
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
fetch_image(slot, name(slot));
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2:
|
||||
fetch_image(slot, base->name_[0]);
|
||||
fetch_image(slot + 1, base->name_[1]);
|
||||
break;
|
||||
}
|
||||
case SpriteMode::Mode2:
|
||||
fetch_image(slot, base->name_[0]);
|
||||
fetch_image(slot + 1, base->name_[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_y(int sprite) {
|
||||
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
|
||||
const uint8_t sprite_y = base->ram_[address];
|
||||
base->posit_sprite(sprite, sprite_y, y);
|
||||
}
|
||||
void fetch_y(int sprite) {
|
||||
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
|
||||
const uint8_t sprite_y = base->ram_[address];
|
||||
base->posit_sprite(sprite, sprite_y, y);
|
||||
}
|
||||
|
||||
private:
|
||||
void fetch_xy(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
buffer.active_sprites[slot].x =
|
||||
base->ram_[
|
||||
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
|
||||
private:
|
||||
void fetch_xy(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
buffer.active_sprites[slot].x =
|
||||
base->ram_[
|
||||
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
|
||||
];
|
||||
}
|
||||
|
||||
uint8_t name(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
const AddressT address =
|
||||
base->sprite_attribute_table_address_ &
|
||||
AttributeAddressMask &
|
||||
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
|
||||
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
|
||||
return name;
|
||||
}
|
||||
|
||||
void fetch_image(int slot, uint8_t name) {
|
||||
uint8_t colour = 0;
|
||||
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
// Fetch colour from the attribute table, per this sprite's slot.
|
||||
colour = base->ram_[
|
||||
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
|
||||
];
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2: {
|
||||
// Fetch colour from the colour table, per this sprite's slot and row.
|
||||
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
|
||||
colour = base->ram_[
|
||||
colour_table_address &
|
||||
bits<9>(
|
||||
AddressT(sprite.index << 4) |
|
||||
AddressT(sprite.row)
|
||||
)
|
||||
];
|
||||
} break;
|
||||
}
|
||||
sprite.image[2] = colour;
|
||||
sprite.x -= sprite.early_clock();
|
||||
|
||||
uint8_t name(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
const AddressT address =
|
||||
base->sprite_attribute_table_address_ &
|
||||
AttributeAddressMask &
|
||||
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
|
||||
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
|
||||
return name;
|
||||
}
|
||||
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
|
||||
sprite.image[0] = base->ram_[graphic_location];
|
||||
sprite.image[1] = base->ram_[graphic_location+16];
|
||||
|
||||
void fetch_image(int slot, uint8_t name) {
|
||||
uint8_t colour = 0;
|
||||
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
// Fetch colour from the attribute table, per this sprite's slot.
|
||||
colour = base->ram_[
|
||||
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
|
||||
];
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2: {
|
||||
// Fetch colour from the colour table, per this sprite's slot and row.
|
||||
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
|
||||
colour = base->ram_[
|
||||
colour_table_address &
|
||||
bits<9>(
|
||||
AddressT(sprite.index << 4) |
|
||||
AddressT(sprite.row)
|
||||
)
|
||||
];
|
||||
} break;
|
||||
}
|
||||
sprite.image[2] = colour;
|
||||
sprite.x -= sprite.early_clock();
|
||||
|
||||
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
|
||||
sprite.image[0] = base->ram_[graphic_location];
|
||||
sprite.image[1] = base->ram_[graphic_location+16];
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
|
||||
base->fetch_sprite_buffer_->is_filling = false;
|
||||
}
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
|
||||
base->fetch_sprite_buffer_->is_filling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
|
@ -439,12 +439,12 @@ struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
private:
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
};
|
||||
|
||||
// Master System-specific storage.
|
||||
|
@ -20,109 +20,109 @@
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
class OPLL: public OPLBase<OPLL, false> {
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
bool is_zero_level() const { return false; } // TODO.
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
bool is_zero_level() const { return false; } // TODO.
|
||||
|
||||
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
|
||||
// rhythm mode, but it's correct for melodic output.
|
||||
double average_output_peak() const { return 0.5; }
|
||||
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
|
||||
// rhythm mode, but it's correct for melodic output.
|
||||
double average_output_peak() const { return 0.5; }
|
||||
|
||||
/// Reads from the OPL.
|
||||
uint8_t read(uint16_t address);
|
||||
/// Reads from the OPL.
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
private:
|
||||
friend OPLBase<OPLL, false>;
|
||||
void write_register(uint8_t address, uint8_t value);
|
||||
private:
|
||||
friend OPLBase<OPLL, false>;
|
||||
void write_register(uint8_t address, uint8_t value);
|
||||
|
||||
int audio_divider_ = 0;
|
||||
int audio_offset_ = 0;
|
||||
std::atomic<int> total_volume_;
|
||||
int audio_divider_ = 0;
|
||||
int audio_offset_ = 0;
|
||||
std::atomic<int> total_volume_;
|
||||
|
||||
int16_t output_levels_[18];
|
||||
void update_all_channels();
|
||||
int16_t output_levels_[18];
|
||||
void update_all_channels();
|
||||
|
||||
int melodic_output(int channel);
|
||||
int bass_drum() const;
|
||||
int tom_tom() const;
|
||||
int snare_drum() const;
|
||||
int cymbal() const;
|
||||
int high_hat() const;
|
||||
int melodic_output(int channel);
|
||||
int bass_drum() const;
|
||||
int tom_tom() const;
|
||||
int snare_drum() const;
|
||||
int cymbal() const;
|
||||
int high_hat() const;
|
||||
|
||||
static constexpr int period_precision = 9;
|
||||
static constexpr int envelope_precision = 7;
|
||||
static constexpr int period_precision = 9;
|
||||
static constexpr int envelope_precision = 7;
|
||||
|
||||
// Standard melodic phase and envelope generators;
|
||||
//
|
||||
// These are assigned as:
|
||||
//
|
||||
// [x], 0 <= x < 9 = carrier for channel x;
|
||||
// [x+9] = modulator for channel x.
|
||||
//
|
||||
PhaseGenerator<period_precision> phase_generators_[18];
|
||||
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
|
||||
KeyLevelScaler<period_precision> key_level_scalers_[18];
|
||||
// Standard melodic phase and envelope generators;
|
||||
//
|
||||
// These are assigned as:
|
||||
//
|
||||
// [x], 0 <= x < 9 = carrier for channel x;
|
||||
// [x+9] = modulator for channel x.
|
||||
//
|
||||
PhaseGenerator<period_precision> phase_generators_[18];
|
||||
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
|
||||
KeyLevelScaler<period_precision> key_level_scalers_[18];
|
||||
|
||||
// Dedicated rhythm envelope generators and attenuations.
|
||||
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
|
||||
enum RhythmIndices {
|
||||
HighHat = 0,
|
||||
Cymbal = 1,
|
||||
TomTom = 2,
|
||||
Snare = 3,
|
||||
BassCarrier = 4,
|
||||
BassModulator = 5
|
||||
};
|
||||
// Dedicated rhythm envelope generators and attenuations.
|
||||
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
|
||||
enum RhythmIndices {
|
||||
HighHat = 0,
|
||||
Cymbal = 1,
|
||||
TomTom = 2,
|
||||
Snare = 3,
|
||||
BassCarrier = 4,
|
||||
BassModulator = 5
|
||||
};
|
||||
|
||||
// Channel specifications.
|
||||
struct Channel {
|
||||
int octave = 0;
|
||||
int period = 0;
|
||||
int instrument = 0;
|
||||
// Channel specifications.
|
||||
struct Channel {
|
||||
int octave = 0;
|
||||
int period = 0;
|
||||
int instrument = 0;
|
||||
|
||||
int attenuation = 0;
|
||||
int modulator_attenuation = 0;
|
||||
int attenuation = 0;
|
||||
int modulator_attenuation = 0;
|
||||
|
||||
Waveform carrier_waveform = Waveform::Sine;
|
||||
Waveform modulator_waveform = Waveform::Sine;
|
||||
Waveform carrier_waveform = Waveform::Sine;
|
||||
Waveform modulator_waveform = Waveform::Sine;
|
||||
|
||||
int carrier_key_rate_scale_multiplier = 0;
|
||||
int modulator_key_rate_scale_multiplier = 0;
|
||||
int carrier_key_rate_scale_multiplier = 0;
|
||||
int modulator_key_rate_scale_multiplier = 0;
|
||||
|
||||
LogSign modulator_output;
|
||||
int modulator_feedback = 0;
|
||||
LogSign modulator_output;
|
||||
int modulator_feedback = 0;
|
||||
|
||||
bool use_sustain = false;
|
||||
} channels_[9];
|
||||
bool use_sustain = false;
|
||||
} channels_[9];
|
||||
|
||||
// The low-frequency oscillator.
|
||||
LowFrequencyOscillator oscillator_;
|
||||
bool rhythm_mode_enabled_ = false;
|
||||
bool is_vrc7_ = false;
|
||||
// The low-frequency oscillator.
|
||||
LowFrequencyOscillator oscillator_;
|
||||
bool rhythm_mode_enabled_ = false;
|
||||
bool is_vrc7_ = false;
|
||||
|
||||
// Contains the current configuration of the custom instrument.
|
||||
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
// Contains the current configuration of the custom instrument.
|
||||
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Helpers to push per-channel information.
|
||||
// Helpers to push per-channel information.
|
||||
|
||||
/// Pushes the current octave and period to channel @c channel.
|
||||
void set_channel_period(int channel);
|
||||
/// Pushes the current octave and period to channel @c channel.
|
||||
void set_channel_period(int channel);
|
||||
|
||||
/// Installs the appropriate instrument on channel @c channel.
|
||||
void install_instrument(int channel);
|
||||
/// Installs the appropriate instrument on channel @c channel.
|
||||
void install_instrument(int channel);
|
||||
|
||||
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
|
||||
/// and the user's selection.
|
||||
void set_use_sustain(int channel);
|
||||
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
|
||||
/// and the user's selection.
|
||||
void set_use_sustain(int channel);
|
||||
|
||||
/// @returns The 8-byte definition of instrument @c instrument.
|
||||
const uint8_t *instrument_definition(int instrument, int channel) const;
|
||||
/// @returns The 8-byte definition of instrument @c instrument.
|
||||
const uint8_t *instrument_definition(int instrument, int channel) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -26,15 +26,15 @@ template <typename Performer> struct TaskQueueStorage {
|
||||
|
||||
Performer performer;
|
||||
|
||||
protected:
|
||||
void update() {
|
||||
auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired_);
|
||||
last_fired_ = time_now;
|
||||
}
|
||||
protected:
|
||||
void update() {
|
||||
auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired_);
|
||||
last_fired_ = time_now;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Nanos last_fired_;
|
||||
private:
|
||||
Time::Nanos last_fired_;
|
||||
};
|
||||
|
||||
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
|
||||
|
@ -45,344 +45,344 @@ enum class Mode {
|
||||
/// Appropriate prefetch offsets are left to other code to handle.
|
||||
/// This is to try to keep this structure independent of a specific ARM implementation.
|
||||
struct Registers {
|
||||
public:
|
||||
// Don't allow copying.
|
||||
Registers(const Registers &) = delete;
|
||||
Registers &operator =(const Registers &) = delete;
|
||||
Registers() = default;
|
||||
public:
|
||||
// Don't allow copying.
|
||||
Registers(const Registers &) = delete;
|
||||
Registers &operator =(const Registers &) = delete;
|
||||
Registers() = default;
|
||||
|
||||
/// Sets the N and Z flags according to the value of @c result.
|
||||
void set_nz(const uint32_t value) {
|
||||
zero_result_ = negative_flag_ = value;
|
||||
/// Sets the N and Z flags according to the value of @c result.
|
||||
void set_nz(const uint32_t value) {
|
||||
zero_result_ = negative_flag_ = value;
|
||||
}
|
||||
|
||||
/// Sets C if @c value is non-zero; resets it otherwise.
|
||||
void set_c(const uint32_t value) {
|
||||
carry_flag_ = value;
|
||||
}
|
||||
|
||||
/// @returns @c 1 if carry is set; @c 0 otherwise.
|
||||
uint32_t c() const {
|
||||
return carry_flag_ ? 1 : 0;
|
||||
}
|
||||
|
||||
/// Sets V if the highest bit of @c value is set; resets it otherwise.
|
||||
void set_v(const uint32_t value) {
|
||||
overflow_flag_ = value;
|
||||
}
|
||||
|
||||
/// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags.
|
||||
uint32_t status() const {
|
||||
return
|
||||
uint32_t(mode_) |
|
||||
(negative_flag_ & ConditionCode::Negative) |
|
||||
(zero_result_ ? 0 : ConditionCode::Zero) |
|
||||
(carry_flag_ ? ConditionCode::Carry : 0) |
|
||||
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
|
||||
interrupt_flags_;
|
||||
}
|
||||
|
||||
/// @returns The full PC + status bits.
|
||||
uint32_t pc_status(const uint32_t offset) const {
|
||||
return
|
||||
((active_[15] + offset) & ConditionCode::Address) |
|
||||
status();
|
||||
}
|
||||
|
||||
/// Sets status bits only, subject to mode.
|
||||
void set_status(const uint32_t status) {
|
||||
// ... in user mode the other flags (I, F, M1, M0) are protected from direct change
|
||||
// but in non-user modes these will also be affected, accepting copies of bits 27, 26,
|
||||
// 1 and 0 of the result respectively.
|
||||
|
||||
negative_flag_ = status;
|
||||
overflow_flag_ = status << 3;
|
||||
carry_flag_ = status & ConditionCode::Carry;
|
||||
zero_result_ = ~status & ConditionCode::Zero;
|
||||
|
||||
if(mode_ != Mode::User) {
|
||||
set_mode(Mode(status & 3));
|
||||
interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets C if @c value is non-zero; resets it otherwise.
|
||||
void set_c(const uint32_t value) {
|
||||
carry_flag_ = value;
|
||||
/// @returns The current mode.
|
||||
Mode mode() const {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
/// Sets a new PC.
|
||||
void set_pc(const uint32_t value) {
|
||||
active_[15] = value & ConditionCode::Address;
|
||||
}
|
||||
|
||||
/// @returns The stored PC plus @c offset limited to 26 bits.
|
||||
uint32_t pc(const uint32_t offset) const {
|
||||
return (active_[15] + offset) & ConditionCode::Address;
|
||||
}
|
||||
|
||||
// MARK: - Exceptions.
|
||||
|
||||
enum class Exception {
|
||||
/// Reset line went from high to low.
|
||||
Reset = 0x00,
|
||||
/// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found.
|
||||
UndefinedInstruction = 0x04,
|
||||
/// Code executed a software interrupt.
|
||||
SoftwareInterrupt = 0x08,
|
||||
/// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue.
|
||||
PrefetchAbort = 0x0c,
|
||||
/// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled
|
||||
/// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM
|
||||
/// otherwise complete, including pointer writeback.
|
||||
DataAbort = 0x10,
|
||||
/// The first data transfer attempted within an instruction was above address 0x3ff'ffff.
|
||||
Address = 0x14,
|
||||
/// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set.
|
||||
IRQ = 0x18,
|
||||
/// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set.
|
||||
FIQ = 0x1c,
|
||||
};
|
||||
static constexpr uint32_t pc_offset_during(const Exception exception) {
|
||||
// The below is somewhat convoluted by the assumed execution model:
|
||||
//
|
||||
// * exceptions occuring during execution of an instruction are taken
|
||||
// to occur after R15 has already been incremented by 4; but
|
||||
// * exceptions occurring instead of execution of an instruction are
|
||||
// taken to occur with R15 pointing to an instruction that hasn't begun.
|
||||
//
|
||||
// i.e. in net R15 always refers to the next instruction
|
||||
// that has not yet started.
|
||||
switch(exception) {
|
||||
// "To return normally from FIQ use SUBS PC, R14_fiq, #4".
|
||||
case Exception::FIQ: return 4;
|
||||
|
||||
// "To return normally from IRQ use SUBS PC, R14_irq, #4".
|
||||
case Exception::IRQ: return 4;
|
||||
|
||||
// "If a return is required from [address exception traps], use
|
||||
// SUBS PC, R14_svc, #4. This will return to the instruction after
|
||||
// the one causing the trap".
|
||||
case Exception::Address: return 4;
|
||||
|
||||
// "A Data Abort requires [work before a return], the return being
|
||||
// done by SUBS PC, R14_svc, #8" (and returning to the instruction
|
||||
// that aborted).
|
||||
case Exception::DataAbort: return 4;
|
||||
|
||||
// "To continue after a Prefetch Abort use SUBS PC, R14_svc #4.
|
||||
// This will attempt to re-execute the aborting instruction."
|
||||
case Exception::PrefetchAbort: return 4;
|
||||
|
||||
// "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction
|
||||
// following the SWI".
|
||||
case Exception::SoftwareInterrupt: return 0;
|
||||
|
||||
// "To return from [an undefined instruction trap] use MOVS PC, R14_svc.
|
||||
// This returns to the instruction following the undefined instruction".
|
||||
case Exception::UndefinedInstruction: return 0;
|
||||
|
||||
// Unspecified; a guess.
|
||||
case Exception::Reset: return 0;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
/// @returns @c 1 if carry is set; @c 0 otherwise.
|
||||
uint32_t c() const {
|
||||
return carry_flag_ ? 1 : 0;
|
||||
/// Updates the program counter, interupt flags and link register if applicable to begin @c exception.
|
||||
template <Exception type>
|
||||
void exception() {
|
||||
const auto r14 = pc_status(pc_offset_during(type));
|
||||
switch(type) {
|
||||
case Exception::IRQ: set_mode(Mode::IRQ); break;
|
||||
case Exception::FIQ: set_mode(Mode::FIQ); break;
|
||||
default: set_mode(Mode::Supervisor); break;
|
||||
}
|
||||
active_[14] = r14;
|
||||
|
||||
/// Sets V if the highest bit of @c value is set; resets it otherwise.
|
||||
void set_v(const uint32_t value) {
|
||||
overflow_flag_ = value;
|
||||
interrupt_flags_ |= ConditionCode::IRQDisable;
|
||||
if constexpr (type == Exception::Reset || type == Exception::FIQ) {
|
||||
interrupt_flags_ |= ConditionCode::FIQDisable;
|
||||
}
|
||||
set_pc(uint32_t(type));
|
||||
}
|
||||
|
||||
/// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags.
|
||||
uint32_t status() const {
|
||||
return
|
||||
uint32_t(mode_) |
|
||||
(negative_flag_ & ConditionCode::Negative) |
|
||||
(zero_result_ ? 0 : ConditionCode::Zero) |
|
||||
(carry_flag_ ? ConditionCode::Carry : 0) |
|
||||
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
|
||||
interrupt_flags_;
|
||||
/// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts.
|
||||
/// Otherwise returns @c false.
|
||||
template <Exception type>
|
||||
bool would_interrupt() {
|
||||
switch(type) {
|
||||
case Exception::IRQ:
|
||||
if(interrupt_flags_ & ConditionCode::IRQDisable) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Exception::FIQ:
|
||||
if(interrupt_flags_ & ConditionCode::FIQDisable) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @returns The full PC + status bits.
|
||||
uint32_t pc_status(const uint32_t offset) const {
|
||||
return
|
||||
((active_[15] + offset) & ConditionCode::Address) |
|
||||
status();
|
||||
}
|
||||
// MARK: - Condition tests.
|
||||
|
||||
/// Sets status bits only, subject to mode.
|
||||
void set_status(const uint32_t status) {
|
||||
// ... in user mode the other flags (I, F, M1, M0) are protected from direct change
|
||||
// but in non-user modes these will also be affected, accepting copies of bits 27, 26,
|
||||
// 1 and 0 of the result respectively.
|
||||
|
||||
negative_flag_ = status;
|
||||
overflow_flag_ = status << 3;
|
||||
carry_flag_ = status & ConditionCode::Carry;
|
||||
zero_result_ = ~status & ConditionCode::Zero;
|
||||
|
||||
if(mode_ != Mode::User) {
|
||||
set_mode(Mode(status & 3));
|
||||
interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable);
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The current mode.
|
||||
Mode mode() const {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
/// Sets a new PC.
|
||||
void set_pc(const uint32_t value) {
|
||||
active_[15] = value & ConditionCode::Address;
|
||||
}
|
||||
|
||||
/// @returns The stored PC plus @c offset limited to 26 bits.
|
||||
uint32_t pc(const uint32_t offset) const {
|
||||
return (active_[15] + offset) & ConditionCode::Address;
|
||||
}
|
||||
|
||||
// MARK: - Exceptions.
|
||||
|
||||
enum class Exception {
|
||||
/// Reset line went from high to low.
|
||||
Reset = 0x00,
|
||||
/// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found.
|
||||
UndefinedInstruction = 0x04,
|
||||
/// Code executed a software interrupt.
|
||||
SoftwareInterrupt = 0x08,
|
||||
/// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue.
|
||||
PrefetchAbort = 0x0c,
|
||||
/// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled
|
||||
/// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM
|
||||
/// otherwise complete, including pointer writeback.
|
||||
DataAbort = 0x10,
|
||||
/// The first data transfer attempted within an instruction was above address 0x3ff'ffff.
|
||||
Address = 0x14,
|
||||
/// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set.
|
||||
IRQ = 0x18,
|
||||
/// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set.
|
||||
FIQ = 0x1c,
|
||||
/// @returns @c true if @c condition tests as true; @c false otherwise.
|
||||
bool test(const Condition condition) const {
|
||||
const auto ne = [&]() -> bool {
|
||||
return zero_result_;
|
||||
};
|
||||
const auto cs = [&]() -> bool {
|
||||
return carry_flag_;
|
||||
};
|
||||
const auto mi = [&]() -> bool {
|
||||
return negative_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto vs = [&]() -> bool {
|
||||
return overflow_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto hi = [&]() -> bool {
|
||||
return carry_flag_ && zero_result_;
|
||||
};
|
||||
const auto lt = [&]() -> bool {
|
||||
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
|
||||
};
|
||||
const auto le = [&]() -> bool {
|
||||
return !zero_result_ || lt();
|
||||
};
|
||||
static constexpr uint32_t pc_offset_during(const Exception exception) {
|
||||
// The below is somewhat convoluted by the assumed execution model:
|
||||
//
|
||||
// * exceptions occuring during execution of an instruction are taken
|
||||
// to occur after R15 has already been incremented by 4; but
|
||||
// * exceptions occurring instead of execution of an instruction are
|
||||
// taken to occur with R15 pointing to an instruction that hasn't begun.
|
||||
//
|
||||
// i.e. in net R15 always refers to the next instruction
|
||||
// that has not yet started.
|
||||
switch(exception) {
|
||||
// "To return normally from FIQ use SUBS PC, R14_fiq, #4".
|
||||
case Exception::FIQ: return 4;
|
||||
|
||||
// "To return normally from IRQ use SUBS PC, R14_irq, #4".
|
||||
case Exception::IRQ: return 4;
|
||||
switch(condition) {
|
||||
case Condition::EQ: return !ne();
|
||||
case Condition::NE: return ne();
|
||||
case Condition::CS: return cs();
|
||||
case Condition::CC: return !cs();
|
||||
case Condition::MI: return mi();
|
||||
case Condition::PL: return !mi();
|
||||
case Condition::VS: return vs();
|
||||
case Condition::VC: return !vs();
|
||||
|
||||
// "If a return is required from [address exception traps], use
|
||||
// SUBS PC, R14_svc, #4. This will return to the instruction after
|
||||
// the one causing the trap".
|
||||
case Exception::Address: return 4;
|
||||
case Condition::HI: return hi();
|
||||
case Condition::LS: return !hi();
|
||||
case Condition::GE: return !lt();
|
||||
case Condition::LT: return lt();
|
||||
case Condition::GT: return !le();
|
||||
case Condition::LE: return le();
|
||||
|
||||
// "A Data Abort requires [work before a return], the return being
|
||||
// done by SUBS PC, R14_svc, #8" (and returning to the instruction
|
||||
// that aborted).
|
||||
case Exception::DataAbort: return 4;
|
||||
|
||||
// "To continue after a Prefetch Abort use SUBS PC, R14_svc #4.
|
||||
// This will attempt to re-execute the aborting instruction."
|
||||
case Exception::PrefetchAbort: return 4;
|
||||
|
||||
// "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction
|
||||
// following the SWI".
|
||||
case Exception::SoftwareInterrupt: return 0;
|
||||
|
||||
// "To return from [an undefined instruction trap] use MOVS PC, R14_svc.
|
||||
// This returns to the instruction following the undefined instruction".
|
||||
case Exception::UndefinedInstruction: return 0;
|
||||
|
||||
// Unspecified; a guess.
|
||||
case Exception::Reset: return 0;
|
||||
}
|
||||
return 4;
|
||||
case Condition::AL: return true;
|
||||
case Condition::NV: return false;
|
||||
}
|
||||
|
||||
/// Updates the program counter, interupt flags and link register if applicable to begin @c exception.
|
||||
template <Exception type>
|
||||
void exception() {
|
||||
const auto r14 = pc_status(pc_offset_during(type));
|
||||
switch(type) {
|
||||
case Exception::IRQ: set_mode(Mode::IRQ); break;
|
||||
case Exception::FIQ: set_mode(Mode::FIQ); break;
|
||||
default: set_mode(Mode::Supervisor); break;
|
||||
}
|
||||
active_[14] = r14;
|
||||
return false;
|
||||
}
|
||||
|
||||
interrupt_flags_ |= ConditionCode::IRQDisable;
|
||||
if constexpr (type == Exception::Reset || type == Exception::FIQ) {
|
||||
interrupt_flags_ |= ConditionCode::FIQDisable;
|
||||
}
|
||||
set_pc(uint32_t(type));
|
||||
/// Sets current execution mode.
|
||||
void set_mode(const Mode target_mode) {
|
||||
if(mode_ == target_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts.
|
||||
/// Otherwise returns @c false.
|
||||
template <Exception type>
|
||||
bool would_interrupt() {
|
||||
switch(type) {
|
||||
case Exception::IRQ:
|
||||
if(interrupt_flags_ & ConditionCode::IRQDisable) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
// For outgoing modes other than FIQ, only save the final two registers for now;
|
||||
// if the incoming mode is FIQ then the other five will be saved in the next switch.
|
||||
// For FIQ, save all seven up front.
|
||||
switch(mode_) {
|
||||
// FIQ outgoing: save R8 to R14.
|
||||
case Mode::FIQ:
|
||||
std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin());
|
||||
break;
|
||||
|
||||
case Exception::FIQ:
|
||||
if(interrupt_flags_ & ConditionCode::FIQDisable) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
// Non-FIQ outgoing: save R13 and R14. If saving to the user registers,
|
||||
// use only the final two slots.
|
||||
case Mode::User:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin());
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
// MARK: - Condition tests.
|
||||
// For all modes except FIQ: restore the final two registers to their appropriate values.
|
||||
// For FIQ: save an additional five, then overwrite seven.
|
||||
switch(target_mode) {
|
||||
case Mode::FIQ:
|
||||
// FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers.
|
||||
std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin());
|
||||
|
||||
/// @returns @c true if @c condition tests as true; @c false otherwise.
|
||||
bool test(const Condition condition) const {
|
||||
const auto ne = [&]() -> bool {
|
||||
return zero_result_;
|
||||
};
|
||||
const auto cs = [&]() -> bool {
|
||||
return carry_flag_;
|
||||
};
|
||||
const auto mi = [&]() -> bool {
|
||||
return negative_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto vs = [&]() -> bool {
|
||||
return overflow_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto hi = [&]() -> bool {
|
||||
return carry_flag_ && zero_result_;
|
||||
};
|
||||
const auto lt = [&]() -> bool {
|
||||
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
|
||||
};
|
||||
const auto le = [&]() -> bool {
|
||||
return !zero_result_ || lt();
|
||||
};
|
||||
|
||||
switch(condition) {
|
||||
case Condition::EQ: return !ne();
|
||||
case Condition::NE: return ne();
|
||||
case Condition::CS: return cs();
|
||||
case Condition::CC: return !cs();
|
||||
case Condition::MI: return mi();
|
||||
case Condition::PL: return !mi();
|
||||
case Condition::VS: return vs();
|
||||
case Condition::VC: return !vs();
|
||||
|
||||
case Condition::HI: return hi();
|
||||
case Condition::LS: return !hi();
|
||||
case Condition::GE: return !lt();
|
||||
case Condition::LT: return lt();
|
||||
case Condition::GT: return !le();
|
||||
case Condition::LE: return le();
|
||||
|
||||
case Condition::AL: return true;
|
||||
case Condition::NV: return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Replace R8 to R14.
|
||||
std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8);
|
||||
break;
|
||||
case Mode::User:
|
||||
std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
}
|
||||
|
||||
/// Sets current execution mode.
|
||||
void set_mode(const Mode target_mode) {
|
||||
if(mode_ == target_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For outgoing modes other than FIQ, only save the final two registers for now;
|
||||
// if the incoming mode is FIQ then the other five will be saved in the next switch.
|
||||
// For FIQ, save all seven up front.
|
||||
switch(mode_) {
|
||||
// FIQ outgoing: save R8 to R14.
|
||||
case Mode::FIQ:
|
||||
std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin());
|
||||
break;
|
||||
|
||||
// Non-FIQ outgoing: save R13 and R14. If saving to the user registers,
|
||||
// use only the final two slots.
|
||||
case Mode::User:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin());
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
// For all modes except FIQ: restore the final two registers to their appropriate values.
|
||||
// For FIQ: save an additional five, then overwrite seven.
|
||||
switch(target_mode) {
|
||||
case Mode::FIQ:
|
||||
// FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers.
|
||||
std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin());
|
||||
|
||||
// Replace R8 to R14.
|
||||
std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8);
|
||||
break;
|
||||
case Mode::User:
|
||||
std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13);
|
||||
break;
|
||||
}
|
||||
|
||||
// If FIQ is outgoing then there's another five registers to restore.
|
||||
if(mode_ == Mode::FIQ) {
|
||||
std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8);
|
||||
}
|
||||
|
||||
mode_ = target_mode;
|
||||
// If FIQ is outgoing then there's another five registers to restore.
|
||||
if(mode_ == Mode::FIQ) {
|
||||
std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8);
|
||||
}
|
||||
|
||||
uint32_t &operator[](const uint32_t offset) {
|
||||
return active_[static_cast<size_t>(offset)];
|
||||
mode_ = target_mode;
|
||||
}
|
||||
|
||||
uint32_t &operator[](const uint32_t offset) {
|
||||
return active_[static_cast<size_t>(offset)];
|
||||
}
|
||||
|
||||
uint32_t operator[](const uint32_t offset) const {
|
||||
return active_[static_cast<size_t>(offset)];
|
||||
}
|
||||
|
||||
/// @returns A reference to the register at @c offset. If @c force_user_mode is true,
|
||||
/// this will the the user-mode register. Otherwise it'll be that for the current mode. These references
|
||||
/// are guaranteed to remain valid only until the next mode change.
|
||||
uint32_t ®(const bool force_user_mode, const uint32_t offset) {
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::User: return active_[offset];
|
||||
|
||||
case Mode::Supervisor:
|
||||
case Mode::IRQ:
|
||||
if(force_user_mode && (offset == 13 || offset == 14)) {
|
||||
return user_registers_[offset - 8];
|
||||
}
|
||||
return active_[offset];
|
||||
|
||||
case Mode::FIQ:
|
||||
if(force_user_mode && (offset >= 8 && offset < 15)) {
|
||||
return user_registers_[offset - 8];
|
||||
}
|
||||
return active_[offset];
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t operator[](const uint32_t offset) const {
|
||||
return active_[static_cast<size_t>(offset)];
|
||||
}
|
||||
private:
|
||||
Mode mode_ = Mode::Supervisor;
|
||||
|
||||
/// @returns A reference to the register at @c offset. If @c force_user_mode is true,
|
||||
/// this will the the user-mode register. Otherwise it'll be that for the current mode. These references
|
||||
/// are guaranteed to remain valid only until the next mode change.
|
||||
uint32_t ®(const bool force_user_mode, const uint32_t offset) {
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::User: return active_[offset];
|
||||
uint32_t zero_result_ = 1;
|
||||
uint32_t negative_flag_ = 0;
|
||||
uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable;
|
||||
uint32_t carry_flag_ = 0;
|
||||
uint32_t overflow_flag_ = 0;
|
||||
|
||||
case Mode::Supervisor:
|
||||
case Mode::IRQ:
|
||||
if(force_user_mode && (offset == 13 || offset == 14)) {
|
||||
return user_registers_[offset - 8];
|
||||
}
|
||||
return active_[offset];
|
||||
// Various shadow registers.
|
||||
std::array<uint32_t, 7> user_registers_{};
|
||||
std::array<uint32_t, 7> fiq_registers_{};
|
||||
std::array<uint32_t, 2> irq_registers_{};
|
||||
std::array<uint32_t, 2> supervisor_registers_{};
|
||||
|
||||
case Mode::FIQ:
|
||||
if(force_user_mode && (offset >= 8 && offset < 15)) {
|
||||
return user_registers_[offset - 8];
|
||||
}
|
||||
return active_[offset];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Mode mode_ = Mode::Supervisor;
|
||||
|
||||
uint32_t zero_result_ = 1;
|
||||
uint32_t negative_flag_ = 0;
|
||||
uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable;
|
||||
uint32_t carry_flag_ = 0;
|
||||
uint32_t overflow_flag_ = 0;
|
||||
|
||||
// Various shadow registers.
|
||||
std::array<uint32_t, 7> user_registers_{};
|
||||
std::array<uint32_t, 7> fiq_registers_{};
|
||||
std::array<uint32_t, 2> irq_registers_{};
|
||||
std::array<uint32_t, 2> supervisor_registers_{};
|
||||
|
||||
// The active register set.
|
||||
std::array<uint32_t, 16> active_{};
|
||||
// The active register set.
|
||||
std::array<uint32_t, 16> active_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -76,37 +76,37 @@ private:
|
||||
Provides dynamic lookup of @c perform(Executor*).
|
||||
*/
|
||||
class PerformerLookup {
|
||||
public:
|
||||
PerformerLookup() {
|
||||
fill<int(MinOperation)>(performers_);
|
||||
public:
|
||||
PerformerLookup() {
|
||||
fill<int(MinOperation)>(performers_);
|
||||
}
|
||||
|
||||
Performer performer(const Operation operation, const AddressingMode addressing_mode) {
|
||||
const auto index =
|
||||
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
|
||||
(int(addressing_mode) - MinAddressingMode);
|
||||
return performers_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
|
||||
|
||||
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
|
||||
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
|
||||
|
||||
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
|
||||
fill_operation<operation, addressing_mode+1>(target + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Performer performer(const Operation operation, const AddressingMode addressing_mode) {
|
||||
const auto index =
|
||||
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
|
||||
(int(addressing_mode) - MinAddressingMode);
|
||||
return performers_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
|
||||
|
||||
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
|
||||
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
|
||||
|
||||
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
|
||||
fill_operation<operation, addressing_mode+1>(target + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<int operation> void fill(Performer *target) {
|
||||
fill_operation<operation, int(MinAddressingMode)>(target);
|
||||
target += 1 + MaxAddressingMode - MinAddressingMode;
|
||||
|
||||
if constexpr (operation+1 <= MaxOperation) {
|
||||
fill<operation+1>(target);
|
||||
}
|
||||
template<int operation> void fill(Performer *target) {
|
||||
fill_operation<operation, int(MinAddressingMode)>(target);
|
||||
target += 1 + MaxAddressingMode - MinAddressingMode;
|
||||
|
||||
if constexpr (operation+1 <= MaxOperation) {
|
||||
fill<operation+1>(target);
|
||||
}
|
||||
}
|
||||
};
|
||||
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.
|
||||
*/
|
||||
void run_for(TimeUnit cycles) {
|
||||
deferrer_.run_for(cycles);
|
||||
}
|
||||
|
||||
/*
|
||||
Descriptions for the setters below are taken verbatim from
|
||||
the Apple IIe Technical Reference. Addresses are the conventional
|
||||
locations within the Apple II memory map. Only those which affect
|
||||
video output are implemented here.
|
||||
/*
|
||||
Descriptions for the setters below are taken verbatim from
|
||||
the Apple IIe Technical Reference. Addresses are the conventional
|
||||
locations within the Apple II memory map. Only those which affect
|
||||
video output are implemented here.
|
||||
|
||||
Those registers which don't exist on a II/II+ are marked.
|
||||
*/
|
||||
Those registers which don't exist on a II/II+ are marked.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
|
||||
/*!
|
||||
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
|
||||
|
||||
* Off: display text using primary character set.
|
||||
* On: display text using alternate character set.
|
||||
* Off: display text using primary character set.
|
||||
* On: display text using alternate character set.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_alternative_character_set(bool alternative_character_set) {
|
||||
external_.alternative_character_set = alternative_character_set;
|
||||
deferrer_.defer(delay_, [this, alternative_character_set] {
|
||||
internal_.alternative_character_set = alternative_character_set;
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_alternative_character_set(bool alternative_character_set) {
|
||||
external_.alternative_character_set = alternative_character_set;
|
||||
deferrer_.defer(delay_, [this, alternative_character_set] {
|
||||
internal_.alternative_character_set = alternative_character_set;
|
||||
|
||||
if(alternative_character_set) {
|
||||
character_zones_[1].address_mask = 0xff;
|
||||
character_zones_[1].xor_mask = 0xff;
|
||||
} else {
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = flash_mask();
|
||||
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
|
||||
}
|
||||
});
|
||||
}
|
||||
bool get_alternative_character_set() const {
|
||||
return external_.alternative_character_set;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for 80COL ($C00C/$C00D; triggers on write only).
|
||||
|
||||
* Off: display 40 columns.
|
||||
* On: display 80 columns.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_columns(bool columns_80) {
|
||||
external_.columns_80 = columns_80;
|
||||
deferrer_.defer(delay_, [this, columns_80] {
|
||||
internal_.columns_80 = columns_80;
|
||||
});
|
||||
}
|
||||
bool get_80_columns() const {
|
||||
return external_.columns_80;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for 80STORE ($C000/$C001; triggers on write only).
|
||||
|
||||
* Off: cause PAGE2 to select auxiliary RAM.
|
||||
* On: cause PAGE2 to switch main RAM areas.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_store(bool store_80) {
|
||||
external_.store_80 = internal_.store_80 = store_80;
|
||||
}
|
||||
bool get_80_store() const {
|
||||
return external_.store_80;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for PAGE2 ($C054/$C055; triggers on read or write).
|
||||
|
||||
* Off: select Page 1.
|
||||
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
|
||||
|
||||
80STORE doesn't exist on a II/II+; therefore this always selects
|
||||
either Page 1 or Page 2 on those machines.
|
||||
*/
|
||||
void set_page2(bool page2) {
|
||||
external_.page2 = internal_.page2 = page2;
|
||||
}
|
||||
bool get_page2() const {
|
||||
return external_.page2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for TEXT ($C050/$C051; triggers on read or write).
|
||||
|
||||
* Off: display graphics or, if MIXED on, mixed.
|
||||
* On: display text.
|
||||
*/
|
||||
void set_text(bool text) {
|
||||
external_.text = text;
|
||||
deferrer_.defer(delay_, [this, text] {
|
||||
internal_.text = text;
|
||||
});
|
||||
}
|
||||
bool get_text() const {
|
||||
return external_.text;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for MIXED ($C052/$C053; triggers on read or write).
|
||||
|
||||
* Off: display only text or only graphics.
|
||||
* On: if TEXT off, display text and graphics.
|
||||
*/
|
||||
void set_mixed(bool mixed) {
|
||||
external_.mixed = mixed;
|
||||
deferrer_.defer(delay_, [this, mixed] {
|
||||
internal_.mixed = mixed;
|
||||
});
|
||||
}
|
||||
bool get_mixed() const {
|
||||
return external_.mixed;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for HIRES ($C056/$C057; triggers on read or write).
|
||||
|
||||
* Off: if TEXT off, display low-resolution graphics.
|
||||
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
|
||||
|
||||
DHIRES doesn't exist on a II/II+; therefore this always selects
|
||||
either high- or low-resolution graphics on those machines.
|
||||
|
||||
Despite Apple's documentation, the IIe also supports double low-resolution
|
||||
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
|
||||
low-resolution graphics.
|
||||
*/
|
||||
void set_high_resolution(bool high_resolution) {
|
||||
external_.high_resolution = high_resolution;
|
||||
deferrer_.defer(delay_, [this, high_resolution] {
|
||||
internal_.high_resolution = high_resolution;
|
||||
});
|
||||
}
|
||||
bool get_high_resolution() const {
|
||||
return external_.high_resolution;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for annunciator 3.
|
||||
|
||||
* On: turn on annunciator 3.
|
||||
* Off: turn off annunciator 3.
|
||||
|
||||
This exists on both the II/II+ and the IIe, but has no effect on
|
||||
video on the older machines. It's intended to be used on the IIe
|
||||
to confirm double-high resolution mode but has side effects in
|
||||
selecting mixed mode output and discarding high-resolution
|
||||
delay bits.
|
||||
*/
|
||||
void set_annunciator_3(bool annunciator_3) {
|
||||
external_.annunciator_3 = annunciator_3;
|
||||
deferrer_.defer(delay_, [this, annunciator_3] {
|
||||
internal_.annunciator_3 = annunciator_3;
|
||||
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
|
||||
});
|
||||
}
|
||||
bool get_annunciator_3() const {
|
||||
return external_.annunciator_3;
|
||||
}
|
||||
|
||||
/// Set the character ROM for this video output.
|
||||
void set_character_rom(const std::vector<uint8_t> &rom) {
|
||||
character_rom_ = rom;
|
||||
|
||||
// There's some inconsistency in bit ordering amongst the common ROM dumps;
|
||||
// detect that based arbitrarily on the second line of the $ graphic and
|
||||
// ensure consistency.
|
||||
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
|
||||
for(auto &graphic : character_rom_) {
|
||||
graphic =
|
||||
((graphic & 0x01) ? 0x40 : 0x00) |
|
||||
((graphic & 0x02) ? 0x20 : 0x00) |
|
||||
((graphic & 0x04) ? 0x10 : 0x00) |
|
||||
((graphic & 0x08) ? 0x08 : 0x00) |
|
||||
((graphic & 0x10) ? 0x04 : 0x00) |
|
||||
((graphic & 0x20) ? 0x02 : 0x00) |
|
||||
((graphic & 0x40) ? 0x01 : 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool has_deferred_actions() const {
|
||||
return !deferrer_.empty();
|
||||
}
|
||||
|
||||
protected:
|
||||
GraphicsMode graphics_mode(int row) const {
|
||||
if(
|
||||
internal_.text ||
|
||||
(internal_.mixed && row >= 160 && row < 192)
|
||||
) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text;
|
||||
if(internal_.high_resolution) {
|
||||
return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
|
||||
if(alternative_character_set) {
|
||||
character_zones_[1].address_mask = 0xff;
|
||||
character_zones_[1].xor_mask = 0xff;
|
||||
} else {
|
||||
if(internal_.columns_80) return GraphicsMode::DoubleLowRes;
|
||||
if(internal_.annunciator_3) return GraphicsMode::FatLowRes;
|
||||
return GraphicsMode::LowRes;
|
||||
}
|
||||
}
|
||||
|
||||
int video_page() const {
|
||||
return (internal_.store_80 || !internal_.page2) ? 0 : 1;
|
||||
}
|
||||
|
||||
uint16_t get_row_address(int row) const {
|
||||
const int character_row = row >> 3;
|
||||
const int pixel_row = row & 7;
|
||||
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
const GraphicsMode pixel_mode = graphics_mode(row);
|
||||
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
|
||||
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
uint16_t(((video_page()+1) * 0x400) + row_address);
|
||||
}
|
||||
|
||||
/*!
|
||||
Should be called by subclasses at the end of each line of the display;
|
||||
this gives the base class a peg on which to hang flashing-character updates.
|
||||
*/
|
||||
void did_end_line() {
|
||||
// Update character set flashing; flashing is applied only when the alternative
|
||||
// character set is not selected.
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
if(!internal_.alternative_character_set) {
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = flash_mask();
|
||||
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
|
||||
}
|
||||
});
|
||||
}
|
||||
bool get_alternative_character_set() const {
|
||||
return external_.alternative_character_set;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for 80COL ($C00C/$C00D; triggers on write only).
|
||||
|
||||
* Off: display 40 columns.
|
||||
* On: display 80 columns.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_columns(bool columns_80) {
|
||||
external_.columns_80 = columns_80;
|
||||
deferrer_.defer(delay_, [this, columns_80] {
|
||||
internal_.columns_80 = columns_80;
|
||||
});
|
||||
}
|
||||
bool get_80_columns() const {
|
||||
return external_.columns_80;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for 80STORE ($C000/$C001; triggers on write only).
|
||||
|
||||
* Off: cause PAGE2 to select auxiliary RAM.
|
||||
* On: cause PAGE2 to switch main RAM areas.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_store(bool store_80) {
|
||||
external_.store_80 = internal_.store_80 = store_80;
|
||||
}
|
||||
bool get_80_store() const {
|
||||
return external_.store_80;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for PAGE2 ($C054/$C055; triggers on read or write).
|
||||
|
||||
* Off: select Page 1.
|
||||
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
|
||||
|
||||
80STORE doesn't exist on a II/II+; therefore this always selects
|
||||
either Page 1 or Page 2 on those machines.
|
||||
*/
|
||||
void set_page2(bool page2) {
|
||||
external_.page2 = internal_.page2 = page2;
|
||||
}
|
||||
bool get_page2() const {
|
||||
return external_.page2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for TEXT ($C050/$C051; triggers on read or write).
|
||||
|
||||
* Off: display graphics or, if MIXED on, mixed.
|
||||
* On: display text.
|
||||
*/
|
||||
void set_text(bool text) {
|
||||
external_.text = text;
|
||||
deferrer_.defer(delay_, [this, text] {
|
||||
internal_.text = text;
|
||||
});
|
||||
}
|
||||
bool get_text() const {
|
||||
return external_.text;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for MIXED ($C052/$C053; triggers on read or write).
|
||||
|
||||
* Off: display only text or only graphics.
|
||||
* On: if TEXT off, display text and graphics.
|
||||
*/
|
||||
void set_mixed(bool mixed) {
|
||||
external_.mixed = mixed;
|
||||
deferrer_.defer(delay_, [this, mixed] {
|
||||
internal_.mixed = mixed;
|
||||
});
|
||||
}
|
||||
bool get_mixed() const {
|
||||
return external_.mixed;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for HIRES ($C056/$C057; triggers on read or write).
|
||||
|
||||
* Off: if TEXT off, display low-resolution graphics.
|
||||
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
|
||||
|
||||
DHIRES doesn't exist on a II/II+; therefore this always selects
|
||||
either high- or low-resolution graphics on those machines.
|
||||
|
||||
Despite Apple's documentation, the IIe also supports double low-resolution
|
||||
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
|
||||
low-resolution graphics.
|
||||
*/
|
||||
void set_high_resolution(bool high_resolution) {
|
||||
external_.high_resolution = high_resolution;
|
||||
deferrer_.defer(delay_, [this, high_resolution] {
|
||||
internal_.high_resolution = high_resolution;
|
||||
});
|
||||
}
|
||||
bool get_high_resolution() const {
|
||||
return external_.high_resolution;
|
||||
}
|
||||
|
||||
/*!
|
||||
Setter for annunciator 3.
|
||||
|
||||
* On: turn on annunciator 3.
|
||||
* Off: turn off annunciator 3.
|
||||
|
||||
This exists on both the II/II+ and the IIe, but has no effect on
|
||||
video on the older machines. It's intended to be used on the IIe
|
||||
to confirm double-high resolution mode but has side effects in
|
||||
selecting mixed mode output and discarding high-resolution
|
||||
delay bits.
|
||||
*/
|
||||
void set_annunciator_3(bool annunciator_3) {
|
||||
external_.annunciator_3 = annunciator_3;
|
||||
deferrer_.defer(delay_, [this, annunciator_3] {
|
||||
internal_.annunciator_3 = annunciator_3;
|
||||
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
|
||||
});
|
||||
}
|
||||
bool get_annunciator_3() const {
|
||||
return external_.annunciator_3;
|
||||
}
|
||||
|
||||
/// Set the character ROM for this video output.
|
||||
void set_character_rom(const std::vector<uint8_t> &rom) {
|
||||
character_rom_ = rom;
|
||||
|
||||
// There's some inconsistency in bit ordering amongst the common ROM dumps;
|
||||
// detect that based arbitrarily on the second line of the $ graphic and
|
||||
// ensure consistency.
|
||||
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
|
||||
for(auto &graphic : character_rom_) {
|
||||
graphic =
|
||||
((graphic & 0x01) ? 0x40 : 0x00) |
|
||||
((graphic & 0x02) ? 0x20 : 0x00) |
|
||||
((graphic & 0x04) ? 0x10 : 0x00) |
|
||||
((graphic & 0x08) ? 0x08 : 0x00) |
|
||||
((graphic & 0x10) ? 0x04 : 0x00) |
|
||||
((graphic & 0x20) ? 0x02 : 0x00) |
|
||||
((graphic & 0x40) ? 0x01 : 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Maintain a DeferredQueue for delayed mode switches.
|
||||
const TimeUnit delay_;
|
||||
DeferredQueuePerformer<TimeUnit> deferrer_;
|
||||
bool has_deferred_actions() const {
|
||||
return !deferrer_.empty();
|
||||
}
|
||||
|
||||
struct Switches {
|
||||
bool alternative_character_set = false;
|
||||
bool columns_80 = false;
|
||||
bool store_80 = false;
|
||||
bool page2 = false;
|
||||
bool text = true;
|
||||
bool mixed = false;
|
||||
bool high_resolution = false;
|
||||
bool annunciator_3 = false;
|
||||
} external_, internal_;
|
||||
|
||||
// 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line
|
||||
// rate of around 15734.26 lines/second, flashing has a frequency of roughly
|
||||
// 15734.26 / 8406, or roughly 1.83Hz.
|
||||
//
|
||||
// Internally that's modelled by a counter that goes to **twice** the value of the
|
||||
// constant specified below. Hence the divide by two.
|
||||
static constexpr int flash_length = 8406 / 2;
|
||||
|
||||
int flash_ = 0;
|
||||
uint8_t flash_mask() const {
|
||||
return uint8_t((flash_ / flash_length) * 0xff);
|
||||
protected:
|
||||
GraphicsMode graphics_mode(int row) const {
|
||||
if(
|
||||
internal_.text ||
|
||||
(internal_.mixed && row >= 160 && row < 192)
|
||||
) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text;
|
||||
if(internal_.high_resolution) {
|
||||
return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
|
||||
} else {
|
||||
if(internal_.columns_80) return GraphicsMode::DoubleLowRes;
|
||||
if(internal_.annunciator_3) return GraphicsMode::FatLowRes;
|
||||
return GraphicsMode::LowRes;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int video_page() const {
|
||||
return (internal_.store_80 || !internal_.page2) ? 0 : 1;
|
||||
}
|
||||
|
||||
// Describes the current text mode mapping from in-memory character index
|
||||
// to output character; subclasses should:
|
||||
//
|
||||
// (i) use the top two-bits of the character code to index character_zones_;
|
||||
// (ii) apply the address_mask to the character code in order to get a character
|
||||
// offset into the character ROM; and
|
||||
// (iii) apply the XOR mask to the output of the character ROM.
|
||||
//
|
||||
// By this means they will properly handle the limited character sets of Apple IIs
|
||||
// prior to the IIe as well as the IIe and onward's alternative character set toggle.
|
||||
struct CharacterMapping {
|
||||
uint8_t address_mask;
|
||||
uint8_t xor_mask;
|
||||
};
|
||||
CharacterMapping character_zones_[4];
|
||||
uint16_t get_row_address(int row) const {
|
||||
const int character_row = row >> 3;
|
||||
const int pixel_row = row & 7;
|
||||
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
// A mask that should be applied to high-resolution graphics bytes before output;
|
||||
// it acts to retain or remove the top bit, affecting whether the half-pixel delay
|
||||
// bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist.
|
||||
uint8_t high_resolution_mask_ = 0xff;
|
||||
const GraphicsMode pixel_mode = graphics_mode(row);
|
||||
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
|
||||
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
uint16_t(((video_page()+1) * 0x400) + row_address);
|
||||
}
|
||||
|
||||
// This holds a copy of the character ROM. The regular character
|
||||
// set is assumed to be in the first 64*8 bytes; the alternative
|
||||
// is in the 128*8 bytes after that.
|
||||
std::vector<uint8_t> character_rom_;
|
||||
/*!
|
||||
Should be called by subclasses at the end of each line of the display;
|
||||
this gives the base class a peg on which to hang flashing-character updates.
|
||||
*/
|
||||
void did_end_line() {
|
||||
// Update character set flashing; flashing is applied only when the alternative
|
||||
// character set is not selected.
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
if(!internal_.alternative_character_set) {
|
||||
character_zones_[1].xor_mask = flash_mask();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Maintain a DeferredQueue for delayed mode switches.
|
||||
const TimeUnit delay_;
|
||||
DeferredQueuePerformer<TimeUnit> deferrer_;
|
||||
|
||||
struct Switches {
|
||||
bool alternative_character_set = false;
|
||||
bool columns_80 = false;
|
||||
bool store_80 = false;
|
||||
bool page2 = false;
|
||||
bool text = true;
|
||||
bool mixed = false;
|
||||
bool high_resolution = false;
|
||||
bool annunciator_3 = false;
|
||||
} external_, internal_;
|
||||
|
||||
// 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line
|
||||
// rate of around 15734.26 lines/second, flashing has a frequency of roughly
|
||||
// 15734.26 / 8406, or roughly 1.83Hz.
|
||||
//
|
||||
// Internally that's modelled by a counter that goes to **twice** the value of the
|
||||
// constant specified below. Hence the divide by two.
|
||||
static constexpr int flash_length = 8406 / 2;
|
||||
|
||||
int flash_ = 0;
|
||||
uint8_t flash_mask() const {
|
||||
return uint8_t((flash_ / flash_length) * 0xff);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Describes the current text mode mapping from in-memory character index
|
||||
// to output character; subclasses should:
|
||||
//
|
||||
// (i) use the top two-bits of the character code to index character_zones_;
|
||||
// (ii) apply the address_mask to the character code in order to get a character
|
||||
// offset into the character ROM; and
|
||||
// (iii) apply the XOR mask to the output of the character ROM.
|
||||
//
|
||||
// By this means they will properly handle the limited character sets of Apple IIs
|
||||
// prior to the IIe as well as the IIe and onward's alternative character set toggle.
|
||||
struct CharacterMapping {
|
||||
uint8_t address_mask;
|
||||
uint8_t xor_mask;
|
||||
};
|
||||
CharacterMapping character_zones_[4];
|
||||
|
||||
// A mask that should be applied to high-resolution graphics bytes before output;
|
||||
// it acts to retain or remove the top bit, affecting whether the half-pixel delay
|
||||
// bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist.
|
||||
uint8_t high_resolution_mask_ = 0xff;
|
||||
|
||||
// This holds a copy of the character ROM. The regular character
|
||||
// set is assumed to be in the first 64*8 bytes; the alternative
|
||||
// is in the 128*8 bytes after that.
|
||||
std::vector<uint8_t> character_rom_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
bool get_switch_is_enabled(Atari2600Switch input) final {
|
||||
bool get_switch_is_enabled(Atari2600Switch input) const final {
|
||||
uint8_t port_input = bus_->mos6532_.get_port_input(1);
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: return !!(port_input & 0x01);
|
||||
|
@ -22,20 +22,20 @@ namespace Atari2600 {
|
||||
Models an Atari 2600.
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine() = default;
|
||||
public:
|
||||
virtual ~Machine() = default;
|
||||
|
||||
/// Creates and returns an Atari 2600 on the heap.
|
||||
static std::unique_ptr<Machine> Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
/// Creates and returns an Atari 2600 on the heap.
|
||||
static std::unique_ptr<Machine> Atari2600(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
|
||||
|
||||
/// Sets the switch @c input to @c state.
|
||||
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
|
||||
/// Sets the switch @c input to @c state.
|
||||
virtual void set_switch_is_enabled(Atari2600Switch, bool) = 0;
|
||||
|
||||
/// Gets the state of switch @c input.
|
||||
virtual bool get_switch_is_enabled(Atari2600Switch input) = 0;
|
||||
/// Gets the state of switch @c input.
|
||||
virtual bool get_switch_is_enabled(Atari2600Switch) const = 0;
|
||||
|
||||
// Presses or releases the reset button.
|
||||
virtual void set_reset_switch(bool state) = 0;
|
||||
// Presses or releases the reset button.
|
||||
virtual void set_reset_switch(bool) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -20,49 +20,49 @@
|
||||
namespace Atari2600 {
|
||||
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_sound_(audio_queue_),
|
||||
speaker_(tia_sound_) {}
|
||||
public:
|
||||
Bus() :
|
||||
tia_sound_(audio_queue_),
|
||||
speaker_(tia_sound_) {}
|
||||
|
||||
virtual ~Bus() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
virtual ~Bus() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
virtual void flush() = 0;
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
virtual void flush() = 0;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
PIA mos6532_;
|
||||
TIA tia_;
|
||||
// the RIOT, TIA and speaker
|
||||
PIA mos6532_;
|
||||
TIA tia_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::PullLowpass<TIASound> speaker_;
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::PullLowpass<TIASound> speaker_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2] = {0xff, 0xff};
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2] = {0xff, 0xff};
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
// video backlog accumulation counter
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
|
||||
}
|
||||
// RIOT backlog accumulation counter
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,36 +11,40 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class ActivisionStack: public BusExtender {
|
||||
public:
|
||||
ActivisionStack(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
last_opcode_(0x00) {}
|
||||
public:
|
||||
ActivisionStack(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
last_opcode_(0x00) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
|
||||
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
|
||||
// RST or JSR.
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||
if(address & 0x2000) {
|
||||
rom_ptr_ = rom_base_;
|
||||
} else {
|
||||
rom_ptr_ = rom_base_ + 4096;
|
||||
}
|
||||
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
|
||||
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
|
||||
// RST or JSR.
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||
if(address & 0x2000) {
|
||||
rom_ptr_ = rom_base_;
|
||||
} else {
|
||||
rom_ptr_ = rom_base_ + 4096;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t last_opcode_;
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t last_opcode_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,49 +13,57 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Atari16k: public BusExtender {
|
||||
public:
|
||||
Atari16k(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari16k(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class Atari16kSuperChip: public BusExtender {
|
||||
public:
|
||||
Atari16kSuperChip(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari16kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,45 +13,55 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Atari32k: public BusExtender {
|
||||
public:
|
||||
Atari32k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari32k(const uint8_t *const rom_base, const std::size_t rom_size)
|
||||
: BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class Atari32kSuperChip: public BusExtender {
|
||||
public:
|
||||
Atari32kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari32kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size)
|
||||
: BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,47 +13,57 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Atari8k: public BusExtender {
|
||||
public:
|
||||
Atari8k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari8k(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class Atari8kSuperChip: public BusExtender {
|
||||
public:
|
||||
Atari8kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Atari8kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,26 +13,31 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class CBSRAMPlus: public BusExtender {
|
||||
public:
|
||||
CBSRAMPlus(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
public:
|
||||
CBSRAMPlus(const uint8_t *const rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
|
||||
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1100) ram_[address & 0xff] = *value;
|
||||
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[256];
|
||||
if(address < 0x1100) ram_[address & 0xff] = *value;
|
||||
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t ram_[256];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -14,202 +14,206 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class BusExtender: public CPU::MOS6502::BusHandler {
|
||||
public:
|
||||
BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
|
||||
public:
|
||||
BusExtender(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
rom_base_(rom_base), rom_size_(rom_size) {}
|
||||
|
||||
void advance_cycles(int) {}
|
||||
void advance_cycles(int) {}
|
||||
|
||||
protected:
|
||||
uint8_t *rom_base_;
|
||||
std::size_t rom_size_;
|
||||
protected:
|
||||
const uint8_t *rom_base_;
|
||||
std::size_t rom_size_;
|
||||
};
|
||||
|
||||
template<class T> class Cartridge:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Bus {
|
||||
|
||||
public:
|
||||
Cartridge(const std::vector<uint8_t> &rom) :
|
||||
m6502_(*this),
|
||||
rom_(rom),
|
||||
bus_extender_(rom_.data(), rom.size()) {
|
||||
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
|
||||
// consider doing something less fragile.
|
||||
}
|
||||
public:
|
||||
Cartridge(const std::vector<uint8_t> &rom) :
|
||||
m6502_(*this),
|
||||
rom_(rom),
|
||||
bus_extender_(rom_.data(), rom.size()) {
|
||||
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
|
||||
// consider doing something less fragile.
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
|
||||
// title. Random memory accesses are likely to trigger random counter resets.
|
||||
horizontal_counter_resets_ = 0;
|
||||
cycle_count_ = cycles;
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
void run_for(const Cycles cycles) override {
|
||||
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
|
||||
// title. Random memory accesses are likely to trigger random counter resets.
|
||||
horizontal_counter_resets_ = 0;
|
||||
cycle_count_ = cycles;
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
/*!
|
||||
Adjusts @c confidence_counter according to the results of the most recent run_for.
|
||||
*/
|
||||
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
|
||||
if(cycle_count_.as_integral() < 200) return;
|
||||
if(horizontal_counter_resets_ > 10)
|
||||
confidence_counter.add_miss();
|
||||
}
|
||||
/*!
|
||||
Adjusts @c confidence_counter according to the results of the most recent run_for.
|
||||
*/
|
||||
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
|
||||
if(cycle_count_.as_integral() < 200) return;
|
||||
if(horizontal_counter_resets_ > 10)
|
||||
confidence_counter.add_miss();
|
||||
}
|
||||
|
||||
void set_reset_line(bool state) override { m6502_.set_reset_line(state); }
|
||||
void set_reset_line(const bool state) override { m6502_.set_reset_line(state); }
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
uint8_t returnValue = 0xff;
|
||||
int cycles_run_for = 3;
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
uint8_t returnValue = 0xff;
|
||||
int cycles_run_for = 3;
|
||||
|
||||
// this occurs as a feedback loop: the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready)
|
||||
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
// this occurs as a feedback loop: the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready)
|
||||
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
|
||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||
bus_extender_.advance_cycles(cycles_run_for / 3);
|
||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||
bus_extender_.advance_cycles(cycles_run_for / 3);
|
||||
|
||||
if(isAccessOperation(operation)) {
|
||||
// give the cartridge a chance to respond to the bus access
|
||||
bus_extender_.perform_bus_operation(operation, address, value);
|
||||
|
||||
// check for a RIOT RAM access
|
||||
if((address&0x1280) == 0x80) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_ram(address);
|
||||
} else {
|
||||
mos6532_.set_ram(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
// check for a TIA access
|
||||
if(!(address&0x1080)) {
|
||||
if(isReadOperation(operation)) {
|
||||
const uint16_t decodedAddress = address & 0xf;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: // missile 0 / player collisions
|
||||
case 0x01: // missile 1 / player collisions
|
||||
case 0x02: // player 0 / playfield / ball collisions
|
||||
case 0x03: // player 1 / playfield / ball collisions
|
||||
case 0x04: // missile 0 / playfield / ball collisions
|
||||
case 0x05: // missile 1 / playfield / ball collisions
|
||||
case 0x06: // ball / playfield collisions
|
||||
case 0x07: // player / player, missile / missile collisions
|
||||
returnValue &= tia_.get_collision_flags(decodedAddress);
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
case 0x0b:
|
||||
// TODO: pot ports
|
||||
returnValue &= 0;
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
returnValue &= tia_input_value_[decodedAddress - 0x0c];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t decodedAddress = address & 0x3f;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
|
||||
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
|
||||
|
||||
case 0x02: m6502_.set_ready_line(true); break;
|
||||
case 0x03:
|
||||
update_video();
|
||||
tia_.reset_horizontal_counter();
|
||||
horizontal_counter_resets_++;
|
||||
break;
|
||||
// TODO: audio will now be out of synchronisation. Fix.
|
||||
|
||||
case 0x04:
|
||||
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
|
||||
case 0x06:
|
||||
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
|
||||
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
|
||||
case 0x09: update_video(); tia_.set_background_colour(*value); break;
|
||||
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
|
||||
case 0x0b:
|
||||
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
|
||||
case 0x0d:
|
||||
case 0x0e:
|
||||
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
|
||||
case 0x10:
|
||||
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
|
||||
case 0x12:
|
||||
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
|
||||
case 0x14: update_video(); tia_.set_ball_position(); break;
|
||||
case 0x1b:
|
||||
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
|
||||
case 0x1d:
|
||||
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
|
||||
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
|
||||
case 0x20:
|
||||
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
|
||||
case 0x22:
|
||||
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
|
||||
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
|
||||
case 0x25:
|
||||
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
|
||||
case 0x27: tia_.set_ball_delay((*value)&1); break;
|
||||
case 0x28:
|
||||
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
|
||||
case 0x2a: update_video(); tia_.move(); break;
|
||||
case 0x2b: update_video(); tia_.clear_motion(); break;
|
||||
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
|
||||
|
||||
case 0x15:
|
||||
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x17:
|
||||
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x19:
|
||||
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a PIA access
|
||||
if((address&0x1280) == 0x280) {
|
||||
update_6532();
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.read(address);
|
||||
} else {
|
||||
mos6532_.write(address, *value);
|
||||
}
|
||||
}
|
||||
if(isAccessOperation(operation)) {
|
||||
// give the cartridge a chance to respond to the bus access
|
||||
bus_extender_.perform_bus_operation(operation, address, value);
|
||||
|
||||
// check for a RIOT RAM access
|
||||
if((address&0x1280) == 0x80) {
|
||||
if(isReadOperation(operation)) {
|
||||
*value &= returnValue;
|
||||
returnValue &= mos6532_.get_ram(address);
|
||||
} else {
|
||||
mos6532_.set_ram(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
|
||||
// check for a TIA access
|
||||
if(!(address&0x1080)) {
|
||||
if(isReadOperation(operation)) {
|
||||
const uint16_t decodedAddress = address & 0xf;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: // missile 0 / player collisions
|
||||
case 0x01: // missile 1 / player collisions
|
||||
case 0x02: // player 0 / playfield / ball collisions
|
||||
case 0x03: // player 1 / playfield / ball collisions
|
||||
case 0x04: // missile 0 / playfield / ball collisions
|
||||
case 0x05: // missile 1 / playfield / ball collisions
|
||||
case 0x06: // ball / playfield collisions
|
||||
case 0x07: // player / player, missile / missile collisions
|
||||
returnValue &= tia_.get_collision_flags(decodedAddress);
|
||||
break;
|
||||
|
||||
return Cycles(cycles_run_for / 3);
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
case 0x0b:
|
||||
// TODO: pot ports
|
||||
returnValue &= 0;
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
returnValue &= tia_input_value_[decodedAddress - 0x0c];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t decodedAddress = address & 0x3f;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
|
||||
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
|
||||
|
||||
case 0x02: m6502_.set_ready_line(true); break;
|
||||
case 0x03:
|
||||
update_video();
|
||||
tia_.reset_horizontal_counter();
|
||||
horizontal_counter_resets_++;
|
||||
break;
|
||||
// TODO: audio will now be out of synchronisation. Fix.
|
||||
|
||||
case 0x04:
|
||||
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
|
||||
case 0x06:
|
||||
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
|
||||
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
|
||||
case 0x09: update_video(); tia_.set_background_colour(*value); break;
|
||||
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
|
||||
case 0x0b:
|
||||
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
|
||||
case 0x0d:
|
||||
case 0x0e:
|
||||
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
|
||||
case 0x10:
|
||||
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
|
||||
case 0x12:
|
||||
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
|
||||
case 0x14: update_video(); tia_.set_ball_position(); break;
|
||||
case 0x1b:
|
||||
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
|
||||
case 0x1d:
|
||||
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
|
||||
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
|
||||
case 0x20:
|
||||
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
|
||||
case 0x22:
|
||||
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
|
||||
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
|
||||
case 0x25:
|
||||
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
|
||||
case 0x27: tia_.set_ball_delay((*value)&1); break;
|
||||
case 0x28:
|
||||
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
|
||||
case 0x2a: update_video(); tia_.move(); break;
|
||||
case 0x2b: update_video(); tia_.clear_motion(); break;
|
||||
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
|
||||
|
||||
case 0x15:
|
||||
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x17:
|
||||
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x19:
|
||||
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a PIA access
|
||||
if((address&0x1280) == 0x280) {
|
||||
update_6532();
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.read(address);
|
||||
} else {
|
||||
mos6532_.write(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value &= returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
void flush() override {
|
||||
update_audio();
|
||||
update_video();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
|
||||
std::vector<uint8_t> rom_;
|
||||
return Cycles(cycles_run_for / 3);
|
||||
}
|
||||
|
||||
private:
|
||||
T bus_extender_;
|
||||
int horizontal_counter_resets_ = 0;
|
||||
Cycles cycle_count_;
|
||||
void flush() override {
|
||||
update_audio();
|
||||
update_video();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
|
||||
std::vector<uint8_t> rom_;
|
||||
|
||||
private:
|
||||
T bus_extender_;
|
||||
int horizontal_counter_resets_ = 0;
|
||||
Cycles cycle_count_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,28 +13,32 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class CommaVid: public BusExtender {
|
||||
public:
|
||||
CommaVid(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
public:
|
||||
CommaVid(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(!(address & 0x1000)) return;
|
||||
address &= 0x1fff;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
if(!(address & 0x1000)) return;
|
||||
address &= 0x1fff;
|
||||
|
||||
if(address < 0x1400) {
|
||||
if(isReadOperation(operation)) *value = ram_[address & 1023];
|
||||
return;
|
||||
}
|
||||
|
||||
if(address < 0x1800) {
|
||||
ram_[address & 1023] = *value;
|
||||
return;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
|
||||
if(address < 0x1400) {
|
||||
if(isReadOperation(operation)) *value = ram_[address & 1023];
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t ram_[1024];
|
||||
if(address < 0x1800) {
|
||||
ram_[address & 1023] = *value;
|
||||
return;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,52 +13,55 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class MNetwork: public BusExtender {
|
||||
public:
|
||||
MNetwork(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size_ - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
high_ram_ptr_ = high_ram_;
|
||||
public:
|
||||
MNetwork(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size_ - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
high_ram_ptr_ = high_ram_;
|
||||
}
|
||||
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address <= 0x1fe6) {
|
||||
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
|
||||
} else if(address == 0x1fe7) {
|
||||
rom_ptr_[0] = nullptr;
|
||||
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
||||
int offset = (address - 0x1ff8) * 256;
|
||||
high_ram_ptr_ = &high_ram_[offset];
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address <= 0x1fe6) {
|
||||
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
|
||||
} else if(address == 0x1fe7) {
|
||||
rom_ptr_[0] = nullptr;
|
||||
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
||||
int offset = (address - 0x1ff8) * 256;
|
||||
high_ram_ptr_ = &high_ram_[offset];
|
||||
}
|
||||
|
||||
if(address & 0x800) {
|
||||
if(address < 0x1900) {
|
||||
high_ram_ptr_[address & 255] = *value;
|
||||
} else if(address < 0x1a00) {
|
||||
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
|
||||
}
|
||||
if(address & 0x800) {
|
||||
if(address < 0x1900) {
|
||||
high_ram_ptr_[address & 255] = *value;
|
||||
} else if(address < 0x1a00) {
|
||||
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
|
||||
} else {
|
||||
if(rom_ptr_[0]) {
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
|
||||
}
|
||||
} else {
|
||||
if(rom_ptr_[0]) {
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
|
||||
} else {
|
||||
if(address < 0x1400) {
|
||||
low_ram_[address & 1023] = *value;
|
||||
} else {
|
||||
if(address < 0x1400) {
|
||||
low_ram_[address & 1023] = *value;
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
|
||||
}
|
||||
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[2];
|
||||
uint8_t *high_ram_ptr_;
|
||||
uint8_t low_ram_[1024], high_ram_[1024];
|
||||
private:
|
||||
const uint8_t *rom_ptr_[2];
|
||||
uint8_t *high_ram_ptr_;
|
||||
uint8_t low_ram_[1024], high_ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,30 +13,31 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class MegaBoy: public BusExtender {
|
||||
public:
|
||||
MegaBoy(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
current_page_(0) {
|
||||
public:
|
||||
MegaBoy(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size), rom_ptr_(rom_base), current_page_(0) {}
|
||||
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff0) {
|
||||
current_page_ = (current_page_ + 1) & 15;
|
||||
rom_ptr_ = rom_base_ + current_page_ * 4096;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff0) {
|
||||
current_page_ = (current_page_ + 1) & 15;
|
||||
rom_ptr_ = rom_base_ + current_page_ * 4096;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t current_page_;
|
||||
private:
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t current_page_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,31 +13,36 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class ParkerBros: public BusExtender {
|
||||
public:
|
||||
ParkerBros(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
||||
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
||||
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
||||
public:
|
||||
ParkerBros(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size)
|
||||
{
|
||||
rom_ptr_[0] = rom_base + 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
||||
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
||||
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
||||
}
|
||||
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address < 0x1ff8) {
|
||||
int slot = (address >> 3)&3;
|
||||
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address < 0x1ff8) {
|
||||
int slot = (address >> 3)&3;
|
||||
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 10)&3][address & 1023];
|
||||
}
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 10)&3][address & 1023];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[4];
|
||||
private:
|
||||
const uint8_t *rom_ptr_[4];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,114 +11,118 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Pitfall2: public BusExtender {
|
||||
public:
|
||||
Pitfall2(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
public:
|
||||
Pitfall2(const uint8_t *const rom_base, const std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
|
||||
void advance_cycles(int cycles) {
|
||||
cycles_since_audio_update_ += cycles;
|
||||
}
|
||||
void advance_cycles(const int cycles) {
|
||||
cycles_since_audio_update_ += cycles;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
uint16_t address,
|
||||
uint8_t *const value)
|
||||
{
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
switch(address) {
|
||||
switch(address) {
|
||||
|
||||
// MARK: - Reads
|
||||
|
||||
// The random number generator
|
||||
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = random_number_generator_;
|
||||
}
|
||||
random_number_generator_ = uint8_t(
|
||||
(random_number_generator_ << 1) |
|
||||
(~( (random_number_generator_ >> 7) ^
|
||||
(random_number_generator_ >> 5) ^
|
||||
(random_number_generator_ >> 4) ^
|
||||
(random_number_generator_ >> 3)
|
||||
) & 1));
|
||||
break;
|
||||
// The random number generator
|
||||
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = random_number_generator_;
|
||||
}
|
||||
random_number_generator_ = uint8_t(
|
||||
(random_number_generator_ << 1) |
|
||||
(~( (random_number_generator_ >> 7) ^
|
||||
(random_number_generator_ >> 5) ^
|
||||
(random_number_generator_ >> 4) ^
|
||||
(random_number_generator_ >> 3)
|
||||
) & 1));
|
||||
break;
|
||||
|
||||
case 0x1005: case 0x1006: case 0x1007:
|
||||
*value = update_audio();
|
||||
break;
|
||||
case 0x1005: case 0x1006: case 0x1007:
|
||||
*value = update_audio();
|
||||
break;
|
||||
|
||||
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
|
||||
*value = rom_base_[8192 + address_for_counter(address & 7)];
|
||||
break;
|
||||
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
|
||||
*value = rom_base_[8192 + address_for_counter(address & 7)];
|
||||
break;
|
||||
|
||||
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
|
||||
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
|
||||
break;
|
||||
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
|
||||
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
|
||||
break;
|
||||
|
||||
// MARK: - Writes
|
||||
|
||||
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
|
||||
top_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
|
||||
bottom_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
|
||||
mask_[address & 7] = 0x00;
|
||||
break;
|
||||
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8);
|
||||
break;
|
||||
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
||||
random_number_generator_ = 0;
|
||||
break;
|
||||
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
|
||||
top_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
|
||||
bottom_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
|
||||
mask_[address & 7] = 0x00;
|
||||
break;
|
||||
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8);
|
||||
break;
|
||||
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
||||
random_number_generator_ = 0;
|
||||
break;
|
||||
|
||||
// MARK: - Paging
|
||||
|
||||
case 0x1ff8: rom_ptr_ = rom_base_; break;
|
||||
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
|
||||
case 0x1ff8: rom_ptr_ = rom_base_; break;
|
||||
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
|
||||
|
||||
// MARK: - Business as usual
|
||||
|
||||
default:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
inline uint16_t address_for_counter(int counter) {
|
||||
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
|
||||
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
|
||||
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
|
||||
featcher_address_[counter]--;
|
||||
return fetch_address;
|
||||
}
|
||||
|
||||
inline uint8_t update_audio() {
|
||||
const unsigned int clock_divisor = 57;
|
||||
int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral());
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]));
|
||||
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
|
||||
table_position |= 0x4 >> c;
|
||||
default:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
|
||||
return level_table[table_position];
|
||||
private:
|
||||
inline uint16_t address_for_counter(const int counter) {
|
||||
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
|
||||
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
|
||||
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
|
||||
featcher_address_[counter]--;
|
||||
return fetch_address;
|
||||
}
|
||||
|
||||
inline uint8_t update_audio() {
|
||||
const unsigned int clock_divisor = 57;
|
||||
int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral());
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]));
|
||||
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
|
||||
table_position |= 0x4 >> c;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t random_number_generator_ = 0;
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t audio_channel_[3];
|
||||
Cycles cycles_since_audio_update_ = 0;
|
||||
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
|
||||
return level_table[table_position];
|
||||
}
|
||||
|
||||
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t random_number_generator_ = 0;
|
||||
const uint8_t *rom_ptr_;
|
||||
uint8_t audio_channel_[3];
|
||||
Cycles cycles_since_audio_update_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,25 +13,28 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Tigervision: public BusExtender {
|
||||
public:
|
||||
Tigervision(uint8_t *rom_base, std::size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
}
|
||||
public:
|
||||
Tigervision(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if((address&0x1fff) == 0x3f) {
|
||||
int offset = ((*value) * 2048) & (rom_size_ - 1);
|
||||
rom_ptr_[0] = rom_base_ + offset;
|
||||
return;
|
||||
} else if((address&0x1000) && isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
||||
}
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
if((address&0x1fff) == 0x3f) {
|
||||
int offset = ((*value) * 2048) & (rom_size_ - 1);
|
||||
rom_ptr_[0] = rom_base_ + offset;
|
||||
return;
|
||||
} else if((address&0x1000) && isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[2];
|
||||
private:
|
||||
const uint8_t *rom_ptr_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,14 +13,18 @@
|
||||
namespace Atari2600::Cartridge {
|
||||
|
||||
class Unpaged: public BusExtender {
|
||||
public:
|
||||
Unpaged(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
public:
|
||||
Unpaged(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(isReadOperation(operation) && (address & 0x1000)) {
|
||||
*value = rom_base_[address & (rom_size_ - 1)];
|
||||
}
|
||||
void perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
if(isReadOperation(operation) && (address & 0x1000)) {
|
||||
*value = rom_base_[address & (rom_size_ - 1)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,23 +15,20 @@
|
||||
namespace Atari2600 {
|
||||
|
||||
class PIA: public MOS::MOS6532<PIA> {
|
||||
public:
|
||||
inline uint8_t get_port_input(int port) {
|
||||
return port_values_[port];
|
||||
}
|
||||
public:
|
||||
inline uint8_t get_port_input(const int port) {
|
||||
return port_values_[port];
|
||||
}
|
||||
|
||||
inline void update_port_input(int port, uint8_t mask, bool set) {
|
||||
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
inline void update_port_input(const int port, const uint8_t mask, const bool set) {
|
||||
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
|
||||
PIA() :
|
||||
port_values_{0xff, 0xff}
|
||||
{}
|
||||
|
||||
private:
|
||||
uint8_t port_values_[2];
|
||||
PIA() : port_values_{0xff, 0xff} {}
|
||||
|
||||
private:
|
||||
uint8_t port_values_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -19,296 +19,295 @@
|
||||
namespace Atari2600 {
|
||||
|
||||
class TIA {
|
||||
public:
|
||||
TIA();
|
||||
public:
|
||||
TIA();
|
||||
|
||||
enum class OutputMode {
|
||||
NTSC, PAL
|
||||
};
|
||||
enum class OutputMode {
|
||||
NTSC, PAL
|
||||
};
|
||||
|
||||
/*!
|
||||
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
|
||||
*/
|
||||
void run_for(const Cycles cycles);
|
||||
void set_output_mode(OutputMode output_mode);
|
||||
/*!
|
||||
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
|
||||
*/
|
||||
void run_for(const Cycles cycles);
|
||||
void set_output_mode(OutputMode output_mode);
|
||||
|
||||
void set_sync(bool sync);
|
||||
void set_blank(bool blank);
|
||||
void reset_horizontal_counter(); // Reset is delayed by four cycles.
|
||||
void set_sync(bool sync);
|
||||
void set_blank(bool blank);
|
||||
void reset_horizontal_counter(); // Reset is delayed by four cycles.
|
||||
|
||||
/*!
|
||||
@returns the number of cycles between (current TIA time) + from_offset to the current or
|
||||
next horizontal blanking period. Returns numbers in the range [0, 227].
|
||||
*/
|
||||
int get_cycles_until_horizontal_blank(const Cycles from_offset);
|
||||
/*!
|
||||
@returns the number of cycles between (current TIA time) + from_offset to the current or
|
||||
next horizontal blanking period. Returns numbers in the range [0, 227].
|
||||
*/
|
||||
int get_cycles_until_horizontal_blank(const Cycles from_offset);
|
||||
|
||||
void set_background_colour(uint8_t colour);
|
||||
void set_background_colour(uint8_t colour);
|
||||
|
||||
void set_playfield(uint16_t offset, uint8_t value);
|
||||
void set_playfield_control_and_ball_size(uint8_t value);
|
||||
void set_playfield_ball_colour(uint8_t colour);
|
||||
void set_playfield(uint16_t offset, uint8_t value);
|
||||
void set_playfield_control_and_ball_size(uint8_t value);
|
||||
void set_playfield_ball_colour(uint8_t colour);
|
||||
|
||||
void set_player_number_and_size(int player, uint8_t value);
|
||||
void set_player_graphic(int player, uint8_t value);
|
||||
void set_player_reflected(int player, bool reflected);
|
||||
void set_player_delay(int player, bool delay);
|
||||
void set_player_position(int player);
|
||||
void set_player_motion(int player, uint8_t motion);
|
||||
void set_player_missile_colour(int player, uint8_t colour);
|
||||
void set_player_number_and_size(int player, uint8_t value);
|
||||
void set_player_graphic(int player, uint8_t value);
|
||||
void set_player_reflected(int player, bool reflected);
|
||||
void set_player_delay(int player, bool delay);
|
||||
void set_player_position(int player);
|
||||
void set_player_motion(int player, uint8_t motion);
|
||||
void set_player_missile_colour(int player, uint8_t colour);
|
||||
|
||||
void set_missile_enable(int missile, bool enabled);
|
||||
void set_missile_position(int missile);
|
||||
void set_missile_position_to_player(int missile, bool lock);
|
||||
void set_missile_motion(int missile, uint8_t motion);
|
||||
void set_missile_enable(int missile, bool enabled);
|
||||
void set_missile_position(int missile);
|
||||
void set_missile_position_to_player(int missile, bool lock);
|
||||
void set_missile_motion(int missile, uint8_t motion);
|
||||
|
||||
void set_ball_enable(bool enabled);
|
||||
void set_ball_delay(bool delay);
|
||||
void set_ball_position();
|
||||
void set_ball_motion(uint8_t motion);
|
||||
void set_ball_enable(bool enabled);
|
||||
void set_ball_delay(bool delay);
|
||||
void set_ball_position();
|
||||
void set_ball_motion(uint8_t motion);
|
||||
|
||||
void move();
|
||||
void clear_motion();
|
||||
void move();
|
||||
void clear_motion();
|
||||
|
||||
uint8_t get_collision_flags(int offset);
|
||||
void clear_collision_flags();
|
||||
uint8_t get_collision_flags(int offset);
|
||||
void clear_collision_flags();
|
||||
|
||||
void set_crt_delegate(Outputs::CRT::Delegate *);
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
void set_crt_delegate(Outputs::CRT::Delegate *);
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||
int horizontal_counter_ = 0;
|
||||
|
||||
// contains flags to indicate whether sync or blank are currently active
|
||||
int output_mode_ = 0;
|
||||
|
||||
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
||||
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
||||
enum class CollisionType : uint8_t {
|
||||
Playfield = (1 << 0),
|
||||
Ball = (1 << 1),
|
||||
Player0 = (1 << 2),
|
||||
Player1 = (1 << 3),
|
||||
Missile0 = (1 << 4),
|
||||
Missile1 = (1 << 5)
|
||||
};
|
||||
|
||||
int collision_flags_ = 0;
|
||||
int collision_flags_by_buffer_vaules_[64];
|
||||
|
||||
// colour mapping tables
|
||||
enum class ColourMode {
|
||||
Standard = 0,
|
||||
ScoreLeft,
|
||||
ScoreRight,
|
||||
OnTop
|
||||
};
|
||||
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
|
||||
|
||||
enum class ColourIndex {
|
||||
Background = 0,
|
||||
PlayfieldBall,
|
||||
PlayerMissile0,
|
||||
PlayerMissile1
|
||||
};
|
||||
struct Colour {
|
||||
uint16_t luminance_phase;
|
||||
uint8_t original;
|
||||
};
|
||||
std::array<Colour, 4> colour_palette_;
|
||||
void set_colour_palette_entry(size_t index, uint8_t colour);
|
||||
OutputMode tv_standard_;
|
||||
|
||||
// playfield state
|
||||
int background_half_mask_ = 0;
|
||||
enum class PlayfieldPriority {
|
||||
Standard,
|
||||
Score,
|
||||
OnTop
|
||||
} playfield_priority_;
|
||||
uint32_t background_[2] = {0, 0};
|
||||
// contains two 20-bit bitfields representing the background state;
|
||||
// at index 0 is the left-hand side of the playfield with bit 0 being
|
||||
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
||||
// a mirror image of index 0. If the playfield is being displayed in
|
||||
// mirroring mode, background_[0] will be output on the left and
|
||||
// background_[1] on the right; otherwise background_[0] will be
|
||||
// output twice.
|
||||
|
||||
// objects
|
||||
template<class T> struct Object {
|
||||
// the two programmer-set values
|
||||
int position = 0;
|
||||
int motion = 0;
|
||||
|
||||
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
||||
int motion_step = 0;
|
||||
int motion_time = 0;
|
||||
|
||||
// indicates whether this object is currently undergoing motion
|
||||
bool is_moving = false;
|
||||
};
|
||||
|
||||
// player state
|
||||
struct Player: public Object<Player> {
|
||||
int adder = 4;
|
||||
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
|
||||
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
|
||||
int reverse_mask = false; // 7 for a reflected player, 0 for normal
|
||||
int graphic_index = 0;
|
||||
|
||||
int pixel_position = 32, pixel_counter = 0;
|
||||
int latched_pixel4_time = -1;
|
||||
const bool enqueues = true;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
int old_pixel_counter = pixel_counter;
|
||||
pixel_position = std::min(32, pixel_position + count * adder);
|
||||
pixel_counter += count;
|
||||
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
|
||||
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = pixel_counter = 0;
|
||||
copy_index_ = copy;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
|
||||
while(queue_read_pointer_ != queue_write_pointer_) {
|
||||
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
|
||||
if(queue_[queue_read_pointer_].end > time_now) {
|
||||
const int length = time_now - queue_[queue_read_pointer_].start;
|
||||
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
|
||||
queue_[queue_read_pointer_].start = time_now;
|
||||
return;
|
||||
} else {
|
||||
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
}
|
||||
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
|
||||
queue_[queue_write_pointer_].start = start;
|
||||
queue_[queue_write_pointer_].end = end;
|
||||
queue_[queue_write_pointer_].pixel_position = pixel_position;
|
||||
queue_[queue_write_pointer_].adder = adder;
|
||||
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
|
||||
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
|
||||
skip_pixels(end - start, from_horizontal_counter);
|
||||
}
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
int copy_index_ = 0;
|
||||
struct QueuedPixels {
|
||||
int start = 0, end = 0;
|
||||
int pixel_position = 0;
|
||||
int adder = 0;
|
||||
int reverse_mask = false;
|
||||
} queue_[4];
|
||||
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
|
||||
|
||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||
int horizontal_counter_ = 0;
|
||||
|
||||
// contains flags to indicate whether sync or blank are currently active
|
||||
int output_mode_ = 0;
|
||||
|
||||
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
||||
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
||||
enum class CollisionType : uint8_t {
|
||||
Playfield = (1 << 0),
|
||||
Ball = (1 << 1),
|
||||
Player0 = (1 << 2),
|
||||
Player1 = (1 << 3),
|
||||
Missile0 = (1 << 4),
|
||||
Missile1 = (1 << 5)
|
||||
};
|
||||
|
||||
int collision_flags_ = 0;
|
||||
int collision_flags_by_buffer_vaules_[64];
|
||||
|
||||
// colour mapping tables
|
||||
enum class ColourMode {
|
||||
Standard = 0,
|
||||
ScoreLeft,
|
||||
ScoreRight,
|
||||
OnTop
|
||||
};
|
||||
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
|
||||
|
||||
enum class ColourIndex {
|
||||
Background = 0,
|
||||
PlayfieldBall,
|
||||
PlayerMissile0,
|
||||
PlayerMissile1
|
||||
};
|
||||
struct Colour {
|
||||
uint16_t luminance_phase;
|
||||
uint8_t original;
|
||||
};
|
||||
std::array<Colour, 4> colour_palette_;
|
||||
void set_colour_palette_entry(size_t index, uint8_t colour);
|
||||
OutputMode tv_standard_;
|
||||
|
||||
// playfield state
|
||||
int background_half_mask_ = 0;
|
||||
enum class PlayfieldPriority {
|
||||
Standard,
|
||||
Score,
|
||||
OnTop
|
||||
} playfield_priority_;
|
||||
uint32_t background_[2] = {0, 0};
|
||||
// contains two 20-bit bitfields representing the background state;
|
||||
// at index 0 is the left-hand side of the playfield with bit 0 being
|
||||
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
||||
// a mirror image of index 0. If the playfield is being displayed in
|
||||
// mirroring mode, background_[0] will be output on the left and
|
||||
// background_[1] on the right; otherwise background_[0] will be
|
||||
// output twice.
|
||||
|
||||
// objects
|
||||
template<class T> struct Object {
|
||||
// the two programmer-set values
|
||||
int position = 0;
|
||||
int motion = 0;
|
||||
|
||||
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
||||
int motion_step = 0;
|
||||
int motion_time = 0;
|
||||
|
||||
// indicates whether this object is currently undergoing motion
|
||||
bool is_moving = false;
|
||||
};
|
||||
|
||||
// player state
|
||||
struct Player: public Object<Player> {
|
||||
int adder = 4;
|
||||
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
|
||||
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
|
||||
int reverse_mask = false; // 7 for a reflected player, 0 for normal
|
||||
int graphic_index = 0;
|
||||
|
||||
int pixel_position = 32, pixel_counter = 0;
|
||||
int latched_pixel4_time = -1;
|
||||
const bool enqueues = true;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
int old_pixel_counter = pixel_counter;
|
||||
pixel_position = std::min(32, pixel_position + count * adder);
|
||||
pixel_counter += count;
|
||||
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
|
||||
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
|
||||
}
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
|
||||
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
|
||||
int output_cursor = 0;
|
||||
while(output_pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
|
||||
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
|
||||
output_cursor++;
|
||||
output_pixel_position += output_adder;
|
||||
}
|
||||
}
|
||||
} player_[2];
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = pixel_counter = 0;
|
||||
copy_index_ = copy;
|
||||
// common actor for things that appear as a horizontal run of pixels
|
||||
struct HorizontalRun: public Object<HorizontalRun> {
|
||||
int pixel_position = 0;
|
||||
int size = 1;
|
||||
const bool enqueues = false;
|
||||
|
||||
inline void skip_pixels(const int count, int) {
|
||||
pixel_position = std::max(0, pixel_position - count);
|
||||
}
|
||||
|
||||
inline void reset_pixels(int) {
|
||||
pixel_position = size;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) {
|
||||
int output_cursor = 0;
|
||||
while(pixel_position && output_cursor < count)
|
||||
{
|
||||
target[output_cursor] |= collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position--;
|
||||
}
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
|
||||
void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {}
|
||||
void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {}
|
||||
};
|
||||
|
||||
// missile state
|
||||
struct Missile: public HorizontalRun {
|
||||
bool enabled = false;
|
||||
bool locked_to_player = false;
|
||||
int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled && !locked_to_player) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
} missile_[2];
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
|
||||
while(queue_read_pointer_ != queue_write_pointer_) {
|
||||
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
|
||||
if(queue_[queue_read_pointer_].end > time_now) {
|
||||
const int length = time_now - queue_[queue_read_pointer_].start;
|
||||
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
|
||||
queue_[queue_read_pointer_].start = time_now;
|
||||
return;
|
||||
} else {
|
||||
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
}
|
||||
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
|
||||
}
|
||||
// ball state
|
||||
struct Ball: public HorizontalRun {
|
||||
bool enabled[2] = {false, false};
|
||||
int enabled_index = 0;
|
||||
const int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled[enabled_index]) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
} ball_;
|
||||
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
|
||||
queue_[queue_write_pointer_].start = start;
|
||||
queue_[queue_write_pointer_].end = end;
|
||||
queue_[queue_write_pointer_].pixel_position = pixel_position;
|
||||
queue_[queue_write_pointer_].adder = adder;
|
||||
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
|
||||
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
|
||||
skip_pixels(end - start, from_horizontal_counter);
|
||||
}
|
||||
// motion
|
||||
bool horizontal_blank_extend_ = false;
|
||||
template<class T> void perform_border_motion(T &object, int start, int end);
|
||||
template<class T> void perform_motion_step(T &object);
|
||||
|
||||
private:
|
||||
int copy_index_ = 0;
|
||||
struct QueuedPixels {
|
||||
int start = 0, end = 0;
|
||||
int pixel_position = 0;
|
||||
int adder = 0;
|
||||
int reverse_mask = false;
|
||||
} queue_[4];
|
||||
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
|
||||
// drawing methods and state
|
||||
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
|
||||
inline void draw_playfield(int start, int end);
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
|
||||
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
|
||||
int output_cursor = 0;
|
||||
while(output_pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
|
||||
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
|
||||
output_cursor++;
|
||||
output_pixel_position += output_adder;
|
||||
}
|
||||
}
|
||||
inline void output_for_cycles(int number_of_cycles);
|
||||
inline void output_line();
|
||||
|
||||
} player_[2];
|
||||
|
||||
// common actor for things that appear as a horizontal run of pixels
|
||||
struct HorizontalRun: public Object<HorizontalRun> {
|
||||
int pixel_position = 0;
|
||||
int size = 1;
|
||||
const bool enqueues = false;
|
||||
|
||||
inline void skip_pixels(const int count, int) {
|
||||
pixel_position = std::max(0, pixel_position - count);
|
||||
}
|
||||
|
||||
inline void reset_pixels(int) {
|
||||
pixel_position = size;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) {
|
||||
int output_cursor = 0;
|
||||
while(pixel_position && output_cursor < count)
|
||||
{
|
||||
target[output_cursor] |= collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position--;
|
||||
}
|
||||
}
|
||||
|
||||
void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {}
|
||||
void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {}
|
||||
};
|
||||
|
||||
// missile state
|
||||
struct Missile: public HorizontalRun {
|
||||
bool enabled = false;
|
||||
bool locked_to_player = false;
|
||||
int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled && !locked_to_player) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
} missile_[2];
|
||||
|
||||
// ball state
|
||||
struct Ball: public HorizontalRun {
|
||||
bool enabled[2] = {false, false};
|
||||
int enabled_index = 0;
|
||||
const int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled[enabled_index]) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
} ball_;
|
||||
|
||||
// motion
|
||||
bool horizontal_blank_extend_ = false;
|
||||
template<class T> void perform_border_motion(T &object, int start, int end);
|
||||
template<class T> void perform_motion_step(T &object);
|
||||
|
||||
// drawing methods and state
|
||||
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
|
||||
inline void draw_playfield(int start, int end);
|
||||
|
||||
inline void output_for_cycles(int number_of_cycles);
|
||||
inline void output_line();
|
||||
|
||||
int pixels_start_location_ = 0;
|
||||
uint16_t *pixel_target_ = nullptr;
|
||||
inline void output_pixels(int start, int end);
|
||||
int pixels_start_location_ = 0;
|
||||
uint16_t *pixel_target_ = nullptr;
|
||||
inline void output_pixels(int start, int end);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,32 +18,32 @@ namespace Atari2600 {
|
||||
constexpr int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
|
||||
public:
|
||||
TIASound(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
public:
|
||||
TIASound(Concurrency::AsyncTaskQueue<false> &);
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
|
||||
// To satisfy ::SampleSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
// To satisfy ::SampleSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
uint8_t control_[2];
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
uint8_t control_[2];
|
||||
|
||||
int poly4_counter_[2];
|
||||
int poly5_counter_[2];
|
||||
int poly9_counter_[2];
|
||||
int output_state_[2];
|
||||
int poly4_counter_[2];
|
||||
int poly5_counter_[2];
|
||||
int poly9_counter_[2];
|
||||
int output_state_[2];
|
||||
|
||||
int divider_counter_[2];
|
||||
int16_t per_channel_volume_ = 0;
|
||||
int divider_counter_[2];
|
||||
int16_t per_channel_volume_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -34,75 +34,75 @@ namespace Coleco {
|
||||
namespace Vision {
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
|
||||
Input('0'), Input('1'), Input('2'),
|
||||
Input('3'), Input('4'), Input('5'),
|
||||
Input('6'), Input('7'), Input('8'),
|
||||
Input('9'), Input('*'), Input('#'),
|
||||
}) {}
|
||||
Input('0'), Input('1'), Input('2'),
|
||||
Input('3'), Input('4'), Input('5'),
|
||||
Input('6'), Input('7'), Input('8'),
|
||||
Input('9'), Input('*'), Input('#'),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
case Input::Key:
|
||||
if(!is_active) keypad_ |= 0xf;
|
||||
else {
|
||||
uint8_t mask = 0xf;
|
||||
switch(digital_input.info.key.symbol) {
|
||||
case '8': mask = 0x1; break;
|
||||
case '4': mask = 0x2; break;
|
||||
case '5': mask = 0x3; break;
|
||||
case '7': mask = 0x5; break;
|
||||
case '#': mask = 0x6; break;
|
||||
case '2': mask = 0x7; break;
|
||||
case '*': mask = 0x9; break;
|
||||
case '0': mask = 0xa; break;
|
||||
case '9': mask = 0xb; break;
|
||||
case '3': mask = 0xc; break;
|
||||
case '1': mask = 0xd; break;
|
||||
case '6': mask = 0xe; break;
|
||||
default: break;
|
||||
}
|
||||
keypad_ = (keypad_ & 0xf0) | mask;
|
||||
}
|
||||
break;
|
||||
|
||||
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
|
||||
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
|
||||
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
|
||||
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
|
||||
case Input::Fire:
|
||||
switch(digital_input.info.control.index) {
|
||||
case Input::Key:
|
||||
if(!is_active) keypad_ |= 0xf;
|
||||
else {
|
||||
uint8_t mask = 0xf;
|
||||
switch(digital_input.info.key.symbol) {
|
||||
case '8': mask = 0x1; break;
|
||||
case '4': mask = 0x2; break;
|
||||
case '5': mask = 0x3; break;
|
||||
case '7': mask = 0x5; break;
|
||||
case '#': mask = 0x6; break;
|
||||
case '2': mask = 0x7; break;
|
||||
case '*': mask = 0x9; break;
|
||||
case '0': mask = 0xa; break;
|
||||
case '9': mask = 0xb; break;
|
||||
case '3': mask = 0xc; break;
|
||||
case '1': mask = 0xd; break;
|
||||
case '6': mask = 0xe; break;
|
||||
default: break;
|
||||
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
|
||||
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
keypad_ = (keypad_ & 0xf0) | mask;
|
||||
}
|
||||
break;
|
||||
|
||||
uint8_t get_direction_input() {
|
||||
return direction_;
|
||||
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
|
||||
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
|
||||
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
|
||||
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
|
||||
case Input::Fire:
|
||||
switch(digital_input.info.control.index) {
|
||||
default: break;
|
||||
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
|
||||
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_keypad_input() {
|
||||
return keypad_;
|
||||
}
|
||||
uint8_t get_direction_input() {
|
||||
return direction_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t direction_ = 0xff;
|
||||
uint8_t keypad_ = 0x7f;
|
||||
uint8_t get_keypad_input() {
|
||||
return keypad_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t direction_ = 0xff;
|
||||
uint8_t keypad_ = 0x7f;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
@ -114,297 +114,297 @@ class ConcreteMachine:
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::JoystickMachine {
|
||||
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
|
||||
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
|
||||
mixer_(sn76489_, ay_),
|
||||
speaker_(mixer_) {
|
||||
speaker_.set_input_rate(3579545.0f / float(sn76489_divider));
|
||||
set_clock_rate(3579545);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
|
||||
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
|
||||
mixer_(sn76489_, ay_),
|
||||
speaker_(mixer_) {
|
||||
speaker_.set_input_rate(3579545.0f / float(sn76489_divider));
|
||||
set_clock_rate(3579545);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
|
||||
const ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
|
||||
const ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
bios_ = roms.find(rom_name)->second;
|
||||
|
||||
if(!target.media.cartridges.empty()) {
|
||||
const auto &segment = target.media.cartridges.front()->get_segments().front();
|
||||
cartridge_ = segment.data;
|
||||
if(cartridge_.size() >= 32768)
|
||||
cartridge_address_limit_ = 0xffff;
|
||||
else
|
||||
cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1);
|
||||
|
||||
if(cartridge_.size() > 32768) {
|
||||
// Ensure the cartrige is a multiple of 16kb in size, as that won't
|
||||
// be checked when paging.
|
||||
const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384;
|
||||
cartridge_.resize(cartridge_.size() + extension);
|
||||
|
||||
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
|
||||
cartridge_pages_[1] = cartridge_.data();
|
||||
is_megacart_ = true;
|
||||
} else {
|
||||
// Ensure at least 32kb is allocated to the cartrige so that
|
||||
// reads are never out of bounds.
|
||||
cartridge_.resize(32768);
|
||||
|
||||
cartridge_pages_[0] = cartridge_.data();
|
||||
cartridge_pages_[1] = cartridge_.data() + 16384;
|
||||
is_megacart_ = false;
|
||||
}
|
||||
bios_ = roms.find(rom_name)->second;
|
||||
|
||||
if(!target.media.cartridges.empty()) {
|
||||
const auto &segment = target.media.cartridges.front()->get_segments().front();
|
||||
cartridge_ = segment.data;
|
||||
if(cartridge_.size() >= 32768)
|
||||
cartridge_address_limit_ = 0xffff;
|
||||
else
|
||||
cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1);
|
||||
|
||||
if(cartridge_.size() > 32768) {
|
||||
// Ensure the cartrige is a multiple of 16kb in size, as that won't
|
||||
// be checked when paging.
|
||||
const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384;
|
||||
cartridge_.resize(cartridge_.size() + extension);
|
||||
|
||||
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
|
||||
cartridge_pages_[1] = cartridge_.data();
|
||||
is_megacart_ = true;
|
||||
} else {
|
||||
// Ensure at least 32kb is allocated to the cartrige so that
|
||||
// reads are never out of bounds.
|
||||
cartridge_.resize(32768);
|
||||
|
||||
cartridge_pages_[0] = cartridge_.data();
|
||||
cartridge_pages_[1] = cartridge_.data() + 16384;
|
||||
is_megacart_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ColecoVisions have composite output only.
|
||||
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
// ColecoVisions have composite output only.
|
||||
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
vdp_.last_valid()->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return vdp_.last_valid()->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
vdp_.last_valid()->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const final {
|
||||
return vdp_.last_valid()->get_display_type();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
// The SN76489 will use its ready line to trigger the Z80's wait, which will add
|
||||
// thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle.
|
||||
// This code works out the delay up front in order to simplify execution flow, though
|
||||
// technically this is a little duplicative.
|
||||
HalfCycles penalty(0);
|
||||
if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) {
|
||||
penalty = HalfCycles(62);
|
||||
} else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) {
|
||||
penalty = HalfCycles(2);
|
||||
}
|
||||
const HalfCycles length = cycle.length + penalty;
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
if(vdp_ += length) {
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
}
|
||||
time_since_sn76489_update_ += length;
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
vdp_.last_valid()->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return vdp_.last_valid()->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
vdp_.last_valid()->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const final {
|
||||
return vdp_.last_valid()->get_display_type();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
// The SN76489 will use its ready line to trigger the Z80's wait, which will add
|
||||
// thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle.
|
||||
// This code works out the delay up front in order to simplify execution flow, though
|
||||
// technically this is a little duplicative.
|
||||
HalfCycles penalty(0);
|
||||
if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) {
|
||||
penalty = HalfCycles(62);
|
||||
} else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) {
|
||||
penalty = HalfCycles(2);
|
||||
}
|
||||
const HalfCycles length = cycle.length + penalty;
|
||||
|
||||
if(vdp_ += length) {
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
}
|
||||
time_since_sn76489_update_ += length;
|
||||
|
||||
// Act only if necessary.
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
[[fallthrough]];
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else {
|
||||
*cycle.value = bios_[address];
|
||||
}
|
||||
} else if(super_game_module_.replace_ram && address < 0x8000) {
|
||||
// Act only if necessary.
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
[[fallthrough]];
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
*cycle.value = ram_[address & 1023];
|
||||
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
|
||||
if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
*cycle.value = bios_[address];
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
ram_[address & 1023] = *cycle.value;
|
||||
} else if(is_megacart_ && address >= 0xffc0) {
|
||||
} else if(super_game_module_.replace_ram && address < 0x8000) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
*cycle.value = ram_[address & 1023];
|
||||
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
|
||||
if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
break;
|
||||
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
*cycle.value = vdp_->read(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
ram_[address & 1023] = *cycle.value;
|
||||
} else if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: {
|
||||
const std::size_t joystick_id = (address&2) >> 1;
|
||||
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
|
||||
if(joysticks_in_keypad_mode_) {
|
||||
*cycle.value = joystick->get_keypad_input();
|
||||
} else {
|
||||
*cycle.value = joystick->get_direction_input();
|
||||
}
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
*cycle.value = vdp_->read(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
break;
|
||||
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
case 7: {
|
||||
const std::size_t joystick_id = (address&2) >> 1;
|
||||
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
|
||||
if(joysticks_in_keypad_mode_) {
|
||||
*cycle.value = joystick->get_keypad_input();
|
||||
} else {
|
||||
*cycle.value = joystick->get_direction_input();
|
||||
}
|
||||
|
||||
default:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// Read AY data.
|
||||
update_audio();
|
||||
*cycle.value = GI::AY38910::Utility::read(ay_);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
const int eighth = (address >> 5) & 7;
|
||||
switch(eighth) {
|
||||
case 4: case 6:
|
||||
joysticks_in_keypad_mode_ = eighth == 4;
|
||||
break;
|
||||
default:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// Read AY data.
|
||||
update_audio();
|
||||
*cycle.value = GI::AY38910::Utility::read(ay_);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
vdp_->write(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
const int eighth = (address >> 5) & 7;
|
||||
switch(eighth) {
|
||||
case 4: case 6:
|
||||
joysticks_in_keypad_mode_ = eighth == 4;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.write(*cycle.value);
|
||||
break;
|
||||
case 5:
|
||||
vdp_->write(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Catch Super Game Module accesses; it decodes more thoroughly.
|
||||
switch(address&0xff) {
|
||||
default: break;
|
||||
case 0x7f:
|
||||
super_game_module_.replace_bios = !((*cycle.value)&0x2);
|
||||
break;
|
||||
case 0x50:
|
||||
// Set AY address.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::select_register(ay_, *cycle.value);
|
||||
break;
|
||||
case 0x51:
|
||||
// Set AY data.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::write_data(ay_, *cycle.value);
|
||||
break;
|
||||
case 0x53:
|
||||
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.write(*cycle.value);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Catch Super Game Module accesses; it decodes more thoroughly.
|
||||
switch(address&0xff) {
|
||||
default: break;
|
||||
case 0x7f:
|
||||
super_game_module_.replace_bios = !((*cycle.value)&0x2);
|
||||
break;
|
||||
case 0x50:
|
||||
// Set AY address.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::select_register(ay_, *cycle.value);
|
||||
break;
|
||||
case 0x51:
|
||||
// Set AY data.
|
||||
update_audio();
|
||||
GI::AY38910::Utility::write_data(ay_, *cycle.value);
|
||||
break;
|
||||
case 0x53:
|
||||
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
void flush_output(int outputs) final {
|
||||
if(outputs & Output::Video) {
|
||||
vdp_.flush();
|
||||
}
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
float get_confidence() final {
|
||||
if(pc_zero_accesses_ > 1) return 0.0f;
|
||||
return confidence_counter_.get_confidence();
|
||||
return penalty;
|
||||
}
|
||||
|
||||
void flush_output(int outputs) final {
|
||||
if(outputs & Output::Video) {
|
||||
vdp_.flush();
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
return options;
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
}
|
||||
float get_confidence() final {
|
||||
if(pc_zero_accesses_ > 1) return 0.0f;
|
||||
return confidence_counter_.get_confidence();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void page_megacart(uint16_t address) {
|
||||
const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size();
|
||||
cartridge_pages_[1] = &cartridge_[selected_start];
|
||||
}
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
|
||||
}
|
||||
// MARK: - Configuration options.
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
return options;
|
||||
}
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
}
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
|
||||
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
|
||||
private:
|
||||
inline void page_megacart(uint16_t address) {
|
||||
const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size();
|
||||
cartridge_pages_[1] = &cartridge_[selected_start];
|
||||
}
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bios_;
|
||||
std::vector<uint8_t> cartridge_;
|
||||
uint8_t *cartridge_pages_[2];
|
||||
uint8_t ram_[1024];
|
||||
bool is_megacart_ = false;
|
||||
uint16_t cartridge_address_limit_ = 0;
|
||||
struct {
|
||||
bool replace_bios = false;
|
||||
bool replace_ram = false;
|
||||
uint8_t ram[32768];
|
||||
} super_game_module_;
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
bool joysticks_in_keypad_mode_ = false;
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
|
||||
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
|
||||
|
||||
HalfCycles time_since_sn76489_update_;
|
||||
std::vector<uint8_t> bios_;
|
||||
std::vector<uint8_t> cartridge_;
|
||||
uint8_t *cartridge_pages_[2];
|
||||
uint8_t ram_[1024];
|
||||
bool is_megacart_ = false;
|
||||
uint16_t cartridge_address_limit_ = 0;
|
||||
struct {
|
||||
bool replace_bios = false;
|
||||
bool replace_ram = false;
|
||||
uint8_t ram[32768];
|
||||
} super_game_module_;
|
||||
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
int pc_zero_accesses_ = 0;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
bool joysticks_in_keypad_mode_ = false;
|
||||
|
||||
HalfCycles time_since_sn76489_update_;
|
||||
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
int pc_zero_accesses_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -112,43 +112,43 @@ class KeyboardMachine: public KeyActions {
|
||||
allowing automatic mapping from keyboard inputs to KeyActions.
|
||||
*/
|
||||
class MappedKeyboardMachine: public Inputs::Keyboard::Delegate, public KeyboardMachine {
|
||||
public:
|
||||
MappedKeyboardMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
|
||||
public:
|
||||
MappedKeyboardMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
|
||||
|
||||
/*!
|
||||
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||
See the character mapper for logical mapping.
|
||||
*/
|
||||
class KeyboardMapper {
|
||||
public:
|
||||
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0;
|
||||
};
|
||||
/*!
|
||||
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||
See the character mapper for logical mapping.
|
||||
*/
|
||||
class KeyboardMapper {
|
||||
public:
|
||||
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0;
|
||||
};
|
||||
|
||||
/// Terminates a key sequence from the character mapper.
|
||||
static constexpr uint16_t KeyEndSequence = 0xffff;
|
||||
/// Terminates a key sequence from the character mapper.
|
||||
static constexpr uint16_t KeyEndSequence = 0xffff;
|
||||
|
||||
/*!
|
||||
Indicates that a key is not mapped (for the keyboard mapper) or that a
|
||||
character cannot be typed (for the character mapper).
|
||||
*/
|
||||
static constexpr uint16_t KeyNotMapped = 0xfffe;
|
||||
/*!
|
||||
Indicates that a key is not mapped (for the keyboard mapper) or that a
|
||||
character cannot be typed (for the character mapper).
|
||||
*/
|
||||
static constexpr uint16_t KeyNotMapped = 0xfffe;
|
||||
|
||||
/*!
|
||||
Allows individual machines to provide the mapping between host keys
|
||||
as per Inputs::Keyboard and their native scheme.
|
||||
*/
|
||||
virtual KeyboardMapper *get_keyboard_mapper();
|
||||
/*!
|
||||
Allows individual machines to provide the mapping between host keys
|
||||
as per Inputs::Keyboard and their native scheme.
|
||||
*/
|
||||
virtual KeyboardMapper *get_keyboard_mapper();
|
||||
|
||||
/*!
|
||||
Provides a keyboard that obtains this machine's keyboard mapper, maps
|
||||
the key and supplies it via the KeyActions.
|
||||
*/
|
||||
virtual Inputs::Keyboard &get_keyboard() override;
|
||||
/*!
|
||||
Provides a keyboard that obtains this machine's keyboard mapper, maps
|
||||
the key and supplies it via the KeyActions.
|
||||
*/
|
||||
virtual Inputs::Keyboard &get_keyboard() override;
|
||||
|
||||
private:
|
||||
bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||
void reset_all_keys(Inputs::Keyboard *keyboard) override;
|
||||
Inputs::Keyboard keyboard_;
|
||||
private:
|
||||
bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||
void reset_all_keys(Inputs::Keyboard *keyboard) override;
|
||||
Inputs::Keyboard keyboard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -23,77 +23,77 @@ namespace MachineTypes {
|
||||
|
||||
*/
|
||||
class TimedMachine {
|
||||
public:
|
||||
/// Runs the machine for @c duration seconds.
|
||||
virtual void run_for(Time::Seconds duration) {
|
||||
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
|
||||
clock_conversion_error_ = std::fmod(cycles, 1.0);
|
||||
run_for(Cycles(int(cycles)));
|
||||
public:
|
||||
/// Runs the machine for @c duration seconds.
|
||||
virtual void run_for(Time::Seconds duration) {
|
||||
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
|
||||
clock_conversion_error_ = std::fmod(cycles, 1.0);
|
||||
run_for(Cycles(int(cycles)));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
|
||||
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
|
||||
fiction: it will apply across the system, including to the CRT.
|
||||
*/
|
||||
virtual void set_speed_multiplier(double multiplier) {
|
||||
if(speed_multiplier_ == multiplier) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
|
||||
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
|
||||
fiction: it will apply across the system, including to the CRT.
|
||||
*/
|
||||
virtual void set_speed_multiplier(double multiplier) {
|
||||
if(speed_multiplier_ == multiplier) {
|
||||
return;
|
||||
}
|
||||
speed_multiplier_ = multiplier;
|
||||
|
||||
speed_multiplier_ = multiplier;
|
||||
auto audio_producer = dynamic_cast<AudioProducer *>(this);
|
||||
if(!audio_producer) return;
|
||||
|
||||
auto audio_producer = dynamic_cast<AudioProducer *>(this);
|
||||
if(!audio_producer) return;
|
||||
|
||||
auto speaker = audio_producer->get_speaker();
|
||||
if(speaker) {
|
||||
speaker->set_input_rate_multiplier(float(multiplier));
|
||||
}
|
||||
auto speaker = audio_producer->get_speaker();
|
||||
if(speaker) {
|
||||
speaker->set_input_rate_multiplier(float(multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The current speed multiplier.
|
||||
*/
|
||||
virtual double get_speed_multiplier() const {
|
||||
return speed_multiplier_;
|
||||
}
|
||||
/*!
|
||||
@returns The current speed multiplier.
|
||||
*/
|
||||
virtual double get_speed_multiplier() const {
|
||||
return speed_multiplier_;
|
||||
}
|
||||
|
||||
/// @returns The confidence that this machine is running content it understands.
|
||||
virtual float get_confidence() { return 0.5f; }
|
||||
virtual std::string debug_type() { return ""; }
|
||||
/// @returns The confidence that this machine is running content it understands.
|
||||
virtual float get_confidence() { return 0.5f; }
|
||||
virtual std::string debug_type() { return ""; }
|
||||
|
||||
struct Output {
|
||||
static constexpr int Video = 1 << 0;
|
||||
static constexpr int Audio = 1 << 1;
|
||||
struct Output {
|
||||
static constexpr int Video = 1 << 0;
|
||||
static constexpr int Audio = 1 << 1;
|
||||
|
||||
static constexpr int All = Video | Audio;
|
||||
};
|
||||
/// Ensures all locally-buffered output is posted onward for the types of output indicated
|
||||
/// by the bitfield argument, which is comprised of flags from the namespace @c Output.
|
||||
virtual void flush_output(int) {}
|
||||
static constexpr int All = Video | Audio;
|
||||
};
|
||||
/// Ensures all locally-buffered output is posted onward for the types of output indicated
|
||||
/// by the bitfield argument, which is comprised of flags from the namespace @c Output.
|
||||
virtual void flush_output(int) {}
|
||||
|
||||
protected:
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
protected:
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
|
||||
/// Sets this machine's clock rate.
|
||||
void set_clock_rate(double clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
/// Sets this machine's clock rate.
|
||||
void set_clock_rate(double clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
|
||||
/// Gets this machine's clock rate.
|
||||
double get_clock_rate() const {
|
||||
return clock_rate_;
|
||||
}
|
||||
/// Gets this machine's clock rate.
|
||||
double get_clock_rate() const {
|
||||
return clock_rate_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Give the ScanProducer access to this machine's clock rate.
|
||||
friend class ScanProducer;
|
||||
private:
|
||||
// Give the ScanProducer access to this machine's clock rate.
|
||||
friend class ScanProducer;
|
||||
|
||||
double clock_rate_ = 1.0;
|
||||
double clock_conversion_error_ = 0.0;
|
||||
double speed_multiplier_ = 1.0;
|
||||
double clock_rate_ = 1.0;
|
||||
double clock_conversion_error_ = 0.0;
|
||||
double speed_multiplier_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -112,8 +112,8 @@ struct TestProcessor: public CPU::MC68000::BusHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int instructions_remaining_;
|
||||
private:
|
||||
int instructions_remaining_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -34,377 +34,377 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
|
||||
using Flags = InstructionSet::x86::Flags;
|
||||
struct Registers {
|
||||
public:
|
||||
// static constexpr bool is_32bit = false;
|
||||
public:
|
||||
// static constexpr bool is_32bit = false;
|
||||
|
||||
uint8_t &al() { return ax_.halves.low; }
|
||||
uint8_t &ah() { return ax_.halves.high; }
|
||||
uint16_t &ax() { return ax_.full; }
|
||||
uint8_t &al() { return ax_.halves.low; }
|
||||
uint8_t &ah() { return ax_.halves.high; }
|
||||
uint16_t &ax() { return ax_.full; }
|
||||
|
||||
CPU::RegisterPair16 &axp() { return ax_; }
|
||||
CPU::RegisterPair16 &axp() { return ax_; }
|
||||
|
||||
uint8_t &cl() { return cx_.halves.low; }
|
||||
uint8_t &ch() { return cx_.halves.high; }
|
||||
uint16_t &cx() { return cx_.full; }
|
||||
uint8_t &cl() { return cx_.halves.low; }
|
||||
uint8_t &ch() { return cx_.halves.high; }
|
||||
uint16_t &cx() { return cx_.full; }
|
||||
|
||||
uint8_t &dl() { return dx_.halves.low; }
|
||||
uint8_t &dh() { return dx_.halves.high; }
|
||||
uint16_t &dx() { return dx_.full; }
|
||||
uint8_t &dl() { return dx_.halves.low; }
|
||||
uint8_t &dh() { return dx_.halves.high; }
|
||||
uint16_t &dx() { return dx_.full; }
|
||||
|
||||
uint8_t &bl() { return bx_.halves.low; }
|
||||
uint8_t &bh() { return bx_.halves.high; }
|
||||
uint16_t &bx() { return bx_.full; }
|
||||
uint8_t &bl() { return bx_.halves.low; }
|
||||
uint8_t &bh() { return bx_.halves.high; }
|
||||
uint16_t &bx() { return bx_.full; }
|
||||
|
||||
uint16_t &sp() { return sp_; }
|
||||
uint16_t &bp() { return bp_; }
|
||||
uint16_t &si() { return si_; }
|
||||
uint16_t &di() { return di_; }
|
||||
uint16_t &sp() { return sp_; }
|
||||
uint16_t &bp() { return bp_; }
|
||||
uint16_t &si() { return si_; }
|
||||
uint16_t &di() { return di_; }
|
||||
|
||||
uint16_t &ip() { return ip_; }
|
||||
uint16_t &ip() { return ip_; }
|
||||
|
||||
uint16_t &es() { return es_; }
|
||||
uint16_t &cs() { return cs_; }
|
||||
uint16_t &ds() { return ds_; }
|
||||
uint16_t &ss() { return ss_; }
|
||||
uint16_t &es() { return es_; }
|
||||
uint16_t &cs() { return cs_; }
|
||||
uint16_t &ds() { return ds_; }
|
||||
uint16_t &ss() { return ss_; }
|
||||
|
||||
const uint16_t es() const { return es_; }
|
||||
const uint16_t cs() const { return cs_; }
|
||||
const uint16_t ds() const { return ds_; }
|
||||
const uint16_t ss() const { return ss_; }
|
||||
const uint16_t es() const { return es_; }
|
||||
const uint16_t cs() const { return cs_; }
|
||||
const uint16_t ds() const { return ds_; }
|
||||
const uint16_t ss() const { return ss_; }
|
||||
|
||||
bool operator ==(const Registers &rhs) const {
|
||||
return
|
||||
ax_.full == rhs.ax_.full &&
|
||||
cx_.full == rhs.cx_.full &&
|
||||
dx_.full == rhs.dx_.full &&
|
||||
bx_.full == rhs.bx_.full &&
|
||||
sp_ == rhs.sp_ &&
|
||||
bp_ == rhs.bp_ &&
|
||||
si_ == rhs.si_ &&
|
||||
di_ == rhs.di_ &&
|
||||
es_ == rhs.es_ &&
|
||||
cs_ == rhs.cs_ &&
|
||||
ds_ == rhs.ds_ &&
|
||||
si_ == rhs.si_ &&
|
||||
ip_ == rhs.ip_;
|
||||
}
|
||||
bool operator ==(const Registers &rhs) const {
|
||||
return
|
||||
ax_.full == rhs.ax_.full &&
|
||||
cx_.full == rhs.cx_.full &&
|
||||
dx_.full == rhs.dx_.full &&
|
||||
bx_.full == rhs.bx_.full &&
|
||||
sp_ == rhs.sp_ &&
|
||||
bp_ == rhs.bp_ &&
|
||||
si_ == rhs.si_ &&
|
||||
di_ == rhs.di_ &&
|
||||
es_ == rhs.es_ &&
|
||||
cs_ == rhs.cs_ &&
|
||||
ds_ == rhs.ds_ &&
|
||||
si_ == rhs.si_ &&
|
||||
ip_ == rhs.ip_;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::RegisterPair16 ax_;
|
||||
CPU::RegisterPair16 cx_;
|
||||
CPU::RegisterPair16 dx_;
|
||||
CPU::RegisterPair16 bx_;
|
||||
private:
|
||||
CPU::RegisterPair16 ax_;
|
||||
CPU::RegisterPair16 cx_;
|
||||
CPU::RegisterPair16 dx_;
|
||||
CPU::RegisterPair16 bx_;
|
||||
|
||||
uint16_t sp_;
|
||||
uint16_t bp_;
|
||||
uint16_t si_;
|
||||
uint16_t di_;
|
||||
uint16_t es_, cs_, ds_, ss_;
|
||||
uint16_t ip_;
|
||||
uint16_t sp_;
|
||||
uint16_t bp_;
|
||||
uint16_t si_;
|
||||
uint16_t di_;
|
||||
uint16_t es_, cs_, ds_, ss_;
|
||||
uint16_t ip_;
|
||||
};
|
||||
class Segments {
|
||||
public:
|
||||
Segments(const Registers ®isters) : registers_(registers) {}
|
||||
public:
|
||||
Segments(const Registers ®isters) : registers_(registers) {}
|
||||
|
||||
using Source = InstructionSet::x86::Source;
|
||||
using Source = InstructionSet::x86::Source;
|
||||
|
||||
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
||||
void did_update(Source segment) {
|
||||
switch(segment) {
|
||||
default: break;
|
||||
case Source::ES: es_base_ = registers_.es() << 4; break;
|
||||
case Source::CS: cs_base_ = registers_.cs() << 4; break;
|
||||
case Source::DS: ds_base_ = registers_.ds() << 4; break;
|
||||
case Source::SS: ss_base_ = registers_.ss() << 4; break;
|
||||
}
|
||||
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
||||
void did_update(Source segment) {
|
||||
switch(segment) {
|
||||
default: break;
|
||||
case Source::ES: es_base_ = registers_.es() << 4; break;
|
||||
case Source::CS: cs_base_ = registers_.cs() << 4; break;
|
||||
case Source::DS: ds_base_ = registers_.ds() << 4; break;
|
||||
case Source::SS: ss_base_ = registers_.ss() << 4; break;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
did_update(Source::ES);
|
||||
did_update(Source::CS);
|
||||
did_update(Source::DS);
|
||||
did_update(Source::SS);
|
||||
}
|
||||
void reset() {
|
||||
did_update(Source::ES);
|
||||
did_update(Source::CS);
|
||||
did_update(Source::DS);
|
||||
did_update(Source::SS);
|
||||
}
|
||||
|
||||
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
||||
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
||||
|
||||
bool operator ==(const Segments &rhs) const {
|
||||
return
|
||||
es_base_ == rhs.es_base_ &&
|
||||
cs_base_ == rhs.cs_base_ &&
|
||||
ds_base_ == rhs.ds_base_ &&
|
||||
ss_base_ == rhs.ss_base_;
|
||||
}
|
||||
bool operator ==(const Segments &rhs) const {
|
||||
return
|
||||
es_base_ == rhs.es_base_ &&
|
||||
cs_base_ == rhs.cs_base_ &&
|
||||
ds_base_ == rhs.ds_base_ &&
|
||||
ss_base_ == rhs.ss_base_;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registers ®isters_;
|
||||
private:
|
||||
const Registers ®isters_;
|
||||
};
|
||||
struct Memory {
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
|
||||
// Constructor.
|
||||
Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {
|
||||
memory.resize(1024*1024);
|
||||
// Constructor.
|
||||
Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {
|
||||
memory.resize(1024*1024);
|
||||
}
|
||||
|
||||
// Initialisation.
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
|
||||
void seed(uint32_t address, uint8_t value) {
|
||||
memory[address] = value;
|
||||
tags[address] = Tag::Seeded;
|
||||
}
|
||||
|
||||
void touch(uint32_t address) {
|
||||
tags[address] = Tag::AccessExpected;
|
||||
}
|
||||
|
||||
//
|
||||
// Preauthorisation call-ins.
|
||||
//
|
||||
void preauthorise_stack_write(uint32_t length) {
|
||||
uint16_t sp = registers_.sp();
|
||||
while(length--) {
|
||||
--sp;
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
}
|
||||
|
||||
// Initialisation.
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
void preauthorise_stack_read(uint32_t length) {
|
||||
uint16_t sp = registers_.sp();
|
||||
while(length--) {
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
++sp;
|
||||
}
|
||||
|
||||
void seed(uint32_t address, uint8_t value) {
|
||||
memory[address] = value;
|
||||
tags[address] = Tag::Seeded;
|
||||
}
|
||||
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(segment, start);
|
||||
++start;
|
||||
}
|
||||
|
||||
void touch(uint32_t address) {
|
||||
tags[address] = Tag::AccessExpected;
|
||||
}
|
||||
void preauthorise_read(uint32_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Preauthorisation call-ins.
|
||||
//
|
||||
void preauthorise_stack_write(uint32_t length) {
|
||||
uint16_t sp = registers_.sp();
|
||||
while(length--) {
|
||||
--sp;
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
//
|
||||
// Access call-ins.
|
||||
//
|
||||
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
|
||||
return access<IntT, type>(segment, offset, Tag::Accessed);
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
|
||||
return access<IntT, type>(address, Tag::Accessed);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
void preauthorise_stack_read(uint32_t length) {
|
||||
uint16_t sp = registers_.sp();
|
||||
while(length--) {
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
++sp;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(segment, start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(uint32_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Direct write.
|
||||
//
|
||||
template <typename IntT>
|
||||
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
|
||||
if(!test_preauthorisation(address(segment, offset))) {
|
||||
printf("Non-preauthorised access\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Access call-ins.
|
||||
//
|
||||
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
|
||||
return access<IntT, type>(segment, offset, Tag::Accessed);
|
||||
// Bytes can be written without further ado.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
|
||||
return access<IntT, type>(address, Tag::Accessed);
|
||||
// Words that straddle the segment end must be split in two.
|
||||
if(offset == 0xffff) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
|
||||
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
const uint32_t target = address(segment, offset) & 0xf'ffff;
|
||||
|
||||
// Words that straddle the end of physical RAM must also be split in two.
|
||||
if(target == 0xf'ffff) {
|
||||
memory[0xf'ffff] = value & 0xff;
|
||||
memory[0x0'0000] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Direct write.
|
||||
//
|
||||
template <typename IntT>
|
||||
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
|
||||
if(!test_preauthorisation(address(segment, offset))) {
|
||||
printf("Non-preauthorised access\n");
|
||||
}
|
||||
// It's safe just to write then.
|
||||
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
|
||||
}
|
||||
|
||||
// Bytes can be written without further ado.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value;
|
||||
return;
|
||||
}
|
||||
private:
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
};
|
||||
|
||||
// Words that straddle the segment end must be split in two.
|
||||
std::unordered_set<uint32_t> preauthorisations;
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
Registers ®isters_;
|
||||
const Segments &segments_;
|
||||
|
||||
void preauthorise(uint32_t address) {
|
||||
preauthorisations.insert(address);
|
||||
}
|
||||
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
|
||||
preauthorise((segment_base(segment) + address) & 0xf'ffff);
|
||||
}
|
||||
bool test_preauthorisation(uint32_t address) {
|
||||
auto authorisation = preauthorisations.find(address);
|
||||
if(authorisation == preauthorisations.end()) {
|
||||
return false;
|
||||
}
|
||||
preauthorisations.erase(authorisation);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: return segments_.ds_base_;
|
||||
case Source::ES: return segments_.es_base_;
|
||||
case Source::CS: return segments_.cs_base_;
|
||||
case Source::SS: return segments_.ss_base_;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) {
|
||||
return (segment_base(segment) + offset) & 0xf'ffff;
|
||||
}
|
||||
|
||||
|
||||
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
|
||||
const uint32_t physical_address = address(segment, offset);
|
||||
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(offset == 0xffff) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
|
||||
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t target = address(segment, offset) & 0xf'ffff;
|
||||
|
||||
// Words that straddle the end of physical RAM must also be split in two.
|
||||
if(target == 0xf'ffff) {
|
||||
memory[0xf'ffff] = value & 0xff;
|
||||
memory[0x0'0000] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
// It's safe just to write then.
|
||||
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
};
|
||||
|
||||
std::unordered_set<uint32_t> preauthorisations;
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
Registers ®isters_;
|
||||
const Segments &segments_;
|
||||
|
||||
void preauthorise(uint32_t address) {
|
||||
preauthorisations.insert(address);
|
||||
}
|
||||
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
|
||||
preauthorise((segment_base(segment) + address) & 0xf'ffff);
|
||||
}
|
||||
bool test_preauthorisation(uint32_t address) {
|
||||
auto authorisation = preauthorisations.find(address);
|
||||
if(authorisation == preauthorisations.end()) {
|
||||
return false;
|
||||
}
|
||||
preauthorisations.erase(authorisation);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: return segments_.ds_base_;
|
||||
case Source::ES: return segments_.es_base_;
|
||||
case Source::CS: return segments_.cs_base_;
|
||||
case Source::SS: return segments_.ss_base_;
|
||||
return split_word<type>(physical_address, address(segment, 0), tag);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) {
|
||||
return (segment_base(segment) + offset) & 0xf'ffff;
|
||||
}
|
||||
return access<IntT, type>(physical_address, tag);
|
||||
}
|
||||
|
||||
|
||||
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
|
||||
const uint32_t physical_address = address(segment, offset);
|
||||
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(offset == 0xffff) {
|
||||
return split_word<type>(physical_address, address(segment, 0), tag);
|
||||
}
|
||||
}
|
||||
|
||||
return access<IntT, type>(physical_address, tag);
|
||||
}
|
||||
|
||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||
// to a segment, they're just at an absolute location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
|
||||
if constexpr (type == AccessType::PreauthorisedRead) {
|
||||
if(!test_preauthorisation(address)) {
|
||||
printf("Non preauthorised access\n");
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t c = 0; c < sizeof(IntT); c++) {
|
||||
tags[(address + c) & 0xf'ffff] = tag;
|
||||
}
|
||||
|
||||
// Dispense with the single-byte case trivially.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
} else if(address != 0xf'ffff) {
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
} else {
|
||||
return split_word<type>(address, 0, tag);
|
||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||
// to a segment, they're just at an absolute location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
|
||||
if constexpr (type == AccessType::PreauthorisedRead) {
|
||||
if(!test_preauthorisation(address)) {
|
||||
printf("Non preauthorised access\n");
|
||||
}
|
||||
}
|
||||
|
||||
template <AccessType type>
|
||||
typename InstructionSet::x86::Accessor<uint16_t, type>::type
|
||||
split_word(uint32_t low_address, uint32_t high_address, Tag tag) {
|
||||
if constexpr (is_writeable(type)) {
|
||||
write_back_address_[0] = low_address;
|
||||
write_back_address_[1] = high_address;
|
||||
tags[low_address] = tag;
|
||||
tags[high_address] = tag;
|
||||
|
||||
// Prepopulate only if this is a modify.
|
||||
if constexpr (type == AccessType::ReadModifyWrite) {
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
}
|
||||
|
||||
return write_back_value_;
|
||||
} else {
|
||||
return memory[low_address] | (memory[high_address] << 8);
|
||||
}
|
||||
for(size_t c = 0; c < sizeof(IntT); c++) {
|
||||
tags[(address + c) & 0xf'ffff] = tag;
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
// Dispense with the single-byte case trivially.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
} else if(address != 0xf'ffff) {
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
} else {
|
||||
return split_word<type>(address, 0, tag);
|
||||
}
|
||||
}
|
||||
|
||||
template <AccessType type>
|
||||
typename InstructionSet::x86::Accessor<uint16_t, type>::type
|
||||
split_word(uint32_t low_address, uint32_t high_address, Tag tag) {
|
||||
if constexpr (is_writeable(type)) {
|
||||
write_back_address_[0] = low_address;
|
||||
write_back_address_[1] = high_address;
|
||||
tags[low_address] = tag;
|
||||
tags[high_address] = tag;
|
||||
|
||||
// Prepopulate only if this is a modify.
|
||||
if constexpr (type == AccessType::ReadModifyWrite) {
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
}
|
||||
|
||||
return write_back_value_;
|
||||
} else {
|
||||
return memory[low_address] | (memory[high_address] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
};
|
||||
struct IO {
|
||||
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
|
||||
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); }
|
||||
};
|
||||
class FlowController {
|
||||
public:
|
||||
FlowController(Registers ®isters, Segments &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
public:
|
||||
FlowController(Registers ®isters, Segments &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
|
||||
// Requirements for perform.
|
||||
template <typename AddressT>
|
||||
void jump(AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
// Requirements for perform.
|
||||
template <typename AddressT>
|
||||
void jump(AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
|
||||
template <typename AddressT>
|
||||
void jump(uint16_t segment, AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.cs() = segment;
|
||||
segments_.did_update(Segments::Source::CS);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
template <typename AddressT>
|
||||
void jump(uint16_t segment, AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.cs() = segment;
|
||||
segments_.did_update(Segments::Source::CS);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
|
||||
void halt() {}
|
||||
void wait() {}
|
||||
void halt() {}
|
||||
void wait() {}
|
||||
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
|
||||
private:
|
||||
Registers ®isters_;
|
||||
Segments &segments_;
|
||||
bool should_repeat_ = false;
|
||||
private:
|
||||
Registers ®isters_;
|
||||
Segments &segments_;
|
||||
bool should_repeat_ = false;
|
||||
};
|
||||
|
||||
struct ExecutionSupport {
|
||||
|
@ -17,15 +17,15 @@
|
||||
#pragma mark - C++ delegate handlers
|
||||
|
||||
class MachineTrapHandler: public CPU::AllRAMProcessor::TrapHandler {
|
||||
public:
|
||||
MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {}
|
||||
public:
|
||||
MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {}
|
||||
|
||||
void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) {
|
||||
[target_ testMachineDidTrapAtAddress:address];
|
||||
}
|
||||
void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) {
|
||||
[target_ testMachineDidTrapAtAddress:address];
|
||||
}
|
||||
|
||||
private:
|
||||
CSTestMachine *target_;
|
||||
private:
|
||||
CSTestMachine *target_;
|
||||
};
|
||||
|
||||
#pragma mark - The test machine
|
||||
|
@ -20,15 +20,15 @@
|
||||
#pragma mark - C++ delegate handlers
|
||||
|
||||
class BusOperationHandler: public CPU::Z80::AllRAMProcessor::MemoryAccessDelegate {
|
||||
public:
|
||||
BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {}
|
||||
public:
|
||||
BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {}
|
||||
|
||||
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) {
|
||||
[target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp];
|
||||
}
|
||||
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) {
|
||||
[target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp];
|
||||
}
|
||||
|
||||
private:
|
||||
CSTestMachineZ80 *target_;
|
||||
private:
|
||||
CSTestMachineZ80 *target_;
|
||||
};
|
||||
|
||||
#pragma mark - Register enum map
|
||||
|
@ -13,44 +13,44 @@
|
||||
#include "68000.hpp"
|
||||
|
||||
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
||||
public:
|
||||
ComparativeBusHandler(const char *trace_name) {
|
||||
trace = gzopen(trace_name, "rt");
|
||||
public:
|
||||
ComparativeBusHandler(const char *trace_name) {
|
||||
trace = gzopen(trace_name, "rt");
|
||||
}
|
||||
|
||||
~ComparativeBusHandler() {
|
||||
gzclose(trace);
|
||||
}
|
||||
|
||||
void will_perform(uint32_t address, uint16_t) {
|
||||
// Obtain the next line from the trace file.
|
||||
char correct_state[300] = "\n";
|
||||
gzgets(trace, correct_state, sizeof(correct_state));
|
||||
++line_count;
|
||||
|
||||
// Generate state locally.
|
||||
const auto state = get_state().registers;
|
||||
char local_state[300];
|
||||
snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
address,
|
||||
state.status,
|
||||
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
|
||||
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
|
||||
state.stack_pointer()
|
||||
);
|
||||
|
||||
// Check that the two coincide.
|
||||
if(strcmp(correct_state, local_state)) {
|
||||
fprintf(stderr, "Diverges at line %d\n", line_count);
|
||||
fprintf(stderr, "Good: %s", correct_state);
|
||||
fprintf(stderr, "Bad: %s", local_state);
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
~ComparativeBusHandler() {
|
||||
gzclose(trace);
|
||||
}
|
||||
virtual CPU::MC68000::State get_state() = 0;
|
||||
|
||||
void will_perform(uint32_t address, uint16_t) {
|
||||
// Obtain the next line from the trace file.
|
||||
char correct_state[300] = "\n";
|
||||
gzgets(trace, correct_state, sizeof(correct_state));
|
||||
++line_count;
|
||||
|
||||
// Generate state locally.
|
||||
const auto state = get_state().registers;
|
||||
char local_state[300];
|
||||
snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
address,
|
||||
state.status,
|
||||
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
|
||||
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
|
||||
state.stack_pointer()
|
||||
);
|
||||
|
||||
// Check that the two coincide.
|
||||
if(strcmp(correct_state, local_state)) {
|
||||
fprintf(stderr, "Diverges at line %d\n", line_count);
|
||||
fprintf(stderr, "Good: %s", correct_state);
|
||||
fprintf(stderr, "Bad: %s", local_state);
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
virtual CPU::MC68000::State get_state() = 0;
|
||||
|
||||
private:
|
||||
int line_count = 0;
|
||||
gzFile trace;
|
||||
private:
|
||||
int line_count = 0;
|
||||
gzFile trace;
|
||||
};
|
||||
|
@ -18,82 +18,82 @@
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
class EmuTOS: public ComparativeBusHandler {
|
||||
public:
|
||||
EmuTOS(const std::vector<uint8_t> &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
|
||||
assert(!(emuTOS.size() & 1));
|
||||
emuTOS_.resize(emuTOS.size() / 2);
|
||||
public:
|
||||
EmuTOS(const std::vector<uint8_t> &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
|
||||
assert(!(emuTOS.size() & 1));
|
||||
emuTOS_.resize(emuTOS.size() / 2);
|
||||
|
||||
for(size_t c = 0; c < emuTOS_.size(); ++c) {
|
||||
emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1];
|
||||
}
|
||||
for(size_t c = 0; c < emuTOS_.size(); ++c) {
|
||||
emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1];
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
CPU::MC68000::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
// As much about the Atari ST's memory map as is relevant here: the ROM begins
|
||||
// at 0xfc0000, and the first eight bytes are mirrored to the first four memory
|
||||
// addresses in order for /RESET to work properly. RAM otherwise fills the first
|
||||
// 512kb of the address space. Trying to write to ROM raises a bus error.
|
||||
|
||||
const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4;
|
||||
const bool is_ram = word_address < ram_.size();
|
||||
const bool is_peripheral = !is_rom && !is_ram;
|
||||
|
||||
uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data();
|
||||
if(is_rom) {
|
||||
word_address %= emuTOS_.size();
|
||||
} else {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
CPU::MC68000::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
// As much about the Atari ST's memory map as is relevant here: the ROM begins
|
||||
// at 0xfc0000, and the first eight bytes are mirrored to the first four memory
|
||||
// addresses in order for /RESET to work properly. RAM otherwise fills the first
|
||||
// 512kb of the address space. Trying to write to ROM raises a bus error.
|
||||
|
||||
const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4;
|
||||
const bool is_ram = word_address < ram_.size();
|
||||
const bool is_peripheral = !is_rom && !is_ram;
|
||||
|
||||
uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data();
|
||||
if(is_rom) {
|
||||
word_address %= emuTOS_.size();
|
||||
} else {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
if(is_peripheral) {
|
||||
switch(address & 0x7ff) {
|
||||
// A hard-coded value for TIMER B.
|
||||
case (0xa21 >> 1):
|
||||
peripheral_result = 0x00000001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace CPU::MC68000;
|
||||
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
case Operation::SelectWord | Operation::Read:
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Operation::SelectByte | Operation::Read:
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Operation::SelectWord:
|
||||
base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Operation::SelectByte:
|
||||
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
if(is_peripheral) {
|
||||
switch(address & 0x7ff) {
|
||||
// A hard-coded value for TIMER B.
|
||||
case (0xa21 >> 1):
|
||||
peripheral_result = 0x00000001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
using namespace CPU::MC68000;
|
||||
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
case Operation::SelectWord | Operation::Read:
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Operation::SelectByte | Operation::Read:
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Operation::SelectWord:
|
||||
base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Operation::SelectByte:
|
||||
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
std::vector<uint16_t> emuTOS_;
|
||||
std::array<uint16_t, 256*1024> ram_;
|
||||
private:
|
||||
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
|
||||
|
||||
std::vector<uint16_t> emuTOS_;
|
||||
std::array<uint16_t, 256*1024> ram_;
|
||||
};
|
||||
|
||||
@interface EmuTOSTests : XCTestCase
|
||||
|
@ -21,73 +21,73 @@
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
class QL: public ComparativeBusHandler {
|
||||
public:
|
||||
QL(const std::vector<uint8_t> &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
|
||||
assert(!(rom.size() & 1));
|
||||
rom_.resize(rom.size() / 2);
|
||||
public:
|
||||
QL(const std::vector<uint8_t> &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) {
|
||||
assert(!(rom.size() & 1));
|
||||
rom_.resize(rom.size() / 2);
|
||||
|
||||
for(size_t c = 0; c < rom_.size(); ++c) {
|
||||
rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1];
|
||||
for(size_t c = 0; c < rom_.size(); ++c) {
|
||||
rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1];
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
CPU::MC68000::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
// QL memory map: ROM is in the lowest area; RAM is from 0x20000.
|
||||
const bool is_rom = word_address < rom_.size();
|
||||
const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size();
|
||||
const bool is_peripheral = !is_ram && !is_rom;
|
||||
|
||||
uint16_t *const base = is_rom ? rom_.data() : ram_.data();
|
||||
if(is_rom) {
|
||||
word_address %= rom_.size();
|
||||
}
|
||||
if(is_ram) {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
|
||||
using namespace CPU::MC68000;
|
||||
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
case Operation::SelectWord | Operation::Read:
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Operation::SelectByte | Operation::Read:
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Operation::SelectWord:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Operation::SelectByte:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
private:
|
||||
CPU::MC68000::Processor<QL, true, false, true> m68000_;
|
||||
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
// QL memory map: ROM is in the lowest area; RAM is from 0x20000.
|
||||
const bool is_rom = word_address < rom_.size();
|
||||
const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size();
|
||||
const bool is_peripheral = !is_ram && !is_rom;
|
||||
|
||||
uint16_t *const base = is_rom ? rom_.data() : ram_.data();
|
||||
if(is_rom) {
|
||||
word_address %= rom_.size();
|
||||
}
|
||||
if(is_ram) {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
|
||||
using namespace CPU::MC68000;
|
||||
switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
case Operation::SelectWord | Operation::Read:
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Operation::SelectByte | Operation::Read:
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Operation::SelectWord:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Operation::SelectByte:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<QL, true, false, true> m68000_;
|
||||
|
||||
std::vector<uint16_t> rom_;
|
||||
std::array<uint16_t, 64*1024> ram_;
|
||||
std::vector<uint16_t> rom_;
|
||||
std::array<uint16_t, 64*1024> ram_;
|
||||
};
|
||||
|
||||
@interface QLTests : XCTestCase
|
||||
|
@ -22,115 +22,115 @@ using namespace InstructionSet::M68k;
|
||||
begin execution at 0x0400.
|
||||
*/
|
||||
class RAM68000: public CPU::MC68000::BusHandler {
|
||||
public:
|
||||
RAM68000() : m68000_(*this) {}
|
||||
public:
|
||||
RAM68000() : m68000_(*this) {}
|
||||
|
||||
uint32_t initial_pc() const {
|
||||
return 0x1000;
|
||||
uint32_t initial_pc() const {
|
||||
return 0x1000;
|
||||
}
|
||||
|
||||
void set_program(
|
||||
const std::vector<uint16_t> &program,
|
||||
uint32_t stack_pointer = 0x206
|
||||
) {
|
||||
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||
|
||||
// Ensure the condition codes start unset and set the initial program counter
|
||||
// and supervisor stack pointer, as well as starting in supervisor mode.
|
||||
auto registers = m68000_.get_state().registers;
|
||||
registers.status = 0x2700;
|
||||
registers.program_counter = initial_pc();
|
||||
registers.supervisor_stack_pointer = stack_pointer;
|
||||
m68000_.decode_from_state(registers);
|
||||
}
|
||||
|
||||
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
|
||||
auto state = m68000_.get_state();
|
||||
func(state.registers);
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
void will_perform(uint32_t, uint16_t) {
|
||||
--instructions_remaining_;
|
||||
if(instructions_remaining_ < 0) {
|
||||
throw StopException();
|
||||
}
|
||||
}
|
||||
|
||||
void set_program(
|
||||
const std::vector<uint16_t> &program,
|
||||
uint32_t stack_pointer = 0x206
|
||||
) {
|
||||
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||
void run_for_instructions(int count) {
|
||||
duration_ = HalfCycles(0);
|
||||
instructions_remaining_ = count;
|
||||
if(!instructions_remaining_) return;
|
||||
|
||||
// Ensure the condition codes start unset and set the initial program counter
|
||||
// and supervisor stack pointer, as well as starting in supervisor mode.
|
||||
auto registers = m68000_.get_state().registers;
|
||||
registers.status = 0x2700;
|
||||
registers.program_counter = initial_pc();
|
||||
registers.supervisor_stack_pointer = stack_pointer;
|
||||
m68000_.decode_from_state(registers);
|
||||
}
|
||||
|
||||
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
|
||||
auto state = m68000_.get_state();
|
||||
func(state.registers);
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
void will_perform(uint32_t, uint16_t) {
|
||||
--instructions_remaining_;
|
||||
if(instructions_remaining_ < 0) {
|
||||
throw StopException();
|
||||
try {
|
||||
while(true) {
|
||||
run_for(HalfCycles(2000));
|
||||
}
|
||||
}
|
||||
} catch (const StopException &) {}
|
||||
}
|
||||
|
||||
void run_for_instructions(int count) {
|
||||
duration_ = HalfCycles(0);
|
||||
instructions_remaining_ = count;
|
||||
if(!instructions_remaining_) return;
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
try {
|
||||
while(true) {
|
||||
run_for(HalfCycles(2000));
|
||||
}
|
||||
} catch (const StopException &) {}
|
||||
}
|
||||
uint16_t *ram_at(uint32_t address) {
|
||||
return &ram_[(address >> 1) % ram_.size()];
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t word_address = cycle.word_address();
|
||||
duration_ += cycle.length;
|
||||
|
||||
uint16_t *ram_at(uint32_t address) {
|
||||
return &ram_[(address >> 1) % ram_.size()];
|
||||
}
|
||||
if(cycle.data_select_active()) {
|
||||
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
|
||||
cycle.value->b = 10;
|
||||
} else {
|
||||
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
const uint32_t word_address = cycle.word_address();
|
||||
duration_ += cycle.length;
|
||||
|
||||
if(cycle.data_select_active()) {
|
||||
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
|
||||
cycle.value->b = 10;
|
||||
} else {
|
||||
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
|
||||
default: break;
|
||||
|
||||
case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read:
|
||||
cycle.value->w = ram_[word_address % ram_.size()];
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read:
|
||||
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectWord:
|
||||
ram_[word_address % ram_.size()] = cycle.value->w;
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectByte:
|
||||
ram_[word_address % ram_.size()] = uint16_t(
|
||||
(cycle.value->b << cycle.byte_shift()) |
|
||||
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
}
|
||||
case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read:
|
||||
cycle.value->w = ram_[word_address % ram_.size()];
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read:
|
||||
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectWord:
|
||||
ram_[word_address % ram_.size()] = cycle.value->w;
|
||||
break;
|
||||
case CPU::MC68000::Operation::SelectByte:
|
||||
ram_[word_address % ram_.size()] = uint16_t(
|
||||
(cycle.value->b << cycle.byte_shift()) |
|
||||
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::State get_processor_state() {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
auto &processor() {
|
||||
return m68000_;
|
||||
}
|
||||
CPU::MC68000::State get_processor_state() {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
int get_cycle_count() {
|
||||
return int(duration_.as_integral()) >> 1;
|
||||
}
|
||||
auto &processor() {
|
||||
return m68000_;
|
||||
}
|
||||
|
||||
void reset_cycle_count() {
|
||||
duration_ = HalfCycles(0);
|
||||
}
|
||||
int get_cycle_count() {
|
||||
return int(duration_.as_integral()) >> 1;
|
||||
}
|
||||
|
||||
private:
|
||||
struct StopException {};
|
||||
void reset_cycle_count() {
|
||||
duration_ = HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true, true, true> m68000_;
|
||||
std::array<uint16_t, 256*1024> ram_{};
|
||||
int instructions_remaining_;
|
||||
HalfCycles duration_;
|
||||
private:
|
||||
struct StopException {};
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true, true, true> m68000_;
|
||||
std::array<uint16_t, 256*1024> ram_{};
|
||||
int instructions_remaining_;
|
||||
HalfCycles duration_;
|
||||
};
|
||||
|
@ -179,13 +179,13 @@ struct CapturingZ80: public CPU::Z80::BusHandler {
|
||||
return contentions_48k_;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::Z80::Processor<CapturingZ80, false, false> z80_;
|
||||
uint8_t ram_[65536];
|
||||
uint16_t code_length_ = 0;
|
||||
private:
|
||||
CPU::Z80::Processor<CapturingZ80, false, false> z80_;
|
||||
uint8_t ram_[65536];
|
||||
uint16_t code_length_ = 0;
|
||||
|
||||
std::vector<BusRecord> bus_records_;
|
||||
std::vector<ContentionCheck> contentions_48k_;
|
||||
std::vector<BusRecord> bus_records_;
|
||||
std::vector<ContentionCheck> contentions_48k_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -93,85 +93,85 @@ struct MachineRunner {
|
||||
std::mutex *machine_mutex;
|
||||
Machine::DynamicMachine *machine;
|
||||
|
||||
private:
|
||||
SDL_TimerID timer_ = 0;
|
||||
Time::Nanos last_time_ = 0;
|
||||
std::atomic<Time::Nanos> vsync_time_;
|
||||
std::atomic_flag frame_lock_;
|
||||
private:
|
||||
SDL_TimerID timer_ = 0;
|
||||
Time::Nanos last_time_ = 0;
|
||||
std::atomic<Time::Nanos> vsync_time_;
|
||||
std::atomic_flag frame_lock_;
|
||||
|
||||
enum class State {
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped
|
||||
};
|
||||
std::atomic<State> state_{State::Running};
|
||||
enum class State {
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped
|
||||
};
|
||||
std::atomic<State> state_{State::Running};
|
||||
|
||||
Time::ScanSynchroniser scan_synchroniser_;
|
||||
Time::ScanSynchroniser scan_synchroniser_;
|
||||
|
||||
// A slightly clumsy means of trying to derive frame rate from calls to
|
||||
// signal_vsync(); SDL_DisplayMode provides only an integral quantity
|
||||
// whereas, empirically, it's fairly common for monitors to run at the
|
||||
// NTSC-esque frame rates of 59.94Hz.
|
||||
std::array<Time::Nanos, 32> frame_times_;
|
||||
Time::Nanos frame_time_average_ = 0;
|
||||
size_t frame_time_pointer_ = 0;
|
||||
std::atomic<double> _frame_period;
|
||||
// A slightly clumsy means of trying to derive frame rate from calls to
|
||||
// signal_vsync(); SDL_DisplayMode provides only an integral quantity
|
||||
// whereas, empirically, it's fairly common for monitors to run at the
|
||||
// NTSC-esque frame rates of 59.94Hz.
|
||||
std::array<Time::Nanos, 32> frame_times_;
|
||||
Time::Nanos frame_time_average_ = 0;
|
||||
size_t frame_time_pointer_ = 0;
|
||||
std::atomic<double> _frame_period;
|
||||
|
||||
static constexpr Uint32 timer_period = 4;
|
||||
static Uint32 sdl_callback(Uint32, void *param) {
|
||||
reinterpret_cast<MachineRunner *>(param)->update();
|
||||
return timer_period;
|
||||
static constexpr Uint32 timer_period = 4;
|
||||
static Uint32 sdl_callback(Uint32, void *param) {
|
||||
reinterpret_cast<MachineRunner *>(param)->update();
|
||||
return timer_period;
|
||||
}
|
||||
|
||||
void update() {
|
||||
// If a shutdown is in progress, signal stoppage and do nothing.
|
||||
if(state_ != State::Running) {
|
||||
state_ = State::Stopped;
|
||||
return;
|
||||
}
|
||||
|
||||
void update() {
|
||||
// If a shutdown is in progress, signal stoppage and do nothing.
|
||||
if(state_ != State::Running) {
|
||||
state_ = State::Stopped;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get time now and determine how long it has been since the last time this
|
||||
// function was called. If it's more than half a second then forego any activity
|
||||
// now, as there's obviously been some sort of substantial time glitch.
|
||||
const auto time_now = Time::nanos_now();
|
||||
if(time_now - last_time_ > Time::Nanos(500'000'000)) {
|
||||
last_time_ = time_now - Time::Nanos(500'000'000);
|
||||
}
|
||||
|
||||
const auto vsync_time = vsync_time_.load();
|
||||
|
||||
std::unique_lock lock_guard(*machine_mutex);
|
||||
const auto scan_producer = machine->scan_producer();
|
||||
const auto timed_machine = machine->timed_machine();
|
||||
|
||||
bool split_and_sync = false;
|
||||
if(last_time_ < vsync_time && time_now >= vsync_time) {
|
||||
split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period);
|
||||
}
|
||||
|
||||
if(split_and_sync) {
|
||||
timed_machine->run_for(double(vsync_time - last_time_) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
timed_machine->set_speed_multiplier(
|
||||
scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status())
|
||||
);
|
||||
|
||||
// This is a bit of an SDL ugliness; wait here until the next frame is drawn.
|
||||
// That is, unless and until I can think of a good way of running background
|
||||
// updates via a share group — possibly an extra intermediate buffer is needed?
|
||||
lock_guard.unlock();
|
||||
while(frame_lock_.test_and_set());
|
||||
lock_guard.lock();
|
||||
|
||||
timed_machine->run_for(double(time_now - vsync_time) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
} else {
|
||||
timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier());
|
||||
timed_machine->run_for(double(time_now - last_time_) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
}
|
||||
last_time_ = time_now;
|
||||
// Get time now and determine how long it has been since the last time this
|
||||
// function was called. If it's more than half a second then forego any activity
|
||||
// now, as there's obviously been some sort of substantial time glitch.
|
||||
const auto time_now = Time::nanos_now();
|
||||
if(time_now - last_time_ > Time::Nanos(500'000'000)) {
|
||||
last_time_ = time_now - Time::Nanos(500'000'000);
|
||||
}
|
||||
|
||||
const auto vsync_time = vsync_time_.load();
|
||||
|
||||
std::unique_lock lock_guard(*machine_mutex);
|
||||
const auto scan_producer = machine->scan_producer();
|
||||
const auto timed_machine = machine->timed_machine();
|
||||
|
||||
bool split_and_sync = false;
|
||||
if(last_time_ < vsync_time && time_now >= vsync_time) {
|
||||
split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period);
|
||||
}
|
||||
|
||||
if(split_and_sync) {
|
||||
timed_machine->run_for(double(vsync_time - last_time_) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
timed_machine->set_speed_multiplier(
|
||||
scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status())
|
||||
);
|
||||
|
||||
// This is a bit of an SDL ugliness; wait here until the next frame is drawn.
|
||||
// That is, unless and until I can think of a good way of running background
|
||||
// updates via a share group — possibly an extra intermediate buffer is needed?
|
||||
lock_guard.unlock();
|
||||
while(frame_lock_.test_and_set());
|
||||
lock_guard.lock();
|
||||
|
||||
timed_machine->run_for(double(time_now - vsync_time) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
} else {
|
||||
timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier());
|
||||
timed_machine->run_for(double(time_now - last_time_) / 1e9);
|
||||
timed_machine->flush_output(MachineTypes::TimedMachine::Output::All);
|
||||
}
|
||||
last_time_ = time_now;
|
||||
}
|
||||
};
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
@ -214,92 +214,92 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
};
|
||||
|
||||
class ActivityObserver: public Activity::Observer {
|
||||
public:
|
||||
ActivityObserver(Activity::Source *source, float aspect_ratio) {
|
||||
// Get the suorce to supply all LEDs and drives.
|
||||
source->set_activity_observer(this);
|
||||
public:
|
||||
ActivityObserver(Activity::Source *source, float aspect_ratio) {
|
||||
// Get the suorce to supply all LEDs and drives.
|
||||
source->set_activity_observer(this);
|
||||
|
||||
// The objective is to display drives on one side of the screen, other LEDs on the other. Drives
|
||||
// may or may not have LEDs and this code intends to display only those which do; so a quick
|
||||
// comparative processing of the two lists is called for.
|
||||
// The objective is to display drives on one side of the screen, other LEDs on the other. Drives
|
||||
// may or may not have LEDs and this code intends to display only those which do; so a quick
|
||||
// comparative processing of the two lists is called for.
|
||||
|
||||
// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed.
|
||||
drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) {
|
||||
return std::find(leds_.begin(), leds_.end(), string) == leds_.end();
|
||||
}) - drives_.begin());
|
||||
// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed.
|
||||
drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) {
|
||||
return std::find(leds_.begin(), leds_.end(), string) == leds_.end();
|
||||
}) - drives_.begin());
|
||||
|
||||
// Remove from the list of LEDs any which are drives. Those will be represented separately.
|
||||
leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) {
|
||||
return std::find(drives_.begin(), drives_.end(), string) != drives_.end();
|
||||
}) - leds_.begin());
|
||||
// Remove from the list of LEDs any which are drives. Those will be represented separately.
|
||||
leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) {
|
||||
return std::find(drives_.begin(), drives_.end(), string) != drives_.end();
|
||||
}) - leds_.begin());
|
||||
|
||||
set_aspect_ratio(aspect_ratio);
|
||||
set_aspect_ratio(aspect_ratio);
|
||||
}
|
||||
|
||||
void set_aspect_ratio(float aspect_ratio) {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
lights_.clear();
|
||||
|
||||
// Generate a bunch of LEDs for connected drives.
|
||||
constexpr float height = 0.05f;
|
||||
const float width = height / aspect_ratio;
|
||||
const float right_x = 1.0f - 2.0f * width;
|
||||
float y = 1.0f - 2.0f * height;
|
||||
for(const auto &drive: drives_) {
|
||||
lights_.emplace(drive, std::make_unique<Outputs::Display::OpenGL::Rectangle>(right_x, y, width, height));
|
||||
y -= height * 2.0f;
|
||||
}
|
||||
|
||||
void set_aspect_ratio(float aspect_ratio) {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
lights_.clear();
|
||||
/*
|
||||
This would generate LEDs for things other than drives; I'm declining for now
|
||||
due to the inexpressiveness of just painting a rectangle.
|
||||
|
||||
// Generate a bunch of LEDs for connected drives.
|
||||
constexpr float height = 0.05f;
|
||||
const float width = height / aspect_ratio;
|
||||
const float right_x = 1.0f - 2.0f * width;
|
||||
float y = 1.0f - 2.0f * height;
|
||||
for(const auto &drive: drives_) {
|
||||
lights_.emplace(drive, std::make_unique<Outputs::Display::OpenGL::Rectangle>(right_x, y, width, height));
|
||||
const float left_x = -1.0f + 2.0f * width;
|
||||
y = 1.0f - 2.0f * height;
|
||||
for(const auto &led: leds_) {
|
||||
lights_.emplace(led, std::make_unique<OpenGL::Rectangle>(left_x, y, width, height));
|
||||
y -= height * 2.0f;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
This would generate LEDs for things other than drives; I'm declining for now
|
||||
due to the inexpressiveness of just painting a rectangle.
|
||||
|
||||
const float left_x = -1.0f + 2.0f * width;
|
||||
y = 1.0f - 2.0f * height;
|
||||
for(const auto &led: leds_) {
|
||||
lights_.emplace(led, std::make_unique<OpenGL::Rectangle>(left_x, y, width, height));
|
||||
y -= height * 2.0f;
|
||||
}
|
||||
*/
|
||||
void draw() {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
for(const auto &lit_led: lit_leds_) {
|
||||
if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end())
|
||||
lights_[lit_led]->draw(0.0, 0.8, 0.0);
|
||||
}
|
||||
blinking_leds_.clear();
|
||||
}
|
||||
|
||||
void draw() {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
for(const auto &lit_led: lit_leds_) {
|
||||
if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end())
|
||||
lights_[lit_led]->draw(0.0, 0.8, 0.0);
|
||||
}
|
||||
blinking_leds_.clear();
|
||||
}
|
||||
private:
|
||||
std::vector<std::string> leds_;
|
||||
void register_led(const std::string &name, uint8_t) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
leds_.push_back(name);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> leds_;
|
||||
void register_led(const std::string &name, uint8_t) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
leds_.push_back(name);
|
||||
}
|
||||
std::vector<std::string> drives_;
|
||||
void register_drive(const std::string &name) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
drives_.push_back(name);
|
||||
}
|
||||
|
||||
std::vector<std::string> drives_;
|
||||
void register_drive(const std::string &name) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
drives_.push_back(name);
|
||||
}
|
||||
void set_led_status(const std::string &name, bool lit) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
if(lit) lit_leds_.insert(name);
|
||||
else lit_leds_.erase(name);
|
||||
}
|
||||
|
||||
void set_led_status(const std::string &name, bool lit) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
if(lit) lit_leds_.insert(name);
|
||||
else lit_leds_.erase(name);
|
||||
}
|
||||
void announce_drive_event(const std::string &name, DriveEvent) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
blinking_leds_.insert(name);
|
||||
}
|
||||
|
||||
void announce_drive_event(const std::string &name, DriveEvent) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
blinking_leds_.insert(name);
|
||||
}
|
||||
|
||||
std::map<std::string, std::unique_ptr<Outputs::Display::OpenGL::Rectangle>> lights_;
|
||||
std::set<std::string> lit_leds_;
|
||||
std::set<std::string> blinking_leds_;
|
||||
std::mutex mutex;
|
||||
std::map<std::string, std::unique_ptr<Outputs::Display::OpenGL::Rectangle>> lights_;
|
||||
std::set<std::string> lit_leds_;
|
||||
std::set<std::string> blinking_leds_;
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key) {
|
||||
@ -465,31 +465,31 @@ std::string system_get(const char *command) {
|
||||
Maintains a communicative window title.
|
||||
*/
|
||||
class DynamicWindowTitler {
|
||||
public:
|
||||
DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {}
|
||||
public:
|
||||
DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {}
|
||||
|
||||
std::string window_title() {
|
||||
if(!mouse_is_captured_) return file_name_;
|
||||
return file_name_ + " (press control+escape to release mouse)";
|
||||
}
|
||||
std::string window_title() {
|
||||
if(!mouse_is_captured_) return file_name_;
|
||||
return file_name_ + " (press control+escape to release mouse)";
|
||||
}
|
||||
|
||||
void set_mouse_is_captured(bool is_captured) {
|
||||
mouse_is_captured_ = is_captured;
|
||||
update_window_title();
|
||||
}
|
||||
void set_mouse_is_captured(bool is_captured) {
|
||||
mouse_is_captured_ = is_captured;
|
||||
update_window_title();
|
||||
}
|
||||
|
||||
void set_file_name(const std::string &name) {
|
||||
file_name_ = name;
|
||||
update_window_title();
|
||||
}
|
||||
void set_file_name(const std::string &name) {
|
||||
file_name_ = name;
|
||||
update_window_title();
|
||||
}
|
||||
|
||||
private:
|
||||
void update_window_title() {
|
||||
SDL_SetWindowTitle(window_, window_title().c_str());
|
||||
}
|
||||
bool mouse_is_captured_ = false;
|
||||
SDL_Window *window_ = nullptr;
|
||||
std::string file_name_;
|
||||
private:
|
||||
void update_window_title() {
|
||||
SDL_SetWindowTitle(window_, window_title().c_str());
|
||||
}
|
||||
bool mouse_is_captured_ = false;
|
||||
SDL_Window *window_ = nullptr;
|
||||
std::string file_name_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -497,37 +497,37 @@ class DynamicWindowTitler {
|
||||
of historic hat values.
|
||||
*/
|
||||
class SDLJoystick {
|
||||
public:
|
||||
SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) {
|
||||
hat_values_.resize(SDL_JoystickNumHats(joystick));
|
||||
}
|
||||
public:
|
||||
SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) {
|
||||
hat_values_.resize(SDL_JoystickNumHats(joystick));
|
||||
}
|
||||
|
||||
~SDLJoystick() {
|
||||
SDL_JoystickClose(joystick_);
|
||||
}
|
||||
~SDLJoystick() {
|
||||
SDL_JoystickClose(joystick_);
|
||||
}
|
||||
|
||||
/// @returns The underlying SDL_Joystick.
|
||||
SDL_Joystick *get() {
|
||||
return joystick_;
|
||||
}
|
||||
/// @returns The underlying SDL_Joystick.
|
||||
SDL_Joystick *get() {
|
||||
return joystick_;
|
||||
}
|
||||
|
||||
/// @returns A reference to the storage for the previous state of hat @c c.
|
||||
Uint8 &last_hat_value(int c) {
|
||||
return hat_values_[c];
|
||||
}
|
||||
/// @returns A reference to the storage for the previous state of hat @c c.
|
||||
Uint8 &last_hat_value(int c) {
|
||||
return hat_values_[c];
|
||||
}
|
||||
|
||||
/// @returns The logic OR of all stored hat states.
|
||||
Uint8 hat_values() {
|
||||
Uint8 value = 0;
|
||||
for(const auto hat_value: hat_values_) {
|
||||
value |= hat_value;
|
||||
}
|
||||
return value;
|
||||
/// @returns The logic OR of all stored hat states.
|
||||
Uint8 hat_values() {
|
||||
Uint8 value = 0;
|
||||
for(const auto hat_value: hat_values_) {
|
||||
value |= hat_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
SDL_Joystick *joystick_;
|
||||
std::vector<Uint8> hat_values_;
|
||||
private:
|
||||
SDL_Joystick *joystick_;
|
||||
std::vector<Uint8> hat_values_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,106 +13,106 @@ using namespace CPU::Z80;
|
||||
namespace {
|
||||
|
||||
class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
|
||||
public:
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {}
|
||||
|
||||
inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
timestamp_ += cycle.length;
|
||||
if(!cycle.is_terminal()) {
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case PartialMachineCycle::ReadOpcode:
|
||||
check_address_for_trap(address);
|
||||
case PartialMachineCycle::Read:
|
||||
*cycle.value = memory_[address];
|
||||
break;
|
||||
case PartialMachineCycle::Write:
|
||||
memory_[address] = *cycle.value;
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Output:
|
||||
break;
|
||||
case PartialMachineCycle::Input:
|
||||
*cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff;
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Internal:
|
||||
case PartialMachineCycle::Refresh:
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Interrupt:
|
||||
// A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise
|
||||
// arbitrary.
|
||||
*cycle.value = 0x21;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(memory_delegate_ != nullptr) {
|
||||
memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
|
||||
}
|
||||
public:
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {}
|
||||
|
||||
inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
timestamp_ += cycle.length;
|
||||
if(!cycle.is_terminal()) {
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case PartialMachineCycle::ReadOpcode:
|
||||
check_address_for_trap(address);
|
||||
case PartialMachineCycle::Read:
|
||||
*cycle.value = memory_[address];
|
||||
break;
|
||||
case PartialMachineCycle::Write:
|
||||
memory_[address] = *cycle.value;
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Output:
|
||||
break;
|
||||
case PartialMachineCycle::Input:
|
||||
*cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff;
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Internal:
|
||||
case PartialMachineCycle::Refresh:
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Interrupt:
|
||||
// A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise
|
||||
// arbitrary.
|
||||
*cycle.value = 0x21;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
void run_for_instruction() final {
|
||||
int toggles = 0;
|
||||
int cycles = 0;
|
||||
if(memory_delegate_ != nullptr) {
|
||||
memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
|
||||
}
|
||||
|
||||
// Run:
|
||||
// (1) until is_starting_new_instruction is true;
|
||||
// (2) until it is false again; and
|
||||
// (3) until it is true again.
|
||||
while(true) {
|
||||
if(z80_.is_starting_new_instruction() != (toggles&1)) {
|
||||
++toggles;
|
||||
if(toggles == 3) break;
|
||||
}
|
||||
z80_.run_for(Cycles(1));
|
||||
++cycles;
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
void run_for_instruction() final {
|
||||
int toggles = 0;
|
||||
int cycles = 0;
|
||||
|
||||
// Run:
|
||||
// (1) until is_starting_new_instruction is true;
|
||||
// (2) until it is false again; and
|
||||
// (3) until it is true again.
|
||||
while(true) {
|
||||
if(z80_.is_starting_new_instruction() != (toggles&1)) {
|
||||
++toggles;
|
||||
if(toggles == 3) break;
|
||||
}
|
||||
z80_.run_for(Cycles(1));
|
||||
++cycles;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t value_of(Register r) final {
|
||||
return z80_.value_of(r);
|
||||
}
|
||||
uint16_t value_of(Register r) final {
|
||||
return z80_.value_of(r);
|
||||
}
|
||||
|
||||
void set_value_of(Register r, uint16_t value) final {
|
||||
z80_.set_value_of(r, value);
|
||||
}
|
||||
void set_value_of(Register r, uint16_t value) final {
|
||||
z80_.set_value_of(r, value);
|
||||
}
|
||||
|
||||
bool get_halt_line() final {
|
||||
return z80_.get_halt_line();
|
||||
}
|
||||
bool get_halt_line() final {
|
||||
return z80_.get_halt_line();
|
||||
}
|
||||
|
||||
void reset_power_on() final {
|
||||
return z80_.reset_power_on();
|
||||
}
|
||||
void reset_power_on() final {
|
||||
return z80_.reset_power_on();
|
||||
}
|
||||
|
||||
void set_interrupt_line(bool value) final {
|
||||
z80_.set_interrupt_line(value);
|
||||
}
|
||||
void set_interrupt_line(bool value) final {
|
||||
z80_.set_interrupt_line(value);
|
||||
}
|
||||
|
||||
void set_non_maskable_interrupt_line(bool value) final {
|
||||
z80_.set_non_maskable_interrupt_line(value);
|
||||
}
|
||||
void set_non_maskable_interrupt_line(bool value) final {
|
||||
z80_.set_non_maskable_interrupt_line(value);
|
||||
}
|
||||
|
||||
void set_wait_line(bool value) final {
|
||||
z80_.set_wait_line(value);
|
||||
}
|
||||
void set_wait_line(bool value) final {
|
||||
z80_.set_wait_line(value);
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor, false, true> z80_;
|
||||
bool was_m1_ = false;
|
||||
private:
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor, false, true> z80_;
|
||||
bool was_m1_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ struct RangeDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <bool use_end, typename... Args> static void dispatch(SequencerT &target, int begin, int end, Args&&... args) {
|
||||
private:
|
||||
template <bool use_end, typename... Args> static void dispatch(SequencerT &target, int begin, int end, Args&&... args) {
|
||||
#define index(n) \
|
||||
case n: \
|
||||
if constexpr (n <= SequencerT::max) { \
|
||||
@ -140,18 +140,18 @@ struct SubrangeDispatcher {
|
||||
#undef index
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int find_begin(int n) {
|
||||
const auto type = ClassifierT::region(n);
|
||||
while(n && ClassifierT::region(n - 1) == type) --n;
|
||||
return n;
|
||||
}
|
||||
private:
|
||||
static constexpr int find_begin(int n) {
|
||||
const auto type = ClassifierT::region(n);
|
||||
while(n && ClassifierT::region(n - 1) == type) --n;
|
||||
return n;
|
||||
}
|
||||
|
||||
static constexpr int find_end(int n) {
|
||||
const auto type = ClassifierT::region(n);
|
||||
while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n;
|
||||
return n;
|
||||
}
|
||||
static constexpr int find_end(int n) {
|
||||
const auto type = ClassifierT::region(n);
|
||||
while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n;
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
#undef switch_indices
|
||||
|
@ -39,132 +39,132 @@ namespace Reflection {
|
||||
No guarantees of speed or any other kind of efficiency are offered.
|
||||
*/
|
||||
class Enum {
|
||||
public:
|
||||
/*!
|
||||
Registers @c name and the entries within @c declaration for the enum type @c Type.
|
||||
public:
|
||||
/*!
|
||||
Registers @c name and the entries within @c declaration for the enum type @c Type.
|
||||
|
||||
Assuming the caller used the macros above, a standard pattern where both things can be placed in
|
||||
the same namespace might look like:
|
||||
Assuming the caller used the macros above, a standard pattern where both things can be placed in
|
||||
the same namespace might look like:
|
||||
|
||||
ReflectableEnum(MyEnum, int, A, B, C);
|
||||
ReflectableEnum(MyEnum, int, A, B, C);
|
||||
|
||||
...
|
||||
...
|
||||
|
||||
AnnounceEnum(MyEnum)
|
||||
AnnounceEnum(MyEnum)
|
||||
|
||||
If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the
|
||||
EnumDeclaration macro.
|
||||
*/
|
||||
template <typename Type> static void declare(const char *name, const char *declaration) {
|
||||
const char *d_ptr = declaration;
|
||||
If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the
|
||||
EnumDeclaration macro.
|
||||
*/
|
||||
template <typename Type> static void declare(const char *name, const char *declaration) {
|
||||
const char *d_ptr = declaration;
|
||||
|
||||
std::vector<std::string> result;
|
||||
while(true) {
|
||||
// Skip non-alphas, and exit if the terminator is found.
|
||||
while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr;
|
||||
if(!*d_ptr) break;
|
||||
std::vector<std::string> result;
|
||||
while(true) {
|
||||
// Skip non-alphas, and exit if the terminator is found.
|
||||
while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr;
|
||||
if(!*d_ptr) break;
|
||||
|
||||
// Note the current location and proceed for all alphas and digits.
|
||||
const auto start = d_ptr;
|
||||
while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr;
|
||||
// Note the current location and proceed for all alphas and digits.
|
||||
const auto start = d_ptr;
|
||||
while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr;
|
||||
|
||||
// Add a string view.
|
||||
result.emplace_back(std::string(start, size_t(d_ptr - start)));
|
||||
}
|
||||
|
||||
members_by_type_.emplace(std::type_index(typeid(Type)), result);
|
||||
names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name));
|
||||
// Add a string view.
|
||||
result.emplace_back(std::string(start, size_t(d_ptr - start)));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the declared name of the enum @c Type if it has been registered; the empty string otherwise.
|
||||
*/
|
||||
template <typename Type> static const std::string &name() {
|
||||
return name(typeid(Type));
|
||||
}
|
||||
members_by_type_.emplace(std::type_index(typeid(Type)), result);
|
||||
names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise.
|
||||
*/
|
||||
static const std::string &name(std::type_index type) {
|
||||
const auto entry = names_by_type_.find(type);
|
||||
if(entry == names_by_type_.end()) return empty_string_;
|
||||
return entry->second;
|
||||
}
|
||||
/*!
|
||||
@returns the declared name of the enum @c Type if it has been registered; the empty string otherwise.
|
||||
*/
|
||||
template <typename Type> static const std::string &name() {
|
||||
return name(typeid(Type));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the number of members of the enum @c Type if it has been registered; 0 otherwise.
|
||||
*/
|
||||
template <typename Type> static size_t size() {
|
||||
return size(typeid(Type));
|
||||
}
|
||||
/*!
|
||||
@returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise.
|
||||
*/
|
||||
static const std::string &name(std::type_index type) {
|
||||
const auto entry = names_by_type_.find(type);
|
||||
if(entry == names_by_type_.end()) return empty_string_;
|
||||
return entry->second;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise.
|
||||
*/
|
||||
static size_t size(std::type_index type) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return std::string::npos;
|
||||
return entry->second.size();
|
||||
}
|
||||
/*!
|
||||
@returns the number of members of the enum @c Type if it has been registered; 0 otherwise.
|
||||
*/
|
||||
template <typename Type> static size_t size() {
|
||||
return size(typeid(Type));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A @c std::string name for the enum value @c e.
|
||||
*/
|
||||
template <typename Type> static const std::string &to_string(Type e) {
|
||||
return to_string(typeid(Type), int(e));
|
||||
}
|
||||
/*!
|
||||
@returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise.
|
||||
*/
|
||||
static size_t size(std::type_index type) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return std::string::npos;
|
||||
return entry->second.size();
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A @c std::string name for the enum value @c e from the enum with type_info @c type.
|
||||
*/
|
||||
static const std::string &to_string(std::type_index type, int e) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return empty_string_;
|
||||
return entry->second[size_t(e)];
|
||||
}
|
||||
/*!
|
||||
@returns A @c std::string name for the enum value @c e.
|
||||
*/
|
||||
template <typename Type> static const std::string &to_string(Type e) {
|
||||
return to_string(typeid(Type), int(e));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise.
|
||||
*/
|
||||
static const std::vector<std::string> &all_values(std::type_index type) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return empty_vector_;
|
||||
return entry->second;
|
||||
}
|
||||
/*!
|
||||
@returns A @c std::string name for the enum value @c e from the enum with type_info @c type.
|
||||
*/
|
||||
static const std::string &to_string(std::type_index type, int e) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return empty_string_;
|
||||
return entry->second[size_t(e)];
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise.
|
||||
*/
|
||||
template <typename Type> static const std::vector<std::string> &all_values() {
|
||||
return all_values(typeid(Type));
|
||||
}
|
||||
/*!
|
||||
@returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise.
|
||||
*/
|
||||
static const std::vector<std::string> &all_values(std::type_index type) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return empty_vector_;
|
||||
return entry->second;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if
|
||||
the name is not found.
|
||||
*/
|
||||
template <typename Type> static Type from_string(const std::string &str) {
|
||||
return Type(from_string(typeid(Type), str));
|
||||
}
|
||||
/*!
|
||||
@returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise.
|
||||
*/
|
||||
template <typename Type> static const std::vector<std::string> &all_values() {
|
||||
return all_values(typeid(Type));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A value for the name @c str in the enum with type_info @c type , or @c -1 if
|
||||
the name is not found.
|
||||
*/
|
||||
static int from_string(std::type_index type, const std::string &str) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return -1;
|
||||
const auto iterator = std::find(entry->second.begin(), entry->second.end(), str);
|
||||
if(iterator == entry->second.end()) return -1;
|
||||
return int(iterator - entry->second.begin());
|
||||
}
|
||||
/*!
|
||||
@returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if
|
||||
the name is not found.
|
||||
*/
|
||||
template <typename Type> static Type from_string(const std::string &str) {
|
||||
return Type(from_string(typeid(Type), str));
|
||||
}
|
||||
|
||||
private:
|
||||
static inline std::unordered_map<std::type_index, std::vector<std::string>> members_by_type_;
|
||||
static inline std::unordered_map<std::type_index, std::string> names_by_type_;
|
||||
static inline const std::string empty_string_;
|
||||
static inline const std::vector<std::string> empty_vector_;
|
||||
/*!
|
||||
@returns A value for the name @c str in the enum with type_info @c type , or @c -1 if
|
||||
the name is not found.
|
||||
*/
|
||||
static int from_string(std::type_index type, const std::string &str) {
|
||||
const auto entry = members_by_type_.find(type);
|
||||
if(entry == members_by_type_.end()) return -1;
|
||||
const auto iterator = std::find(entry->second.begin(), entry->second.end(), str);
|
||||
if(iterator == entry->second.end()) return -1;
|
||||
return int(iterator - entry->second.begin());
|
||||
}
|
||||
|
||||
private:
|
||||
static inline std::unordered_map<std::type_index, std::vector<std::string>> members_by_type_;
|
||||
static inline std::unordered_map<std::type_index, std::string> names_by_type_;
|
||||
static inline const std::string empty_string_;
|
||||
static inline const std::vector<std::string> empty_vector_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -435,11 +435,11 @@ struct ArrayReceiver: public Reflection::Struct {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Reflection::Struct *target_;
|
||||
const std::type_info *type_;
|
||||
std::string key_;
|
||||
size_t count_;
|
||||
private:
|
||||
Reflection::Struct *target_;
|
||||
const std::type_info *type_;
|
||||
std::string key_;
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ struct Struct {
|
||||
*/
|
||||
virtual bool should_serialise([[maybe_unused]] const std::string &key) const { return true; }
|
||||
|
||||
private:
|
||||
void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const;
|
||||
bool deserialise(const uint8_t *bson, size_t size);
|
||||
private:
|
||||
void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const;
|
||||
bool deserialise(const uint8_t *bson, size_t size);
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -149,209 +149,209 @@ template <typename Type> bool get(const Struct &target, const std::string &name,
|
||||
template <typename Type> Type get(const Struct &target, const std::string &name, size_t offset = 0);
|
||||
|
||||
template <typename Owner> class StructImpl: public Struct {
|
||||
public:
|
||||
/*!
|
||||
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
|
||||
It is the caller's responsibility to provide an appropriate type of data.
|
||||
*/
|
||||
void *get(const std::string &name) final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
|
||||
}
|
||||
|
||||
/*!
|
||||
Stores the @c value of type @c Type to the offset registered for the field @c name.
|
||||
|
||||
public:
|
||||
/*!
|
||||
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
|
||||
It is the caller's responsibility to provide an appropriate type of data.
|
||||
*/
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return;
|
||||
assert(offset < iterator->second.count);
|
||||
memcpy(reinterpret_cast<uint8_t *>(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size);
|
||||
}
|
||||
*/
|
||||
void *get(const std::string &name) final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c type_info for the field @c name.
|
||||
*/
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return iterator->second.type;
|
||||
}
|
||||
/*!
|
||||
Stores the @c value of type @c Type to the offset registered for the field @c name.
|
||||
|
||||
/*!
|
||||
@returns The number of instances of objects of the same type as @c name that sit consecutively in memory.
|
||||
*/
|
||||
size_t count_of(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return 0;
|
||||
return iterator->second.count;
|
||||
}
|
||||
It is the caller's responsibility to provide an appropriate type of data.
|
||||
*/
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return;
|
||||
assert(offset < iterator->second.count);
|
||||
memcpy(reinterpret_cast<uint8_t *>(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
|
||||
the empty list otherwise.
|
||||
*/
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::vector<std::string> result;
|
||||
/*!
|
||||
@returns @c type_info for the field @c name.
|
||||
*/
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return iterator->second.type;
|
||||
}
|
||||
|
||||
// Return an empty vector if this field isn't declared.
|
||||
const auto type = type_of(name);
|
||||
if(!type) return result;
|
||||
/*!
|
||||
@returns The number of instances of objects of the same type as @c name that sit consecutively in memory.
|
||||
*/
|
||||
size_t count_of(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return 0;
|
||||
return iterator->second.count;
|
||||
}
|
||||
|
||||
// Also return an empty vector if this field isn't a registered enum.
|
||||
const auto all_values = Enum::all_values(*type);
|
||||
if(all_values.empty()) return result;
|
||||
/*!
|
||||
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
|
||||
the empty list otherwise.
|
||||
*/
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::vector<std::string> result;
|
||||
|
||||
// If no restriction is stored, return all values.
|
||||
const auto permitted_values = permitted_enum_values_.find(name);
|
||||
if(permitted_values == permitted_enum_values_.end()) return all_values;
|
||||
// Return an empty vector if this field isn't declared.
|
||||
const auto type = type_of(name);
|
||||
if(!type) return result;
|
||||
|
||||
// Compile a vector of only those values the stored set indicates.
|
||||
auto value = all_values.begin();
|
||||
auto flag = permitted_values->second.begin();
|
||||
while(value != all_values.end() && flag != permitted_values->second.end()) {
|
||||
if(*flag) {
|
||||
result.push_back(*value);
|
||||
}
|
||||
++flag;
|
||||
++value;
|
||||
// Also return an empty vector if this field isn't a registered enum.
|
||||
const auto all_values = Enum::all_values(*type);
|
||||
if(all_values.empty()) return result;
|
||||
|
||||
// If no restriction is stored, return all values.
|
||||
const auto permitted_values = permitted_enum_values_.find(name);
|
||||
if(permitted_values == permitted_enum_values_.end()) return all_values;
|
||||
|
||||
// Compile a vector of only those values the stored set indicates.
|
||||
auto value = all_values.begin();
|
||||
auto flag = permitted_values->second.begin();
|
||||
while(value != all_values.end() && flag != permitted_values->second.end()) {
|
||||
if(*flag) {
|
||||
result.push_back(*value);
|
||||
}
|
||||
|
||||
return result;
|
||||
++flag;
|
||||
++value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A vector of all declared fields for this struct.
|
||||
*/
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::vector<std::string> keys;
|
||||
for(const auto &pair: contents_) {
|
||||
keys.push_back(pair.first);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns A vector of all declared fields for this struct.
|
||||
*/
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::vector<std::string> keys;
|
||||
for(const auto &pair: contents_) {
|
||||
keys.push_back(pair.first);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
protected:
|
||||
/*
|
||||
This interface requires reflective structs to declare all fields;
|
||||
specifically they should call:
|
||||
|
||||
declare_field(&field1, "field1");
|
||||
declare_field(&field2, "field2");
|
||||
|
||||
Fields are registered in class storage. So callers can use needs_declare()
|
||||
to determine whether a class of this type has already established the
|
||||
reflective fields.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
|
||||
it'll be the struct that's exposed.
|
||||
*/
|
||||
template <typename Type> void declare(Type *t, const std::string &name) {
|
||||
// If the declared item is a class, see whether it can be dynamically cast
|
||||
// to a reflectable for emplacement. If so, exit early.
|
||||
if constexpr (std::is_class<Type>()) {
|
||||
if(declare_reflectable(t, name)) return;
|
||||
}
|
||||
|
||||
// If the declared item is an array, record it as a pointer to the
|
||||
// first element plus a size.
|
||||
if constexpr (std::is_array<Type>()) {
|
||||
declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
declare_emplace(t, name);
|
||||
}
|
||||
|
||||
/*!
|
||||
If @c t is a previously-declared field that links to a declared enum then the variable
|
||||
arguments provide a list of the acceptable values for that field. The list should be terminated
|
||||
with a value of -1.
|
||||
*/
|
||||
template <typename Type> void limit_enum(Type *t, ...) {
|
||||
const auto name = name_of(t);
|
||||
if(name.empty()) return;
|
||||
|
||||
// The default vector size of '8' isn't especially scientific,
|
||||
// but I feel like it's a good choice.
|
||||
std::vector<bool> permitted_values(8);
|
||||
|
||||
va_list list;
|
||||
va_start(list, t);
|
||||
while(true) {
|
||||
const int next = va_arg(list, int);
|
||||
if(next < 0) break;
|
||||
|
||||
if(permitted_values.size() <= size_t(next)) {
|
||||
permitted_values.resize(permitted_values.size() << 1);
|
||||
}
|
||||
return keys;
|
||||
permitted_values[size_t(next)] = true;
|
||||
}
|
||||
va_end(list);
|
||||
|
||||
permitted_enum_values_.emplace(name, permitted_values);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if this subclass of @c Struct has not yet declared any fields.
|
||||
*/
|
||||
bool needs_declare() {
|
||||
return contents_.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
Performs a reverse lookup from field to name.
|
||||
*/
|
||||
std::string name_of(void *field) {
|
||||
const ssize_t offset = reinterpret_cast<uint8_t *>(field) - reinterpret_cast<uint8_t *>(this);
|
||||
|
||||
auto iterator = contents_.begin();
|
||||
while(iterator != contents_.end()) {
|
||||
if(iterator->second.offset == offset) break;
|
||||
++iterator;
|
||||
}
|
||||
|
||||
protected:
|
||||
/*
|
||||
This interface requires reflective structs to declare all fields;
|
||||
specifically they should call:
|
||||
if(iterator != contents_.end()) {
|
||||
return iterator->first;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
declare_field(&field1, "field1");
|
||||
declare_field(&field2, "field2");
|
||||
|
||||
Fields are registered in class storage. So callers can use needs_declare()
|
||||
to determine whether a class of this type has already established the
|
||||
reflective fields.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
|
||||
it'll be the struct that's exposed.
|
||||
*/
|
||||
template <typename Type> void declare(Type *t, const std::string &name) {
|
||||
// If the declared item is a class, see whether it can be dynamically cast
|
||||
// to a reflectable for emplacement. If so, exit early.
|
||||
if constexpr (std::is_class<Type>()) {
|
||||
if(declare_reflectable(t, name)) return;
|
||||
}
|
||||
|
||||
// If the declared item is an array, record it as a pointer to the
|
||||
// first element plus a size.
|
||||
if constexpr (std::is_array<Type>()) {
|
||||
declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
declare_emplace(t, name);
|
||||
private:
|
||||
template <typename Type> bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) {
|
||||
if constexpr (std::is_base_of<Reflection::Struct, Type>::value) {
|
||||
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
|
||||
declare_emplace(str, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
If @c t is a previously-declared field that links to a declared enum then the variable
|
||||
arguments provide a list of the acceptable values for that field. The list should be terminated
|
||||
with a value of -1.
|
||||
*/
|
||||
template <typename Type> void limit_enum(Type *t, ...) {
|
||||
const auto name = name_of(t);
|
||||
if(name.empty()) return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The default vector size of '8' isn't especially scientific,
|
||||
// but I feel like it's a good choice.
|
||||
std::vector<bool> permitted_values(8);
|
||||
template <typename Type> void declare_emplace(Type *t, const std::string &name, size_t count = 1) {
|
||||
contents_.emplace(
|
||||
std::make_pair(
|
||||
name,
|
||||
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type), count)
|
||||
));
|
||||
}
|
||||
|
||||
va_list list;
|
||||
va_start(list, t);
|
||||
while(true) {
|
||||
const int next = va_arg(list, int);
|
||||
if(next < 0) break;
|
||||
|
||||
if(permitted_values.size() <= size_t(next)) {
|
||||
permitted_values.resize(permitted_values.size() << 1);
|
||||
}
|
||||
permitted_values[size_t(next)] = true;
|
||||
}
|
||||
va_end(list);
|
||||
|
||||
permitted_enum_values_.emplace(name, permitted_values);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if this subclass of @c Struct has not yet declared any fields.
|
||||
*/
|
||||
bool needs_declare() {
|
||||
return contents_.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
Performs a reverse lookup from field to name.
|
||||
*/
|
||||
std::string name_of(void *field) {
|
||||
const ssize_t offset = reinterpret_cast<uint8_t *>(field) - reinterpret_cast<uint8_t *>(this);
|
||||
|
||||
auto iterator = contents_.begin();
|
||||
while(iterator != contents_.end()) {
|
||||
if(iterator->second.offset == offset) break;
|
||||
++iterator;
|
||||
}
|
||||
|
||||
if(iterator != contents_.end()) {
|
||||
return iterator->first;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Type> bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) {
|
||||
if constexpr (std::is_base_of<Reflection::Struct, Type>::value) {
|
||||
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
|
||||
declare_emplace(str, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Type> void declare_emplace(Type *t, const std::string &name, size_t count = 1) {
|
||||
contents_.emplace(
|
||||
std::make_pair(
|
||||
name,
|
||||
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type), count)
|
||||
));
|
||||
}
|
||||
|
||||
struct Field {
|
||||
const std::type_info *type;
|
||||
ssize_t offset;
|
||||
size_t size;
|
||||
size_t count;
|
||||
Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) :
|
||||
type(&type), offset(offset), size(size), count(count) {}
|
||||
};
|
||||
static inline std::unordered_map<std::string, Field> contents_;
|
||||
static inline std::unordered_map<std::string, std::vector<bool>> permitted_enum_values_;
|
||||
struct Field {
|
||||
const std::type_info *type;
|
||||
ssize_t offset;
|
||||
size_t size;
|
||||
size_t count;
|
||||
Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) :
|
||||
type(&type), offset(offset), size(size), count(count) {}
|
||||
};
|
||||
static inline std::unordered_map<std::string, Field> contents_;
|
||||
static inline std::unordered_map<std::string, std::vector<bool>> permitted_enum_values_;
|
||||
};
|
||||
|
||||
|
||||
|
@ -28,75 +28,87 @@ namespace SignalProcessing {
|
||||
smaller numbers permit a filter that operates more quickly and with less lag but less effectively.
|
||||
*/
|
||||
class FIRFilter {
|
||||
private:
|
||||
static constexpr float FixedMultiplier = 32767.0f;
|
||||
static constexpr int FixedShift = 15;
|
||||
private:
|
||||
static constexpr float FixedMultiplier = 32767.0f;
|
||||
static constexpr int FixedShift = 15;
|
||||
|
||||
public:
|
||||
/*! A suggested default attenuation value. */
|
||||
constexpr static float DefaultAttenuation = 60.0f;
|
||||
/*!
|
||||
Creates an instance of @c FIRFilter.
|
||||
public:
|
||||
/*! A suggested default attenuation value. */
|
||||
constexpr static float DefaultAttenuation = 60.0f;
|
||||
/*!
|
||||
Creates an instance of @c FIRFilter.
|
||||
|
||||
@param number_of_taps The size of window for input data.
|
||||
@param input_sample_rate The sampling rate of the input signal.
|
||||
@param low_frequency The lowest frequency of signal to retain in the output.
|
||||
@param high_frequency The highest frequency of signal to retain in the output.
|
||||
@param attenuation The attenuation of the discarded frequencies.
|
||||
*/
|
||||
FIRFilter(std::size_t number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation = DefaultAttenuation);
|
||||
FIRFilter(const std::vector<float> &coefficients);
|
||||
@param number_of_taps The size of window for input data.
|
||||
@param input_sample_rate The sampling rate of the input signal.
|
||||
@param low_frequency The lowest frequency of signal to retain in the output.
|
||||
@param high_frequency The highest frequency of signal to retain in the output.
|
||||
@param attenuation The attenuation of the discarded frequencies.
|
||||
*/
|
||||
FIRFilter(
|
||||
std::size_t number_of_taps,
|
||||
float input_sample_rate,
|
||||
float low_frequency,
|
||||
float high_frequency,
|
||||
float attenuation = DefaultAttenuation
|
||||
);
|
||||
FIRFilter(const std::vector<float> &coefficients);
|
||||
|
||||
/*!
|
||||
Applies the filter to one batch of input samples, returning the net result.
|
||||
/*!
|
||||
Applies the filter to one batch of input samples, returning the net result.
|
||||
|
||||
@param src The source buffer to apply the filter to.
|
||||
@returns The result of applying the filter.
|
||||
*/
|
||||
inline short apply(const short *src, size_t stride = 1) const {
|
||||
#ifdef USE_ACCELERATE
|
||||
short result;
|
||||
vDSP_dotpr_s1_15(filter_coefficients_.data(), 1, src, vDSP_Stride(stride), &result, filter_coefficients_.size());
|
||||
return result;
|
||||
#else
|
||||
int outputValue = 0;
|
||||
for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) {
|
||||
outputValue += filter_coefficients_[c] * src[c * stride];
|
||||
}
|
||||
return short(outputValue >> FixedShift);
|
||||
#endif
|
||||
}
|
||||
@param src The source buffer to apply the filter to.
|
||||
@returns The result of applying the filter.
|
||||
*/
|
||||
inline short apply(const short *src, size_t stride = 1) const {
|
||||
#ifdef USE_ACCELERATE
|
||||
short result;
|
||||
vDSP_dotpr_s1_15(
|
||||
filter_coefficients_.data(),
|
||||
1,
|
||||
src,
|
||||
vDSP_Stride(stride), &result, filter_coefficients_.size()
|
||||
);
|
||||
return result;
|
||||
#else
|
||||
int outputValue = 0;
|
||||
for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) {
|
||||
outputValue += filter_coefficients_[c] * src[c * stride];
|
||||
}
|
||||
return short(outputValue >> FixedShift);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! @returns The number of taps used by this filter. */
|
||||
inline std::size_t get_number_of_taps() const {
|
||||
return filter_coefficients_.size();
|
||||
}
|
||||
/*! @returns The number of taps used by this filter. */
|
||||
inline std::size_t get_number_of_taps() const {
|
||||
return filter_coefficients_.size();
|
||||
}
|
||||
|
||||
/*! @returns The weighted coefficients that describe this filter. */
|
||||
std::vector<float> get_coefficients() const;
|
||||
/*! @returns The weighted coefficients that describe this filter. */
|
||||
std::vector<float> get_coefficients() const;
|
||||
|
||||
/*!
|
||||
@returns A filter that would have the effect of adding (and scaling) the outputs of the two filters.
|
||||
Defined only if both have the same number of taps.
|
||||
*/
|
||||
FIRFilter operator+(const FIRFilter &) const;
|
||||
/*!
|
||||
@returns A filter that would have the effect of adding (and scaling) the outputs of the two filters.
|
||||
Defined only if both have the same number of taps.
|
||||
*/
|
||||
FIRFilter operator+(const FIRFilter &) const;
|
||||
|
||||
/*!
|
||||
@returns A filter that would have the effect of applying the two filters in succession.
|
||||
Defined only if both have the same number of taps.
|
||||
*/
|
||||
FIRFilter operator*(const FIRFilter &) const;
|
||||
/*!
|
||||
@returns A filter that would have the effect of applying the two filters in succession.
|
||||
Defined only if both have the same number of taps.
|
||||
*/
|
||||
FIRFilter operator*(const FIRFilter &) const;
|
||||
|
||||
/*!
|
||||
@returns A filter that would have the opposite effect of this filter.
|
||||
*/
|
||||
FIRFilter operator-() const;
|
||||
/*!
|
||||
@returns A filter that would have the opposite effect of this filter.
|
||||
*/
|
||||
FIRFilter operator-() const;
|
||||
|
||||
private:
|
||||
std::vector<short> filter_coefficients_;
|
||||
private:
|
||||
std::vector<short> filter_coefficients_;
|
||||
|
||||
static void coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps);
|
||||
static float ino(float a);
|
||||
static void coefficients_for_idealised_filter_response(
|
||||
short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps);
|
||||
static float ino(float a);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -23,73 +23,73 @@ namespace SignalProcessing {
|
||||
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
|
||||
*/
|
||||
class Stepper {
|
||||
public:
|
||||
/*!
|
||||
Establishes a stepper with a one-to-one conversion rate.
|
||||
*/
|
||||
Stepper() : Stepper(1,1) {}
|
||||
public:
|
||||
/*!
|
||||
Establishes a stepper with a one-to-one conversion rate.
|
||||
*/
|
||||
Stepper() : Stepper(1,1) {}
|
||||
|
||||
/*!
|
||||
Establishes a stepper that will receive steps at the @c input_rate and dictate the number
|
||||
of steps that should be taken at the @c output_rate.
|
||||
*/
|
||||
Stepper(uint64_t output_rate, uint64_t input_rate) :
|
||||
accumulated_error_(-(int64_t(input_rate) << 1)),
|
||||
input_rate_(input_rate),
|
||||
output_rate_(output_rate),
|
||||
whole_step_(output_rate / input_rate),
|
||||
adjustment_up_(int64_t(output_rate % input_rate) << 1),
|
||||
adjustment_down_(int64_t(input_rate) << 1) {}
|
||||
/*!
|
||||
Establishes a stepper that will receive steps at the @c input_rate and dictate the number
|
||||
of steps that should be taken at the @c output_rate.
|
||||
*/
|
||||
Stepper(uint64_t output_rate, uint64_t input_rate) :
|
||||
accumulated_error_(-(int64_t(input_rate) << 1)),
|
||||
input_rate_(input_rate),
|
||||
output_rate_(output_rate),
|
||||
whole_step_(output_rate / input_rate),
|
||||
adjustment_up_(int64_t(output_rate % input_rate) << 1),
|
||||
adjustment_down_(int64_t(input_rate) << 1) {}
|
||||
|
||||
/*!
|
||||
Advances one step at the input rate.
|
||||
/*!
|
||||
Advances one step at the input rate.
|
||||
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step() {
|
||||
uint64_t update = whole_step_;
|
||||
accumulated_error_ += adjustment_up_;
|
||||
if(accumulated_error_ > 0) {
|
||||
update++;
|
||||
accumulated_error_ -= adjustment_down_;
|
||||
}
|
||||
return update;
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step() {
|
||||
uint64_t update = whole_step_;
|
||||
accumulated_error_ += adjustment_up_;
|
||||
if(accumulated_error_ > 0) {
|
||||
update++;
|
||||
accumulated_error_ -= adjustment_down_;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances by @c number_of_steps steps at the input rate.
|
||||
/*!
|
||||
Advances by @c number_of_steps steps at the input rate.
|
||||
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step(uint64_t number_of_steps) {
|
||||
uint64_t update = whole_step_ * number_of_steps;
|
||||
accumulated_error_ += adjustment_up_ * int64_t(number_of_steps);
|
||||
if(accumulated_error_ > 0) {
|
||||
update += 1 + uint64_t(accumulated_error_ / adjustment_down_);
|
||||
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
|
||||
}
|
||||
return update;
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step(uint64_t number_of_steps) {
|
||||
uint64_t update = whole_step_ * number_of_steps;
|
||||
accumulated_error_ += adjustment_up_ * int64_t(number_of_steps);
|
||||
if(accumulated_error_ > 0) {
|
||||
update += 1 + uint64_t(accumulated_error_ / adjustment_down_);
|
||||
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the output rate.
|
||||
*/
|
||||
inline uint64_t get_output_rate() {
|
||||
return output_rate_;
|
||||
}
|
||||
/*!
|
||||
@returns the output rate.
|
||||
*/
|
||||
inline uint64_t get_output_rate() const {
|
||||
return output_rate_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the input rate.
|
||||
*/
|
||||
inline uint64_t get_input_rate() {
|
||||
return input_rate_;
|
||||
}
|
||||
/*!
|
||||
@returns the input rate.
|
||||
*/
|
||||
inline uint64_t get_input_rate() const {
|
||||
return input_rate_;
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t accumulated_error_;
|
||||
uint64_t input_rate_, output_rate_;
|
||||
uint64_t whole_step_;
|
||||
int64_t adjustment_up_, adjustment_down_;
|
||||
private:
|
||||
int64_t accumulated_error_;
|
||||
uint64_t input_rate_, output_rate_;
|
||||
uint64_t whole_step_;
|
||||
int64_t adjustment_up_, adjustment_down_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,59 +24,59 @@ namespace Storage::Cartridge {
|
||||
making the base class 100% descriptive.
|
||||
*/
|
||||
class Cartridge {
|
||||
public:
|
||||
struct Segment {
|
||||
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> &&data) :
|
||||
start_address(start_address), end_address(end_address), data(data) {}
|
||||
public:
|
||||
struct Segment {
|
||||
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> &&data) :
|
||||
start_address(start_address), end_address(end_address), data(data) {}
|
||||
|
||||
Segment(size_t start_address, size_t end_address, const std::vector<uint8_t> &data) :
|
||||
start_address(start_address), end_address(end_address), data(data) {}
|
||||
Segment(size_t start_address, size_t end_address, const std::vector<uint8_t> &data) :
|
||||
start_address(start_address), end_address(end_address), data(data) {}
|
||||
|
||||
Segment(size_t start_address, std::vector<uint8_t> &&data) :
|
||||
Segment(start_address, start_address + data.size(), data) {}
|
||||
Segment(size_t start_address, std::vector<uint8_t> &&data) :
|
||||
Segment(start_address, start_address + data.size(), data) {}
|
||||
|
||||
Segment(size_t start_address, const std::vector<uint8_t> &data) :
|
||||
Segment(start_address, start_address + data.size(), data) {}
|
||||
Segment(size_t start_address, const std::vector<uint8_t> &data) :
|
||||
Segment(start_address, start_address + data.size(), data) {}
|
||||
|
||||
Segment(Segment &&segment) :
|
||||
start_address(segment.start_address),
|
||||
end_address(segment.end_address),
|
||||
data(std::move(segment.data)) {}
|
||||
Segment(Segment &&segment) :
|
||||
start_address(segment.start_address),
|
||||
end_address(segment.end_address),
|
||||
data(std::move(segment.data)) {}
|
||||
|
||||
Segment(const Segment &segment) :
|
||||
start_address(segment.start_address),
|
||||
end_address(segment.end_address),
|
||||
data(segment.data) {}
|
||||
Segment(const Segment &segment) :
|
||||
start_address(segment.start_address),
|
||||
end_address(segment.end_address),
|
||||
data(segment.data) {}
|
||||
|
||||
/// Indicates that an address is unknown.
|
||||
static const size_t UnknownAddress;
|
||||
/// Indicates that an address is unknown.
|
||||
static const size_t UnknownAddress;
|
||||
|
||||
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
|
||||
size_t start_address;
|
||||
/*!
|
||||
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
|
||||
to start_address + data_length due to potential paging.
|
||||
*/
|
||||
size_t end_address;
|
||||
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
|
||||
size_t start_address;
|
||||
/*!
|
||||
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
|
||||
to start_address + data_length due to potential paging.
|
||||
*/
|
||||
size_t end_address;
|
||||
|
||||
/*!
|
||||
The data contents for this segment. If @c start_address and @c end_address are suppled then
|
||||
the first end_address - start_address bytes will be those initially visible. The size will
|
||||
not necessarily be the same as @c end_address - @c start_address due to potential paging.
|
||||
*/
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
/*!
|
||||
The data contents for this segment. If @c start_address and @c end_address are suppled then
|
||||
the first end_address - start_address bytes will be those initially visible. The size will
|
||||
not necessarily be the same as @c end_address - @c start_address due to potential paging.
|
||||
*/
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
const std::vector<Segment> &get_segments() const {
|
||||
return segments_;
|
||||
}
|
||||
virtual ~Cartridge() = default;
|
||||
const std::vector<Segment> &get_segments() const {
|
||||
return segments_;
|
||||
}
|
||||
virtual ~Cartridge() = default;
|
||||
|
||||
Cartridge() = default;
|
||||
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
|
||||
Cartridge() = default;
|
||||
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
|
||||
|
||||
protected:
|
||||
std::vector<Segment> segments_;
|
||||
protected:
|
||||
std::vector<Segment> segments_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,12 +15,12 @@
|
||||
namespace Storage::Cartridge {
|
||||
|
||||
class BinaryDump : public Cartridge {
|
||||
public:
|
||||
BinaryDump(const std::string &file_name);
|
||||
public:
|
||||
BinaryDump(const std::string &file_name);
|
||||
|
||||
enum {
|
||||
ErrorNotAccessible
|
||||
};
|
||||
enum {
|
||||
ErrorNotAccessible
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,12 +15,12 @@
|
||||
namespace Storage::Cartridge {
|
||||
|
||||
class PRG : public Cartridge {
|
||||
public:
|
||||
PRG(const std::string &file_name);
|
||||
public:
|
||||
PRG(const std::string &file_name);
|
||||
|
||||
enum {
|
||||
ErrorNotROM
|
||||
};
|
||||
enum {
|
||||
ErrorNotROM
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -30,139 +30,139 @@ class Controller:
|
||||
public ClockingHint::Source,
|
||||
private Drive::EventDelegate,
|
||||
private ClockingHint::Observer {
|
||||
protected:
|
||||
/*!
|
||||
Constructs a @c Controller that will be run at @c clock_rate.
|
||||
*/
|
||||
Controller(Cycles clock_rate);
|
||||
protected:
|
||||
/*!
|
||||
Constructs a @c Controller that will be run at @c clock_rate.
|
||||
*/
|
||||
Controller(Cycles clock_rate);
|
||||
|
||||
/*!
|
||||
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
||||
*/
|
||||
void set_expected_bit_length(Time bit_length);
|
||||
/*!
|
||||
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
||||
*/
|
||||
void set_expected_bit_length(Time bit_length);
|
||||
|
||||
/*!
|
||||
Advances the drive by @c number_of_cycles cycles.
|
||||
*/
|
||||
void run_for(const Cycles cycles);
|
||||
/*!
|
||||
Advances the drive by @c number_of_cycles cycles.
|
||||
*/
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/*!
|
||||
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
|
||||
machines allow zero or multiple drives to be attached, with useless results.
|
||||
/*!
|
||||
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
|
||||
machines allow zero or multiple drives to be attached, with useless results.
|
||||
|
||||
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
|
||||
*/
|
||||
void set_drive(int index_mask);
|
||||
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
|
||||
*/
|
||||
void set_drive(int index_mask);
|
||||
|
||||
/*!
|
||||
Adds a new drive to the drive list, returning its index.
|
||||
*/
|
||||
template<typename... Args> size_t emplace_drive(Args&&... args) {
|
||||
drives_.emplace_back(new Drive(std::forward<Args>(args)...));
|
||||
drives_.back()->set_clocking_hint_observer(this);
|
||||
return drives_.size() - 1;
|
||||
/*!
|
||||
Adds a new drive to the drive list, returning its index.
|
||||
*/
|
||||
template<typename... Args> size_t emplace_drive(Args&&... args) {
|
||||
drives_.emplace_back(new Drive(std::forward<Args>(args)...));
|
||||
drives_.back()->set_clocking_hint_observer(this);
|
||||
return drives_.size() - 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds @c count new drives to the drive list, returning the index of the final one added.
|
||||
*/
|
||||
template<typename... Args> size_t emplace_drives(size_t count, Args&&... args) {
|
||||
while(count--) {
|
||||
emplace_drive(std::forward<Args>(args)...);
|
||||
}
|
||||
return drives_.size() - 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds @c count new drives to the drive list, returning the index of the final one added.
|
||||
*/
|
||||
template<typename... Args> size_t emplace_drives(size_t count, Args&&... args) {
|
||||
while(count--) {
|
||||
emplace_drive(std::forward<Args>(args)...);
|
||||
}
|
||||
return drives_.size() - 1;
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates each bit that the PLL recognises.
|
||||
*/
|
||||
virtual void process_input_bit(int value) = 0;
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates that the index hole has been reached.
|
||||
*/
|
||||
virtual void process_index_hole() = 0;
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses if they implement writing; communicates that
|
||||
all bits supplied to write_bit have now been written.
|
||||
*/
|
||||
virtual void process_write_completed() override;
|
||||
|
||||
/*!
|
||||
Puts the controller and the drive returned by get_drive() into write mode, supplying to
|
||||
the drive the current bit length.
|
||||
|
||||
While the controller is in write mode it disconnects the PLL. So subclasses will not
|
||||
receive any calls to @c process_input_bit.
|
||||
|
||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||
the index hole. Writing will continue over the index hole otherwise.
|
||||
*/
|
||||
void begin_writing(bool clamp_to_index_hole);
|
||||
|
||||
/*!
|
||||
Puts the drive returned by get_drive() out of write mode, and marks the controller
|
||||
as no longer being in write mode.
|
||||
*/
|
||||
void end_writing();
|
||||
|
||||
/*!
|
||||
@returns @c true if the controller is in reading mode; @c false otherwise.
|
||||
*/
|
||||
bool is_reading();
|
||||
|
||||
/*!
|
||||
Returns the connected drive or, if none is connected, an invented one. No guarantees are
|
||||
made about the lifetime or the exclusivity of the invented drive.
|
||||
*/
|
||||
Drive &get_drive();
|
||||
const Drive &get_drive() const;
|
||||
|
||||
Drive &get_drive(size_t index) {
|
||||
return *drives_[index];
|
||||
}
|
||||
const Drive &get_drive(size_t index) const {
|
||||
return *drives_[index];
|
||||
}
|
||||
|
||||
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
|
||||
size_t index = 0;
|
||||
for(auto &drive: drives_) {
|
||||
func(*drive, index);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates each bit that the PLL recognises.
|
||||
*/
|
||||
virtual void process_input_bit(int value) = 0;
|
||||
/*!
|
||||
As per ClockingHint::Source.
|
||||
*/
|
||||
ClockingHint::Preference preferred_clocking() const override;
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates that the index hole has been reached.
|
||||
*/
|
||||
virtual void process_index_hole() = 0;
|
||||
private:
|
||||
Time bit_length_;
|
||||
Cycles::IntType clock_rate_multiplier_ = 1;
|
||||
Cycles::IntType clock_rate_ = 1;
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses if they implement writing; communicates that
|
||||
all bits supplied to write_bit have now been written.
|
||||
*/
|
||||
virtual void process_write_completed() override;
|
||||
bool is_reading_ = true;
|
||||
|
||||
/*!
|
||||
Puts the controller and the drive returned by get_drive() into write mode, supplying to
|
||||
the drive the current bit length.
|
||||
DigitalPhaseLockedLoop<Controller> pll_;
|
||||
friend DigitalPhaseLockedLoop<Controller>;
|
||||
|
||||
While the controller is in write mode it disconnects the PLL. So subclasses will not
|
||||
receive any calls to @c process_input_bit.
|
||||
Drive empty_drive_;
|
||||
std::vector<std::unique_ptr<Drive>> drives_;
|
||||
Drive *drive_;
|
||||
int drive_selection_mask_ = 0xff;
|
||||
|
||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||
the index hole. Writing will continue over the index hole otherwise.
|
||||
*/
|
||||
void begin_writing(bool clamp_to_index_hole);
|
||||
// ClockingHint::Observer.
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
|
||||
/*!
|
||||
Puts the drive returned by get_drive() out of write mode, and marks the controller
|
||||
as no longer being in write mode.
|
||||
*/
|
||||
void end_writing();
|
||||
// for Drive::EventDelegate
|
||||
void process_event(const Drive::Event &event) final;
|
||||
void advance(const Cycles cycles) final;
|
||||
|
||||
/*!
|
||||
@returns @c true if the controller is in reading mode; @c false otherwise.
|
||||
*/
|
||||
bool is_reading();
|
||||
|
||||
/*!
|
||||
Returns the connected drive or, if none is connected, an invented one. No guarantees are
|
||||
made about the lifetime or the exclusivity of the invented drive.
|
||||
*/
|
||||
Drive &get_drive();
|
||||
const Drive &get_drive() const;
|
||||
|
||||
Drive &get_drive(size_t index) {
|
||||
return *drives_[index];
|
||||
}
|
||||
const Drive &get_drive(size_t index) const {
|
||||
return *drives_[index];
|
||||
}
|
||||
|
||||
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
|
||||
size_t index = 0;
|
||||
for(auto &drive: drives_) {
|
||||
func(*drive, index);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
As per ClockingHint::Source.
|
||||
*/
|
||||
ClockingHint::Preference preferred_clocking() const override;
|
||||
|
||||
private:
|
||||
Time bit_length_;
|
||||
Cycles::IntType clock_rate_multiplier_ = 1;
|
||||
Cycles::IntType clock_rate_ = 1;
|
||||
|
||||
bool is_reading_ = true;
|
||||
|
||||
DigitalPhaseLockedLoop<Controller> pll_;
|
||||
friend DigitalPhaseLockedLoop<Controller>;
|
||||
|
||||
Drive empty_drive_;
|
||||
std::vector<std::unique_ptr<Drive>> drives_;
|
||||
Drive *drive_;
|
||||
int drive_selection_mask_ = 0xff;
|
||||
|
||||
// ClockingHint::Observer.
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
|
||||
// for Drive::EventDelegate
|
||||
void process_event(const Drive::Event &event) final;
|
||||
void advance(const Cycles cycles) final;
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -20,148 +20,148 @@ namespace Storage::Disk {
|
||||
being able to post event messages to subclasses.
|
||||
*/
|
||||
class MFMController: public Controller {
|
||||
public:
|
||||
MFMController(Cycles clock_rate);
|
||||
public:
|
||||
MFMController(Cycles clock_rate);
|
||||
|
||||
protected:
|
||||
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
|
||||
void set_is_double_density(bool);
|
||||
protected:
|
||||
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
|
||||
void set_is_double_density(bool);
|
||||
|
||||
/// @returns @c true if currently decoding MFM content; @c false otherwise.
|
||||
bool get_is_double_density();
|
||||
/// @returns @c true if currently decoding MFM content; @c false otherwise.
|
||||
bool get_is_double_density();
|
||||
|
||||
enum DataMode {
|
||||
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
|
||||
Scanning,
|
||||
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
|
||||
Reading,
|
||||
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
|
||||
Writing
|
||||
};
|
||||
/// Sets the current data mode.
|
||||
void set_data_mode(DataMode);
|
||||
enum DataMode {
|
||||
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
|
||||
Scanning,
|
||||
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
|
||||
Reading,
|
||||
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
|
||||
Writing
|
||||
};
|
||||
/// Sets the current data mode.
|
||||
void set_data_mode(DataMode);
|
||||
|
||||
/*!
|
||||
Describes a token found in the incoming PLL bit stream. Tokens can be one of:
|
||||
/*!
|
||||
Describes a token found in the incoming PLL bit stream. Tokens can be one of:
|
||||
|
||||
Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole;
|
||||
ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc.
|
||||
Data: the pattern that begins a data section, i.e. sector contents.
|
||||
DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents.
|
||||
Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole;
|
||||
ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc.
|
||||
Data: the pattern that begins a data section, i.e. sector contents.
|
||||
DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents.
|
||||
|
||||
Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types
|
||||
of token listed above; this class combines notification of that mark and the distinct index sync mark.
|
||||
Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an
|
||||
announcement of sync followed by an announcement of one of the above four types of token.
|
||||
Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types
|
||||
of token listed above; this class combines notification of that mark and the distinct index sync mark.
|
||||
Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an
|
||||
announcement of sync followed by an announcement of one of the above four types of token.
|
||||
|
||||
Byte: reports reading of an ordinary byte, with expected timing bits.
|
||||
Byte: reports reading of an ordinary byte, with expected timing bits.
|
||||
|
||||
When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token
|
||||
is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as
|
||||
spurious sync signals can otherwise be found in ordinary data, causing framing errors.
|
||||
*/
|
||||
struct Token {
|
||||
enum Type {
|
||||
Index, ID, Data, DeletedData, Sync, Byte
|
||||
} type;
|
||||
uint8_t byte_value;
|
||||
};
|
||||
/// @returns The most-recently read token from the surface of the disk.
|
||||
Token get_latest_token();
|
||||
When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token
|
||||
is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as
|
||||
spurious sync signals can otherwise be found in ordinary data, causing framing errors.
|
||||
*/
|
||||
struct Token {
|
||||
enum Type {
|
||||
Index, ID, Data, DeletedData, Sync, Byte
|
||||
} type;
|
||||
uint8_t byte_value;
|
||||
};
|
||||
/// @returns The most-recently read token from the surface of the disk.
|
||||
Token get_latest_token();
|
||||
|
||||
/// @returns The controller's CRC generator. This is automatically fed during reading.
|
||||
CRC::CCITT &get_crc_generator();
|
||||
/// @returns The controller's CRC generator. This is automatically fed during reading.
|
||||
CRC::CCITT &get_crc_generator();
|
||||
|
||||
// Events
|
||||
enum class Event: int {
|
||||
Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
|
||||
IndexHole = (1 << 1), // Indicates the passing of a physical index hole.
|
||||
DataWritten = (1 << 2), // Indicates that all queued bits have been written
|
||||
};
|
||||
// Events
|
||||
enum class Event: int {
|
||||
Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
|
||||
IndexHole = (1 << 1), // Indicates the passing of a physical index hole.
|
||||
DataWritten = (1 << 2), // Indicates that all queued bits have been written
|
||||
};
|
||||
|
||||
/*!
|
||||
Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream.
|
||||
Therefore it is called to announce when:
|
||||
/*!
|
||||
Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream.
|
||||
Therefore it is called to announce when:
|
||||
|
||||
(i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data.
|
||||
(ii) the index hole passes; or
|
||||
(iii) the queue of data to be written has been exhausted.
|
||||
*/
|
||||
virtual void posit_event(int type) = 0;
|
||||
(i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data.
|
||||
(ii) the index hole passes; or
|
||||
(iii) the queue of data to be written has been exhausted.
|
||||
*/
|
||||
virtual void posit_event(int type) = 0;
|
||||
|
||||
/*!
|
||||
Encodes @c bit according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_bit(int bit);
|
||||
/*!
|
||||
Encodes @c bit according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_bit(int bit);
|
||||
|
||||
/*!
|
||||
Encodes @c byte according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_byte(uint8_t byte);
|
||||
/*!
|
||||
Encodes @c byte according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_byte(uint8_t byte);
|
||||
|
||||
/*!
|
||||
Serialises @c value into the controller's write buffer without adjustment.
|
||||
*/
|
||||
void write_raw_short(uint16_t value);
|
||||
/*!
|
||||
Serialises @c value into the controller's write buffer without adjustment.
|
||||
*/
|
||||
void write_raw_short(uint16_t value);
|
||||
|
||||
/*!
|
||||
Gets the current value of the CRC generator and makes two calls to @c write_byte, to
|
||||
write first its higher-value byte and then its lower.
|
||||
*/
|
||||
void write_crc();
|
||||
/*!
|
||||
Gets the current value of the CRC generator and makes two calls to @c write_byte, to
|
||||
write first its higher-value byte and then its lower.
|
||||
*/
|
||||
void write_crc();
|
||||
|
||||
/*!
|
||||
Calls @c write_byte with @c value, @c quantity times.
|
||||
*/
|
||||
void write_n_bytes(int quantity, uint8_t value);
|
||||
/*!
|
||||
Calls @c write_byte with @c value, @c quantity times.
|
||||
*/
|
||||
void write_n_bytes(int quantity, uint8_t value);
|
||||
|
||||
/*!
|
||||
Writes everything that should per the spec appear prior to the address contained
|
||||
in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator.
|
||||
*/
|
||||
void write_id_joiner();
|
||||
/*!
|
||||
Writes everything that should per the spec appear prior to the address contained
|
||||
in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator.
|
||||
*/
|
||||
void write_id_joiner();
|
||||
|
||||
/*!
|
||||
Writes at most what should, per the spec, appear after the ID's CRC, up to and
|
||||
including the mark that indicates the beginning of data, appropriately seeding
|
||||
the CRC generator; if @c skip_first_gap is set then the initial gap after the
|
||||
CRC isn't written.
|
||||
*/
|
||||
void write_id_data_joiner(bool is_deleted, bool skip_first_gap);
|
||||
/*!
|
||||
Writes at most what should, per the spec, appear after the ID's CRC, up to and
|
||||
including the mark that indicates the beginning of data, appropriately seeding
|
||||
the CRC generator; if @c skip_first_gap is set then the initial gap after the
|
||||
CRC isn't written.
|
||||
*/
|
||||
void write_id_data_joiner(bool is_deleted, bool skip_first_gap);
|
||||
|
||||
/*!
|
||||
Writes the gap expected after a sector's data CRC and before the beginning of the
|
||||
next ID joiner.
|
||||
*/
|
||||
void write_post_data_gap();
|
||||
/*!
|
||||
Writes the gap expected after a sector's data CRC and before the beginning of the
|
||||
next ID joiner.
|
||||
*/
|
||||
void write_post_data_gap();
|
||||
|
||||
/*!
|
||||
Writes everything that should, per the spec, following the index hole and prior
|
||||
to any sectors.
|
||||
*/
|
||||
void write_start_of_track();
|
||||
/*!
|
||||
Writes everything that should, per the spec, following the index hole and prior
|
||||
to any sectors.
|
||||
*/
|
||||
void write_start_of_track();
|
||||
|
||||
private:
|
||||
// Storage::Disk::Controller
|
||||
virtual void process_input_bit(int value);
|
||||
virtual void process_index_hole();
|
||||
virtual void process_write_completed();
|
||||
private:
|
||||
// Storage::Disk::Controller
|
||||
virtual void process_input_bit(int value);
|
||||
virtual void process_index_hole();
|
||||
virtual void process_write_completed();
|
||||
|
||||
// Reading state.
|
||||
Token latest_token_;
|
||||
Encodings::MFM::Shifter shifter_;
|
||||
// Reading state.
|
||||
Token latest_token_;
|
||||
Encodings::MFM::Shifter shifter_;
|
||||
|
||||
// input configuration
|
||||
bool is_double_density_;
|
||||
DataMode data_mode_ = DataMode::Scanning;
|
||||
// input configuration
|
||||
bool is_double_density_;
|
||||
DataMode data_mode_ = DataMode::Scanning;
|
||||
|
||||
// writing
|
||||
int last_bit_;
|
||||
// writing
|
||||
int last_bit_;
|
||||
|
||||
// CRC generator
|
||||
CRC::CCITT crc_generator_;
|
||||
// CRC generator
|
||||
CRC::CCITT crc_generator_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,110 +24,110 @@ namespace Storage {
|
||||
@c length_of_history The number of historic pulses to consider in locking to phase.
|
||||
*/
|
||||
template <typename BitHandler, size_t length_of_history = 3> class DigitalPhaseLockedLoop {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a @c DigitalPhaseLockedLoop.
|
||||
public:
|
||||
/*!
|
||||
Instantiates a @c DigitalPhaseLockedLoop.
|
||||
|
||||
@param clocks_per_bit The expected number of cycles between each bit of input.
|
||||
*/
|
||||
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
|
||||
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
|
||||
@param clocks_per_bit The expected number of cycles between each bit of input.
|
||||
*/
|
||||
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
|
||||
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
|
||||
|
||||
/*!
|
||||
Changes the expected window length.
|
||||
*/
|
||||
void set_clocks_per_bit(int clocks_per_bit) {
|
||||
window_length_ = clocks_per_bit_ = clocks_per_bit;
|
||||
/*!
|
||||
Changes the expected window length.
|
||||
*/
|
||||
void set_clocks_per_bit(int clocks_per_bit) {
|
||||
window_length_ = clocks_per_bit_ = clocks_per_bit;
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the loop, impliedly posting no pulses during that period.
|
||||
|
||||
@c number_of_cycles The time to run the loop for.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
offset_ += cycles.as_integral();
|
||||
phase_ += cycles.as_integral();
|
||||
if(phase_ >= window_length_) {
|
||||
auto windows_crossed = phase_ / window_length_;
|
||||
|
||||
// Check whether this triggers any 0s.
|
||||
if(window_was_filled_) --windows_crossed;
|
||||
for(int c = 0; c < windows_crossed; c++)
|
||||
bit_handler_.digital_phase_locked_loop_output_bit(0);
|
||||
|
||||
window_was_filled_ = false;
|
||||
phase_ %= window_length_;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the loop, impliedly posting no pulses during that period.
|
||||
|
||||
@c number_of_cycles The time to run the loop for.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
offset_ += cycles.as_integral();
|
||||
phase_ += cycles.as_integral();
|
||||
if(phase_ >= window_length_) {
|
||||
auto windows_crossed = phase_ / window_length_;
|
||||
|
||||
// Check whether this triggers any 0s.
|
||||
if(window_was_filled_) --windows_crossed;
|
||||
for(int c = 0; c < windows_crossed; c++)
|
||||
bit_handler_.digital_phase_locked_loop_output_bit(0);
|
||||
|
||||
window_was_filled_ = false;
|
||||
phase_ %= window_length_;
|
||||
}
|
||||
/*!
|
||||
Announces a pulse at the current time.
|
||||
*/
|
||||
void add_pulse() {
|
||||
if(!window_was_filled_) {
|
||||
bit_handler_.digital_phase_locked_loop_output_bit(1);
|
||||
window_was_filled_ = true;
|
||||
post_phase_offset(phase_, offset_);
|
||||
offset_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces a pulse at the current time.
|
||||
*/
|
||||
void add_pulse() {
|
||||
if(!window_was_filled_) {
|
||||
bit_handler_.digital_phase_locked_loop_output_bit(1);
|
||||
window_was_filled_ = true;
|
||||
post_phase_offset(phase_, offset_);
|
||||
offset_ = 0;
|
||||
}
|
||||
}
|
||||
private:
|
||||
BitHandler &bit_handler_;
|
||||
|
||||
private:
|
||||
BitHandler &bit_handler_;
|
||||
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
||||
// Erase the effect of whatever is currently in this slot.
|
||||
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
|
||||
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
|
||||
|
||||
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
||||
// Erase the effect of whatever is currently in this slot.
|
||||
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
|
||||
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
|
||||
// Fill in the new fields.
|
||||
const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1));
|
||||
offset_history_[offset_history_pointer_].divisor = multiple;
|
||||
offset_history_[offset_history_pointer_].spacing = new_offset;
|
||||
|
||||
// Fill in the new fields.
|
||||
const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1));
|
||||
offset_history_[offset_history_pointer_].divisor = multiple;
|
||||
offset_history_[offset_history_pointer_].spacing = new_offset;
|
||||
// Add in the new values;
|
||||
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
|
||||
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
|
||||
|
||||
// Add in the new values;
|
||||
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
|
||||
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
|
||||
|
||||
// Advance the write slot.
|
||||
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
||||
// Advance the write slot.
|
||||
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
||||
|
||||
#ifndef NDEBUG
|
||||
Cycles::IntType td = 0, ts = 0;
|
||||
for(auto offset: offset_history_) {
|
||||
td += offset.divisor;
|
||||
ts += offset.spacing;
|
||||
}
|
||||
assert(ts == total_spacing_);
|
||||
assert(td == total_divisor_);
|
||||
Cycles::IntType td = 0, ts = 0;
|
||||
for(auto offset: offset_history_) {
|
||||
td += offset.divisor;
|
||||
ts += offset.spacing;
|
||||
}
|
||||
assert(ts == total_spacing_);
|
||||
assert(td == total_divisor_);
|
||||
#endif
|
||||
|
||||
// In net: use an unweighted average of the stored offsets to compute current window size,
|
||||
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
||||
window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1));
|
||||
// In net: use an unweighted average of the stored offsets to compute current window size,
|
||||
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
||||
window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1));
|
||||
|
||||
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
|
||||
const auto error = new_phase - (window_length_ >> 1);
|
||||
phase_ -= (error + 1) >> 1;
|
||||
}
|
||||
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
|
||||
const auto error = new_phase - (window_length_ >> 1);
|
||||
phase_ -= (error + 1) >> 1;
|
||||
}
|
||||
|
||||
struct LoggedOffset {
|
||||
Cycles::IntType divisor = 1, spacing = 1;
|
||||
};
|
||||
std::array<LoggedOffset, length_of_history> offset_history_;
|
||||
std::size_t offset_history_pointer_ = 0;
|
||||
struct LoggedOffset {
|
||||
Cycles::IntType divisor = 1, spacing = 1;
|
||||
};
|
||||
std::array<LoggedOffset, length_of_history> offset_history_;
|
||||
std::size_t offset_history_pointer_ = 0;
|
||||
|
||||
Cycles::IntType total_spacing_ = length_of_history;
|
||||
Cycles::IntType total_divisor_ = length_of_history;
|
||||
Cycles::IntType total_spacing_ = length_of_history;
|
||||
Cycles::IntType total_divisor_ = length_of_history;
|
||||
|
||||
Cycles::IntType phase_ = 0;
|
||||
Cycles::IntType window_length_ = 0;
|
||||
Cycles::IntType phase_ = 0;
|
||||
Cycles::IntType window_length_ = 0;
|
||||
|
||||
Cycles::IntType offset_ = 0;
|
||||
bool window_was_filled_ = false;
|
||||
Cycles::IntType offset_ = 0;
|
||||
bool window_was_filled_ = false;
|
||||
|
||||
int clocks_per_bit_ = 0;
|
||||
int clocks_per_bit_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -23,48 +23,48 @@ namespace Storage::Disk {
|
||||
Models a flopy disk.
|
||||
*/
|
||||
class Disk {
|
||||
public:
|
||||
virtual ~Disk() = default;
|
||||
public:
|
||||
virtual ~Disk() = default;
|
||||
|
||||
/*!
|
||||
@returns the number of discrete positions that this disk uses to model its complete surface area.
|
||||
/*!
|
||||
@returns the number of discrete positions that this disk uses to model its complete surface area.
|
||||
|
||||
This is not necessarily a track count. There is no implicit guarantee that every position will
|
||||
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
|
||||
*/
|
||||
virtual HeadPosition get_maximum_head_position() = 0;
|
||||
This is not necessarily a track count. There is no implicit guarantee that every position will
|
||||
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
|
||||
*/
|
||||
virtual HeadPosition get_maximum_head_position() = 0;
|
||||
|
||||
/*!
|
||||
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
|
||||
*/
|
||||
virtual int get_head_count() = 0;
|
||||
/*!
|
||||
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
|
||||
*/
|
||||
virtual int get_head_count() = 0;
|
||||
|
||||
/*!
|
||||
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
|
||||
returns @c nullptr otherwise.
|
||||
*/
|
||||
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
|
||||
/*!
|
||||
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
|
||||
returns @c nullptr otherwise.
|
||||
*/
|
||||
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
|
||||
|
||||
/*!
|
||||
Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only.
|
||||
*/
|
||||
virtual void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track) = 0;
|
||||
/*!
|
||||
Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only.
|
||||
*/
|
||||
virtual void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track) = 0;
|
||||
|
||||
/*!
|
||||
Provides a hint that no further tracks are likely to be written for a while.
|
||||
*/
|
||||
virtual void flush_tracks() = 0;
|
||||
/*!
|
||||
Provides a hint that no further tracks are likely to be written for a while.
|
||||
*/
|
||||
virtual void flush_tracks() = 0;
|
||||
|
||||
/*!
|
||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||
*/
|
||||
virtual bool get_is_read_only() = 0;
|
||||
/*!
|
||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||
*/
|
||||
virtual bool get_is_read_only() = 0;
|
||||
|
||||
/*!
|
||||
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||
*/
|
||||
virtual bool tracks_differ(Track::Address, Track::Address) = 0;
|
||||
/*!
|
||||
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||
*/
|
||||
virtual bool tracks_differ(Track::Address, Track::Address) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -30,48 +30,48 @@ enum class Error {
|
||||
if it matches the media.
|
||||
*/
|
||||
class DiskImage {
|
||||
public:
|
||||
virtual ~DiskImage() = default;
|
||||
public:
|
||||
virtual ~DiskImage() = default;
|
||||
|
||||
/*!
|
||||
@returns the distance at which there stops being any further content.
|
||||
/*!
|
||||
@returns the distance at which there stops being any further content.
|
||||
|
||||
This is not necessarily a track count. There is no implicit guarantee that every position will
|
||||
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
|
||||
*/
|
||||
virtual HeadPosition get_maximum_head_position() = 0;
|
||||
This is not necessarily a track count. There is no implicit guarantee that every position will
|
||||
return a distinct track, or, e.g. if the media is holeless, will return any track at all.
|
||||
*/
|
||||
virtual HeadPosition get_maximum_head_position() = 0;
|
||||
|
||||
/*!
|
||||
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
|
||||
*/
|
||||
virtual int get_head_count() { return 1; }
|
||||
/*!
|
||||
@returns the number of heads (and, therefore, impliedly surfaces) available on this disk.
|
||||
*/
|
||||
virtual int get_head_count() { return 1; }
|
||||
|
||||
/*!
|
||||
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
|
||||
returns @c nullptr otherwise.
|
||||
*/
|
||||
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
|
||||
/*!
|
||||
@returns the @c Track at @c position underneath @c head if there are any detectable events there;
|
||||
returns @c nullptr otherwise.
|
||||
*/
|
||||
virtual std::shared_ptr<Track> get_track_at_position(Track::Address address) = 0;
|
||||
|
||||
/*!
|
||||
Replaces the Tracks indicated by the map, that maps from physical address to track content.
|
||||
*/
|
||||
virtual void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) {}
|
||||
/*!
|
||||
Replaces the Tracks indicated by the map, that maps from physical address to track content.
|
||||
*/
|
||||
virtual void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) {}
|
||||
|
||||
/*!
|
||||
Communicates that it is likely to be a while before any more tracks are written.
|
||||
*/
|
||||
virtual void flush_tracks() {}
|
||||
/*!
|
||||
Communicates that it is likely to be a while before any more tracks are written.
|
||||
*/
|
||||
virtual void flush_tracks() {}
|
||||
|
||||
/*!
|
||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||
*/
|
||||
virtual bool get_is_read_only() { return true; }
|
||||
/*!
|
||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||
*/
|
||||
virtual bool get_is_read_only() { return true; }
|
||||
|
||||
/*!
|
||||
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||
*/
|
||||
virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; }
|
||||
/*!
|
||||
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||
*/
|
||||
virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; }
|
||||
};
|
||||
|
||||
class DiskImageHolderBase: public Disk {
|
||||
@ -90,29 +90,29 @@ class DiskImageHolderBase: public Disk {
|
||||
the underlying image doesn't implement TypeDistinguisher, or else to pass the call along.
|
||||
*/
|
||||
template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher {
|
||||
public:
|
||||
template <typename... Ts> DiskImageHolder(Ts&&... args) :
|
||||
disk_image_(args...) {}
|
||||
~DiskImageHolder();
|
||||
public:
|
||||
template <typename... Ts> DiskImageHolder(Ts&&... args) :
|
||||
disk_image_(args...) {}
|
||||
~DiskImageHolder();
|
||||
|
||||
HeadPosition get_maximum_head_position();
|
||||
int get_head_count();
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address);
|
||||
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
|
||||
void flush_tracks();
|
||||
bool get_is_read_only();
|
||||
bool tracks_differ(Track::Address lhs, Track::Address rhs);
|
||||
HeadPosition get_maximum_head_position();
|
||||
int get_head_count();
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address);
|
||||
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
|
||||
void flush_tracks();
|
||||
bool get_is_read_only();
|
||||
bool tracks_differ(Track::Address lhs, Track::Address rhs);
|
||||
|
||||
private:
|
||||
T disk_image_;
|
||||
private:
|
||||
T disk_image_;
|
||||
|
||||
TargetPlatform::Type target_platform_type() final {
|
||||
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) {
|
||||
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type();
|
||||
} else {
|
||||
return TargetPlatform::Type(~0);
|
||||
}
|
||||
TargetPlatform::Type target_platform_type() final {
|
||||
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) {
|
||||
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type();
|
||||
} else {
|
||||
return TargetPlatform::Type(~0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include "DiskImageImplementation.hpp"
|
||||
|
@ -28,9 +28,10 @@ namespace Storage::Disk {
|
||||
*/
|
||||
|
||||
class Disk2MG {
|
||||
public:
|
||||
using DiskOrMassStorageDevice = std::variant<std::nullptr_t, DiskImageHolderBase *, Storage::MassStorage::MassStorageDevice *>;
|
||||
static DiskOrMassStorageDevice open(const std::string &file_name);
|
||||
public:
|
||||
using DiskOrMassStorageDevice =
|
||||
std::variant<std::nullptr_t, DiskImageHolderBase *, Storage::MassStorage::MassStorageDevice *>;
|
||||
static DiskOrMassStorageDevice open(const std::string &file_name);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,23 +18,23 @@ namespace Storage::Disk {
|
||||
Provides a @c Disk containing an ADF disk image: a decoded sector dump of an Acorn ADFS disk.
|
||||
*/
|
||||
class AcornADF: public MFMSectorDump {
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AcornADF containing content from the file with name @c file_name.
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AcornADF containing content from the file with name @c file_name.
|
||||
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||
*/
|
||||
AcornADF(const std::string &file_name);
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||
*/
|
||||
AcornADF(const std::string &file_name);
|
||||
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
|
||||
private:
|
||||
long get_file_offset_for_position(Track::Address address) final;
|
||||
int head_count_ = 1;
|
||||
uint8_t sector_size_ = 1;
|
||||
int sectors_per_track_ = 16;
|
||||
private:
|
||||
long get_file_offset_for_position(Track::Address) final;
|
||||
int head_count_ = 1;
|
||||
uint8_t sector_size_ = 1;
|
||||
int sectors_per_track_ = 16;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -19,23 +19,23 @@ namespace Storage::Disk {
|
||||
but the Amiga doesn't use IBM-style sector demarcation.
|
||||
*/
|
||||
class AmigaADF: public DiskImage {
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AmigaADF containing content from the file with name @c file_name.
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AmigaADF containing content from the file with name @c file_name.
|
||||
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image.
|
||||
*/
|
||||
AmigaADF(const std::string &file_name);
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image.
|
||||
*/
|
||||
AmigaADF(const std::string &file_name);
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
||||
// implemented to satisfy @c Disk
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address) final;
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
long get_file_offset_for_position(Track::Address address);
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
long get_file_offset_for_position(Track::Address);
|
||||
|
||||
};
|
||||
|
||||
|
@ -20,28 +20,28 @@ namespace Storage::Disk {
|
||||
implicitly numbered and located.
|
||||
*/
|
||||
class AppleDSK: public DiskImage {
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AppleDSK containing content from the file with name @c file_name.
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AppleDSK containing content from the file with name @c file_name.
|
||||
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image.
|
||||
*/
|
||||
AppleDSK(const std::string &file_name);
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image.
|
||||
*/
|
||||
AppleDSK(const std::string &file_name);
|
||||
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
||||
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) final;
|
||||
bool get_is_read_only() final;
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address) final;
|
||||
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &) final;
|
||||
bool get_is_read_only() final;
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
int sectors_per_track_ = 16;
|
||||
bool is_prodos_ = false;
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
int sectors_per_track_ = 16;
|
||||
bool is_prodos_ = false;
|
||||
|
||||
long file_offset(Track::Address address);
|
||||
size_t logical_sector_for_physical_sector(size_t physical);
|
||||
long file_offset(Track::Address);
|
||||
size_t logical_sector_for_physical_sector(size_t physical);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -21,51 +21,51 @@ namespace Storage::Disk {
|
||||
Provides a @c Disk containing an Amstrad CPC-type disk image: some arrangement of sectors with status bits.
|
||||
*/
|
||||
class CPCDSK: public DiskImage {
|
||||
public:
|
||||
/*!
|
||||
Construct a @c CPCDSK containing content from the file with name @c file_name.
|
||||
public:
|
||||
/*!
|
||||
Construct a @c CPCDSK containing content from the file with name @c file_name.
|
||||
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||
*/
|
||||
CPCDSK(const std::string &file_name);
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||
*/
|
||||
CPCDSK(const std::string &file_name);
|
||||
|
||||
// DiskImage interface.
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
bool get_is_read_only() final;
|
||||
void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final;
|
||||
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
||||
// DiskImage interface.
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
int get_head_count() final;
|
||||
bool get_is_read_only() final;
|
||||
void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final;
|
||||
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
||||
|
||||
private:
|
||||
struct Track {
|
||||
uint8_t track;
|
||||
uint8_t side;
|
||||
enum class DataRate {
|
||||
Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity
|
||||
} data_rate;
|
||||
enum class DataEncoding {
|
||||
Unknown, FM, MFM
|
||||
} data_encoding;
|
||||
uint8_t sector_length;
|
||||
uint8_t gap3_length;
|
||||
uint8_t filler_byte;
|
||||
private:
|
||||
struct Track {
|
||||
uint8_t track;
|
||||
uint8_t side;
|
||||
enum class DataRate {
|
||||
Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity
|
||||
} data_rate;
|
||||
enum class DataEncoding {
|
||||
Unknown, FM, MFM
|
||||
} data_encoding;
|
||||
uint8_t sector_length;
|
||||
uint8_t gap3_length;
|
||||
uint8_t filler_byte;
|
||||
|
||||
struct Sector: public ::Storage::Encodings::MFM::Sector {
|
||||
uint8_t fdc_status1;
|
||||
uint8_t fdc_status2;
|
||||
};
|
||||
|
||||
std::vector<Sector> sectors;
|
||||
struct Sector: public ::Storage::Encodings::MFM::Sector {
|
||||
uint8_t fdc_status1;
|
||||
uint8_t fdc_status2;
|
||||
};
|
||||
std::string file_name_;
|
||||
std::vector<std::unique_ptr<Track>> tracks_;
|
||||
std::size_t index_for_track(::Storage::Disk::Track::Address address);
|
||||
|
||||
int head_count_;
|
||||
int head_position_count_;
|
||||
bool is_extended_;
|
||||
bool is_read_only_;
|
||||
std::vector<Sector> sectors;
|
||||
};
|
||||
std::string file_name_;
|
||||
std::vector<std::unique_ptr<Track>> tracks_;
|
||||
std::size_t index_for_track(::Storage::Disk::Track::Address address);
|
||||
|
||||
int head_count_;
|
||||
int head_position_count_;
|
||||
bool is_extended_;
|
||||
bool is_read_only_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,24 +17,24 @@ namespace Storage::Disk {
|
||||
Provides a @c Disk containing a D64 disk image: a decoded sector dump of a C1540-format disk.
|
||||
*/
|
||||
class D64: public DiskImage {
|
||||
public:
|
||||
/*!
|
||||
Construct a @c D64 containing content from the file with name @c file_name.
|
||||
public:
|
||||
/*!
|
||||
Construct a @c D64 containing content from the file with name @c file_name.
|
||||
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image.
|
||||
*/
|
||||
D64(const std::string &file_name);
|
||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||
@throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image.
|
||||
*/
|
||||
D64(const std::string &file_name);
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
using DiskImage::get_is_read_only;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
||||
// implemented to satisfy @c Disk
|
||||
HeadPosition get_maximum_head_position() final;
|
||||
using DiskImage::get_is_read_only;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
int number_of_tracks_;
|
||||
uint16_t disk_id_;
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
int number_of_tracks_;
|
||||
uint16_t disk_id_;
|
||||
};
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user