1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-03-17 14:16:45 +00:00

Take another big swing at indentation, some consts.

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,327 +29,327 @@ constexpr bool is_text_mode(const GraphicsMode m) { return m <= GraphicsMode::Do
constexpr bool is_double_mode(const GraphicsMode m) { return int(m) & 1; }
template <typename TimeUnit> class VideoSwitches {
public:
/*!
Constructs a new instance of VideoSwitches in which changes to relevant switches
affect the video mode only after @c delay cycles.
public:
/*!
Constructs a new instance of VideoSwitches in which changes to relevant switches
affect the video mode only after @c delay cycles.
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
set of potential flashing characters and alternate video modes.
*/
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
character_zones_[0].xor_mask = 0xff;
character_zones_[0].address_mask = 0x3f;
character_zones_[1].xor_mask = 0;
character_zones_[1].address_mask = 0x3f;
character_zones_[2].xor_mask = 0;
character_zones_[2].address_mask = 0x3f;
character_zones_[3].xor_mask = 0;
character_zones_[3].address_mask = 0x3f;
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
set of potential flashing characters and alternate video modes.
*/
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
character_zones_[0].xor_mask = 0xff;
character_zones_[0].address_mask = 0x3f;
character_zones_[1].xor_mask = 0;
character_zones_[1].address_mask = 0x3f;
character_zones_[2].xor_mask = 0;
character_zones_[2].address_mask = 0x3f;
character_zones_[3].xor_mask = 0;
character_zones_[3].address_mask = 0x3f;
if(is_iie) {
character_zones_[1].xor_mask =
character_zones_[2].xor_mask =
character_zones_[3].xor_mask = 0xff;
character_zones_[2].address_mask =
character_zones_[3].address_mask = 0xff;
}
if(is_iie) {
character_zones_[1].xor_mask =
character_zones_[2].xor_mask =
character_zones_[3].xor_mask = 0xff;
character_zones_[2].address_mask =
character_zones_[3].address_mask = 0xff;
}
}
/*!
Advances @c cycles.
*/
void run_for(TimeUnit cycles) {
deferrer_.run_for(cycles);
}
/*!
Advances @c cycles.
</