1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-14 20:31:53 +00:00

Adopt new indentation, improve constness.

This commit is contained in:
Thomas Harte 2024-11-30 15:53:58 -05:00
parent 36edfe9715
commit 5545906063
25 changed files with 1242 additions and 1186 deletions

@ -74,7 +74,7 @@ uint8_t MFP68901::read(int address) {
return 0x00;
}
void MFP68901::write(int address, uint8_t value) {
void MFP68901::write(int address, const uint8_t value) {
address &= 0x1f;
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
@ -181,7 +181,7 @@ void MFP68901::write(int address, uint8_t value) {
}
template <int timer>
void MFP68901::run_timer_for(int cycles) {
void MFP68901::run_timer_for(const int cycles) {
if(timers_[timer].mode >= TimerMode::Delay) {
// This code applies the timer prescaling only. prescale_count is used to count
// upwards rather than downwards for simplicity, but on the real hardware it's
@ -202,7 +202,7 @@ void MFP68901::run_timer_for(int cycles) {
}
}
void MFP68901::run_for(HalfCycles time) {
void MFP68901::run_for(const HalfCycles time) {
cycles_left_ += time;
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
@ -220,7 +220,7 @@ HalfCycles MFP68901::next_sequence_point() {
// MARK: - Timers
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
void MFP68901::set_timer_mode(const int timer, const TimerMode mode, const int prescale, const bool reset_timer) {
logger.error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
timers_[timer].mode = mode;
if(reset_timer) {
@ -236,19 +236,19 @@ void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool rese
timers_[timer].prescale = prescale;
}
void MFP68901::set_timer_data(int timer, uint8_t value) {
void MFP68901::set_timer_data(const int timer, const uint8_t value) {
if(timers_[timer].mode == TimerMode::Stopped) {
timers_[timer].value = value;
}
timers_[timer].reload_value = value;
}
uint8_t MFP68901::get_timer_data(int timer) {
uint8_t MFP68901::get_timer_data(const int timer) {
return timers_[timer].value;
}
template <int channel>
void MFP68901::set_timer_event_input(bool value) {
void MFP68901::set_timer_event_input(const bool value) {
if(timers_[channel].event_input == value) return;
timers_[channel].event_input = value;
@ -316,7 +316,7 @@ void MFP68901::decrement_timer(int amount) {
}
// MARK: - GPIP
void MFP68901::set_port_input(uint8_t input) {
void MFP68901::set_port_input(const uint8_t input) {
gpip_input_ = input;
reevaluate_gpip_interrupts();
}
@ -342,12 +342,12 @@ void MFP68901::reevaluate_gpip_interrupts() {
// MARK: - Interrupts
void MFP68901::begin_interrupts(int interrupt) {
void MFP68901::begin_interrupts(const int interrupt) {
interrupt_pending_ |= interrupt & interrupt_enable_;
update_interrupts();
}
void MFP68901::end_interrupts(int interrupt) {
void MFP68901::end_interrupts(const int interrupt) {
interrupt_pending_ &= ~interrupt;
update_interrupts();
}
@ -403,6 +403,6 @@ int MFP68901::acknowledge_interrupt() {
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
void MFP68901::set_interrupt_delegate(InterruptDelegate *const delegate) {
interrupt_delegate_ = delegate;
}

@ -24,162 +24,162 @@ class PortHandler {
Models the Motorola 68901 Multi-Function Peripheral ('MFP').
*/
class MFP68901: public ClockingHint::Source {
public:
/// @returns the result of a read from @c address.
uint8_t read(int address);
public:
/// @returns the result of a read from @c address.
uint8_t read(int address);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Advances the MFP by the supplied number of HalfCycles.
void run_for(HalfCycles);
/// Advances the MFP by the supplied number of HalfCycles.
void run_for(HalfCycles);
/// @returns the number of cycles until the next possible sequence point — the next time
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles next_sequence_point();
/// @returns the number of cycles until the next possible sequence point — the next time
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles next_sequence_point();
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
template <int channel>
void set_timer_event_input(bool value);
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
template <int channel>
void set_timer_event_input(bool value);
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
///
/// @discussion TODO.
void set_port_handler(PortHandler *);
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
///
/// @discussion TODO.
void set_port_handler(PortHandler *);
/// Sets the current input GPIP values.
void set_port_input(uint8_t);
/// Sets the current input GPIP values.
void set_port_input(uint8_t);
/// @returns the current GPIP output values.
///
/// @discussion TODO.
uint8_t get_port_output();
/// @returns the current GPIP output values.
///
/// @discussion TODO.
uint8_t get_port_output();
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
bool get_interrupt_line();
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
bool get_interrupt_line();
static constexpr int NoAcknowledgement = 0x100;
static constexpr int NoAcknowledgement = 0x100;
/// Communicates an interrupt acknowledge cycle.
///
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
int acknowledge_interrupt();
/// Communicates an interrupt acknowledge cycle.
///
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
int acknowledge_interrupt();
struct InterruptDelegate {
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
};
/// Sets a delegate that will be notified upon any change in the interrupt line.
void set_interrupt_delegate(InterruptDelegate *delegate);
struct InterruptDelegate {
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
};
/// Sets a delegate that will be notified upon any change in the interrupt line.
void set_interrupt_delegate(InterruptDelegate *);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
private:
// MARK: - Timers
enum class TimerMode {
Stopped, EventCount, Delay, PulseWidth
};
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
void set_timer_data(int timer, uint8_t);
uint8_t get_timer_data(int timer);
template <int timer> void decrement_timer(int amount);
template <int timer> void run_timer_for(int cycles);
private:
// MARK: - Timers
enum class TimerMode {
Stopped, EventCount, Delay, PulseWidth
};
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
void set_timer_data(int timer, uint8_t);
uint8_t get_timer_data(int timer);
template <int timer> void decrement_timer(int amount);
template <int timer> void run_timer_for(int cycles);
struct Timer {
TimerMode mode = TimerMode::Stopped;
uint8_t value = 0;
uint8_t reload_value = 0;
int prescale = 1;
int prescale_count = 1;
bool event_input = false;
} timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 };
uint8_t timer_cd_control_ = 0;
struct Timer {
TimerMode mode = TimerMode::Stopped;
uint8_t value = 0;
uint8_t reload_value = 0;
int prescale = 1;
int prescale_count = 1;
bool event_input = false;
} timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 };
uint8_t timer_cd_control_ = 0;
HalfCycles cycles_left_;
HalfCycles cycles_left_;
// MARK: - GPIP
uint8_t gpip_input_ = 0;
uint8_t gpip_output_ = 0;
uint8_t gpip_active_edge_ = 0;
uint8_t gpip_direction_ = 0;
uint8_t gpip_interrupt_state_ = 0;
// MARK: - GPIP
uint8_t gpip_input_ = 0;
uint8_t gpip_output_ = 0;
uint8_t gpip_active_edge_ = 0;
uint8_t gpip_direction_ = 0;
uint8_t gpip_interrupt_state_ = 0;
void reevaluate_gpip_interrupts();
void reevaluate_gpip_interrupts();
// MARK: - Interrupts
// MARK: - Interrupts
InterruptDelegate *interrupt_delegate_ = nullptr;
InterruptDelegate *interrupt_delegate_ = nullptr;
// Ad hoc documentation:
//
// An interrupt becomes pending if it is enabled at the time it occurs.
//
// If a pending interrupt is enabled in the interrupt mask, a processor
// interrupt is generated. Otherwise no processor interrupt is generated.
//
// (Disabling a bit in the enabled mask also instantaneously clears anything
// in the pending mask.)
//
// The user can write to the pending interrupt register; a write
// masks whatever is there — so you can disable bits but you cannot set them.
//
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
// its in-service bit. That bit will remain set until the user writes a zero to its position.
// If any bits are set in the in-service register, then they will prevent lower-priority
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
// priority may occur.
//
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
// Acknowledgement of an interrupt will automatically clear the corresponding
// pending bit.
//
int interrupt_enable_ = 0;
int interrupt_pending_ = 0;
int interrupt_mask_ = 0;
int interrupt_in_service_ = 0;
bool interrupt_line_ = false;
uint8_t interrupt_vector_ = 0;
// Ad hoc documentation:
//
// An interrupt becomes pending if it is enabled at the time it occurs.
//
// If a pending interrupt is enabled in the interrupt mask, a processor
// interrupt is generated. Otherwise no processor interrupt is generated.
//
// (Disabling a bit in the enabled mask also instantaneously clears anything
// in the pending mask.)
//
// The user can write to the pending interrupt register; a write
// masks whatever is there — so you can disable bits but you cannot set them.
//
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
// its in-service bit. That bit will remain set until the user writes a zero to its position.
// If any bits are set in the in-service register, then they will prevent lower-priority
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
// priority may occur.
//
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
// Acknowledgement of an interrupt will automatically clear the corresponding
// pending bit.
//
int interrupt_enable_ = 0;
int interrupt_pending_ = 0;
int interrupt_mask_ = 0;
int interrupt_in_service_ = 0;
bool interrupt_line_ = false;
uint8_t interrupt_vector_ = 0;
enum Interrupt {
GPIP0 = (1 << 0),
GPIP1 = (1 << 1),
GPIP2 = (1 << 2),
GPIP3 = (1 << 3),
TimerD = (1 << 4),
TimerC = (1 << 5),
GPIP4 = (1 << 6),
GPIP5 = (1 << 7),
enum Interrupt {
GPIP0 = (1 << 0),
GPIP1 = (1 << 1),
GPIP2 = (1 << 2),
GPIP3 = (1 << 3),
TimerD = (1 << 4),
TimerC = (1 << 5),
GPIP4 = (1 << 6),
GPIP5 = (1 << 7),
TimerB = (1 << 8),
TransmitError = (1 << 9),
TransmitBufferEmpty = (1 << 10),
ReceiveError = (1 << 11),
ReceiveBufferFull = (1 << 12),
TimerA = (1 << 13),
GPIP6 = (1 << 14),
GPIP7 = (1 << 15),
};
void begin_interrupts(int interrupt);
void end_interrupts(int interrupt);
void update_interrupts();
TimerB = (1 << 8),
TransmitError = (1 << 9),
TransmitBufferEmpty = (1 << 10),
ReceiveError = (1 << 11),
ReceiveBufferFull = (1 << 12),
TimerA = (1 << 13),
GPIP6 = (1 << 14),
GPIP7 = (1 << 15),
};
void begin_interrupts(int interrupt);
void end_interrupts(int interrupt);
void update_interrupts();
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
inline static int msb16(int v) {
// Saturate all bits below the MSB.
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
static constexpr int msb16(int v) {
// Saturate all bits below the MSB.
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
// Throw away lesser bits.
return (v+1) >> 1;
}
// Throw away lesser bits.
return (v+1) >> 1;
}
};
}

@ -54,75 +54,75 @@ namespace TI::TMS {
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
*/
template <Personality personality> class TMS9918: private Base<personality> {
public:
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
TMS9918();
public:
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
TMS9918();
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard standard);
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard);
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/*! 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 CRT display. */
void set_display_type(Outputs::Display::DisplayType);
/*! Sets the type of CRT display. */
void set_display_type(Outputs::Display::DisplayType);
/*! Gets the type of CRT display. */
Outputs::Display::DisplayType get_display_type() const;
/*! Gets the type of CRT display. */
Outputs::Display::DisplayType get_display_type() const;
/*!
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
/*!
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
For everything except the Mega Drive VDP:
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
For everything except the Mega Drive VDP:
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
For the Mega Drive:
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
*/
void run_for(const HalfCycles cycles);
For the Mega Drive:
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
*/
void run_for(const HalfCycles);
/*! Sets a register value. */
void write(int address, uint8_t value);
/*! Sets a register value. */
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t read(int address);
/*! Gets a register value. */
uint8_t read(int address);
/*! Gets the current scan line; provided by the Sega VDPs only. */
uint8_t get_current_line() const;
/*! Gets the current scan line; provided by the Sega VDPs only. */
uint8_t get_current_line() const;
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
uint8_t get_latched_horizontal_counter() const;
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
uint8_t get_latched_horizontal_counter() const;
/*! Latches the current horizontal counter. */
void latch_horizontal_counter();
/*! Latches the current horizontal counter. */
void latch_horizontal_counter();
/*!
Returns the amount of time until @c get_interrupt_line would next change if
there are no interceding calls to @c write or to @c read.
/*!
Returns the amount of time until @c get_interrupt_line would next change if
there are no interceding calls to @c write or to @c read.
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles next_sequence_point() const;
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles next_sequence_point() const;
/*!
Returns the amount of time until the nominated line interrupt position is
reached on line @c line. If no line interrupt position is defined for
this VDP, returns the time until the 'beginning' of that line, whatever
that may mean.
/*!
Returns the amount of time until the nominated line interrupt position is
reached on line @c line. If no line interrupt position is defined for
this VDP, returns the time until the 'beginning' of that line, whatever
that may mean.
@line is relative to the first pixel line of the display and may be negative.
*/
HalfCycles get_time_until_line(int line);
@line is relative to the first pixel line of the display and may be negative.
*/
HalfCycles get_time_until_line(int line);
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line() const;
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line() const;
};
}

@ -86,7 +86,7 @@ TMS9918<personality>::TMS9918() {
}
template <Personality personality>
void TMS9918<personality>::set_tv_standard(TVStandard standard) {
void TMS9918<personality>::set_tv_standard(const TVStandard standard) {
// TODO: the Yamaha is programmable on this at runtime.
this->tv_standard_ = standard;
switch(standard) {
@ -104,7 +104,7 @@ void TMS9918<personality>::set_tv_standard(TVStandard standard) {
}
template <Personality personality>
void TMS9918<personality>::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
void TMS9918<personality>::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
this->crt_.set_scan_target(scan_target);
}
@ -118,7 +118,7 @@ Outputs::Display::ScanStatus TMS9918<personality>::get_scaled_scan_status() cons
}
template <Personality personality>
void TMS9918<personality>::set_display_type(Outputs::Display::DisplayType display_type) {
void TMS9918<personality>::set_display_type(const Outputs::Display::DisplayType display_type) {
this->crt_.set_display_type(display_type);
}
@ -137,7 +137,7 @@ void SpriteBuffer::reset_sprite_collection() {
}
template <Personality personality>
void Base<personality>::posit_sprite(int sprite_number, int sprite_position, uint8_t screen_row) {
void Base<personality>::posit_sprite(const int sprite_number, const int sprite_position, const uint8_t screen_row) {
// Evaluation of visibility of sprite 0 is always the first step in
// populating a sprite buffer; so use it to uncork a new one.
if(!sprite_number) {
@ -661,7 +661,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
}
template <Personality personality>
void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram_dot) {
void Base<personality>::output_border(int cycles, [[maybe_unused]] const uint32_t cram_dot) {
cycles = from_internal<personality, Clock::CRT>(cycles);
uint32_t border_colour;
@ -694,7 +694,7 @@ void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram
// MARK: - External interface.
template <Personality personality>
int Base<personality>::masked_address(int address) const {
int Base<personality>::masked_address(const int address) const {
if constexpr (is_yamaha_vdp(personality)) {
return address & 3;
} else {
@ -703,7 +703,7 @@ int Base<personality>::masked_address(int address) const {
}
template <Personality personality>
void Base<personality>::write_vram(uint8_t value) {
void Base<personality>::write_vram(const uint8_t value) {
write_phase_ = false;
// Enqueue the write to occur at the next available slot.
@ -713,7 +713,7 @@ void Base<personality>::write_vram(uint8_t value) {
}
template <Personality personality>
void Base<personality>::commit_register(int reg, uint8_t value) {
void Base<personality>::commit_register(int reg, const uint8_t value) {
if constexpr (is_yamaha_vdp(personality)) {
reg &= 0x3f;
} else if constexpr (is_sega_vdp(personality)) {
@ -1017,7 +1017,7 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
}
template <Personality personality>
void Base<personality>::write_register(uint8_t value) {
void Base<personality>::write_register(const uint8_t value) {
// Writes to address 1 are performed in pairs; if this is the
// low byte of a value, store it and wait for the high byte.
if(!write_phase_) {
@ -1068,7 +1068,7 @@ void Base<personality>::write_register(uint8_t value) {
}
template <Personality personality>
void Base<personality>::write_palette(uint8_t value) {
void Base<personality>::write_palette(const uint8_t value) {
if constexpr (is_yamaha_vdp(personality)) {
if(!Storage<personality>::palette_write_phase_) {
Storage<personality>::new_colour_ = value;
@ -1091,7 +1091,7 @@ void Base<personality>::write_palette(uint8_t value) {
}
template <Personality personality>
void Base<personality>::write_register_indirect([[maybe_unused]] uint8_t value) {
void Base<personality>::write_register_indirect([[maybe_unused]] const uint8_t value) {
if constexpr (is_yamaha_vdp(personality)) {
// Register 17 cannot be written to indirectly.
if(Storage<personality>::indirect_register_ != 17) {
@ -1188,7 +1188,7 @@ uint8_t Base<personality>::read_register() {
}
template <Personality personality>
uint8_t TMS9918<personality>::read(int address) {
uint8_t TMS9918<personality>::read(const int address) {
const int target = this->masked_address(address);
if(target < 2) {

@ -41,7 +41,7 @@ template <Personality personality> struct Base: public Storage<personality> {
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles
// after corresponding data read.
static constexpr uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
static constexpr uint32_t palette_pack(const uint8_t r, const uint8_t g, const uint8_t b) {
#if TARGET_RT_BIG_ENDIAN
return uint32_t((r << 24) | (g << 16) | (b << 8));
#else
@ -85,7 +85,7 @@ template <Personality personality> struct Base: public Storage<personality> {
/// Mutates @c target such that @c source replaces the @c length bits that currently start
/// at bit @c shift . Subsequently ensures @c target is constrained by the
/// applicable @c memory_mask.
template <int shift, int length = 8> void install_field(AddressT &target, uint8_t source) {
template <int shift, int length = 8> void install_field(AddressT &target, const uint8_t source) {
static_assert(length > 0 && length <= 8);
constexpr auto source_mask = (1 << length) - 1;
constexpr auto mask = AddressT(~(source_mask << shift));
@ -302,11 +302,11 @@ template <Personality personality> struct Base: public Storage<personality> {
return ScreenMode::Blank;
}
static AddressT rotate(AddressT address) {
static AddressT rotate(const AddressT address) {
return AddressT((address >> 1) | (address << 16)) & memory_mask(personality);
}
AddressT command_address(Vector location, bool expansion) const {
AddressT command_address(const Vector location, const bool expansion) const {
if constexpr (is_yamaha_vdp(personality)) {
switch(this->underlying_mode_) {
default:
@ -345,7 +345,7 @@ template <Personality personality> struct Base: public Storage<personality> {
}
}
uint8_t extract_colour(uint8_t byte, Vector location) const {
uint8_t extract_colour(const uint8_t byte, const Vector location) const {
switch(this->screen_mode_) {
default:
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
@ -360,7 +360,7 @@ template <Personality personality> struct Base: public Storage<personality> {
}
}
std::pair<uint8_t, uint8_t> command_colour_mask(Vector location) const {
std::pair<uint8_t, uint8_t> command_colour_mask(const Vector location) const {
if constexpr (is_yamaha_vdp(personality)) {
auto &context = Storage<personality>::command_context_;
auto colour = context.latched_colour.has_value() ? context.latched_colour : context.colour;
@ -394,7 +394,7 @@ template <Personality personality> struct Base: public Storage<personality> {
}
}
void do_external_slot(int access_column) {
void do_external_slot(const int access_column) {
// Don't do anything if the required time for the access to become executable
// has yet to pass.
if(queued_access_ == MemoryAccess::None || access_column < minimum_access_column_) {
@ -588,7 +588,7 @@ template <Personality personality> struct Base: public Storage<personality> {
///
/// i.e. it provides standard glue to enter a fetch sequence at any point, while the fetches themselves are templated on the cycle
/// at which they appear for neater expression.
template<bool use_end, typename Fetcher> void dispatch(Fetcher &fetcher, int start, int end);
template<bool use_end, typename Fetcher> void dispatch(Fetcher &, int start, int end);
// Various fetchers.
template<bool use_end> void fetch_tms_refresh(uint8_t y, int start, int end);
@ -616,7 +616,14 @@ template <Personality personality> struct Base: public Storage<personality> {
template<ScreenMode mode> void draw_yamaha(uint8_t y, int start, int end);
void draw_yamaha(uint8_t y, int start, int end);
template <SpriteMode mode, bool double_width> void draw_sprites(uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer = nullptr);
template <SpriteMode mode, bool double_width>
void draw_sprites(
uint8_t y,
int start,
int end,
const std::array<uint32_t, 16> &palette,
int *colour_buffer = nullptr
);
};
}

@ -36,7 +36,7 @@ enum class ScreenMode {
YamahaGraphics2 = Graphics,
};
constexpr int pixels_per_byte(ScreenMode mode) {
constexpr int pixels_per_byte(const ScreenMode mode) {
switch(mode) {
default:
case ScreenMode::Blank: return 1;
@ -54,7 +54,7 @@ constexpr int pixels_per_byte(ScreenMode mode) {
}
}
constexpr int width(ScreenMode mode) {
constexpr int width(const ScreenMode mode) {
switch(mode) {
default:
case ScreenMode::Blank: return 0;
@ -72,11 +72,11 @@ constexpr int width(ScreenMode mode) {
}
}
constexpr bool interleaves_banks(ScreenMode mode) {
constexpr bool interleaves_banks(const ScreenMode mode) {
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
}
constexpr bool is_text(ScreenMode mode) {
constexpr bool is_text(const ScreenMode mode) {
return mode == ScreenMode::Text || mode == ScreenMode::YamahaText80;
}

@ -89,77 +89,77 @@ template <Personality personality, Clock head, Clock... tail> constexpr int from
an error term be tracked.
*/
template <Personality personality> class ClockConverter {
public:
/*!
Given that another @c source external **half-cycles** has occurred,
indicates how many complete internal **cycles** have additionally elapsed
since the last call to @c to_internal.
public:
/*!
Given that another @c source external **half-cycles** has occurred,
indicates how many complete internal **cycles** have additionally elapsed
since the last call to @c to_internal.
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
*/
int to_internal(int source) {
switch(personality) {
// Default behaviour is to apply a multiplication by 3/4;
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
default: {
const int result = source * 3 + cycles_error_;
cycles_error_ = result & 3;
return result >> 2;
}
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
*/
int to_internal(const int source) {
switch(personality) {
// Default behaviour is to apply a multiplication by 3/4;
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
default: {
const int result = source * 3 + cycles_error_;
cycles_error_ = result & 3;
return result >> 2;
}
// The two Yamaha chips have an internal clock that is four times
// as fast as the TMS, therefore a stateless translation is possible.
case Personality::V9938:
case Personality::V9958:
return source * 3;
// The two Yamaha chips have an internal clock that is four times
// as fast as the TMS, therefore a stateless translation is possible.
case Personality::V9938:
case Personality::V9958:
return source * 3;
// The Mega Drive runs at 3420 master clocks per line, which is then
// divided by 4 or 5 depending on other state. That's 7 times the
// rate provided to the CPU; given that the input is in half-cycles
// the proper multiplier is therefore 3.5.
case Personality::MDVDP: {
const int result = source * 7 + cycles_error_;
cycles_error_ = result & 1;
return result >> 1;
}
// The Mega Drive runs at 3420 master clocks per line, which is then
// divided by 4 or 5 depending on other state. That's 7 times the
// rate provided to the CPU; given that the input is in half-cycles
// the proper multiplier is therefore 3.5.
case Personality::MDVDP: {
const int result = source * 7 + cycles_error_;
cycles_error_ = result & 1;
return result >> 1;
}
}
}
/*!
Provides the number of external cycles that need to begin from now in order to
get at least @c internal_cycles into the future.
*/
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
// Logic here correlates with multipliers as per @c to_internal.
switch(personality) {
default:
// Relative to the external clock multiplied by 3, it will definitely take this
// many cycles to complete a further (internal_cycles - 1) after the current one.
internal_cycles = (internal_cycles - 1) << 2;
/*!
Provides the number of external cycles that need to begin from now in order to
get at least @c internal_cycles into the future.
*/
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
// Logic here correlates with multipliers as per @c to_internal.
switch(personality) {
default:
// Relative to the external clock multiplied by 3, it will definitely take this
// many cycles to complete a further (internal_cycles - 1) after the current one.
internal_cycles = (internal_cycles - 1) << 2;
// It will also be necessary to complete the current one.
internal_cycles += 4 - cycles_error_;
// It will also be necessary to complete the current one.
internal_cycles += 4 - cycles_error_;
// Round up to get the first external cycle after
// the number of internal_cycles has elapsed.
return HalfCycles((internal_cycles + 2) / 3);
// Round up to get the first external cycle after
// the number of internal_cycles has elapsed.
return HalfCycles((internal_cycles + 2) / 3);
case Personality::V9938:
case Personality::V9958:
return HalfCycles((internal_cycles + 2) / 3);
case Personality::V9938:
case Personality::V9958:
return HalfCycles((internal_cycles + 2) / 3);
case Personality::MDVDP:
internal_cycles = (internal_cycles - 1) << 1;
internal_cycles += 2 - cycles_error_;
return HalfCycles((internal_cycles + 6) / 7);
}
case Personality::MDVDP:
internal_cycles = (internal_cycles - 1) << 1;
internal_cycles += 2 - cycles_error_;
return HalfCycles((internal_cycles + 6) / 7);
}
}
private:
// Holds current residue in conversion from the external to
// internal clock.
int cycles_error_ = 0;
private:
// Holds current residue in conversion from the external to
// internal clock.
int cycles_error_ = 0;
};
}

@ -14,7 +14,13 @@ namespace TI::TMS {
template <Personality personality>
template <SpriteMode mode, bool double_width>
void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer) {
void Base<personality>::draw_sprites(
[[maybe_unused]] const uint8_t y,
const int start,
const int end,
const std::array<uint32_t, 16> &palette,
int *const colour_buffer
) {
if(!draw_line_buffer_->sprites) {
return;
}
@ -255,7 +261,7 @@ void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int
template <Personality personality>
template <SpriteMode sprite_mode>
void Base<personality>::draw_tms_character(int start, int end) {
void Base<personality>::draw_tms_character(const int start, const int end) {
auto &line_buffer = *draw_line_buffer_;
// Paint the background tiles.
@ -304,7 +310,7 @@ void Base<personality>::draw_tms_character(int start, int end) {
template <Personality personality>
template <bool apply_blink>
void Base<personality>::draw_tms_text(int start, int end) {
void Base<personality>::draw_tms_text(const int start, const int end) {
auto &line_buffer = *draw_line_buffer_;
uint32_t colours[2][2] = {
{palette()[background_colour_], palette()[text_colour_]},
@ -345,7 +351,11 @@ void Base<personality>::draw_tms_text(int start, int end) {
// MARK: - Master System
template <Personality personality>
void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] uint32_t cram_dot) {
void Base<personality>::draw_sms(
[[maybe_unused]] const int start,
[[maybe_unused]] const int end,
[[maybe_unused]] const uint32_t cram_dot
) {
if constexpr (is_sega_vdp(personality)) {
int colour_buffer[256];
auto &line_buffer = *draw_line_buffer_;
@ -449,7 +459,7 @@ void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] in
template <Personality personality>
template <ScreenMode mode>
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
void Base<personality>::draw_yamaha(const uint8_t y, int start, int end) {
[[maybe_unused]] const auto active_palette = palette();
const int sprite_start = start >> 2;
const int sprite_end = end >> 2;
@ -536,7 +546,7 @@ void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
}
template <Personality personality>
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
void Base<personality>::draw_yamaha(const uint8_t y, const int start, const int end) {
if constexpr (is_yamaha_vdp(personality)) {
switch(draw_line_buffer_->screen_mode) {
// Modes that are the same (or close enough) to those on the TMS.

@ -57,23 +57,40 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
switch(start) {
default: assert(false);
index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9);
index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19);
index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29);
index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39);
index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49);
index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59);
index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69);
index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79);
index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89);
index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99);
index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109);
index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119);
index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129);
index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139);
index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149);
index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159);
index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169);
index(0); index(1); index(2); index(3); index(4);
index(5); index(6); index(7); index(8); index(9);
index(10); index(11); index(12); index(13); index(14);
index(15); index(16); index(17); index(18); index(19);
index(20); index(21); index(22); index(23); index(24);
index(25); index(26); index(27); index(28); index(29);
index(30); index(31); index(32); index(33); index(34);
index(35); index(36); index(37); index(38); index(39);
index(40); index(41); index(42); index(43); index(44);
index(45); index(46); index(47); index(48); index(49);
index(50); index(51); index(52); index(53); index(54);
index(55); index(56); index(57); index(58); index(59);
index(60); index(61); index(62); index(63); index(64);
index(65); index(66); index(67); index(68); index(69);
index(70); index(71); index(72); index(73); index(74);
index(75); index(76); index(77); index(78); index(79);
index(80); index(81); index(82); index(83); index(84);
index(85); index(86); index(87); index(88); index(89);
index(90); index(91); index(92); index(93); index(94);
index(95); index(96); index(97); index(98); index(99);
index(100); index(101); index(102); index(103); index(104);
index(105); index(106); index(107); index(108); index(109);
index(110); index(111); index(112); index(113); index(114);
index(115); index(116); index(117); index(118); index(119);
index(120); index(121); index(122); index(123); index(124);
index(125); index(126); index(127); index(128); index(129);
index(130); index(131); index(132); index(133); index(134);
index(135); index(136); index(137); index(138); index(139);
index(140); index(141); index(142); index(143); index(144);
index(145); index(146); index(147); index(148); index(149);
index(150); index(151); index(152); index(153); index(154);
index(155); index(156); index(157); index(158); index(159);
index(160); index(161); index(162); index(163); index(164);
index(165); index(166); index(167); index(168); index(169);
index(170);
}
@ -108,7 +125,7 @@ template <Personality personality>
struct CharacterFetcher {
using AddressT = typename Base<personality>::AddressT;
CharacterFetcher(Base<personality> *base, uint8_t y) :
CharacterFetcher(Base<personality> *const base, const uint8_t y) :
base(base),
y(y),
row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31)))
@ -137,15 +154,15 @@ struct CharacterFetcher {
}
}
void fetch_name(int column) {
void fetch_name(const int column) {
base->tile_offset_ = base->ram_[row_base + AddressT(column)];
}
void fetch_pattern(int column) {
void fetch_pattern(const int column) {
base->fetch_line_buffer_->tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)];
}
void fetch_colour(int column) {
void fetch_colour(const int column) {
base->fetch_line_buffer_->tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)];
}

@ -11,16 +11,16 @@
namespace TI::TMS {
// Genus determinants for the various personalityes.
constexpr bool is_sega_vdp(Personality p) {
constexpr bool is_sega_vdp(const Personality p) {
return p >= Personality::SMSVDP;
}
constexpr bool is_yamaha_vdp(Personality p) {
constexpr bool is_yamaha_vdp(const Personality p) {
return p == Personality::V9938 || p == Personality::V9958;
}
// i.e. one with the original internal timings.
constexpr bool is_classic_vdp(Personality p) {
constexpr bool is_classic_vdp(const Personality p) {
return
p == Personality::TMS9918A ||
p == Personality::SMSVDP ||
@ -28,7 +28,7 @@ constexpr bool is_classic_vdp(Personality p) {
p == Personality::GGVDP;
}
constexpr size_t memory_size(Personality p) {
constexpr size_t memory_size(const Personality p) {
switch(p) {
case TI::TMS::TMS9918A:
case TI::TMS::SMSVDP:
@ -41,7 +41,7 @@ constexpr size_t memory_size(Personality p) {
return 0;
}
constexpr size_t memory_mask(Personality p) {
constexpr size_t memory_mask(const Personality p) {
return memory_size(p) - 1;
}

@ -28,269 +28,269 @@ template <> struct Storage<Personality::TMS9918A> {
};
struct YamahaFetcher {
public:
/// Describes an _observable_ memory access event. i.e. anything that it is safe
/// (and convenient) to treat as atomic in between external slots.
struct Event {
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
/// external data must be ready by in order to take part in those slots.
uint16_t offset = 1368;
enum class Type: uint8_t {
/// A slot for reading or writing data on behalf of the CPU or the command engine.
External,
public:
/// Describes an _observable_ memory access event. i.e. anything that it is safe
/// (and convenient) to treat as atomic in between external slots.
struct Event {
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
/// external data must be ready by in order to take part in those slots.
uint16_t offset = 1368;
enum class Type: uint8_t {
/// A slot for reading or writing data on behalf of the CPU or the command engine.
External,
//
// Sprites.
//
SpriteY,
SpriteLocation,
SpritePattern,
//
// Sprites.
//
SpriteY,
SpriteLocation,
SpritePattern,
//
// Backgrounds.
//
Name,
Colour,
Pattern,
} type = Type::External;
uint8_t id = 0;
//
// Backgrounds.
//
Name,
Colour,
Pattern,
} type = Type::External;
uint8_t id = 0;
constexpr Event(Type type, uint8_t id = 0) noexcept :
type(type),
id(id) {}
constexpr Event(Type type, uint8_t id = 0) noexcept :
type(type),
id(id) {}
constexpr Event() noexcept = default;
};
constexpr Event() noexcept = default;
};
// State that tracks fetching position within a line.
const Event *next_event_ = nullptr;
// State that tracks fetching position within a line.
const Event *next_event_ = nullptr;
// Sprite collection state.
bool sprites_enabled_ = true;
// Sprite collection state.
bool sprites_enabled_ = true;
protected:
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
template <typename GeneratorT> static constexpr size_t events_size() {
size_t size = 0;
for(int c = 0; c < 1368; c++) {
const auto event_type = GeneratorT::event(c);
size += event_type.has_value();
}
return size + 1;
protected:
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
template <typename GeneratorT> static constexpr size_t events_size() {
size_t size = 0;
for(int c = 0; c < 1368; c++) {
const auto event_type = GeneratorT::event(c);
size += event_type.has_value();
}
return size + 1;
}
/// @return An array of all events generated by @c GeneratorT in line order.
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
static constexpr std::array<Event, size> events() {
std::array<Event, size> result{};
size_t index = 0;
for(int c = 0; c < 1368; c++) {
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
if(!event) {
continue;
}
result[index] = *event;
result[index].offset = uint16_t(c);
++index;
/// @return An array of all events generated by @c GeneratorT in line order.
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
static constexpr std::array<Event, size> events() {
std::array<Event, size> result{};
size_t index = 0;
for(int c = 0; c < 1368; c++) {
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
if(!event) {
continue;
}
result[index] = Event();
return result;
result[index] = *event;
result[index].offset = uint16_t(c);
++index;
}
result[index] = Event();
return result;
}
struct StandardGenerators {
static constexpr std::optional<Event> external_every_eight(int index) {
if(index & 7) return std::nullopt;
struct StandardGenerators {
static constexpr std::optional<Event> external_every_eight(int index) {
if(index & 7) return std::nullopt;
return Event::Type::External;
}
};
struct RefreshGenerator {
static constexpr std::optional<Event> event(const int grauw_index) {
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
if(grauw_index < 126) {
return StandardGenerators::external_every_eight(grauw_index - 0);
}
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
// CPU/CMD and the final being refresh.
if(grauw_index >= 164 && grauw_index < 1234) {
const int offset = grauw_index - 164;
if(offset & 7) return std::nullopt;
if(((offset >> 3) & 15) == 15) return std::nullopt;
return Event::Type::External;
}
};
struct RefreshGenerator {
static constexpr std::optional<Event> event(int grauw_index) {
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
if(grauw_index < 126) {
return StandardGenerators::external_every_eight(grauw_index - 0);
}
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
// CPU/CMD and the final being refresh.
if(grauw_index >= 164 && grauw_index < 1234) {
const int offset = grauw_index - 164;
if(offset & 7) return std::nullopt;
if(((offset >> 3) & 15) == 15) return std::nullopt;
return Event::Type::External;
}
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
if(grauw_index >= 1268 && grauw_index < 1330) {
return StandardGenerators::external_every_eight(grauw_index - 1268);
}
// A CPU/CMD at 1334.
if(grauw_index == 1334) {
return Event::Type::External;
}
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
if(grauw_index >= 1344 && grauw_index < 1366) {
return StandardGenerators::external_every_eight(grauw_index - 1344);
}
// Otherwise: nothing.
return std::nullopt;
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
if(grauw_index >= 1268 && grauw_index < 1330) {
return StandardGenerators::external_every_eight(grauw_index - 1268);
}
};
template <bool include_sprites> struct BitmapGenerator {
static constexpr std::optional<Event> event(int grauw_index) {
if(!include_sprites) {
// Various standard zones of one-every-eight external slots.
if(grauw_index < 124) {
return StandardGenerators::external_every_eight(grauw_index + 2);
}
if(grauw_index > 1266) {
return StandardGenerators::external_every_eight(grauw_index - 1266);
}
} else {
// This records collection points for all data for selected sprites.
// There's only four of them (each site covering two sprites),
// so it's clearer just to be explicit.
//
// There's also a corresponding number of extra external slots to spell out.
switch(grauw_index) {
default: break;
case 1238: return Event(Event::Type::SpriteLocation, 0);
case 1302: return Event(Event::Type::SpriteLocation, 2);
case 2: return Event(Event::Type::SpriteLocation, 4);
case 66: return Event(Event::Type::SpriteLocation, 6);
case 1270: return Event(Event::Type::SpritePattern, 0);
case 1338: return Event(Event::Type::SpritePattern, 2);
case 34: return Event(Event::Type::SpritePattern, 4);
case 98: return Event(Event::Type::SpritePattern, 6);
case 1264: case 1330: case 28: case 92:
return Event::Type::External;
}
}
if(grauw_index >= 162 && grauw_index < 176) {
return StandardGenerators::external_every_eight(grauw_index - 162);
}
// Everywhere else the pattern is:
//
// external or sprite y, external, data block
//
// Subject to caveats:
//
// 1) the first data block is just a dummy fetch with no side effects,
// so this emulator declines to record it; and
// 2) every fourth block, the second external is actually a refresh.
//
if(grauw_index >= 182 && grauw_index < 1238) {
const int offset = grauw_index - 182;
const int block = offset / 32;
const int sub_block = offset & 31;
switch(sub_block) {
default: return std::nullopt;
case 0:
if(include_sprites) {
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
if(block < 32) {
return Event(Event::Type::SpriteY, uint8_t(block));
}
} else {
return Event::Type::External;
}
case 6:
if((block & 3) != 3) {
return Event::Type::External;
}
break;
case 12:
if(block) {
return Event(Event::Type::Pattern, uint8_t(block - 1));
}
break;
}
}
return std::nullopt;
// A CPU/CMD at 1334.
if(grauw_index == 1334) {
return Event::Type::External;
}
};
struct TextGenerator {
static constexpr std::optional<Event> event(int grauw_index) {
// Capture various one-in-eight zones.
if(grauw_index < 72) {
return StandardGenerators::external_every_eight(grauw_index - 2);
}
if(grauw_index >= 166 && grauw_index < 228) {
return StandardGenerators::external_every_eight(grauw_index - 166);
}
if(grauw_index >= 1206 && grauw_index < 1332) {
return StandardGenerators::external_every_eight(grauw_index - 1206);
}
if(grauw_index == 1336) {
return Event::Type::External;
}
if(grauw_index >= 1346) {
return StandardGenerators::external_every_eight(grauw_index - 1346);
}
// Elsewhere...
if(grauw_index >= 246) {
const int offset = grauw_index - 246;
const int block = offset / 48;
const int sub_block = offset % 48;
switch(sub_block) {
default: break;
case 0: return Event(Event::Type::Name, uint8_t(block));
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
case 24: return Event(Event::Type::Pattern, uint8_t(block));
}
}
return std::nullopt;
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
if(grauw_index >= 1344 && grauw_index < 1366) {
return StandardGenerators::external_every_eight(grauw_index - 1344);
}
};
struct CharacterGenerator {
static constexpr std::optional<Event> event(int grauw_index) {
// Grab sprite events.
// Otherwise: nothing.
return std::nullopt;
}
};
template <bool include_sprites> struct BitmapGenerator {
static constexpr std::optional<Event> event(const int grauw_index) {
if(!include_sprites) {
// Various standard zones of one-every-eight external slots.
if(grauw_index < 124) {
return StandardGenerators::external_every_eight(grauw_index + 2);
}
if(grauw_index > 1266) {
return StandardGenerators::external_every_eight(grauw_index - 1266);
}
} else {
// This records collection points for all data for selected sprites.
// There's only four of them (each site covering two sprites),
// so it's clearer just to be explicit.
//
// There's also a corresponding number of extra external slots to spell out.
switch(grauw_index) {
default: break;
case 1242: return Event(Event::Type::SpriteLocation, 0);
case 1306: return Event(Event::Type::SpriteLocation, 1);
case 6: return Event(Event::Type::SpriteLocation, 2);
case 70: return Event(Event::Type::SpriteLocation, 3);
case 1274: return Event(Event::Type::SpritePattern, 0);
case 1342: return Event(Event::Type::SpritePattern, 1);
case 38: return Event(Event::Type::SpritePattern, 2);
case 102: return Event(Event::Type::SpritePattern, 3);
case 1268: case 1334: case 32: case 96: return Event::Type::External;
case 1238: return Event(Event::Type::SpriteLocation, 0);
case 1302: return Event(Event::Type::SpriteLocation, 2);
case 2: return Event(Event::Type::SpriteLocation, 4);
case 66: return Event(Event::Type::SpriteLocation, 6);
case 1270: return Event(Event::Type::SpritePattern, 0);
case 1338: return Event(Event::Type::SpritePattern, 2);
case 34: return Event(Event::Type::SpritePattern, 4);
case 98: return Event(Event::Type::SpritePattern, 6);
case 1264: case 1330: case 28: case 92:
return Event::Type::External;
}
if(grauw_index >= 166 && grauw_index < 180) {
return StandardGenerators::external_every_eight(grauw_index - 166);
}
if(grauw_index >= 182 && grauw_index < 1238) {
const int offset = grauw_index - 182;
const int block = offset / 32;
const int sub_block = offset & 31;
switch(sub_block) {
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
case 6: if((sub_block & 3) != 3) return Event::Type::External;
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
}
}
return std::nullopt;
}
};
if(grauw_index >= 162 && grauw_index < 176) {
return StandardGenerators::external_every_eight(grauw_index - 162);
}
// Everywhere else the pattern is:
//
// external or sprite y, external, data block
//
// Subject to caveats:
//
// 1) the first data block is just a dummy fetch with no side effects,
// so this emulator declines to record it; and
// 2) every fourth block, the second external is actually a refresh.
//
if(grauw_index >= 182 && grauw_index < 1238) {
const int offset = grauw_index - 182;
const int block = offset / 32;
const int sub_block = offset & 31;
switch(sub_block) {
default: return std::nullopt;
case 0:
if(include_sprites) {
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
if(block < 32) {
return Event(Event::Type::SpriteY, uint8_t(block));
}
} else {
return Event::Type::External;
}
case 6:
if((block & 3) != 3) {
return Event::Type::External;
}
break;
case 12:
if(block) {
return Event(Event::Type::Pattern, uint8_t(block - 1));
}
break;
}
}
return std::nullopt;
}
};
struct TextGenerator {
static constexpr std::optional<Event> event(const int grauw_index) {
// Capture various one-in-eight zones.
if(grauw_index < 72) {
return StandardGenerators::external_every_eight(grauw_index - 2);
}
if(grauw_index >= 166 && grauw_index < 228) {
return StandardGenerators::external_every_eight(grauw_index - 166);
}
if(grauw_index >= 1206 && grauw_index < 1332) {
return StandardGenerators::external_every_eight(grauw_index - 1206);
}
if(grauw_index == 1336) {
return Event::Type::External;
}
if(grauw_index >= 1346) {
return StandardGenerators::external_every_eight(grauw_index - 1346);
}
// Elsewhere...
if(grauw_index >= 246) {
const int offset = grauw_index - 246;
const int block = offset / 48;
const int sub_block = offset % 48;
switch(sub_block) {
default: break;
case 0: return Event(Event::Type::Name, uint8_t(block));
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
case 24: return Event(Event::Type::Pattern, uint8_t(block));
}
}
return std::nullopt;
}
};
struct CharacterGenerator {
static constexpr std::optional<Event> event(const int grauw_index) {
// Grab sprite events.
switch(grauw_index) {
default: break;
case 1242: return Event(Event::Type::SpriteLocation, 0);
case 1306: return Event(Event::Type::SpriteLocation, 1);
case 6: return Event(Event::Type::SpriteLocation, 2);
case 70: return Event(Event::Type::SpriteLocation, 3);
case 1274: return Event(Event::Type::SpritePattern, 0);
case 1342: return Event(Event::Type::SpritePattern, 1);
case 38: return Event(Event::Type::SpritePattern, 2);
case 102: return Event(Event::Type::SpritePattern, 3);
case 1268: case 1334: case 32: case 96: return Event::Type::External;
}
if(grauw_index >= 166 && grauw_index < 180) {
return StandardGenerators::external_every_eight(grauw_index - 166);
}
if(grauw_index >= 182 && grauw_index < 1238) {
const int offset = grauw_index - 182;
const int block = offset / 32;
const int sub_block = offset & 31;
switch(sub_block) {
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
case 6: if((sub_block & 3) != 3) return Event::Type::External;
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
}
}
return std::nullopt;
}
};
};
struct YamahaCommandState {
@ -314,7 +314,7 @@ struct YamahaCommandState {
int minimum_command_column_ = 0;
uint8_t command_latch_ = 0;
void update_command_step(int current_column) {
void update_command_step(const int current_column) {
if(!command_) {
next_command_step_ = CommandStep::None;
return;
@ -354,7 +354,10 @@ struct YamahaCommandState {
};
// Yamaha-specific storage.
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public YamahaFetcher, public YamahaCommandState {
template <Personality personality>
struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>:
public YamahaFetcher, public YamahaCommandState
{
using AddressT = uint32_t;
// The Yamaha's (optional in real hardware) additional 64kb of expansion RAM.
@ -410,7 +413,7 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
}
/// Resets line-ephemeral state for a new line.
void begin_line(ScreenMode mode, bool is_refresh) {
void begin_line(const ScreenMode mode, const bool is_refresh) {
if(is_refresh) {
next_event_ = refresh_events.data();
return;

@ -17,13 +17,13 @@ namespace TI::TMS {
struct Vector {
int v[2]{};
template <int offset, bool high> void set(uint8_t value) {
template <int offset, bool high> void set(const uint8_t value) {
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
constexpr int shift = high ? 8 : 0;
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
}
template <int offset> void add(int amount) {
template <int offset> void add(const int amount) {
v[offset] += amount;
if constexpr (offset == 1) {
@ -41,7 +41,7 @@ struct Vector {
};
struct Colour {
void set(uint8_t value) {
void set(const uint8_t value) {
colour = value;
colour4bpp = uint8_t((value & 0xf) | (value << 4));
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
@ -131,7 +131,8 @@ struct Command {
/// Current command parameters.
CommandContext &context;
ModeDescription &mode_description;
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
Command(CommandContext &context, ModeDescription &mode_description) :
context(context), mode_description(mode_description) {}
virtual ~Command() = default;
/// @returns @c true if all output from this command is done; @c false otherwise.
@ -142,7 +143,7 @@ struct Command {
virtual void advance() = 0;
protected:
template <int axis, bool include_source> void advance_axis(int offset = 1) {
template <int axis, bool include_source> void advance_axis(const int offset = 1) {
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
if constexpr (include_source) {
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
@ -161,58 +162,58 @@ namespace Commands {
/// * 88 cycles between every pixel plot;
/// * plus an additional 32 cycles if a step along the minor axis is taken.
struct Line: public Command {
public:
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
// context.destination = start position;
// context.size.v[0] = long side dots;
// context.size.v[1] = short side dots;
// context.arguments => direction
public:
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
// context.destination = start position;
// context.size.v[0] = long side dots;
// context.size.v[1] = short side dots;
// context.arguments => direction
position_ = context.size.v[1];
numerator_ = position_ << 1;
denominator_ = context.size.v[0] << 1;
position_ = context.size.v[1];
numerator_ = position_ << 1;
denominator_ = context.size.v[0] << 1;
cycles = 32;
access = AccessType::PlotPoint;
cycles = 32;
access = AccessType::PlotPoint;
}
bool done() final {
return !context.size.v[0];
}
void advance() final {
--context.size.v[0];
cycles = 88;
// b0: 1 => long direction is y;
// 0 => long direction is x.
//
// b2: 1 => x direction is left;
// 0 => x direction is right.
//
// b3: 1 => y direction is up;
// 0 => y direction is down.
if(context.arguments & 0x1) {
advance_axis<1, false>();
} else {
advance_axis<0, false>();
}
bool done() final {
return !context.size.v[0];
}
position_ -= numerator_;
if(position_ < 0) {
position_ += denominator_;
cycles += 32;
void advance() final {
--context.size.v[0];
cycles = 88;
// b0: 1 => long direction is y;
// 0 => long direction is x.
//
// b2: 1 => x direction is left;
// 0 => x direction is right.
//
// b3: 1 => y direction is up;
// 0 => y direction is down.
if(context.arguments & 0x1) {
advance_axis<1, false>();
} else {
advance_axis<0, false>();
}
position_ -= numerator_;
if(position_ < 0) {
position_ += denominator_;
cycles += 32;
if(context.arguments & 0x1) {
advance_axis<0, false>();
} else {
advance_axis<1, false>();
}
} else {
advance_axis<1, false>();
}
}
}
private:
int position_, numerator_, denominator_;
private:
int position_, numerator_, denominator_;
};
// MARK: - Single pixel manipulation.
@ -221,88 +222,90 @@ struct Line: public Command {
///
/// No timings are documented, so this'll output or input as quickly as possible.
template <bool is_read> struct Point: public Command {
public:
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
cycles = 0; // TODO.
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
}
public:
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
cycles = 0; // TODO.
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
}
bool done() final {
return done_;
}
bool done() final {
return done_;
}
void advance() final {
done_ = true;
}
void advance() final {
done_ = true;
}
private:
bool done_ = false;
private:
bool done_ = false;
};
// MARK: - Rectangular base.
/// Useful base class for anything that does logical work in a rectangle.
template <bool logical, bool include_source> struct Rectangle: public Command {
public:
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
if constexpr (include_source) {
start_x_[0] = context.source.v[0];
}
start_x_[1] = context.destination.v[0];
width_ = context.size.v[0];
public:
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
if constexpr (include_source) {
start_x_[0] = context.source.v[0];
}
start_x_[1] = context.destination.v[0];
width_ = context.size.v[0];
if(!width_) {
// Width = 0 => maximal width for this mode.
// (aside: it's still unclear to me whether commands are
// automatically clipped to the display; I think so but
// don't want to spend any time on it until I'm certain)
if(!width_) {
// Width = 0 => maximal width for this mode.
// (aside: it's still unclear to me whether commands are
// automatically clipped to the display; I think so but
// don't want to spend any time on it until I'm certain)
// context.size.v[0] = width_ = mode_description.width;
}
}
/// Advances the current destination and, if @c include_source is @c true also the source;
/// @returns @c true if a new row was started; @c false otherwise.
bool advance_pixel() {
if constexpr (logical) {
advance_axis<0, include_source>();
--context.size.v[0];
if(context.size.v[0]) {
return false;
}
} else {
advance_axis<0, include_source>(mode_description.pixels_per_byte);
context.size.v[0] -= mode_description.pixels_per_byte;
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
return false;
}
}
/// Advances the current destination and, if @c include_source is @c true also the source;
/// @returns @c true if a new row was started; @c false otherwise.
bool advance_pixel() {
if constexpr (logical) {
advance_axis<0, include_source>();
--context.size.v[0];
if(context.size.v[0]) {
return false;
}
} else {
advance_axis<0, include_source>(mode_description.pixels_per_byte);
context.size.v[0] -= mode_description.pixels_per_byte;
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
return false;
}
}
context.size.v[0] = width_;
if constexpr (include_source) {
context.source.v[0] = start_x_[0];
}
context.destination.v[0] = start_x_[1];
advance_axis<1, include_source>();
--context.size.v[1];
return true;
context.size.v[0] = width_;
if constexpr (include_source) {
context.source.v[0] = start_x_[0];
}
context.destination.v[0] = start_x_[1];
bool done() final {
return !context.size.v[1] || !width_;
}
advance_axis<1, include_source>();
--context.size.v[1];
private:
int start_x_[2]{}, width_ = 0;
return true;
}
bool done() final {
return !context.size.v[1] || !width_;
}
private:
int start_x_[2]{}, width_ = 0;
};
// MARK: - Rectangular moves to/from CPU.
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) :
Rectangle<logical, false>(context, mode_description)
{
Command::is_cpu_transfer = true;
// This command is started with the first colour ready to transfer.

@ -22,7 +22,11 @@ using namespace GI::AY38910;
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
template <bool is_stereo>
AY38910SampleSource<is_stereo>::AY38910SampleSource(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
AY38910SampleSource<is_stereo>::AY38910SampleSource(
Personality personality,
Concurrency::AsyncTaskQueue<false> &task_queue)
: task_queue_(task_queue)
{
// Don't use the low bit of the envelope position if this is an AY.
envelope_position_mask_ |= personality == Personality::AY38910;
@ -82,7 +86,7 @@ AY38910SampleSource<is_stereo>::AY38910SampleSource(Personality personality, Con
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_sample_volume_range(std::int16_t range) {
void AY38910SampleSource<is_stereo>::set_sample_volume_range(const std::int16_t range) {
// Set up volume lookup table; the function below is based on a combination of the graph
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
// values reported elsewhere.
@ -101,7 +105,14 @@ void AY38910SampleSource<is_stereo>::set_sample_volume_range(std::int16_t range)
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
void AY38910SampleSource<is_stereo>::set_output_mixing(
const float a_left,
const float b_left,
const float c_left,
const float a_right,
const float b_right,
const float c_right
) {
a_left_ = uint8_t(a_left * 255.0f);
b_left_ = uint8_t(b_left * 255.0f);
c_left_ = uint8_t(c_left * 255.0f);
@ -223,12 +234,12 @@ bool AY38910SampleSource<is_stereo>::is_zero_level() const {
// MARK: - Register manipulation
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::select_register(uint8_t r) {
void AY38910SampleSource<is_stereo>::select_register(const uint8_t r) {
selected_register_ = r;
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_register_value(uint8_t value) {
void AY38910SampleSource<is_stereo>::set_register_value(const uint8_t value) {
// There are only 16 registers.
if(selected_register_ > 15) return;
@ -298,7 +309,7 @@ void AY38910SampleSource<is_stereo>::set_register_value(uint8_t value) {
}
template <bool is_stereo>
uint8_t AY38910SampleSource<is_stereo>::get_register_value() {
uint8_t AY38910SampleSource<is_stereo>::get_register_value() const {
// This table ensures that bits that aren't defined within the AY are returned as 0s
// when read, conforming to CPC-sourced unit tests.
const uint8_t register_masks[16] = {
@ -313,27 +324,27 @@ uint8_t AY38910SampleSource<is_stereo>::get_register_value() {
// MARK: - Port querying
template <bool is_stereo>
uint8_t AY38910SampleSource<is_stereo>::get_port_output(bool port_b) {
uint8_t AY38910SampleSource<is_stereo>::get_port_output(const bool port_b) const {
return registers_[port_b ? 15 : 14];
}
// MARK: - Bus handling
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_port_handler(PortHandler *handler) {
void AY38910SampleSource<is_stereo>::set_port_handler(PortHandler *const handler) {
port_handler_ = handler;
set_port_output(true);
set_port_output(false);
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_data_input(uint8_t r) {
void AY38910SampleSource<is_stereo>::set_data_input(const uint8_t r) {
data_input_ = r;
update_bus();
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_port_output(bool port_b) {
void AY38910SampleSource<is_stereo>::set_port_output(const bool port_b) {
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
// so that when in the "input" mode, all pins will read normally high". Therefore,
// report programmer selection of input mode as creating an output of 0xff.
@ -344,7 +355,7 @@ void AY38910SampleSource<is_stereo>::set_port_output(bool port_b) {
}
template <bool is_stereo>
uint8_t AY38910SampleSource<is_stereo>::get_data_output() {
uint8_t AY38910SampleSource<is_stereo>::get_data_output() const {
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
// value returned to the CPU when reading it is the and of the output value and any input.
@ -361,7 +372,7 @@ uint8_t AY38910SampleSource<is_stereo>::get_data_output() {
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_control_lines(ControlLines control_lines) {
void AY38910SampleSource<is_stereo>::set_control_lines(const ControlLines control_lines) {
switch(int(control_lines)) {
default: control_state_ = Inactive; break;
@ -377,7 +388,7 @@ void AY38910SampleSource<is_stereo>::set_control_lines(ControlLines control_line
}
template <bool is_stereo>
void AY38910SampleSource<is_stereo>::set_reset(bool active) {
void AY38910SampleSource<is_stereo>::set_reset(const bool active) {
if(active == reset_) return;
reset_ = active;

@ -24,23 +24,23 @@ namespace GI::AY38910 {
instead use AY38910.get_port_output.
*/
class PortHandler {
public:
/*!
Requests the current input on an AY port.
public:
/*!
Requests the current input on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
return 0xff;
}
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
return 0xff;
}
/*!
Sets the current output on an AY port.
/*!
Sets the current output on an AY port.
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
};
/*!
@ -67,110 +67,110 @@ enum class Personality {
This AY has an attached mono or stereo mixer.
*/
template <bool stereo> class AY38910SampleSource {
public:
/// Creates a new AY38910.
AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue<false> &);
AY38910SampleSource(const AY38910SampleSource &) = delete;
public:
/// Creates a new AY38910.
AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue<false> &);
AY38910SampleSource(const AY38910SampleSource &) = delete;
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
/// Gets the value that would appear on the data lines if only the AY is outputting.
uint8_t get_data_output();
/// Gets the value that would appear on the data lines if only the AY is outputting.
uint8_t get_data_output() const;
/// Sets the current control line state, as a bit field.
void set_control_lines(ControlLines control_lines);
/// Sets the current control line state, as a bit field.
void set_control_lines(ControlLines control_lines);
/// Strobes the reset line.
void reset();
/// Strobes the reset line.
void reset();
/// Sets the current value of the reset line.
void set_reset(bool reset);
/// Sets the current value of the reset line.
void set_reset(bool reset);
/*!
Gets the value that would appear on the requested interface port if it were in output mode.
@parameter port_b @c true to get the value for Port B, @c false to get the value for Port A.
*/
uint8_t get_port_output(bool port_b);
/*!
Gets the value that would appear on the requested interface port if it were in output mode.
@parameter port_b @c true to get the value for Port B, @c false to get the value for Port A.
*/
uint8_t get_port_output(bool port_b) const;
/*!
Sets the port handler, which will receive a call every time the AY either wants to sample
input or else declare new output. As a convenience, current port output can be obtained
without installing a port handler via get_port_output.
*/
void set_port_handler(PortHandler *);
/*!
Sets the port handler, which will receive a call every time the AY either wants to sample
input or else declare new output. As a convenience, current port output can be obtained
without installing a port handler via get_port_output.
*/
void set_port_handler(PortHandler *);
/*!
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
channels in each of the output channels.
/*!
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
channels in each of the output channels.
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
*/
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
*/
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
// Sample generation.
typename Outputs::Speaker::SampleT<stereo>::type level() const;
void advance();
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);
// Sample generation.
typename Outputs::Speaker::SampleT<stereo>::type level() const;
void advance();
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::AsyncTaskQueue<false> &task_queue_;
private:
Concurrency::AsyncTaskQueue<false> &task_queue_;
bool reset_ = false;
bool reset_ = false;
int selected_register_ = 0;
uint8_t registers_[16]{};
uint8_t output_registers_[16]{};
int selected_register_ = 0;
uint8_t registers_[16]{};
uint8_t output_registers_[16]{};
int tone_periods_[3]{};
int tone_counters_[3]{};
int tone_outputs_[3]{};
int tone_periods_[3]{};
int tone_counters_[3]{};
int tone_outputs_[3]{};
int noise_period_ = 0;
int noise_counter_ = 0;
int noise_shift_register_ = 0xffff;
int noise_output_ = 0;
int noise_period_ = 0;
int noise_counter_ = 0;
int noise_shift_register_ = 0xffff;
int noise_output_ = 0;
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0, envelope_position_mask_ = 0;
int envelope_shapes_[16][64];
int envelope_overflow_masks_[16];
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0, envelope_position_mask_ = 0;
int envelope_shapes_[16][64];
int envelope_overflow_masks_[16];
int volumes_[32];
int volumes_[32];
enum ControlState {
Inactive,
LatchAddress,
Read,
Write
} control_state_;
enum ControlState {
Inactive,
LatchAddress,
Read,
Write
} control_state_;
void select_register(uint8_t r);
void set_register_value(uint8_t value);
uint8_t get_register_value();
void select_register(uint8_t r);
void set_register_value(uint8_t value);
uint8_t get_register_value() const;
uint8_t data_input_, data_output_;
uint8_t data_input_, data_output_;
typename Outputs::Speaker::SampleT<stereo>::type output_volume_;
typename Outputs::Speaker::SampleT<stereo>::type output_volume_;
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
void evaluate_output_volume();
void evaluate_output_volume();
// Output mixing control.
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
// Output mixing control.
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
friend struct State;
friend struct State;
};
/// Defines a default AY to be the sample source with a master divider of 4;
@ -193,7 +193,9 @@ template <bool stereo> struct AY38910:
*/
struct Utility {
template <typename AY> static void write(AY &ay, bool is_data_write, uint8_t data) {
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1)));
ay.set_control_lines(
GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1))
);
ay.set_data_input(data);
ay.set_control_lines(GI::AY38910::ControlLines(0));
}

@ -20,273 +20,272 @@ namespace Apple::Clock {
will also signal interrupts this is just the storage and time counting.
*/
class ClockStorage {
public:
/*!
Advances the clock by 1 second.
public:
/*!
Advances the clock by 1 second.
The caller should also signal an interrupt if applicable.
The caller should also signal an interrupt if applicable.
*/
void update() {
for(size_t c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
/*!
Sets the current [P/B]RAM contents.
*/
template <typename CollectionT> void set_data(const CollectionT &collection) {
set_data(collection.begin(), collection.end());
}
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
size_t c = 0;
while(begin != end && c < 256) {
data_[c] = *begin;
++begin;
++c;
}
}
protected:
static constexpr uint16_t NoResult = 0x100;
static constexpr uint16_t DidComplete = 0x101;
uint16_t perform(const uint8_t command) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z0111abc, followed by 0defgh00
RAM address abcdefgh
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
*/
void update() {
for(size_t c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
switch(phase_) {
case Phase::Command:
// Decode an address.
switch(command & 0x70) {
default:
if(command & 0x40) {
// RAM addresses 0x00 0x0f.
address_ = (command >> 2) & 0xf;
} else return DidComplete; // Unrecognised.
break;
/*!
Sets the current [P/B]RAM contents.
*/
template <typename CollectionT> void set_data(const CollectionT &collection) {
set_data(collection.begin(), collection.end());
}
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
size_t c = 0;
while(begin != end && c < 256) {
data_[c] = *begin;
++begin;
++c;
}
}
protected:
static constexpr uint16_t NoResult = 0x100;
static constexpr uint16_t DidComplete = 0x101;
uint16_t perform(uint8_t command) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z0111abc, followed by 0defgh00
RAM address abcdefgh
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
*/
switch(phase_) {
case Phase::Command:
// Decode an address.
switch(command & 0x70) {
default:
if(command & 0x40) {
// RAM addresses 0x00 0x0f.
address_ = (command >> 2) & 0xf;
} else return DidComplete; // Unrecognised.
break;
case 0x00:
// A time access.
address_ = SecondsBuffer + ((command >> 2)&3);
break;
case 0x30:
// Either a register access or an extended instruction.
if(command & 0x08) {
address_ = unsigned((command & 0x7) << 5);
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
return NoResult;
} else {
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
}
break;
case 0x20:
// RAM addresses 0x10 0x13.
address_ = 0x10 + ((command >> 2) & 0x3);
break;
}
// If this is a read, return a result; otherwise prepare to write.
if(command & 0x80) {
// The two registers are write-only.
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
return DidComplete;
}
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
}
phase_ = Phase::WriteData;
return NoResult;
case Phase::SecondAddressByteRead:
case Phase::SecondAddressByteWrite:
if(command & 0x83) {
return DidComplete;
}
address_ |= command >> 2;
if(phase_ == Phase::SecondAddressByteRead) {
phase_ = Phase::Command;
return data_[address_]; // Only RAM accesses can get this far.
} else {
phase_ = Phase::WriteData;
}
return NoResult;
case Phase::WriteData:
// First test: is this to the write-protect register?
if(address_ == RegisterWriteProtect) {
write_protect_ = command;
return DidComplete;
}
if(address_ == RegisterTest) {
// No documentation here.
return DidComplete;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address_ >= SecondsBuffer) {
seconds_[address_ & 0xff] = command;
case 0x00:
// A time access.
address_ = SecondsBuffer + ((command >> 2)&3);
break;
case 0x30:
// Either a register access or an extended instruction.
if(command & 0x08) {
address_ = unsigned((command & 0x7) << 5);
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
return NoResult;
} else {
data_[address_] = command;
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
}
break;
case 0x20:
// RAM addresses 0x10 0x13.
address_ = 0x10 + ((command >> 2) & 0x3);
break;
}
// If this is a read, return a result; otherwise prepare to write.
if(command & 0x80) {
// The two registers are write-only.
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
return DidComplete;
}
phase_ = Phase::Command;
return DidComplete;
}
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
}
phase_ = Phase::WriteData;
return NoResult;
case Phase::SecondAddressByteRead:
case Phase::SecondAddressByteWrite:
if(command & 0x83) {
return DidComplete;
}
address_ |= command >> 2;
if(phase_ == Phase::SecondAddressByteRead) {
phase_ = Phase::Command;
return data_[address_]; // Only RAM accesses can get this far.
} else {
phase_ = Phase::WriteData;
}
return NoResult;
case Phase::WriteData:
// First test: is this to the write-protect register?
if(address_ == RegisterWriteProtect) {
write_protect_ = command;
return DidComplete;
}
if(address_ == RegisterTest) {
// No documentation here.
return DidComplete;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address_ >= SecondsBuffer) {
seconds_[address_ & 0xff] = command;
} else {
data_[address_] = command;
}
}
phase_ = Phase::Command;
return DidComplete;
}
return NoResult;
}
private:
std::array<uint8_t, 256> data_{0xff};
std::array<uint8_t, 4> seconds_{};
uint8_t write_protect_ = 0;
unsigned int address_ = 0;
static constexpr int SecondsBuffer = 0x100;
static constexpr int RegisterTest = 0x200;
static constexpr int RegisterWriteProtect = 0x201;
private:
std::array<uint8_t, 256> data_{0xff};
std::array<uint8_t, 4> seconds_{};
uint8_t write_protect_ = 0;
unsigned int address_ = 0;
enum class Phase {
Command,
SecondAddressByteRead,
SecondAddressByteWrite,
WriteData
};
Phase phase_ = Phase::Command;
static constexpr int SecondsBuffer = 0x100;
static constexpr int RegisterTest = 0x200;
static constexpr int RegisterWriteProtect = 0x201;
enum class Phase {
Command,
SecondAddressByteRead,
SecondAddressByteWrite,
WriteData
};
Phase phase_ = Phase::Command;
};
/*!
Provides the serial interface implemented by the Macintosh.
*/
class SerialClock: public ClockStorage {
public:
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(bool clock, bool data) {
// The data line is valid when the clock transitions to level 0.
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
public:
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(const bool clock, const bool data) {
// The data line is valid when the clock transitions to level 0.
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
// Increment phase.
++phase_;
// Increment phase.
++phase_;
// If a whole byte has been collected, push it onwards.
if(!(phase_&7)) {
// Begin pessimistically.
const auto effect = perform(uint8_t(command_));
// If a whole byte has been collected, push it onwards.
if(!(phase_&7)) {
// Begin pessimistically.
const auto effect = perform(uint8_t(command_));
switch(effect) {
case ClockStorage::NoResult:
break;
default:
result_ = uint8_t(effect);
break;
case ClockStorage::DidComplete:
abort();
break;
}
switch(effect) {
case ClockStorage::NoResult:
break;
default:
result_ = uint8_t(effect);
break;
case ClockStorage::DidComplete:
abort();
break;
}
}
previous_clock_ = clock;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() {
return !!(result_ & 0x80);
}
previous_clock_ = clock;
}
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() const {
return !!(result_ & 0x80);
}
private:
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
bool previous_clock_ = false;
private:
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
bool previous_clock_ = false;
};
/*!
Provides the parallel interface implemented by the IIgs.
*/
class ParallelClock: public ClockStorage {
public:
void set_control(uint8_t control) {
if(!(control&0x80)) return;
public:
void set_control(const uint8_t control) {
if(!(control&0x80)) return;
if(control & 0x40) {
// Read from the RTC.
// A no-op for now.
} else {
// Write to the RTC. Which in this implementation also sets up a future read.
const auto result = perform(data_);
if(result < 0x100) {
data_ = uint8_t(result);
}
if(control & 0x40) {
// Read from the RTC.
// A no-op for now.
} else {
// Write to the RTC. Which in this implementation also sets up a future read.
const auto result = perform(data_);
if(result < 0x100) {
data_ = uint8_t(result);
}
// MAGIC! The transaction took 0 seconds.
// TODO: no magic.
control_ = control & 0x7f;
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
}
uint8_t get_control() {
return control_;
}
// MAGIC! The transaction took 0 seconds.
// TODO: no magic.
control_ = control & 0x7f;
void set_data(uint8_t data) {
data_ = data;
}
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
}
uint8_t get_data() {
return data_;
}
uint8_t get_control() const {
return control_;
}
private:
uint8_t data_;
uint8_t control_;
void set_data(const uint8_t data) {
data_ = data;
}
uint8_t get_data() const {
return data_;
}
private:
uint8_t data_;
uint8_t control_;
};
}

@ -15,12 +15,12 @@ using namespace Audio;
Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue) :
audio_queue_(audio_queue) {}
void Toggle::set_sample_volume_range(std::int16_t range) {
void Toggle::set_sample_volume_range(const std::int16_t range) {
volume_ = range;
level_ = level_active_ ? volume_ : 0;
}
void Toggle::set_output(bool enabled) {
void Toggle::set_output(const bool enabled) {
if(is_enabled_ == enabled) return;
is_enabled_ = enabled;
audio_queue_.enqueue([this, enabled] {

@ -17,29 +17,29 @@ namespace Audio {
Provides a sample source that can programmatically be set to one of two values.
*/
class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
public:
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
public:
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
}
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const {
return !level_;
}
template <Outputs::Speaker::Action action>
void apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
}
void set_sample_volume_range(const std::int16_t range);
bool is_zero_level() const {
return !level_;
}
void set_output(bool enabled);
bool get_output() const;
void set_output(bool enabled);
bool get_output() const;
private:
// Accessed on the calling thread.
bool is_enabled_ = false;
Concurrency::AsyncTaskQueue<false> &audio_queue_;
private:
// Accessed on the calling thread.
bool is_enabled_ = false;
Concurrency::AsyncTaskQueue<false> &audio_queue_;
// Accessed on the audio thread.
int16_t level_ = 0, volume_ = 0;
bool level_active_ = false;
// Accessed on the audio thread.
int16_t level_ = 0, volume_ = 0;
bool level_active_ = false;
};
}

@ -19,7 +19,7 @@ namespace {
const uint8_t input_flux = 0x1;
}
DiskII::DiskII(int clock_rate) :
DiskII::DiskII(const int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{
@ -38,7 +38,7 @@ DiskII::DiskII(int clock_rate) :
drives_[active_drive_].set_event_delegate(this);
}
void DiskII::set_control(Control control, bool on) {
void DiskII::set_control(const Control control, const bool on) {
int previous_stepper_mask = stepper_mask_;
switch(control) {
case Control::P0: stepper_mask_ = (stepper_mask_ & 0xe) | (on ? 0x1 : 0x0); break;
@ -76,7 +76,7 @@ void DiskII::set_control(Control control, bool on) {
}
}
void DiskII::select_drive(int drive) {
void DiskII::select_drive(const int drive) {
if((drive&1) == active_drive_) return;
drives_[active_drive_].set_event_delegate(this);
@ -152,7 +152,7 @@ void DiskII::run_for(const Cycles cycles) {
}
void DiskII::decide_clocking_preference() {
ClockingHint::Preference prior_preference = clocking_preference_;
const ClockingHint::Preference prior_preference = clocking_preference_;
// If in read mode, clocking is either:
//
@ -183,7 +183,7 @@ void DiskII::decide_clocking_preference() {
update_clocking_observer();
}
bool DiskII::is_write_protected() {
bool DiskII::is_write_protected() const {
return (stepper_mask_ & 2) || drives_[active_drive_].get_is_read_only();
}

@ -29,99 +29,99 @@ class DiskII :
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
public:
DiskII(int clock_rate);
public:
DiskII(int clock_rate);
/// Sets the current external value of the data bus.
void set_data_input(uint8_t input);
/// Sets the current external value of the data bus.
void set_data_input(uint8_t input);
/*!
Submits an access to address @c address.
/*!
Submits an access to address @c address.
@returns The 8-bit value loaded to the data bus by the DiskII if any;
@c DidNotLoad otherwise.
*/
int read_address(int address);
@returns The 8-bit value loaded to the data bus by the DiskII if any;
@c DidNotLoad otherwise.
*/
int read_address(int address);
/*!
The value returned by @c read_address if accessing that address
didn't cause the disk II to place anything onto the bus.
*/
static constexpr int DidNotLoad = -1;
/*!
The value returned by @c read_address if accessing that address
didn't cause the disk II to place anything onto the bus.
*/
static constexpr int DidNotLoad = -1;
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
/*!
Supplies the image of the state machine (i.e. P6) ROM,
which dictates how the Disk II will respond to input.
/*!
Supplies the image of the state machine (i.e. P6) ROM,
which dictates how the Disk II will respond to input.
To reduce processing costs, some assumptions are made by
the implementation as to the content of this ROM.
Including:
To reduce processing costs, some assumptions are made by
the implementation as to the content of this ROM.
Including:
* If Q6 is set and Q7 is reset, the controller is testing
for write protect. If and when the shift register has
become full with the state of the write protect value,
no further processing is required.
* If Q6 is set and Q7 is reset, the controller is testing
for write protect. If and when the shift register has
become full with the state of the write protect value,
no further processing is required.
* If both Q6 and Q7 are reset, the drive motor is disabled,
and the shift register is all zeroes, no further processing
is required.
*/
void set_state_machine(const std::vector<uint8_t> &);
* If both Q6 and Q7 are reset, the drive motor is disabled,
and the shift register is all zeroes, no further processing
is required.
*/
void set_state_machine(const std::vector<uint8_t> &);
/// Inserts @c disk into the drive @c drive.
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
/// Inserts @c disk into the drive @c drive.
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() const final;
// As per Sleeper.
ClockingHint::Preference preferred_clocking() const final;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);
// Returns the Storage::Disk::Drive in use for drive @c index.
// *NOT FOR HARDWARE EMULATION USAGE*.
Storage::Disk::Drive &get_drive(int index);
// Returns the Storage::Disk::Drive in use for drive @c index.
// *NOT FOR HARDWARE EMULATION USAGE*.
Storage::Disk::Drive &get_drive(int index);
private:
enum class Control {
P0, P1, P2, P3,
Motor,
};
enum class Mode {
Read, Write
};
void set_control(Control control, bool on);
void set_mode(Mode mode);
void select_drive(int drive);
private:
enum class Control {
P0, P1, P2, P3,
Motor,
};
enum class Mode {
Read, Write
};
void set_control(Control control, bool on);
void set_mode(Mode mode);
void select_drive(int drive);
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Drive::Event &event) final;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Drive::Event &event) final;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
const Cycles::IntType clock_rate_ = 0;
const Cycles::IntType clock_rate_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
uint8_t shift_register_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
uint8_t shift_register_ = 0;
int stepper_mask_ = 0;
int stepper_position_ = 0;
Cycles::IntType motor_off_time_ = -1;
int stepper_mask_ = 0;
int stepper_position_ = 0;
Cycles::IntType motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;
Storage::Disk::Drive drives_[2];
bool drive_is_sleeping_[2];
int active_drive_ = 0;
bool motor_is_enabled_ = false;
bool is_write_protected() const;
std::array<uint8_t, 256> state_machine_;
Storage::Disk::Drive drives_[2];
bool drive_is_sleeping_[2];
int active_drive_ = 0;
bool motor_is_enabled_ = false;
void decide_clocking_preference();
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
void decide_clocking_preference();
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
};
}

@ -10,16 +10,16 @@
using namespace Apple::Disk;
DiskIIDrive::DiskIIDrive(int input_clock_rate) :
DiskIIDrive::DiskIIDrive(const int input_clock_rate) :
IWMDrive(input_clock_rate, 1) {
Drive::set_rotation_speed(300.0f);
}
void DiskIIDrive::set_enabled(bool enabled) {
void DiskIIDrive::set_enabled(const bool enabled) {
set_motor_on(enabled);
}
void DiskIIDrive::set_control_lines(int lines) {
void DiskIIDrive::set_control_lines(const int lines) {
// If the stepper magnet selections have changed, and any is on, see how
// that moves the head.
if(lines ^ stepper_mask_ && lines) {

@ -13,16 +13,16 @@
namespace Apple::Disk {
class DiskIIDrive: public IWMDrive {
public:
DiskIIDrive(int input_clock_rate);
public:
DiskIIDrive(int input_clock_rate);
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
int stepper_mask_ = 0;
int stepper_position_ = 0;
int stepper_mask_ = 0;
int stepper_position_ = 0;
};
}

@ -21,7 +21,8 @@ namespace {
constexpr int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
constexpr int Q6 = 1 << 6;
constexpr int Q7 = 1 << 7;
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a
confusingly-similar name to SELECT but a distinct purpose. */
Log::Logger<Log::Source::IWM> logger;
}
@ -31,7 +32,7 @@ IWM::IWM(int clock_rate) :
// MARK: - Bus accessors
uint8_t IWM::read(int address) {
uint8_t IWM::read(const int address) {
access(address);
// Per Inside Macintosh:
@ -103,7 +104,7 @@ uint8_t IWM::read(int address) {
return 0xff;
}
void IWM::write(int address, uint8_t input) {
void IWM::write(const int address, const uint8_t input) {
access(address);
switch(state_ & (Q6 | Q7 | ENABLE)) {
@ -205,7 +206,7 @@ void IWM::access(int address) {
}
}
void IWM::set_select(bool enabled) {
void IWM::set_select(const bool enabled) {
// Store SEL as an extra state bit.
if(enabled) state_ |= SEL;
else state_ &= ~SEL;
@ -367,7 +368,7 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
}
}
void IWM::propose_shift(uint8_t bit) {
void IWM::propose_shift(const uint8_t bit) {
// TODO: synchronous mode.
// logger.info().append("Shifting at %d", cycles_since_shift_.as<int>());
@ -397,7 +398,7 @@ void IWM::propose_shift(uint8_t bit) {
cycles_since_shift_ -= bit_length_;
}
void IWM::set_drive(int slot, IWMDrive *drive) {
void IWM::set_drive(const int slot, IWMDrive *const drive) {
drives_[slot] = drive;
if(drive) {
drive->set_event_delegate(this);
@ -407,7 +408,10 @@ void IWM::set_drive(int slot, IWMDrive *drive) {
}
}
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
void IWM::set_component_prefers_clocking(
ClockingHint::Source *const component,
const ClockingHint::Preference clocking
) {
const bool is_rotating = clocking != ClockingHint::Preference::None;
if(drives_[0] && component == static_cast<ClockingHint::Source *>(drives_[0])) {
@ -417,7 +421,7 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
}
}
void IWM::set_activity_observer(Activity::Observer *observer) {
void IWM::set_activity_observer(Activity::Observer *const observer) {
if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
}

@ -45,77 +45,77 @@ struct IWMDrive: public Storage::Disk::Drive {
class IWM:
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Observer {
public:
IWM(int clock_rate);
public:
IWM(int clock_rate);
/// Sets the current external value of the data bus.
void write(int address, uint8_t value);
/// Sets the current external value of the data bus.
void write(int address, uint8_t value);
/*!
Submits an access to address @c address.
/*!
Submits an access to address @c address.
@returns The 8-bit value loaded to the data bus by the IWM.
*/
uint8_t read(int address);
@returns The 8-bit value loaded to the data bus by the IWM.
*/
uint8_t read(int address);
/*!
Sets the current input of the IWM's SEL line.
*/
void set_select(bool enabled);
/*!
Sets the current input of the IWM's SEL line.
*/
void set_select(bool);
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
/// Advances the controller.
void run_for(const Cycles);
/// Connects a drive to the IWM.
void set_drive(int slot, IWMDrive *drive);
/// Connects a drive to the IWM.
void set_drive(int slot, IWMDrive *);
/// Registers the currently-connected drives as @c Activity::Sources ;
/// the first will be declared 'Internal', the second 'External'.
void set_activity_observer(Activity::Observer *observer);
/// Registers the currently-connected drives as @c Activity::Sources ;
/// the first will be declared 'Internal', the second 'External'.
void set_activity_observer(Activity::Observer *);
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) final;
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &) final;
const int clock_rate_;
const int clock_rate_;
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
// These related to functionality not-yet implemented.
// bool read_write_ready_ = true;
// bool write_overran_ = false;
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
// These related to functionality not-yet implemented.
// bool read_write_ready_ = true;
// bool write_overran_ = false;
int state_ = 0;
int state_ = 0;
int active_drive_ = 0;
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
int active_drive_ = 0;
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;
void access(int address);
void access(int address);
uint8_t shift_register_ = 0;
uint8_t next_output_ = 0;
int output_bits_remaining_ = 0;
uint8_t shift_register_ = 0;
uint8_t next_output_ = 0;
int output_bits_remaining_ = 0;
void propose_shift(uint8_t bit);
Cycles cycles_since_shift_;
Cycles bit_length_ = Cycles(16);
void propose_shift(uint8_t bit);
Cycles cycles_since_shift_;
Cycles bit_length_ = Cycles(16);
void push_drive_state();
void push_drive_state();
enum class ShiftMode {
Reading,
Writing,
CheckingWriteProtect
} shift_mode_;
enum class ShiftMode {
Reading,
Writing,
CheckingWriteProtect
} shift_mode_;
uint8_t sense();
void select_shift_mode();
uint8_t sense();
void select_shift_mode();
};
}

@ -18,7 +18,7 @@
using namespace Apple::Macintosh;
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
DoubleDensityDrive::DoubleDensityDrive(const int input_clock_rate, const bool is_800k) :
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
is_800k_(is_800k) {
// Start with a valid rotation speed.
@ -29,7 +29,7 @@ DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
// MARK: - Speed Selection
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
void DoubleDensityDrive::did_step(const Storage::Disk::HeadPosition to_position) {
// The 800kb drive automatically selects rotation speed as a function of
// head position; the 400kb drive doesn't do so.
if(is_800k_) {
@ -60,7 +60,7 @@ void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
}
}
void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
void DoubleDensityDrive::set_rotation_speed(const float revolutions_per_minute) {
if(!is_800k_) {
// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
// to sanity.
@ -70,12 +70,12 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
// MARK: - Control input/output.
void DoubleDensityDrive::set_enabled(bool enabled) {
void DoubleDensityDrive::set_enabled(const bool enabled) {
// Disabling a drive also stops its motor.
if(!enabled) set_motor_on(false);
}
void DoubleDensityDrive::set_control_lines(int lines) {
void DoubleDensityDrive::set_control_lines(const int lines) {
const auto old_state = control_state_;
control_state_ = lines;

@ -13,36 +13,36 @@
namespace Apple::Macintosh {
class DoubleDensityDrive: public IWMDrive {
public:
DoubleDensityDrive(int input_clock_rate, bool is_800k);
public:
DoubleDensityDrive(int input_clock_rate, bool is_800k);
/*!
@returns @c true if this is an 800kb drive; @c false otherwise.
*/
bool is_800k() const {
return is_800k_;
}
/*!
@returns @c true if this is an 800kb drive; @c false otherwise.
*/
bool is_800k() const {
return is_800k_;
}
/*!
Sets the current rotation speed of this drive only if it is a 400kb drive.
800kb drives select their own rotation speed based on head position,
and ignore this input.
*/
void set_rotation_speed(float revolutions_per_minute);
/*!
Sets the current rotation speed of this drive only if it is a 400kb drive.
800kb drives select their own rotation speed based on head position,
and ignore this input.
*/
void set_rotation_speed(float revolutions_per_minute);
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) final;
void did_set_disk(bool) final;
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) final;
void did_set_disk(bool) final;
const bool is_800k_;
bool has_new_disk_ = false;
int control_state_ = 0;
int step_direction_ = 1;
const bool is_800k_;
bool has_new_disk_ = false;
int control_state_ = 0;
int step_direction_ = 1;
};
}