1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-14 03:37:04 +00:00

Begin a reformatting of components.

This commit is contained in:
Thomas Harte 2024-11-29 22:43:54 -05:00
parent 86fa8da8c5
commit 088bc14b11
25 changed files with 2052 additions and 1973 deletions

View File

@ -17,7 +17,7 @@ Log::Logger<Log::Source::WDFDC> logger;
using namespace WD;
WD1770::WD1770(Personality p) :
WD1770::WD1770(const Personality p) :
Storage::Disk::MFMController(8000000),
personality_(p),
interesting_event_mask_(int(Event1770::Command)) {
@ -25,7 +25,7 @@ WD1770::WD1770(Personality p) :
posit_event(int(Event1770::Command));
}
void WD1770::write(int address, uint8_t value) {
void WD1770::write(const int address, const uint8_t value) {
switch(address&3) {
case 0: {
if((value&0xf0) == 0xd0) {
@ -56,7 +56,7 @@ void WD1770::write(int address, uint8_t value) {
}
}
uint8_t WD1770::read(int address) {
uint8_t WD1770::read(const int address) {
switch(address&3) {
default: {
update_status([] (Status &status) {
@ -177,7 +177,7 @@ void WD1770::run_for(const Cycles cycles) {
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(int new_event_type) {
void WD1770::posit_event(const int new_event_type) {
if(new_event_type == int(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
@ -809,7 +809,7 @@ void WD1770::posit_event(int new_event_type) {
END_SECTION()
}
void WD1770::update_status(std::function<void(Status &)> updater) {
void WD1770::update_status(const std::function<void(Status &)> updater) {
const Status old_status = status_;
if(delegate_) {
@ -827,7 +827,7 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
void WD1770::set_head_load_request(bool) {}
void WD1770::set_motor_on(bool) {}
void WD1770::set_head_loaded(bool head_loaded) {
void WD1770::set_head_loaded(const bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}

View File

@ -17,126 +17,125 @@ namespace WD {
WD1770, WD1772, FDC1773 and FDC1793.
*/
class WD1770: public Storage::Disk::MFMController {
public:
enum Personality {
P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable
P1772, // as per the 1770, with different stepping rates
P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic
P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
};
public:
enum Personality {
P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable
P1772, // as per the 1770, with different stepping rates
P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic
P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
};
/*!
Constructs an instance of the drive controller that behaves according to personality @c p.
@param p The type of controller to emulate.
*/
WD1770(Personality p);
virtual ~WD1770() = default;
/*!
Constructs an instance of the drive controller that behaves according to the specified personality.
*/
WD1770(Personality);
virtual ~WD1770() = default;
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
using Storage::Disk::MFMController::set_is_double_density;
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
using Storage::Disk::MFMController::set_is_double_density;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
enum Flag: uint8_t {
NotReady = 0x80, // 0x80
MotorOn = 0x80,
WriteProtect = 0x40, // 0x40
RecordType = 0x20, // 0x20
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10, // 0x10
SeekError = 0x10,
CRCError = 0x08, // 0x08
LostData = 0x04, // 0x04
TrackZero = 0x04,
DataRequest = 0x02, // 0x02
Index = 0x02,
Busy = 0x01 // 0x01
};
enum Flag: uint8_t {
NotReady = 0x80, // 0x80
MotorOn = 0x80,
WriteProtect = 0x40, // 0x40
RecordType = 0x20, // 0x20
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10, // 0x10
SeekError = 0x10,
CRCError = 0x08, // 0x08
LostData = 0x04, // 0x04
TrackZero = 0x04,
DataRequest = 0x02, // 0x02
Index = 0x02,
Busy = 0x01 // 0x01
};
/// @returns The current value of the IRQ line output.
inline bool get_interrupt_request_line() const { return status_.interrupt_request; }
/// @returns The current value of the IRQ line output.
inline bool get_interrupt_request_line() const { return status_.interrupt_request; }
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() const { return status_.data_request; }
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() const { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
ClockingHint::Preference preferred_clocking() const final;
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded() const;
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded() const;
private:
const Personality personality_;
bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
bool has_head_load_line() const { return (personality_ == P1793 ); }
private:
const Personality personality_;
bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
bool has_head_load_line() const { return (personality_ == P1793 ); }
struct Status {
bool write_protect = false;
bool record_type = false;
bool spin_up = false;
bool record_not_found = false;
bool crc_error = false;
bool seek_error = false;
bool lost_data = false;
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
bool track_zero = false;
enum {
One, Two, Three
} type = One;
} status_;
uint8_t track_;
uint8_t sector_;
uint8_t data_;
uint8_t command_;
struct Status {
bool write_protect = false;
bool record_type = false;
bool spin_up = false;
bool record_not_found = false;
bool crc_error = false;
bool seek_error = false;
bool lost_data = false;
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
bool track_zero = false;
enum {
One, Two, Three
} type = One;
} status_;
uint8_t track_;
uint8_t sector_;
uint8_t data_;
uint8_t command_;
int index_hole_count_;
int index_hole_count_target_ = -1;
int distance_into_section_;
int index_hole_count_;
int index_hole_count_target_ = -1;
int distance_into_section_;
int step_direction_;
void update_status(std::function<void(Status &)> updater);
int step_direction_;
void update_status(std::function<void(Status &)> updater);
// Events
enum Event1770: int {
Command = (1 << 3), // Indicates receipt of a new command.
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
};
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
Cycles::IntType delay_time_ = 0;
// Events
enum Event1770: int {
Command = (1 << 3), // Indicates receipt of a new command.
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
};
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
Cycles::IntType delay_time_ = 0;
// ID buffer
uint8_t header_[6];
// ID buffer
uint8_t header_[6];
// 1793 head-loading logic
bool head_is_loaded_ = false;
// 1793 head-loading logic
bool head_is_loaded_ = false;
// delegate
Delegate *delegate_ = nullptr;
// delegate
Delegate *delegate_ = nullptr;
};
}

View File

@ -21,7 +21,7 @@ Log::Logger<Log::Source::NCR5380> logger;
using namespace NCR::NCR5380;
using SCSI::Line;
NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
NCR5380::NCR5380(SCSI::Bus &bus, const int clock_rate) :
bus_(bus),
clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
@ -33,7 +33,7 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
(void)expected_phase_;
}
void NCR5380::write(int address, uint8_t value, bool) {
void NCR5380::write(const int address, const uint8_t value, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Set current SCSI bus state to %02x", value);
@ -101,11 +101,11 @@ void NCR5380::write(int address, uint8_t value, bool) {
update_control_output();
break;
case 3: {
case 3:
logger.info().append("[3] Set target command: %02x", value);
target_command_ = value;
update_control_output();
} break;
break;
case 4:
logger.info().append("[4] Set select enabled: %02x", value);
@ -143,7 +143,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
}
}
uint8_t NCR5380::read(int address, bool) {
uint8_t NCR5380::read(const int address, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Get current SCSI bus state: %02x", (bus_.get_state() & 0xff));
@ -154,7 +154,10 @@ uint8_t NCR5380::read(int address, bool) {
return uint8_t(bus_.get_state());
case 1:
logger.info().append("[1] Initiator command register get: %c%c", arbitration_in_progress_ ? 'p' : '-', lost_arbitration_ ? 'l' : '-');
logger.info().append(
"[1] Initiator command register get: %c%c",
arbitration_in_progress_ ? 'p' : '-',
lost_arbitration_ ? 'l' : '-');
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
@ -239,7 +242,7 @@ void NCR5380::update_control_output() {
}
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
void NCR5380::scsi_bus_did_change(SCSI::Bus *, const SCSI::BusState new_state, const double time_since_change) {
/*
When connected as an Initiator with DMA Mode True,
if the phase lines I//O, C//D, and /MSG do not match the
@ -333,7 +336,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
}
}
void NCR5380::set_execution_state(ExecutionState state) {
void NCR5380::set_execution_state(const ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}
@ -357,7 +360,7 @@ uint8_t NCR5380::dma_acknowledge() {
return bus_state;
}
void NCR5380::dma_acknowledge(uint8_t value) {
void NCR5380::dma_acknowledge(const uint8_t value) {
data_bus_ = value;
dma_acknowledge_ = true;

View File

@ -19,69 +19,69 @@ namespace NCR::NCR5380 {
Models the NCR 5380, a SCSI interface chip.
*/
class NCR5380 final: public SCSI::Bus::Observer {
public:
NCR5380(SCSI::Bus &bus, int clock_rate);
public:
NCR5380(SCSI::Bus &, int clock_rate);
/*! Writes @c value to @c address. */
void write(int address, uint8_t value, bool dma_acknowledge = false);
/*! Writes @c value to @c address. */
void write(int address, uint8_t value, bool dma_acknowledge = false);
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
/*! @returns The SCSI ID assigned to this device. */
size_t scsi_id();
/*! @returns The SCSI ID assigned to this device. */
size_t scsi_id();
/*! @return @c true if DMA request is active; @c false otherwise. */
bool dma_request();
/*! @return @c true if DMA request is active; @c false otherwise. */
bool dma_request();
/*! Signals DMA acknowledge with a simultaneous read. */
uint8_t dma_acknowledge();
/*! Signals DMA acknowledge with a simultaneous read. */
uint8_t dma_acknowledge();
/*! Signals DMA acknowledge with a simultaneous write. */
void dma_acknowledge(uint8_t);
/*! Signals DMA acknowledge with a simultaneous write. */
void dma_acknowledge(uint8_t);
private:
SCSI::Bus &bus_;
private:
SCSI::Bus &bus_;
const int clock_rate_;
size_t device_id_;
const int clock_rate_;
size_t device_id_;
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
uint8_t mode_ = 0xff;
uint8_t initiator_command_ = 0xff;
uint8_t data_bus_ = 0xff;
uint8_t target_command_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
bool end_of_dma_ = false;
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
uint8_t mode_ = 0xff;
uint8_t initiator_command_ = 0xff;
uint8_t data_bus_ = 0xff;
uint8_t target_command_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
bool end_of_dma_ = false;
bool irq_ = false;
bool phase_mismatch_ = false;
bool irq_ = false;
bool phase_mismatch_ = false;
enum class ExecutionState {
None,
WaitingForBusy,
WatchingBusy,
PerformingDMA,
} state_ = ExecutionState::None;
enum class DMAOperation {
Ready,
Send,
TargetReceive,
InitiatorReceive
} dma_operation_ = DMAOperation::Ready;
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
enum class ExecutionState {
None,
WaitingForBusy,
WatchingBusy,
PerformingDMA,
} state_ = ExecutionState::None;
enum class DMAOperation {
Ready,
Send,
TargetReceive,
InitiatorReceive
} dma_operation_ = DMAOperation::Ready;
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
void set_execution_state(ExecutionState state);
void set_execution_state(ExecutionState state);
SCSI::BusState target_output() const;
void update_control_output();
SCSI::BusState target_output() const;
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
bool phase_matches() const;
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
bool phase_matches() const;
};
}

View File

@ -31,26 +31,26 @@ enum Line {
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
*/
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input([[maybe_unused]] Port port) {
return 0xff;
}
public:
/// Requests the current input value of the named port from the port handler.
uint8_t get_port_input(Port) {
return 0xff;
}
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current output value of the named oprt and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output(Port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
/// Sets the current logical output level for the named line on the specified port.
void set_control_line_output(Port, Line, [[maybe_unused]] bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for([[maybe_unused]] HalfCycles duration) {}
/// Provides a measure of time elapsed between other calls.
void run_for(HalfCycles) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
};
/*!
@ -58,21 +58,21 @@ class PortHandler {
a virtual level of indirection for receiving changes to the interrupt line.
*/
class IRQDelegatePortHandler: public PortHandler {
public:
class Delegate {
public:
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
};
public:
class Delegate {
public:
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
};
/// Sets the delegate that will receive notification of changes in the interrupt line.
void set_interrupt_delegate(Delegate *delegate);
/// Sets the delegate that will receive notification of changes in the interrupt line.
void set_interrupt_delegate(Delegate *);
/// Overrides @c PortHandler::set_interrupt_status, notifying the delegate if one is set.
void set_interrupt_status(bool new_status);
/// Overrides @c PortHandler::set_interrupt_status, notifying the delegate if one is set.
void set_interrupt_status(bool);
private:
Delegate *delegate_ = nullptr;
private:
Delegate *delegate_ = nullptr;
};
/*!
@ -87,53 +87,53 @@ class IRQDelegatePortHandler: public PortHandler {
implementing bus communications as required.
*/
template <class BusHandlerT> class MOS6522: public MOS6522Storage {
public:
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
public:
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! 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);
/*! @returns the bus handler. */
BusHandlerT &bus_handler();
/*! @returns the bus handler. */
BusHandlerT &bus_handler();
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
/// Sets the input value of the named line and port.
void set_control_line_input(Port, Line, bool value);
/// Runs for a specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Runs for a specified number of half cycles.
void run_for(const HalfCycles);
/// Runs for a specified number of cycles.
void run_for(const Cycles cycles);
/// Runs for a specified number of cycles.
void run_for(const Cycles);
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line() const;
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line() const;
/// Updates the port handler to the current time and then requests that it flush.
void flush();
/// Updates the port handler to the current time and then requests that it flush.
void flush();
private:
void do_phase1();
void do_phase2();
void shift_in();
void shift_out();
private:
void do_phase1();
void do_phase2();
void shift_in();
void shift_out();
BusHandlerT &bus_handler_;
HalfCycles time_since_bus_handler_call_;
BusHandlerT &bus_handler_;
HalfCycles time_since_bus_handler_call_;
void access(int address);
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
inline void reevaluate_interrupts();
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
void evaluate_port_b_output();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
void evaluate_port_b_output();
};
}

View File

@ -14,7 +14,7 @@
namespace MOS::MOS6522 {
template <typename T> void MOS6522<T>::access(int address) {
template <typename T> void MOS6522<T>::access(const int address) {
switch(address) {
case 0x0:
// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
@ -33,7 +33,7 @@ template <typename T> void MOS6522<T>::access(int address) {
}
}
template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
template <typename T> void MOS6522<T>::write(int address, const uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
@ -44,7 +44,10 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
evaluate_port_b_output();
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
registers_.interrupt_flags &= ~(
InterruptFlag::CB1ActiveEdge |
((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)
);
reevaluate_interrupts();
break;
case 0xf:
@ -58,7 +61,10 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
set_control_line_output(Port::A, Line::Two, LineState::Off);
}
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
registers_.interrupt_flags &= ~(
InterruptFlag::CA1ActiveEdge |
((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)
);
reevaluate_interrupts();
break;
@ -230,7 +236,12 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
return 0xff;
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
template <typename T> uint8_t MOS6522<T>::get_port_input(
const Port port,
const uint8_t output_mask,
uint8_t output,
const uint8_t timer_mask
) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
@ -252,7 +263,7 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() {
}
}
template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
template <typename T> void MOS6522<T>::set_control_line_input(const Port port, const Line line, const bool value) {
switch(line) {
case Line::One:
if(value != control_inputs_[port].lines[line]) {
@ -448,7 +459,8 @@ template <typename T> void MOS6522<T>::evaluate_cb2_output() {
}
}
template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
template <typename T>
void MOS6522<T>::set_control_line_output(const Port port, const Line line, const LineState value) {
if(port == Port::B && line == Line::Two) {
control_outputs_[port].lines[line] = value;
evaluate_cb2_output();

View File

@ -13,96 +13,96 @@
namespace MOS::MOS6522 {
class MOS6522Storage {
protected:
// Phase toggle
bool is_phase2_ = false;
protected:
// Phase toggle
bool is_phase2_ = false;
// The registers
struct Registers {
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
uint8_t output[2] = {0, 0};
uint8_t input[2] = {0, 0};
uint8_t data_direction[2] = {0, 0};
uint16_t timer[2] = {0, 0};
uint16_t timer_latch[2] = {0, 0};
uint16_t last_timer[2] = {0, 0};
int next_timer[2] = {-1, -1};
uint8_t shift = 0;
uint8_t auxiliary_control = 0;
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
// The registers
struct Registers {
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
uint8_t output[2] = {0, 0};
uint8_t input[2] = {0, 0};
uint8_t data_direction[2] = {0, 0};
uint16_t timer[2] = {0, 0};
uint16_t timer_latch[2] = {0, 0};
uint16_t last_timer[2] = {0, 0};
int next_timer[2] = {-1, -1};
uint8_t shift = 0;
uint8_t auxiliary_control = 0;
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_;
bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_;
// Control state.
struct {
bool lines[2] = {false, false};
} control_inputs_[2];
// Control state.
struct {
bool lines[2] = {false, false};
} control_inputs_[2];
enum class LineState {
On, Off, Input
};
struct {
LineState lines[2] = {LineState::Input, LineState::Input};
} control_outputs_[2];
enum class LineState {
On, Off, Input
};
struct {
LineState lines[2] = {LineState::Input, LineState::Input};
} control_outputs_[2];
enum class HandshakeMode {
None,
Handshake,
Pulse
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
enum class HandshakeMode {
None,
Handshake,
Pulse
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
bool timer_is_running_[2] = {false, false};
bool last_posted_interrupt_status_ = false;
int shift_bits_remaining_ = 8;
bool timer_is_running_[2] = {false, false};
bool last_posted_interrupt_status_ = false;
int shift_bits_remaining_ = 8;
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
CA1ActiveEdge = 1 << 1,
ShiftRegister = 1 << 2,
CB2ActiveEdge = 1 << 3,
CB1ActiveEdge = 1 << 4,
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
CA1ActiveEdge = 1 << 1,
ShiftRegister = 1 << 2,
CB2ActiveEdge = 1 << 3,
CB1ActiveEdge = 1 << 4,
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
enum class ShiftMode {
Disabled = 0,
InUnderT2 = 1,
InUnderPhase2 = 2,
InUnderCB1 = 3,
OutUnderT2FreeRunning = 4,
OutUnderT2 = 5,
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
bool timer1_is_controlling_pb7() const {
return registers_.auxiliary_control & 0x80;
}
bool timer1_is_continuous() const {
return registers_.auxiliary_control & 0x40;
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
int timer2_clock_decrement() const {
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
}
int timer2_pb6_decrement() const {
return (registers_.auxiliary_control >> 5)&1;
}
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool portb_is_latched() const {
return registers_.auxiliary_control & 0x02;
}
bool port1_is_latched() const {
return registers_.auxiliary_control & 0x01;
}
enum class ShiftMode {
Disabled = 0,
InUnderT2 = 1,
InUnderPhase2 = 2,
InUnderCB1 = 3,
OutUnderT2FreeRunning = 4,
OutUnderT2 = 5,
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
bool timer1_is_controlling_pb7() const {
return registers_.auxiliary_control & 0x80;
}
bool timer1_is_continuous() const {
return registers_.auxiliary_control & 0x40;
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
int timer2_clock_decrement() const {
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
}
int timer2_pb6_decrement() const {
return (registers_.auxiliary_control >> 5)&1;
}
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool portb_is_latched() const {
return registers_.auxiliary_control & 0x02;
}
bool port1_is_latched() const {
return registers_.auxiliary_control & 0x01;
}
};
}

View File

@ -10,7 +10,7 @@
using namespace MOS::MOS6522;
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *const delegate) {
delegate_ = delegate;
}

View File

@ -41,49 +41,49 @@ template <typename PortHandlerT, Personality personality> class MOS6526:
private MOS6526Storage,
private Serial::Line<true>::ReadDelegate
{
public:
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
serial_input.set_read_delegate(this);
}
MOS6526(const MOS6526 &) = delete;
public:
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
serial_input.set_read_delegate(this);
}
MOS6526(const MOS6526 &) = delete;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Pulses Phi2 to advance by the specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Pulses Phi2 to advance by the specified number of half cycles.
void run_for(const HalfCycles);
/// Pulses the TOD input the specified number of times.
void advance_tod(int count);
/// Pulses the TOD input the specified number of times.
void advance_tod(int count);
/// @returns @c true if the interrupt output is active, @c false otherwise.
bool get_interrupt_line();
/// @returns @c true if the interrupt output is active, @c false otherwise.
bool get_interrupt_line();
/// Sets the current state of the CNT input.
void set_cnt_input(bool active);
/// Sets the current state of the CNT input.
void set_cnt_input(bool);
/// Provides both the serial input bit and an additional source of CNT.
Serial::Line<true> serial_input;
/// Provides both the serial input bit and an additional source of CNT.
Serial::Line<true> serial_input;
/// Sets the current state of the FLG input.
void set_flag_input(bool low);
/// Sets the current state of the FLG input.
void set_flag_input(bool);
private:
PortHandlerT &port_handler_;
TODStorage<personality == Personality::P8250> tod_;
private:
PortHandlerT &port_handler_;
TODStorage<personality == Personality::P8250> tod_;
template <int port> void set_port_output();
template <int port> uint8_t get_port_input();
void update_interrupts();
void posit_interrupt(uint8_t mask);
void advance_counters(int);
template <int port> void set_port_output();
template <int port> uint8_t get_port_input();
void update_interrupts();
void posit_interrupt(uint8_t mask);
void advance_counters(int);
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
Log::Logger<Log::Source::MOS6526> log;
Log::Logger<Log::Source::MOS6526> log;
};
}

View File

@ -33,7 +33,7 @@ template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input()
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
void MOS6526<BusHandlerT, personality>::posit_interrupt(const uint8_t mask) {
if(!mask) {
return;
}
@ -54,13 +54,13 @@ bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
void MOS6526<BusHandlerT, personality>::set_cnt_input(const bool active) {
cnt_edge_ = active && !cnt_state_;
cnt_state_ = active;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
void MOS6526<BusHandlerT, personality>::set_flag_input(const bool low) {
if(low && !flag_state_) {
posit_interrupt(Interrupts::Flag);
}
@ -68,7 +68,7 @@ void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
void MOS6526<BusHandlerT, personality>::write(int address, const uint8_t value) {
address &= 0xf;
switch(address) {
// Port output.
@ -204,7 +204,7 @@ void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
void MOS6526<BusHandlerT, personality>::advance_tod(const int count) {
if(!count) return;
if(tod_.advance(count)) {
posit_interrupt(Interrupts::Alarm);
@ -212,7 +212,7 @@ void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
}
template <typename BusHandlerT, Personality personality>
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, const int bit) {
// TODO: post CNT change; might affect timer.
if(!shifter_is_output_) {

View File

@ -16,161 +16,161 @@
namespace MOS::MOS6526 {
class TODBase {
public:
template <bool is_timer2> void set_control(uint8_t value) {
if constexpr (is_timer2) {
write_alarm = value & 0x80;
} else {
is_50Hz = value & 0x80;
}
public:
template <bool is_timer2> void set_control(const uint8_t value) {
if constexpr (is_timer2) {
write_alarm = value & 0x80;
} else {
is_50Hz = value & 0x80;
}
}
protected:
bool write_alarm = false, is_50Hz = false;
protected:
bool write_alarm = false, is_50Hz = false;
};
template <bool is_8250> class TODStorage {};
template <> class TODStorage<false>: public TODBase {
private:
bool increment_ = true, latched_ = false;
int divider_ = 0;
std::array<uint8_t, 4> value_;
std::array<uint8_t, 4> latch_;
std::array<uint8_t, 4> alarm_;
private:
bool increment_ = true, latched_ = false;
int divider_ = 0;
std::array<uint8_t, 4> value_;
std::array<uint8_t, 4> latch_;
std::array<uint8_t, 4> alarm_;
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
void bcd_increment(uint8_t &value) {
++value;
if((value&0x0f) > 0x09) value += 0x06;
}
void bcd_increment(uint8_t &value) {
++value;
if((value&0x0f) > 0x09) value += 0x06;
}
public:
template <int byte> void write(uint8_t v) {
if(write_alarm) {
alarm_[byte] = v & masks[byte];
} else {
value_[byte] = v & masks[byte];
if constexpr (byte == 0) {
increment_ = true;
}
if constexpr (byte == 3) {
increment_ = false;
}
}
}
template <int byte> uint8_t read() {
if(latched_) {
const uint8_t result = latch_[byte];
if constexpr (byte == 0) {
latched_ = false;
}
return result;
}
if constexpr (byte == 3) {
latched_ = true;
latch_ = value_;
}
return value_[byte];
}
bool advance(int count) {
if(!increment_) {
return false;
}
while(count--) {
// Increment the pre-10ths divider.
++divider_;
if(divider_ < 5) continue;
if(divider_ < 6 && !is_50Hz) continue;
divider_ = 0;
// Increments 10ths of a second. One BCD digit.
++value_[0];
if(value_[0] < 10) {
continue;
}
// Increment seconds. Actual BCD needed from here onwards.
bcd_increment(value_[1]);
if(value_[1] != 60) {
continue;
}
value_[1] = 0;
// Increment minutes.
bcd_increment(value_[2]);
if(value_[2] != 60) {
continue;
}
value_[2] = 0;
// TODO: increment hours, keeping AM/PM separate?
}
return false; // TODO: test against alarm.
}
};
template <> class TODStorage<true>: public TODBase {
private:
uint32_t increment_mask_ = uint32_t(~0);
uint32_t latch_ = 0;
uint32_t value_ = 0;
uint32_t alarm_ = 0xff'ffff;
public:
template <int byte> void write(uint8_t v) {
if constexpr (byte == 3) {
return;
}
constexpr int shift = byte << 3;
// Write to either the alarm or the current value as directed;
// writing to any part of the current value other than the LSB
// pauses incrementing until the LSB is written.
const uint32_t mask = uint32_t(~(0xff << shift));
if(write_alarm) {
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
} else {
value_ = (value_ & mask) | uint32_t(v << shift);
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
}
}
template <int byte> uint8_t read() {
if constexpr (byte == 3) {
return 0xff; // Assumed. Just a guess.
}
constexpr int shift = byte << 3;
if constexpr (byte == 2) {
latch_ = value_ | 0xff00'0000;
}
const uint32_t source = latch_ ? latch_ : value_;
const uint8_t result = uint8_t((source >> shift) & 0xff);
public:
template <int byte> void write(const uint8_t v) {
if(write_alarm) {
alarm_[byte] = v & masks[byte];
} else {
value_[byte] = v & masks[byte];
if constexpr (byte == 0) {
latch_ = 0;
increment_ = true;
}
if constexpr (byte == 3) {
increment_ = false;
}
}
}
template <int byte> uint8_t read() {
if(latched_) {
const uint8_t result = latch_[byte];
if constexpr (byte == 0) {
latched_ = false;
}
return result;
}
bool advance(int count) {
// The 8250 uses a simple binary counter to replace the
// 6526's time-of-day clock. So this is easy.
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
const auto increment = uint32_t(count) & increment_mask_;
value_ = (value_ + increment) & 0xff'ffff;
return distance_to_alarm <= increment;
if constexpr (byte == 3) {
latched_ = true;
latch_ = value_;
}
return value_[byte];
}
bool advance(int count) {
if(!increment_) {
return false;
}
while(count--) {
// Increment the pre-10ths divider.
++divider_;
if(divider_ < 5) continue;
if(divider_ < 6 && !is_50Hz) continue;
divider_ = 0;
// Increments 10ths of a second. One BCD digit.
++value_[0];
if(value_[0] < 10) {
continue;
}
// Increment seconds. Actual BCD needed from here onwards.
bcd_increment(value_[1]);
if(value_[1] != 60) {
continue;
}
value_[1] = 0;
// Increment minutes.
bcd_increment(value_[2]);
if(value_[2] != 60) {
continue;
}
value_[2] = 0;
// TODO: increment hours, keeping AM/PM separate?
}
return false; // TODO: test against alarm.
}
};
template <> class TODStorage<true>: public TODBase {
private:
uint32_t increment_mask_ = uint32_t(~0);
uint32_t latch_ = 0;
uint32_t value_ = 0;
uint32_t alarm_ = 0xff'ffff;
public:
template <int byte> void write(uint8_t v) {
if constexpr (byte == 3) {
return;
}
constexpr int shift = byte << 3;
// Write to either the alarm or the current value as directed;
// writing to any part of the current value other than the LSB
// pauses incrementing until the LSB is written.
const uint32_t mask = uint32_t(~(0xff << shift));
if(write_alarm) {
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
} else {
value_ = (value_ & mask) | uint32_t(v << shift);
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
}
}
template <int byte> uint8_t read() {
if constexpr (byte == 3) {
return 0xff; // Assumed. Just a guess.
}
constexpr int shift = byte << 3;
if constexpr (byte == 2) {
latch_ = value_ | 0xff00'0000;
}
const uint32_t source = latch_ ? latch_ : value_;
const uint8_t result = uint8_t((source >> shift) & 0xff);
if constexpr (byte == 0) {
latch_ = 0;
}
return result;
}
bool advance(int count) {
// The 8250 uses a simple binary counter to replace the
// 6526's time-of-day clock. So this is easy.
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
const auto increment = uint32_t(count) & increment_mask_;
value_ = (value_ + increment) & 0xff'ffff;
return distance_to_alarm <= increment;
}
};
struct MOS6526Storage {
@ -306,23 +306,23 @@ struct MOS6526Storage {
return should_reload;
}
private:
int pending = 0;
private:
int pending = 0;
static constexpr int ReloadInOne = 1 << 0;
static constexpr int ReloadNow = 1 << 1;
static constexpr int ReloadInOne = 1 << 0;
static constexpr int ReloadNow = 1 << 1;
static constexpr int OneShotInOne = 1 << 2;
static constexpr int OneShotNow = 1 << 3;
static constexpr int OneShotInOne = 1 << 2;
static constexpr int OneShotNow = 1 << 3;
static constexpr int ApplyClockInTwo = 1 << 4;
static constexpr int ApplyClockInOne = 1 << 5;
static constexpr int ApplyClockNow = 1 << 6;
static constexpr int ApplyClockInTwo = 1 << 4;
static constexpr int ApplyClockInOne = 1 << 5;
static constexpr int ApplyClockNow = 1 << 6;
static constexpr int TestInputInOne = 1 << 7;
static constexpr int TestInputNow = 1 << 8;
static constexpr int TestInputInOne = 1 << 7;
static constexpr int TestInputNow = 1 << 8;
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
} counter_[2];
static constexpr int InterruptInOne = 1 << 0;

View File

@ -27,163 +27,176 @@ namespace MOS {
implementing bus communications as required.
*/
template <class T> class MOS6532 {
public:
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
public:
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
inline void write(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
case 0x00: case 0x02:
port_[decodedAddress / 2].output = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
set_port_did_change(decodedAddress / 2);
break;
case 0x01: case 0x03:
port_[decodedAddress / 2].output_mask = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
set_port_did_change(decodedAddress / 2);
break;
inline void write(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
case 0x00: case 0x02:
port_[decodedAddress / 2].output = value;
static_cast<T *>(this)->set_port_output(
decodedAddress / 2,
port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask
);
set_port_did_change(decodedAddress / 2);
break;
case 0x01: case 0x03:
port_[decodedAddress / 2].output_mask = value;
static_cast<T *>(this)->set_port_output(
decodedAddress / 2, port_[decodedAddress/2].output,
port_[decodedAddress / 2].output_mask
);
set_port_did_change(decodedAddress / 2);
break;
// The timer and edge detect control
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = (unsigned(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
} else {
a7_interrupt_.enabled = !!(address&0x2);
a7_interrupt_.active_on_positive = !!(address & 0x01);
}
break;
}
}
inline uint8_t read(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
case 0x00: case 0x02: {
const int port = decodedAddress / 2;
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
}
break;
case 0x01: case 0x03:
return port_[decodedAddress / 2].output_mask;
break;
// Timer and interrupt control
case 0x04: case 0x06: {
uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
// The timer and edge detect control
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift =
(decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = (unsigned(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
if(timer_.activeShift != timer_.writtenShift) {
unsigned int shift = timer_.writtenShift - timer_.activeShift;
timer_.value = (timer_.value << shift) | ((1 << shift) - 1);
timer_.activeShift = timer_.writtenShift;
}
return value;
} else {
a7_interrupt_.enabled = !!(address&0x2);
a7_interrupt_.active_on_positive = !!(address & 0x01);
}
break;
case 0x05: case 0x07: {
uint8_t value = interrupt_status_;
interrupt_status_ &= ~InterruptFlag::PA7;
evaluate_interrupts();
return value;
}
break;
}
return 0xff;
break;
}
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = unsigned(cycles.as_integral());
inline uint8_t read(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
case 0x00: case 0x02: {
const int port = decodedAddress / 2;
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
}
break;
case 0x01: case 0x03:
return port_[decodedAddress / 2].output_mask;
break;
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
timer_.value -= number_of_cycles;
} else {
number_of_cycles -= timer_.value;
timer_.value = (0x100 - number_of_cycles) & 0xff;
timer_.activeShift = 0;
interrupt_status_ |= InterruptFlag::Timer;
// Timer and interrupt control
case 0x04: case 0x06: {
uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
if(timer_.activeShift != timer_.writtenShift) {
unsigned int shift = timer_.writtenShift - timer_.activeShift;
timer_.value = (timer_.value << shift) | ((1 << shift) - 1);
timer_.activeShift = timer_.writtenShift;
}
return value;
}
break;
case 0x05: case 0x07: {
uint8_t value = interrupt_status_;
interrupt_status_ &= ~InterruptFlag::PA7;
evaluate_interrupts();
return value;
}
break;
}
MOS6532() {
timer_.value = unsigned((rand() & 0xff) << 10);
}
return 0xff;
}
inline void set_port_did_change(int port) {
if(!port) {
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
a7_interrupt_.last_port_value = new_port_a_value;
if(difference&0x80) {
if(
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
) {
interrupt_status_ |= InterruptFlag::PA7;
evaluate_interrupts();
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = unsigned(cycles.as_integral());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
timer_.value -= number_of_cycles;
} else {
number_of_cycles -= timer_.value;
timer_.value = (0x100 - number_of_cycles) & 0xff;
timer_.activeShift = 0;
interrupt_status_ |= InterruptFlag::Timer;
evaluate_interrupts();
}
}
MOS6532() {
timer_.value = unsigned((rand() & 0xff) << 10);
}
inline void set_port_did_change(int port) {
if(!port) {
uint8_t new_port_a_value =
(get_port_input(0) & ~port_[0].output_mask) |
(port_[0].output & port_[0].output_mask);
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
a7_interrupt_.last_port_value = new_port_a_value;
if(difference&0x80) {
if(
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
) {
interrupt_status_ |= InterruptFlag::PA7;
evaluate_interrupts();
}
}
}
}
inline bool get_inerrupt_line() const {
return interrupt_line_;
}
inline bool get_inerrupt_line() const {
return interrupt_line_;
}
private:
uint8_t ram_[128];
private:
uint8_t ram_[128];
struct {
unsigned int value;
unsigned int activeShift = 10, writtenShift = 10;
bool interrupt_enabled = false;
} timer_;
struct {
unsigned int value;
unsigned int activeShift = 10, writtenShift = 10;
bool interrupt_enabled = false;
} timer_;
struct {
bool enabled = false;
bool active_on_positive = false;
uint8_t last_port_value = 0;
} a7_interrupt_;
struct {
bool enabled = false;
bool active_on_positive = false;
uint8_t last_port_value = 0;
} a7_interrupt_;
struct {
uint8_t output_mask = 0, output = 0;
} port_[2];
struct {
uint8_t output_mask = 0, output = 0;
} port_[2];
uint8_t interrupt_status_ = 0;
enum InterruptFlag: uint8_t {
Timer = 0x80,
PA7 = 0x40
};
bool interrupt_line_ = false;
uint8_t interrupt_status_ = 0;
enum InterruptFlag: uint8_t {
Timer = 0x80,
PA7 = 0x40
};
bool interrupt_line_ = false;
// expected to be overridden
void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {}
uint8_t get_port_input([[maybe_unused]] int port) {
return 0xff;
}
void set_irq_line(bool) {}
// Expected to be overridden.
void set_port_output(
[[maybe_unused]] int port,
[[maybe_unused]] uint8_t value,
[[maybe_unused]] uint8_t output_mask
) {}
uint8_t get_port_input([[maybe_unused]] int port) {
return 0xff;
}
void set_irq_line(bool) {}
inline void evaluate_interrupts() {
interrupt_line_ =
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
set_irq_line(interrupt_line_);
}
inline void evaluate_interrupts() {
interrupt_line_ =
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
set_irq_line(interrupt_line_);
}
};
}

View File

@ -16,21 +16,21 @@ AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue)
audio_queue_(audio_queue) {}
void AudioGenerator::set_volume(uint8_t volume) {
void AudioGenerator::set_volume(const uint8_t volume) {
audio_queue_.enqueue([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
dc_offset_ = volume_ >> 4;
});
}
void AudioGenerator::set_control(int channel, uint8_t value) {
void AudioGenerator::set_control(const int channel, const uint8_t value) {
audio_queue_.enqueue([this, channel, value]() {
control_registers_[channel] = value;
});
}
// Source: VICE. Not original.
static uint8_t noise_pattern[] = {
constexpr uint8_t noise_pattern[] = {
0x07, 0x1e, 0x1e, 0x1c, 0x1c, 0x3e, 0x3c, 0x38, 0x78, 0xf8, 0x7c, 0x1e, 0x1f, 0x8f, 0x07, 0x07,
0xc1, 0xc0, 0xe0, 0xf1, 0xe0, 0xf0, 0xe3, 0xe1, 0xc0, 0xe0, 0x78, 0x7e, 0x3c, 0x38, 0xe0, 0xe1,
0xc3, 0xc3, 0x87, 0xc7, 0x07, 0x1e, 0x1c, 0x1f, 0x0e, 0x0e, 0x1e, 0x0e, 0x0f, 0x0f, 0xc3, 0xc3,
@ -97,17 +97,19 @@ static uint8_t noise_pattern[] = {
0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f,
};
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define shift(r) shift_registers_[r] = \
(shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
#define update(r, m, up) \
counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to
// happen is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No
// increment ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing
// regardless and testing against 0x80. The effect should be the same: loading with 0x7f means an output update every
// cycle, loading with 0x7e means every second cycle, etc.
template <Outputs::Speaker::Action action>
void AudioGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
void AudioGenerator::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
for(unsigned int c = 0; c < number_of_samples; ++c) {
update(0, 2, shift);
update(1, 1, shift);
@ -129,9 +131,12 @@ void AudioGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speak
));
}
}
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Mix>(
std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(
std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(
std::size_t, Outputs::Speaker::MonoSample *);
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = int16_t(range / 64);

View File

@ -18,30 +18,30 @@ namespace MOS::MOS6560 {
// audio state
class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, false> {
public:
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
public:
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
void set_volume(uint8_t);
void set_control(int channel, uint8_t value);
// For ::SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
// For ::SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0;
int16_t dc_offset_ = 0;
int16_t range_multiplier_ = 1;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0;
int16_t dc_offset_ = 0;
int16_t range_multiplier_ = 1;
};
struct BusHandler {
void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) {
void perform_read(uint16_t, uint8_t *const pixel_data, uint8_t *const colour_data) {
*pixel_data = 0xff;
*colour_data = 0xff;
}
@ -60,462 +60,495 @@ enum class OutputMode {
@c write and @c read provide register access.
*/
template <class BusHandler> class MOS6560 {
public:
MOS6560(BusHandler &bus_handler) :
bus_handler_(bus_handler),
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
// default to s-video output
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
public:
MOS6560(BusHandler &bus_handler) :
bus_handler_(bus_handler),
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
// default to s-video output
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
// default to NTSC
set_output_mode(OutputMode::NTSC);
// default to NTSC
set_output_mode(OutputMode::NTSC);
}
~MOS6560() {
audio_queue_.flush();
}
void set_clock_rate(const double clock_rate) {
speaker_.set_input_rate(float(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 4.0f;
}
void set_display_type(const Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
}
void set_high_frequency_cutoff(const float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
}
/*!
Sets the output mode to either PAL or NTSC.
*/
void set_output_mode(const OutputMode output_mode) {
output_mode_ = output_mode;
// Luminances are encoded trivially: on a 0-255 scale.
const uint8_t luminances[16] = {
0, 255, 64, 192,
128, 128, 64, 192,
128, 192, 128, 255,
192, 192, 128, 255
};
// Chrominances are encoded such that 0-128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green (NTSC) or blue/violet (PAL).
const uint8_t pal_chrominances[16] = {
255, 255, 90, 20,
96, 42, 8, 72,
84, 90, 90, 20,
96, 42, 8, 72,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 121, 57,
103, 42, 80, 16,
0, 9, 121, 57,
103, 42, 80, 16,
};
const uint8_t *chrominances;
Outputs::Display::Type display_type;
switch(output_mode) {
default:
chrominances = pal_chrominances;
display_type = Outputs::Display::Type::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 4;
timing_.final_line_increment_position =
timing_.cycles_per_line - timing_.line_counter_increment_offset;
timing_.lines_per_progressive_field = 312;
timing_.supports_interlacing = false;
break;
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::Display::Type::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 40;
timing_.final_line_increment_position = 58;
timing_.lines_per_progressive_field = 261;
timing_.supports_interlacing = true;
break;
}
~MOS6560() {
audio_queue_.flush();
crt_.set_new_display_type(timing_.cycles_per_line*4, display_type);
switch(output_mode) {
case OutputMode::PAL:
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(float(clock_rate / 4.0));
for(int c = 0; c < 16; c++) {
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
colour[0] = luminances[c];
colour[1] = chrominances[c];
}
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
/*!
Runs for cycles. Derr.
*/
inline void run_for(const Cycles cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
void set_high_frequency_cutoff(float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
}
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_;
/*!
Sets the output mode to either PAL or NTSC.
*/
void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode;
// Luminances are encoded trivially: on a 0-255 scale.
const uint8_t luminances[16] = {
0, 255, 64, 192,
128, 128, 64, 192,
128, 192, 128, 255,
192, 192, 128, 255
};
// Chrominances are encoded such that 0-128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green (NTSC) or blue/violet (PAL).
const uint8_t pal_chrominances[16] = {
255, 255, 90, 20,
96, 42, 8, 72,
84, 90, 90, 20,
96, 42, 8, 72,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 121, 57,
103, 42, 80, 16,
0, 9, 121, 57,
103, 42, 80, 16,
};
const uint8_t *chrominances;
Outputs::Display::Type display_type;
switch(output_mode) {
default:
chrominances = pal_chrominances;
display_type = Outputs::Display::Type::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 4;
timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
timing_.lines_per_progressive_field = 312;
timing_.supports_interlacing = false;
break;
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::Display::Type::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 40;
timing_.final_line_increment_position = 58;
timing_.lines_per_progressive_field = 261;
timing_.supports_interlacing = true;
break;
}
crt_.set_new_display_type(timing_.cycles_per_line*4, display_type);
switch(output_mode) {
case OutputMode::PAL:
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
for(int c = 0; c < 16; c++) {
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
colour[0] = luminances[c];
colour[1] = chrominances[c];
}
}
/*!
Runs for cycles. Derr.
*/
inline void run_for(const Cycles cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_;
// keep track of internal time relative to this scanline
++horizontal_counter_;
if(horizontal_counter_ == timing_.cycles_per_line) {
if(horizontal_drawing_latch_) {
++current_character_row_;
if(
(current_character_row_ == 16) ||
(current_character_row_ == 8 && !registers_.tall_characters)
) {
current_character_row_ = 0;
++current_row_;
}
pixel_line_cycle_ = -1;
columns_this_line_ = -1;
column_counter_ = -1;
}
horizontal_counter_ = 0;
if(output_mode_ == OutputMode::PAL) is_odd_line_ ^= true;
horizontal_drawing_latch_ = false;
++vertical_counter_;
if(vertical_counter_ == lines_this_field()) {
vertical_counter_ = 0;
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
current_row_ = 0;
rows_this_field_ = -1;
vertical_drawing_latch_ = false;
base_video_matrix_address_counter_ = 0;
// keep track of internal time relative to this scanline
++horizontal_counter_;
if(horizontal_counter_ == timing_.cycles_per_line) {
if(horizontal_drawing_latch_) {
++current_character_row_;
if(
(current_character_row_ == 16) ||
(current_character_row_ == 8 && !registers_.tall_characters)
) {
current_character_row_ = 0;
++current_row_;
}
pixel_line_cycle_ = -1;
columns_this_line_ = -1;
column_counter_ = -1;
}
// check for vertical starting events
vertical_drawing_latch_ |= registers_.first_row_location == (previous_vertical_counter >> 1);
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
horizontal_counter_ = 0;
if(output_mode_ == OutputMode::PAL) is_odd_line_ ^= true;
horizontal_drawing_latch_ = false;
if(pixel_line_cycle_ >= 0) ++pixel_line_cycle_;
switch(pixel_line_cycle_) {
case -1:
if(horizontal_drawing_latch_) {
pixel_line_cycle_ = 0;
video_matrix_address_counter_ = base_video_matrix_address_counter_;
}
++vertical_counter_;
if(vertical_counter_ == lines_this_field()) {
vertical_counter_ = 0;
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
current_row_ = 0;
rows_this_field_ = -1;
vertical_drawing_latch_ = false;
base_video_matrix_address_counter_ = 0;
current_character_row_ = 0;
}
}
// check for vertical starting events
vertical_drawing_latch_ |= registers_.first_row_location == (previous_vertical_counter >> 1);
horizontal_drawing_latch_ |=
vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
if(pixel_line_cycle_ >= 0) ++pixel_line_cycle_;
switch(pixel_line_cycle_) {
case -1:
if(horizontal_drawing_latch_) {
pixel_line_cycle_ = 0;
video_matrix_address_counter_ = base_video_matrix_address_counter_;
}
break;
case 1: columns_this_line_ = registers_.number_of_columns; break;
case 2: if(rows_this_field_ < 0) rows_this_field_ = registers_.number_of_rows; break;
case 3: if(current_row_ < rows_this_field_) column_counter_ = 0; break;
}
uint16_t fetch_address = 0x1c;
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
if(column_counter_&1) {
fetch_address =
registers_.character_cell_start_address +
(character_code_*(registers_.tall_characters ? 16 : 8)) +
current_character_row_;
} else {
fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
++video_matrix_address_counter_;
if(
(current_character_row_ == 15) ||
(current_character_row_ == 7 && !registers_.tall_characters)
) {
base_video_matrix_address_counter_ = video_matrix_address_counter_;
}
}
}
fetch_address &= 0x3fff;
uint8_t pixel_data;
uint8_t colour_data;
bus_handler_.perform_read(fetch_address, &pixel_data, &colour_data);
// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should
// divide the byte it is set for 3:1 and then continue as usual.
// determine output state; colour burst and sync timing are currently a guess
State this_state;
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state = State::ColourBurst;
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state = State::Sync;
else {
this_state = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ?
State::Pixels : State::Border;
}
// apply vertical sync
if(
(vertical_counter_ < 3 && is_odd_frame()) ||
(registers_.interlaced &&
(
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
(vertical_counter_ == 1) || (vertical_counter_ == 2) ||
(vertical_counter_ == 3 && horizontal_counter_ <= 32)
)
))
this_state = State::Sync;
// update the CRT
if(this_state != output_state_) {
switch(output_state_) {
case State::Sync:
crt_.output_sync(cycles_in_state_ * 4);
break;
case State::ColourBurst:
crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);
break;
case State::Border:
crt_.output_level<uint16_t>(cycles_in_state_ * 4, registers_.border_colour);
break;
case State::Pixels:
crt_.output_data(cycles_in_state_ * 4);
break;
case 1: columns_this_line_ = registers_.number_of_columns; break;
case 2: if(rows_this_field_ < 0) rows_this_field_ = registers_.number_of_rows; break;
case 3: if(current_row_ < rows_this_field_) column_counter_ = 0; break;
}
output_state_ = this_state;
cycles_in_state_ = 0;
uint16_t fetch_address = 0x1c;
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
} else {
fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
++video_matrix_address_counter_;
if(
(current_character_row_ == 15) ||
(current_character_row_ == 7 && !registers_.tall_characters)
) {
base_video_matrix_address_counter_ = video_matrix_address_counter_;
}
}
}
fetch_address &= 0x3fff;
uint8_t pixel_data;
uint8_t colour_data;
bus_handler_.perform_read(fetch_address, &pixel_data, &colour_data);
// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should
// divide the byte it is set for 3:1 and then continue as usual.
// determine output state; colour burst and sync timing are currently a guess
State this_state;
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state = State::ColourBurst;
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state = State::Sync;
else {
this_state = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
}
// apply vertical sync
if(
(vertical_counter_ < 3 && is_odd_frame()) ||
(registers_.interlaced &&
(
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
(vertical_counter_ == 1) || (vertical_counter_ == 2) ||
(vertical_counter_ == 3 && horizontal_counter_ <= 32)
)
))
this_state = State::Sync;
// update the CRT
if(this_state != output_state_) {
switch(output_state_) {
case State::Sync: crt_.output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: crt_.output_level<uint16_t>(cycles_in_state_ * 4, registers_.border_colour); break;
case State::Pixels: crt_.output_data(cycles_in_state_ * 4); break;
}
output_state_ = this_state;
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels) {
pixel_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(260));
}
}
++cycles_in_state_;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels) {
// TODO: palette changes can happen within half-characters; the below needs to be divided.
// Also: a perfect opportunity to rearrange this inner loop for no longer needing to be
// two parts with a cooperative owner?
if(column_counter_&1) {
character_value_ = pixel_data;
pixel_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(260));
}
}
++cycles_in_state_;
if(pixel_pointer) {
uint16_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8)) {
uint16_t colours[2];
if(registers_.invertedCells) {
colours[0] = cell_colour;
colours[1] = registers_.background_colour;
} else {
colours[0] = registers_.background_colour;
colours[1] = cell_colour;
}
pixel_pointer[0] = colours[(character_value_ >> 7)&1];
pixel_pointer[1] = colours[(character_value_ >> 6)&1];
pixel_pointer[2] = colours[(character_value_ >> 5)&1];
pixel_pointer[3] = colours[(character_value_ >> 4)&1];
pixel_pointer[4] = colours[(character_value_ >> 3)&1];
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
if(output_state_ == State::Pixels) {
// TODO: palette changes can happen within half-characters; the below needs to be divided.
// Also: a perfect opportunity to rearrange this inner loop for no longer needing to be
// two parts with a cooperative owner?
if(column_counter_&1) {
character_value_ = pixel_data;
if(pixel_pointer) {
const uint16_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8)) {
uint16_t colours[2];
if(registers_.invertedCells) {
colours[0] = cell_colour;
colours[1] = registers_.background_colour;
} else {
uint16_t colours[4] = {registers_.background_colour, registers_.border_colour, cell_colour, registers_.auxiliary_colour};
pixel_pointer[0] =
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
pixel_pointer[2] =
pixel_pointer[3] = colours[(character_value_ >> 4)&3];
pixel_pointer[4] =
pixel_pointer[5] = colours[(character_value_ >> 2)&3];
pixel_pointer[6] =
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
colours[0] = registers_.background_colour;
colours[1] = cell_colour;
}
pixel_pointer += 8;
pixel_pointer[0] = colours[(character_value_ >> 7)&1];
pixel_pointer[1] = colours[(character_value_ >> 6)&1];
pixel_pointer[2] = colours[(character_value_ >> 5)&1];
pixel_pointer[3] = colours[(character_value_ >> 4)&1];
pixel_pointer[4] = colours[(character_value_ >> 3)&1];
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
} else {
const uint16_t colours[4] = {
registers_.background_colour,
registers_.border_colour,
cell_colour,
registers_.auxiliary_colour
};
pixel_pointer[0] =
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
pixel_pointer[2] =
pixel_pointer[3] = colours[(character_value_ >> 4)&3];
pixel_pointer[4] =
pixel_pointer[5] = colours[(character_value_ >> 2)&3];
pixel_pointer[6] =
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
}
} else {
character_code_ = pixel_data;
character_colour_ = colour_data;
pixel_pointer += 8;
}
}
// Keep counting columns even if sync or the colour burst have interceded.
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
++column_counter_;
} else {
character_code_ = pixel_data;
character_colour_ = colour_data;
}
}
// Keep counting columns even if sync or the colour burst have interceded.
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
++column_counter_;
}
}
}
/*!
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void flush() {
update_audio();
audio_queue_.perform();
}
/*!
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void flush() {
update_audio();
audio_queue_.perform();
}
/*!
Writes to a 6560 register.
*/
void write(int address, uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address) {
case 0x0:
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
registers_.first_column_location = value & 0x7f;
break;
/*!
Writes to a 6560 register.
*/
void write(int address, const uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address) {
case 0x0:
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
registers_.first_column_location = value & 0x7f;
break;
case 0x1:
registers_.first_row_location = value;
break;
case 0x1:
registers_.first_row_location = value;
break;
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = uint16_t(
(registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)
);
break;
case 0x3:
registers_.number_of_rows = (value >> 1)&0x3f;
registers_.tall_characters = !!(value&0x01);
break;
case 0x3:
registers_.number_of_rows = (value >> 1)&0x3f;
registers_.tall_characters = !!(value&0x01);
break;
case 0x5:
registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
break;
case 0x5:
registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
registers_.video_matrix_start_address = uint16_t(
(registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)
);
break;
case 0xa:
case 0xb:
case 0xc:
case 0xd:
update_audio();
audio_generator_.set_control(address - 0xa, value);
break;
case 0xa:
case 0xb:
case 0xc:
case 0xd:
update_audio();
audio_generator_.set_control(address - 0xa, value);
break;
case 0xe:
update_audio();
registers_.auxiliary_colour = colours_[value >> 4];
audio_generator_.set_volume(value & 0xf);
break;
case 0xe:
update_audio();
registers_.auxiliary_colour = colours_[value >> 4];
audio_generator_.set_volume(value & 0xf);
break;
case 0xf: {
const uint16_t new_border_colour = colours_[value & 0x07];
if(new_border_colour != registers_.border_colour) {
if(output_state_ == State::Border) {
crt_.output_level<uint16_t>(cycles_in_state_ * 4, registers_.border_colour);
cycles_in_state_ = 0;
}
registers_.border_colour = new_border_colour;
case 0xf: {
const uint16_t new_border_colour = colours_[value & 0x07];
if(new_border_colour != registers_.border_colour) {
if(output_state_ == State::Border) {
crt_.output_level<uint16_t>(cycles_in_state_ * 4, registers_.border_colour);
cycles_in_state_ = 0;
}
registers_.invertedCells = !((value >> 3)&1);
registers_.background_colour = colours_[value >> 4];
registers_.border_colour = new_border_colour;
}
break;
// TODO: the lightpen, etc
default:
break;
registers_.invertedCells = !((value >> 3)&1);
registers_.background_colour = colours_[value >> 4];
}
break;
// TODO: the lightpen, etc
default:
break;
}
}
/*
Reads from a 6560 register.
*/
uint8_t read(int address) const {
address &= 0xf;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (raster_value() >> 1) & 0xff;
}
/*
Reads from a 6560 register.
*/
uint8_t read(int address) const {
address &= 0xf;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (raster_value() >> 1) & 0xff;
}
}
private:
BusHandler &bus_handler_;
Outputs::CRT::CRT crt_;
private:
BusHandler &bus_handler_;
Outputs::CRT::CRT crt_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
}
// register state
struct {
bool interlaced = false, tall_characters = false;
uint8_t first_column_location = 0, first_row_location = 0;
uint8_t number_of_columns = 0, number_of_rows = 0;
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
uint16_t border_colour = 0;
uint16_t background_colour = 0;
uint16_t auxiliary_colour = 0;
bool invertedCells = false;
uint8_t direct_values[16]{};
} registers_;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} output_state_ = State::Sync;
int cycles_in_state_ = 0;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
int lines_this_field() const {
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
}
int raster_value() const {
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
const int line = vertical_counter_ + bonus_line;
const int final_line = lines_this_field();
if(line < final_line)
return line;
if(is_odd_frame()) {
return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1;
} else {
return line % final_line;
}
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
}
bool is_odd_frame() const {
return is_odd_frame_ || !registers_.interlaced;
}
// register state
struct {
bool interlaced = false, tall_characters = false;
uint8_t first_column_location = 0, first_row_location = 0;
uint8_t number_of_columns = 0, number_of_rows = 0;
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
uint16_t border_colour = 0;
uint16_t background_colour = 0;
uint16_t auxiliary_colour = 0;
bool invertedCells = false;
// latches dictating start and length of drawing
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_ = 0, columns_this_line_ = 0;
uint8_t direct_values[16]{};
} registers_;
// current drawing position counter
int pixel_line_cycle_ = 0, column_counter_ = 0;
int current_row_ = 0;
uint16_t current_character_row_ = 0;
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} output_state_ = State::Sync;
int cycles_in_state_ = 0;
// data latched from the bus
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
int lines_this_field() const {
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
}
int raster_value() const {
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
const int line = vertical_counter_ + bonus_line;
const int final_line = lines_this_field();
bool is_odd_frame_ = false, is_odd_line_ = false;
if(line < final_line)
return line;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
if(is_odd_frame()) {
return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1;
} else {
return line % final_line;
}
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
}
bool is_odd_frame() const {
return is_odd_frame_ || !registers_.interlaced;
}
uint16_t *pixel_pointer = nullptr;
// latches dictating start and length of drawing
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_ = 0, columns_this_line_ = 0;
// current drawing position counter
int pixel_line_cycle_ = 0, column_counter_ = 0;
int current_row_ = 0;
uint16_t current_character_row_ = 0;
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
// data latched from the bus
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t *pixel_pointer = nullptr;
struct {
int cycles_per_line = 0;
int line_counter_increment_offset = 0;
int final_line_increment_position = 0;
int lines_per_progressive_field = 0;
bool supports_interlacing = 0;
} timing_;
OutputMode output_mode_ = OutputMode::NTSC;
struct {
int cycles_per_line = 0;
int line_counter_increment_offset = 0;
int final_line_increment_position = 0;
int lines_per_progressive_field = 0;
bool supports_interlacing = 0;
} timing_;
OutputMode output_mode_ = OutputMode::NTSC;
};
}

View File

@ -63,371 +63,376 @@ enum class CursorType {
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
template <class BusHandlerT, Personality personality, CursorType cursor_type> class CRTC6845 {
public:
CRTC6845(BusHandlerT &bus_handler) noexcept :
bus_handler_(bus_handler), status_(0) {}
public:
CRTC6845(BusHandlerT &bus_handler) noexcept :
bus_handler_(bus_handler), status_(0) {}
void select_register(uint8_t r) {
selected_register_ = r;
void select_register(uint8_t r) {
selected_register_ = r;
}
uint8_t get_status() {
switch(personality) {
case Personality::UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
case Personality::AMS40226: return get_register();
default: return 0xff;
}
return 0xff;
}
uint8_t get_status() {
switch(personality) {
case Personality::UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
case Personality::AMS40226: return get_register();
default: return 0xff;
}
return 0xff;
}
uint8_t get_register() {
if(selected_register_ == 31) status_ &= ~0x80;
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
uint8_t get_register() {
if(selected_register_ == 31) status_ &= ~0x80;
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_];
}
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_];
}
void set_register(const uint8_t value) {
static constexpr bool is_ega = is_egavga(personality);
void set_register(uint8_t value) {
static constexpr bool is_ega = is_egavga(personality);
const auto load_low = [value](uint16_t &target) {
target = (target & 0xff00) | value;
};
const auto load_high = [value](uint16_t &target) {
constexpr uint8_t mask = RefreshMask >> 8;
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
};
switch(selected_register_) {
case 0: layout_.horizontal.total = value; break;
case 1: layout_.horizontal.displayed = value; break;
case 2: layout_.horizontal.start_sync = value; break;
case 3:
layout_.horizontal.sync_width = value & 0xf;
layout_.vertical.sync_lines = value >> 4;
// TODO: vertical sync lines:
// "(0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)"
break;
case 4: layout_.vertical.total = value & 0x7f; break;
case 5: layout_.vertical.adjust = value & 0x1f; break;
case 6: layout_.vertical.displayed = value & 0x7f; break;
case 7: layout_.vertical.start_sync = value & 0x7f; break;
case 8:
switch(value & 3) {
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
case 0b01: layout_.interlace_mode_ = InterlaceMode::InterlaceSync; break;
case 0b11: layout_.interlace_mode_ = InterlaceMode::InterlaceSyncAndVideo; break;
}
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
if(personality != Personality::UM6845R && personality != Personality::MC6845) {
switch((value >> 4)&3) {
default: display_skew_mask_ = 1; break;
case 1: display_skew_mask_ = 2; break;
case 2: display_skew_mask_ = 4; break;
}
}
break;
case 9: layout_.vertical.end_row = value & 0x1f; break;
case 10:
layout_.vertical.start_cursor = value & 0x1f;
layout_.cursor_flags = (value >> 5) & 3;
break;
case 11:
layout_.vertical.end_cursor = value & 0x1f;
break;
case 12: load_high(layout_.start_address); break;
case 13: load_low(layout_.start_address); break;
case 14: load_high(layout_.cursor_address); break;
case 15: load_low(layout_.cursor_address); break;
}
static constexpr uint8_t masks[] = {
0xff, // Horizontal total.
0xff, // Horizontal display end.
0xff, // Start horizontal blank.
0xff, //
// EGA: b0b4: end of horizontal blank;
// b5b6: "Number of character clocks to delay start of display after Horizontal Total has been reached."
is_ega ? 0xff : 0x7f, // Start horizontal retrace.
0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f,
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
};
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
}
if(selected_register_ == 31 && personality == Personality::UM6845R) {
dummy_register_ = value;
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
status_ |= 0x40;
}
void run_for(Cycles cycles) {
auto cyles_remaining = cycles.as_integral();
while(cyles_remaining--) {
// Intention of code below: all conditionals are evaluated as if functional; they should be
// ordered so that whatever assignments result don't affect any subsequent conditionals
// Do bus work.
bus_state_.cursor = is_cursor_line_ &&
bus_state_.refresh_address == layout_.cursor_address;
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle(bus_state_);
//
// Shared, stateless signals.
//
const bool character_total_hit = character_counter_ == layout_.horizontal.total;
const uint8_t lines_per_row = layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? layout_.vertical.end_row & ~1 : layout_.vertical.end_row;
const bool row_end_hit = bus_state_.row_address == lines_per_row && !is_in_adjustment_period_;
const bool was_eof = eof_latched_;
const bool new_frame =
character_total_hit && was_eof &&
(
layout_.interlace_mode_ == InterlaceMode::Off ||
!odd_field_
);
//
// Horizontal.
//
// Update horizontal sync.
if(bus_state_.hsync) {
++hsync_counter_;
bus_state_.hsync = hsync_counter_ != layout_.horizontal.sync_width;
}
if(character_counter_ == layout_.horizontal.start_sync) {
hsync_counter_ = 0;
bus_state_.hsync = true;
}
// Check for end-of-line.
character_reset_history_ <<= 1;
if(character_total_hit) {
character_counter_ = 0;
character_is_visible_ = true;
character_reset_history_ |= 1;
} else {
character_counter_++;
}
// Check for end of visible characters.
if(character_counter_ == layout_.horizontal.displayed) {
character_is_visible_ = false;
}
//
// End-of-frame.
//
if(character_total_hit) {
if(was_eof) {
eof_latched_ = eom_latched_ = is_in_adjustment_period_ = false;
adjustment_counter_ = 0;
} else if(is_in_adjustment_period_) {
adjustment_counter_ = (adjustment_counter_ + 1) & 31;
}
}
if(character_reset_history_ & 2) {
eom_latched_ |= row_end_hit && row_counter_ == layout_.vertical.total;
}
if(character_reset_history_ & 4 && eom_latched_) {
// TODO: I don't believe the "add 1 for interlaced" test here is accurate; others represent the extra scanline as
// additional state, presumably because adjust total might be reprogrammed at any time.
const auto adjust_length = layout_.vertical.adjust + (layout_.interlace_mode_ != InterlaceMode::Off && odd_field_ ? 1 : 0);
is_in_adjustment_period_ |= adjustment_counter_ != adjust_length;
eof_latched_ |= adjustment_counter_ == adjust_length;
}
//
// Vertical.
//
// Sync.
const bool vsync_horizontal =
(!odd_field_ && !character_counter_) ||
(odd_field_ && character_counter_ == (layout_.horizontal.total >> 1));
if(vsync_horizontal) {
if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row_address) || bus_state_.vsync) {
bus_state_.vsync = true;
vsync_counter_ = (vsync_counter_ + 1) & 0xf;
} else {
vsync_counter_ = 0;
}
if(vsync_counter_ == layout_.vertical.sync_lines) {
bus_state_.vsync = false;
}
}
// Row address.
if(character_total_hit) {
if(was_eof) {
bus_state_.row_address = 0;
eof_latched_ = eom_latched_ = false;
} else if(row_end_hit) {
bus_state_.row_address = 0;
} else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) {
bus_state_.row_address = (bus_state_.row_address + 2) & ~1 & 31;
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 31;
}
}
// Row counter.
row_counter_ = next_row_counter_;
if(new_frame) {
next_row_counter_ = 0;
is_first_scanline_ = true;
} else {
next_row_counter_ = row_end_hit && character_total_hit ? (next_row_counter_ + 1) : next_row_counter_;
is_first_scanline_ &= !row_end_hit;
}
// Vertical display enable.
if(is_first_scanline_) {
line_is_visible_ = true;
odd_field_ = bus_state_.field_count & 1;
} else if(line_is_visible_ && row_counter_ == layout_.vertical.displayed) {
line_is_visible_ = false;
++bus_state_.field_count;
}
// Cursor.
if constexpr (cursor_type != CursorType::None) {
// Check for cursor enable.
is_cursor_line_ |= bus_state_.row_address == layout_.vertical.start_cursor;
is_cursor_line_ &= bus_state_.row_address != layout_.vertical.end_cursor;
switch(cursor_type) {
// MDA-style blinking.
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
case CursorType::MDA:
switch(layout_.cursor_flags) {
case 0b11: is_cursor_line_ &= (bus_state_.field_count & 8) < 3; break;
case 0b00: is_cursor_line_ &= bool(bus_state_.field_count & 8); break;
case 0b01: is_cursor_line_ = false; break;
case 0b10: is_cursor_line_ = true; break;
default: break;
}
break;
}
}
//
// Addressing.
//
if(new_frame) {
bus_state_.refresh_address = layout_.start_address;
} else if(character_total_hit) {
bus_state_.refresh_address = line_address_;
} else {
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask;
}
if(new_frame) {
line_address_ = layout_.start_address;
} else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) {
line_address_ = bus_state_.refresh_address;
}
}
}
const BusState &get_bus_state() const {
return bus_state_;
}
private:
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
BusHandlerT &bus_handler_;
BusState bus_state_;
enum class InterlaceMode {
Off,
InterlaceSync,
InterlaceSyncAndVideo,
const auto load_low = [value](uint16_t &target) {
target = (target & 0xff00) | value;
};
enum class BlinkMode {
// TODO.
const auto load_high = [value](uint16_t &target) {
constexpr uint8_t mask = RefreshMask >> 8;
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
};
switch(selected_register_) {
case 0: layout_.horizontal.total = value; break;
case 1: layout_.horizontal.displayed = value; break;
case 2: layout_.horizontal.start_sync = value; break;
case 3:
layout_.horizontal.sync_width = value & 0xf;
layout_.vertical.sync_lines = value >> 4;
// TODO: vertical sync lines:
// "(0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)"
break;
case 4: layout_.vertical.total = value & 0x7f; break;
case 5: layout_.vertical.adjust = value & 0x1f; break;
case 6: layout_.vertical.displayed = value & 0x7f; break;
case 7: layout_.vertical.start_sync = value & 0x7f; break;
case 8:
switch(value & 3) {
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
case 0b01: layout_.interlace_mode_ = InterlaceMode::InterlaceSync; break;
case 0b11: layout_.interlace_mode_ = InterlaceMode::InterlaceSyncAndVideo; break;
}
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
if(personality != Personality::UM6845R && personality != Personality::MC6845) {
switch((value >> 4)&3) {
default: display_skew_mask_ = 1; break;
case 1: display_skew_mask_ = 2; break;
case 2: display_skew_mask_ = 4; break;
}
}
break;
case 9: layout_.vertical.end_row = value & 0x1f; break;
case 10:
layout_.vertical.start_cursor = value & 0x1f;
layout_.cursor_flags = (value >> 5) & 3;
break;
case 11:
layout_.vertical.end_cursor = value & 0x1f;
break;
case 12: load_high(layout_.start_address); break;
case 13: load_low(layout_.start_address); break;
case 14: load_high(layout_.cursor_address); break;
case 15: load_low(layout_.cursor_address); break;
}
static constexpr uint8_t masks[] = {
0xff, // Horizontal total.
0xff, // Horizontal display end.
0xff, // Start horizontal blank.
0xff, //
// EGA: b0b4: end of horizontal blank;
// b5b6: "Number of character clocks to delay start of display after Horizontal Total has been reached."
is_ega ? 0xff : 0x7f, // Start horizontal retrace.
0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f,
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
};
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
}
if(selected_register_ == 31 && personality == Personality::UM6845R) {
dummy_register_ = value;
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
status_ |= 0x40;
}
void run_for(const Cycles cycles) {
auto cyles_remaining = cycles.as_integral();
while(cyles_remaining--) {
// Intention of code below: all conditionals are evaluated as if functional; they should be
// ordered so that whatever assignments result don't affect any subsequent conditionals
// Do bus work.
bus_state_.cursor = is_cursor_line_ &&
bus_state_.refresh_address == layout_.cursor_address;
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle(bus_state_);
//
// Shared, stateless signals.
//
const bool character_total_hit = character_counter_ == layout_.horizontal.total;
const uint8_t lines_per_row =
layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ?
layout_.vertical.end_row & ~1 : layout_.vertical.end_row;
const bool row_end_hit = bus_state_.row_address == lines_per_row && !is_in_adjustment_period_;
const bool was_eof = eof_latched_;
const bool new_frame =
character_total_hit && was_eof &&
(
layout_.interlace_mode_ == InterlaceMode::Off ||
!odd_field_
);
//
// Horizontal.
//
// Update horizontal sync.
if(bus_state_.hsync) {
++hsync_counter_;
bus_state_.hsync = hsync_counter_ != layout_.horizontal.sync_width;
}
if(character_counter_ == layout_.horizontal.start_sync) {
hsync_counter_ = 0;
bus_state_.hsync = true;
}
// Check for end-of-line.
character_reset_history_ <<= 1;
if(character_total_hit) {
character_counter_ = 0;
character_is_visible_ = true;
character_reset_history_ |= 1;
} else {
character_counter_++;
}
// Check for end of visible characters.
if(character_counter_ == layout_.horizontal.displayed) {
character_is_visible_ = false;
}
//
// End-of-frame.
//
if(character_total_hit) {
if(was_eof) {
eof_latched_ = eom_latched_ = is_in_adjustment_period_ = false;
adjustment_counter_ = 0;
} else if(is_in_adjustment_period_) {
adjustment_counter_ = (adjustment_counter_ + 1) & 31;
}
}
if(character_reset_history_ & 2) {
eom_latched_ |= row_end_hit && row_counter_ == layout_.vertical.total;
}
if(character_reset_history_ & 4 && eom_latched_) {
// TODO: I don't believe the "add 1 for interlaced" test here is accurate;
// others represent the extra scanline as additional state, presumably because
// adjust total might be reprogrammed at any time.
const auto adjust_length =
layout_.vertical.adjust + (layout_.interlace_mode_ != InterlaceMode::Off && odd_field_ ? 1 : 0);
is_in_adjustment_period_ |= adjustment_counter_ != adjust_length;
eof_latched_ |= adjustment_counter_ == adjust_length;
}
//
// Vertical.
//
// Sync.
const bool vsync_horizontal =
(!odd_field_ && !character_counter_) ||
(odd_field_ && character_counter_ == (layout_.horizontal.total >> 1));
if(vsync_horizontal) {
if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row_address) || bus_state_.vsync) {
bus_state_.vsync = true;
vsync_counter_ = (vsync_counter_ + 1) & 0xf;
} else {
vsync_counter_ = 0;
}
if(vsync_counter_ == layout_.vertical.sync_lines) {
bus_state_.vsync = false;
}
}
// Row address.
if(character_total_hit) {
if(was_eof) {
bus_state_.row_address = 0;
eof_latched_ = eom_latched_ = false;
} else if(row_end_hit) {
bus_state_.row_address = 0;
} else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) {
bus_state_.row_address = (bus_state_.row_address + 2) & ~1 & 31;
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 31;
}
}
// Row counter.
row_counter_ = next_row_counter_;
if(new_frame) {
next_row_counter_ = 0;
is_first_scanline_ = true;
} else {
next_row_counter_ = row_end_hit && character_total_hit ?
(next_row_counter_ + 1) : next_row_counter_;
is_first_scanline_ &= !row_end_hit;
}
// Vertical display enable.
if(is_first_scanline_) {
line_is_visible_ = true;
odd_field_ = bus_state_.field_count & 1;
} else if(line_is_visible_ && row_counter_ == layout_.vertical.displayed) {
line_is_visible_ = false;
++bus_state_.field_count;
}
// Cursor.
if constexpr (cursor_type != CursorType::None) {
// Check for cursor enable.
is_cursor_line_ |= bus_state_.row_address == layout_.vertical.start_cursor;
is_cursor_line_ &= bus_state_.row_address != layout_.vertical.end_cursor;
switch(cursor_type) {
// MDA-style blinking.
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
case CursorType::MDA:
switch(layout_.cursor_flags) {
case 0b11: is_cursor_line_ &= (bus_state_.field_count & 8) < 3; break;
case 0b00: is_cursor_line_ &= bool(bus_state_.field_count & 8); break;
case 0b01: is_cursor_line_ = false; break;
case 0b10: is_cursor_line_ = true; break;
default: break;
}
break;
}
}
//
// Addressing.
//
if(new_frame) {
bus_state_.refresh_address = layout_.start_address;
} else if(character_total_hit) {
bus_state_.refresh_address = line_address_;
} else {
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask;
}
if(new_frame) {
line_address_ = layout_.start_address;
} else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) {
line_address_ = bus_state_.refresh_address;
}
}
}
const BusState &get_bus_state() const {
return bus_state_;
}
private:
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
BusHandlerT &bus_handler_;
BusState bus_state_;
enum class InterlaceMode {
Off,
InterlaceSync,
InterlaceSyncAndVideo,
};
enum class BlinkMode {
// TODO.
};
struct {
struct {
struct {
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_width;
} horizontal;
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_width;
} horizontal;
struct {
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_lines;
uint8_t adjust;
struct {
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_lines;
uint8_t adjust;
uint8_t end_row;
uint8_t start_cursor;
uint8_t end_cursor;
} vertical;
uint8_t end_row;
uint8_t start_cursor;
uint8_t end_cursor;
} vertical;
InterlaceMode interlace_mode_ = InterlaceMode::Off;
uint8_t end_row() const {
return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? vertical.end_row & ~1 : vertical.end_row;
}
InterlaceMode interlace_mode_ = InterlaceMode::Off;
uint8_t end_row() const {
return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? vertical.end_row & ~1 : vertical.end_row;
}
uint16_t start_address;
uint16_t cursor_address;
uint16_t light_pen_address;
uint8_t cursor_flags;
} layout_;
uint16_t start_address;
uint16_t cursor_address;
uint16_t light_pen_address;
uint8_t cursor_flags;
} layout_;
uint8_t registers_[18]{};
uint8_t dummy_register_ = 0;
int selected_register_ = 0;
uint8_t registers_[18]{};
uint8_t dummy_register_ = 0;
int selected_register_ = 0;
uint8_t character_counter_ = 0;
uint32_t character_reset_history_ = 0;
uint8_t row_counter_ = 0, next_row_counter_ = 0;
uint8_t adjustment_counter_ = 0;
uint8_t character_counter_ = 0;
uint32_t character_reset_history_ = 0;
uint8_t row_counter_ = 0, next_row_counter_ = 0;
uint8_t adjustment_counter_ = 0;
bool character_is_visible_ = false;
bool line_is_visible_ = false;
bool is_first_scanline_ = false;
bool is_cursor_line_ = false;
bool character_is_visible_ = false;
bool line_is_visible_ = false;
bool is_first_scanline_ = false;
bool is_cursor_line_ = false;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
uint16_t line_address_ = 0;
uint8_t status_ = 0;
uint16_t line_address_ = 0;
uint8_t status_ = 0;
int display_skew_mask_ = 1;
unsigned int character_is_visible_shifter_ = 0;
int display_skew_mask_ = 1;
unsigned int character_is_visible_shifter_ = 0;
bool eof_latched_ = false;
bool eom_latched_ = false;
uint16_t next_row_address_ = 0;
bool odd_field_ = false;
bool eof_latched_ = false;
bool eom_latched_ = false;
uint16_t next_row_address_ = 0;
bool odd_field_ = false;
};
}

View File

@ -21,7 +21,7 @@ ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
request_to_send.set_writer_clock_rate(transmit_clock_rate);
}
uint8_t ACIA::read(int address) {
uint8_t ACIA::read(const int address) {
if(address&1) {
overran_ = false;
received_data_ |= NoValueMask;
@ -46,7 +46,7 @@ void ACIA::reset() {
assert(!interrupt_line_);
}
void ACIA::write(int address, uint8_t value) {
void ACIA::write(const int address, const uint8_t value) {
if(address&1) {
next_transmission_ = value;
consider_transmission();
@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, const int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;
@ -172,7 +172,7 @@ bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
return true;
}
void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) {
void ACIA::set_interrupt_delegate(InterruptDelegate *const delegate) {
interrupt_delegate_ = delegate;
}

View File

@ -17,111 +17,111 @@
namespace Motorola::ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
/*!
Constructs a new instance of ACIA which will receive a transmission clock at a rate of
@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
*/
ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
/*!
Constructs a new instance of ACIA which will receive a transmission clock at a rate of
@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
*/
ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
/*!
Reads from the ACIA.
/*!
Reads from the ACIA.
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
uint8_t read(int address);
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
uint8_t read(int address);
/*!
Writes to the ACIA.
/*!
Writes to the ACIA.
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
void write(int address, uint8_t value);
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
void write(int address, uint8_t value);
/*!
Advances @c transmission_cycles in time, which should be
counted relative to the @c transmit_clock_rate.
*/
forceinline void run_for(HalfCycles transmission_cycles) {
if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
const auto write_data_time_remaining = transmit.write_data_time_remaining();
/*!
Advances @c transmission_cycles in time, which should be
counted relative to the @c transmit_clock_rate.
*/
forceinline void run_for(const HalfCycles transmission_cycles) {
if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
const auto write_data_time_remaining = transmit.write_data_time_remaining();
// There's at most one further byte available to enqueue, so a single 'if'
// rather than a 'while' is correct here. It's the responsibilit of the caller
// to ensure run_for lengths are appropriate for longer sequences.
if(transmission_cycles >= write_data_time_remaining) {
if(next_transmission_ != NoValueMask) {
transmit.advance_writer(write_data_time_remaining);
consider_transmission();
transmit.advance_writer(transmission_cycles - write_data_time_remaining);
} else {
transmit.advance_writer(transmission_cycles);
update_clocking_observer();
update_interrupt_line();
}
// There's at most one further byte available to enqueue, so a single 'if'
// rather than a 'while' is correct here. It's the responsibilit of the caller
// to ensure run_for lengths are appropriate for longer sequences.
if(transmission_cycles >= write_data_time_remaining) {
if(next_transmission_ != NoValueMask) {
transmit.advance_writer(write_data_time_remaining);
consider_transmission();
transmit.advance_writer(transmission_cycles - write_data_time_remaining);
} else {
transmit.advance_writer(transmission_cycles);
update_clocking_observer();
update_interrupt_line();
}
} else {
transmit.advance_writer(transmission_cycles);
}
}
}
bool get_interrupt_line() const;
void reset();
bool get_interrupt_line() const;
void reset();
// Input lines.
Serial::Line<false> receive;
Serial::Line<false> clear_to_send;
Serial::Line<false> data_carrier_detect;
// Input lines.
Serial::Line<false> receive;
Serial::Line<false> clear_to_send;
Serial::Line<false> data_carrier_detect;
// Output lines.
Serial::Line<false> transmit;
Serial::Line<false> request_to_send;
// Output lines.
Serial::Line<false> transmit;
Serial::Line<false> request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
private:
int divider_ = 1;
enum class Parity {
Even, Odd, None
} parity_ = Parity::None;
int data_bits_ = 7, stop_bits_ = 2;
private:
int divider_ = 1;
enum class Parity {
Even, Odd, None
} parity_ = Parity::None;
int data_bits_ = 7, stop_bits_ = 2;
static constexpr int NoValueMask = 0x100;
int next_transmission_ = NoValueMask;
int received_data_ = NoValueMask;
static constexpr int NoValueMask = 0x100;
int next_transmission_ = NoValueMask;
int received_data_ = NoValueMask;
int bits_received_ = 0;
int bits_incoming_ = 0;
bool overran_ = false;
int bits_received_ = 0;
int bits_incoming_ = 0;
bool overran_ = false;
void consider_transmission();
int expected_bits();
uint8_t parity(uint8_t value);
void consider_transmission();
int expected_bits();
uint8_t parity(uint8_t value);
bool receive_interrupt_enabled_ = false;
bool transmit_interrupt_enabled_ = false;
bool receive_interrupt_enabled_ = false;
bool transmit_interrupt_enabled_ = false;
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
bool interrupt_line_ = false;
void update_interrupt_line();
InterruptDelegate *interrupt_delegate_ = nullptr;
uint8_t get_status();
bool interrupt_line_ = false;
void update_interrupt_line();
InterruptDelegate *interrupt_delegate_ = nullptr;
uint8_t get_status();
};
}

View File

@ -13,77 +13,80 @@
namespace Intel::i8255 {
class PortHandler {
public:
void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
public:
void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
};
// TODO: Modes 1 and 2.
template <class T> class i8255 {
public:
i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
public:
i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
/*!
Stores the value @c value to the register at @c address. If this causes a change in 8255 output
then the PortHandler will be informed.
*/
void write(int address, uint8_t value) {
switch(address & 3) {
case 0:
if(!(control_ & 0x10)) {
// TODO: so what would output be when switching from input to output mode?
outputs_[0] = value; port_handler_.set_value(0, value);
}
break;
case 1:
if(!(control_ & 0x02)) {
outputs_[1] = value; port_handler_.set_value(1, value);
}
break;
case 2: outputs_[2] = value; port_handler_.set_value(2, value); break;
case 3:
if(value & 0x80) {
control_ = value;
} else {
if(value & 1) {
outputs_[2] |= 1 << ((value >> 1)&7);
} else {
outputs_[2] &= ~(1 << ((value >> 1)&7));
}
}
update_outputs();
break;
}
}
/*!
Obtains the current value for the register at @c address. If this provides a reading
of input then the PortHandler will be queried.
*/
uint8_t read(int address) {
switch(address & 3) {
case 0: return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
case 1: return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];
case 2: {
if(!(control_ & 0x09)) return outputs_[2];
uint8_t input = port_handler_.get_value(2);
return ((control_ & 0x01) ? (input & 0x0f) : (outputs_[2] & 0x0f)) | ((control_ & 0x08) ? (input & 0xf0) : (outputs_[2] & 0xf0));
/*!
Stores the value @c value to the register at @c address. If this causes a change in 8255 output
then the PortHandler will be informed.
*/
void write(const int address, const uint8_t value) {
switch(address & 3) {
case 0:
if(!(control_ & 0x10)) {
// TODO: so what would output be when switching from input to output mode?
outputs_[0] = value; port_handler_.set_value(0, value);
}
case 3: return control_;
break;
case 1:
if(!(control_ & 0x02)) {
outputs_[1] = value; port_handler_.set_value(1, value);
}
break;
case 2: outputs_[2] = value; port_handler_.set_value(2, value); break;
case 3:
if(value & 0x80) {
control_ = value;
} else {
if(value & 1) {
outputs_[2] |= 1 << ((value >> 1)&7);
} else {
outputs_[2] &= ~(1 << ((value >> 1)&7));
}
}
update_outputs();
break;
}
}
/*!
Obtains the current value for the register at @c address. If this provides a reading
of input then the PortHandler will be queried.
*/
uint8_t read(const int address) {
switch(address & 3) {
case 0: return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
case 1: return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];
case 2: {
if(!(control_ & 0x09)) return outputs_[2];
uint8_t input = port_handler_.get_value(2);
return ((control_ & 0x01) ?
(input & 0x0f) :
(outputs_[2] & 0x0f)) | ((control_ & 0x08) ?
(input & 0xf0) : (outputs_[2] & 0xf0));
}
return 0xff;
case 3: return control_;
}
return 0xff;
}
private:
void update_outputs() {
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}
private:
void update_outputs() {
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}
uint8_t control_;
uint8_t outputs_[3];
T &port_handler_;
uint8_t control_;
uint8_t outputs_[3];
T &port_handler_;
};
}

View File

@ -40,179 +40,179 @@ enum class Command {
};
class CommandDecoder {
public:
/// Add a byte to the current command.
void push_back(uint8_t byte) {
command_.push_back(byte);
public:
/// Add a byte to the current command.
void push_back(uint8_t byte) {
command_.push_back(byte);
}
/// Reset decoding.
void clear() {
command_.clear();
}
/// @returns @c true if an entire command has been received; @c false if further bytes are needed.
bool has_command() const {
if(!command_.size()) {
return false;
}
/// Reset decoding.
void clear() {
command_.clear();
}
static constexpr std::size_t required_lengths[32] = {
0, 0, 9, 3, 2, 9, 9, 2,
1, 9, 2, 0, 9, 6, 0, 3,
0, 9, 0, 0, 0, 0, 0, 0,
0, 9, 0, 0, 0, 9, 0, 0,
};
/// @returns @c true if an entire command has been received; @c false if further bytes are needed.
bool has_command() const {
if(!command_.size()) {
return command_.size() >= required_lengths[command_[0] & 0x1f];
}
/// @returns The command requested. Valid only if @c has_command() is @c true.
Command command() const {
const auto command = Command(command_[0] & 0x1f);
switch(command) {
case Command::ReadData: case Command::ReadDeletedData:
case Command::WriteData: case Command::WriteDeletedData:
case Command::ReadTrack: case Command::ReadID:
case Command::FormatTrack:
case Command::ScanLow: case Command::ScanLowOrEqual:
case Command::ScanHighOrEqual:
case Command::Recalibrate: case Command::Seek:
case Command::SenseInterruptStatus:
case Command::Specify: case Command::SenseDriveStatus:
return command;
default: return Command::Invalid;
}
}
//
// Commands that specify geometry; i.e.
//
// * ReadData;
// * ReadDeletedData;
// * WriteData;
// * WriteDeletedData;
// * ReadTrack;
// * ScanEqual;
// * ScanLowOrEqual;
// * ScanHighOrEqual.
//
/// @returns @c true if this command specifies geometry, in which case geomtry() is well-defined.
/// @c false otherwise.
bool has_geometry() const { return command_.size() == 9; }
struct Geometry {
uint8_t cylinder, head, sector, size, end_of_track;
};
Geometry geometry() const {
Geometry result;
result.cylinder = command_[2];
result.head = command_[3];
result.sector = command_[4];
result.size = command_[5];
result.end_of_track = command_[6];
return result;
}
//
// Commands that imply data access; i.e.
//
// * ReadData;
// * ReadDeletedData;
// * WriteData;
// * WriteDeletedData;
// * ReadTrack;
// * ReadID;
// * FormatTrack;
// * ScanLow;
// * ScanLowOrEqual;
// * ScanHighOrEqual.
//
/// @returns @c true if this command involves reading or writing data, in which case target() will be valid.
/// @c false otherwise.
bool is_access() const {
switch(command()) {
case Command::ReadData: case Command::ReadDeletedData:
case Command::WriteData: case Command::WriteDeletedData:
case Command::ReadTrack: case Command::ReadID:
case Command::FormatTrack:
case Command::ScanLow: case Command::ScanLowOrEqual:
case Command::ScanHighOrEqual:
return true;
default:
return false;
}
static constexpr std::size_t required_lengths[32] = {
0, 0, 9, 3, 2, 9, 9, 2,
1, 9, 2, 0, 9, 6, 0, 3,
0, 9, 0, 0, 0, 0, 0, 0,
0, 9, 0, 0, 0, 9, 0, 0,
};
return command_.size() >= required_lengths[command_[0] & 0x1f];
}
}
struct AccessTarget {
uint8_t drive, head;
bool mfm, skip_deleted;
};
AccessTarget target() const {
AccessTarget result;
result.drive = command_[1] & 0x03;
result.head = (command_[1] >> 2) & 0x01;
result.mfm = command_[0] & 0x40;
result.skip_deleted = command_[0] & 0x20;
return result;
}
uint8_t drive_head() const {
return command_[1] & 7;
}
/// @returns The command requested. Valid only if @c has_command() is @c true.
Command command() const {
const auto command = Command(command_[0] & 0x1f);
//
// Command::FormatTrack
//
switch(command) {
case Command::ReadData: case Command::ReadDeletedData:
case Command::WriteData: case Command::WriteDeletedData:
case Command::ReadTrack: case Command::ReadID:
case Command::FormatTrack:
case Command::ScanLow: case Command::ScanLowOrEqual:
case Command::ScanHighOrEqual:
case Command::Recalibrate: case Command::Seek:
case Command::SenseInterruptStatus:
case Command::Specify: case Command::SenseDriveStatus:
return command;
struct FormatSpecs {
uint8_t bytes_per_sector;
uint8_t sectors_per_track;
uint8_t gap3_length;
uint8_t filler;
};
FormatSpecs format_specs() const {
FormatSpecs result;
result.bytes_per_sector = command_[2];
result.sectors_per_track = command_[3];
result.gap3_length = command_[4];
result.filler = command_[5];
return result;
}
default: return Command::Invalid;
}
}
//
// Command::Seek
//
//
// Commands that specify geometry; i.e.
//
// * ReadData;
// * ReadDeletedData;
// * WriteData;
// * WriteDeletedData;
// * ReadTrack;
// * ScanEqual;
// * ScanLowOrEqual;
// * ScanHighOrEqual.
//
/// @returns The desired target track.
uint8_t seek_target() const {
return command_[2];
}
/// @returns @c true if this command specifies geometry, in which case geomtry() is well-defined.
/// @c false otherwise.
bool has_geometry() const { return command_.size() == 9; }
struct Geometry {
uint8_t cylinder, head, sector, size, end_of_track;
};
Geometry geometry() const {
Geometry result;
result.cylinder = command_[2];
result.head = command_[3];
result.sector = command_[4];
result.size = command_[5];
result.end_of_track = command_[6];
return result;
}
//
// Command::Specify
//
//
// Commands that imply data access; i.e.
//
// * ReadData;
// * ReadDeletedData;
// * WriteData;
// * WriteDeletedData;
// * ReadTrack;
// * ReadID;
// * FormatTrack;
// * ScanLow;
// * ScanLowOrEqual;
// * ScanHighOrEqual.
//
struct SpecifySpecs {
// The below are all in milliseconds.
uint8_t step_rate_time;
uint8_t head_unload_time;
uint8_t head_load_time;
bool use_dma;
};
SpecifySpecs specify_specs() const {
SpecifySpecs result;
result.step_rate_time = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
result.head_unload_time = uint8_t((command_[1] & 0x0f) << 4); // i.e. 16 to 240ms
result.head_load_time = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
result.use_dma = !(command_[2] & 1);
return result;
}
/// @returns @c true if this command involves reading or writing data, in which case target() will be valid.
/// @c false otherwise.
bool is_access() const {
switch(command()) {
case Command::ReadData: case Command::ReadDeletedData:
case Command::WriteData: case Command::WriteDeletedData:
case Command::ReadTrack: case Command::ReadID:
case Command::FormatTrack:
case Command::ScanLow: case Command::ScanLowOrEqual:
case Command::ScanHighOrEqual:
return true;
default:
return false;
}
}
struct AccessTarget {
uint8_t drive, head;
bool mfm, skip_deleted;
};
AccessTarget target() const {
AccessTarget result;
result.drive = command_[1] & 0x03;
result.head = (command_[1] >> 2) & 0x01;
result.mfm = command_[0] & 0x40;
result.skip_deleted = command_[0] & 0x20;
return result;
}
uint8_t drive_head() const {
return command_[1] & 7;
}
//
// Command::FormatTrack
//
struct FormatSpecs {
uint8_t bytes_per_sector;
uint8_t sectors_per_track;
uint8_t gap3_length;
uint8_t filler;
};
FormatSpecs format_specs() const {
FormatSpecs result;
result.bytes_per_sector = command_[2];
result.sectors_per_track = command_[3];
result.gap3_length = command_[4];
result.filler = command_[5];
return result;
}
//
// Command::Seek
//
/// @returns The desired target track.
uint8_t seek_target() const {
return command_[2];
}
//
// Command::Specify
//
struct SpecifySpecs {
// The below are all in milliseconds.
uint8_t step_rate_time;
uint8_t head_unload_time;
uint8_t head_load_time;
bool use_dma;
};
SpecifySpecs specify_specs() const {
SpecifySpecs result;
result.step_rate_time = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
result.head_unload_time = uint8_t((command_[1] & 0x0f) << 4); // i.e. 16 to 240ms
result.head_load_time = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
result.use_dma = !(command_[2] & 1);
return result;
}
private:
std::vector<uint8_t> command_;
private:
std::vector<uint8_t> command_;
};
}

View File

@ -14,49 +14,55 @@
namespace Intel::i8272 {
class Results {
public:
/// Serialises the response to Command::Invalid and Command::SenseInterruptStatus when no interrupt source was found.
void serialise_none() {
result_ = { 0x80 };
}
public:
/// Serialises the response to Command::Invalid and Command::SenseInterruptStatus when no interrupt source was found.
void serialise_none() {
result_ = { 0x80 };
}
/// Serialises the response to Command::SenseInterruptStatus for a found drive.
void serialise(const Status &status, uint8_t cylinder) {
result_ = { cylinder, status[0] };
}
/// Serialises the response to Command::SenseInterruptStatus for a found drive.
void serialise(const Status &status, const uint8_t cylinder) {
result_ = { cylinder, status[0] };
}
/// Serialises the seven-byte response to Command::SenseDriveStatus.
void serialise(uint8_t flags, uint8_t drive_side) {
result_ = { uint8_t(flags | drive_side) };
}
/// Serialises the seven-byte response to Command::SenseDriveStatus.
void serialise(const uint8_t flags, const uint8_t drive_side) {
result_ = { uint8_t(flags | drive_side) };
}
/// Serialises the response to:
///
/// * Command::ReadData;
/// * Command::ReadDeletedData;
/// * Command::WriteData;
/// * Command::WriteDeletedData;
/// * Command::ReadID;
/// * Command::ReadTrack;
/// * Command::FormatTrack;
/// * Command::ScanLow; and
/// * Command::ScanHighOrEqual.
void serialise(const Status &status, uint8_t cylinder, uint8_t head, uint8_t sector, uint8_t size) {
result_ = { size, sector, head, cylinder, status[2], status[1], status[0] };
}
/// Serialises the response to:
///
/// * Command::ReadData;
/// * Command::ReadDeletedData;
/// * Command::WriteData;
/// * Command::WriteDeletedData;
/// * Command::ReadID;
/// * Command::ReadTrack;
/// * Command::FormatTrack;
/// * Command::ScanLow; and
/// * Command::ScanHighOrEqual.
void serialise(
const Status &status,
const uint8_t cylinder,
const uint8_t head,
const uint8_t sector,
const uint8_t size
) {
result_ = { size, sector, head, cylinder, status[2], status[1], status[0] };
}
/// @returns @c true if all result bytes are exhausted; @c false otherwise.
bool empty() const { return result_.empty(); }
/// @returns @c true if all result bytes are exhausted; @c false otherwise.
bool empty() const { return result_.empty(); }
/// @returns The next byte of the result.
uint8_t next() {
const uint8_t next = result_.back();
result_.pop_back();
return next;
}
/// @returns The next byte of the result.
uint8_t next() {
const uint8_t next = result_.back();
result_.pop_back();
return next;
}
private:
std::vector<uint8_t> result_;
private:
std::vector<uint8_t> result_;
};
}

View File

@ -66,66 +66,66 @@ enum class Status3: uint8_t {
};
class Status {
public:
Status() {
reset();
public:
Status() {
reset();
}
void reset() {
main_status_ = 0;
set(MainStatus::DataReady, true);
status_[0] = status_[1] = status_[2] = 0;
}
/// @returns The main status register value.
uint8_t main() const {
return main_status_;
}
uint8_t operator [](const int index) const {
return status_[index];
}
//
// Flag setters.
//
void set(const MainStatus flag, const bool value) {
set(uint8_t(flag), value, main_status_);
}
void start_seek(const int drive) { main_status_ |= 1 << drive; }
void set(const Status0 flag) { set(uint8_t(flag), true, status_[0]); }
void set(const Status1 flag) { set(uint8_t(flag), true, status_[1]); }
void set(const Status2 flag) { set(uint8_t(flag), true, status_[2]); }
void set_status0(uint8_t value) { status_[0] = value; }
//
// Flag getters.
//
bool get(const MainStatus flag) { return main_status_ & uint8_t(flag); }
bool get(const Status2 flag) { return status_[2] & uint8_t(flag); }
/// Begin execution of whatever @c CommandDecoder currently describes, setting internal
/// state appropriately.
void begin(const CommandDecoder &command) {
set(MainStatus::DataReady, false);
set(MainStatus::CommandInProgress, true);
if(command.is_access()) {
status_[0] = command.drive_head();
}
}
void reset() {
main_status_ = 0;
set(MainStatus::DataReady, true);
status_[0] = status_[1] = status_[2] = 0;
private:
void set(const uint8_t flag, const bool value, uint8_t &target) {
if(value) {
target |= flag;
} else {
target &= ~flag;
}
}
/// @returns The main status register value.
uint8_t main() const {
return main_status_;
}
uint8_t operator [](int index) const {
return status_[index];
}
//
// Flag setters.
//
void set(MainStatus flag, bool value) {
set(uint8_t(flag), value, main_status_);
}
void start_seek(int drive) { main_status_ |= 1 << drive; }
void set(Status0 flag) { set(uint8_t(flag), true, status_[0]); }
void set(Status1 flag) { set(uint8_t(flag), true, status_[1]); }
void set(Status2 flag) { set(uint8_t(flag), true, status_[2]); }
void set_status0(uint8_t value) { status_[0] = value; }
//
// Flag getters.
//
bool get(MainStatus flag) { return main_status_ & uint8_t(flag); }
bool get(Status2 flag) { return status_[2] & uint8_t(flag); }
/// Begin execution of whatever @c CommandDecoder currently describes, setting internal
/// state appropriately.
void begin(const CommandDecoder &command) {
set(MainStatus::DataReady, false);
set(MainStatus::CommandInProgress, true);
if(command.is_access()) {
status_[0] = command.drive_head();
}
}
private:
void set(uint8_t flag, bool value, uint8_t &target) {
if(value) {
target |= flag;
} else {
target &= ~flag;
}
}
uint8_t main_status_;
uint8_t status_[3];
uint8_t main_status_;
uint8_t status_[3];
};
}

View File

@ -18,7 +18,7 @@ Log::Logger<Log::Source::i8272> logger;
using namespace Intel::i8272;
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
i8272::i8272(BusHandler &bus_handler, const Cycles clock_rate) :
Storage::Disk::MFMController(clock_rate),
bus_handler_(bus_handler) {
posit_event(int(Event8272::CommandByte));
@ -34,7 +34,7 @@ ClockingHint::Preference i8272::preferred_clocking() const {
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
void i8272::run_for(Cycles cycles) {
void i8272::run_for(const Cycles cycles) {
Storage::Disk::MFMController::run_for(cycles);
if(is_sleeping_) return;
@ -109,7 +109,7 @@ void i8272::run_for(Cycles cycles) {
if(is_sleeping_) update_clocking_observer();
}
void i8272::write(int address, uint8_t value) {
void i8272::write(const int address, const uint8_t value) {
// don't consider attempted sets to the status register
if(!address) return;
@ -127,7 +127,7 @@ void i8272::write(int address, uint8_t value) {
}
}
uint8_t i8272::read(int address) {
uint8_t i8272::read(const int address) {
if(address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
@ -208,7 +208,7 @@ uint8_t i8272::read(int address) {
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
}
void i8272::posit_event(int event_type) {
void i8272::posit_event(const int event_type) {
if(event_type == int(Event::IndexHole)) index_hole_count_++;
if(event_type == int(Event8272::NoLongerReady)) {
status_.set(Status0::NotReady);

View File

@ -20,116 +20,116 @@
namespace Intel::i8272 {
class BusHandler {
public:
virtual ~BusHandler() = default;
virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
virtual void set_interrupt([[maybe_unused]] bool irq) {}
public:
virtual ~BusHandler() = default;
virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
virtual void set_interrupt([[maybe_unused]] bool irq) {}
};
class i8272 : public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
void run_for(Cycles);
void run_for(Cycles);
void set_data_input(uint8_t value);
uint8_t get_data_output();
void set_data_input(uint8_t value);
uint8_t get_data_output();
void write(int address, uint8_t value);
uint8_t read(int address);
void write(int address, uint8_t value);
uint8_t read(int address);
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() const final;
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void select_drive(int number) = 0;
protected:
virtual void select_drive(int number) = 0;
private:
// The bus handler, for interrupt and DMA-driven usage. [TODO]
BusHandler &bus_handler_;
std::unique_ptr<BusHandler> allocated_bus_handler_;
private:
// The bus handler, for interrupt and DMA-driven usage. [TODO]
BusHandler &bus_handler_;
std::unique_ptr<BusHandler> allocated_bus_handler_;
// Status registers.
Status status_;
// Status registers.
Status status_;
// The incoming command.
CommandDecoder command_;
// The incoming command.
CommandDecoder command_;
// A buffer to accumulate the result.
std::vector<uint8_t> result_stack_;
uint8_t input_ = 0;
bool has_input_ = false;
bool expects_input_ = false;
// A buffer to accumulate the result.
std::vector<uint8_t> result_stack_;
uint8_t input_ = 0;
bool has_input_ = false;
bool expects_input_ = false;
// Event stream: the 8272-specific events, plus the current event state.
enum class Event8272: int {
CommandByte = (1 << 3),
Timer = (1 << 4),
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type) final;
int interesting_event_mask_ = int(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;
// Event stream: the 8272-specific events, plus the current event state.
enum class Event8272: int {
CommandByte = (1 << 3),
Timer = (1 << 4),
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type) final;
int interesting_event_mask_ = int(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;
// The counter used for ::Timer events.
Cycles::IntType delay_time_ = 0;
// The counter used for ::Timer events.
Cycles::IntType delay_time_ = 0;
// The connected drives.
struct Drive {
uint8_t head_position = 0;
// The connected drives.
struct Drive {
uint8_t head_position = 0;
// Seeking: persistent state.
enum Phase {
NotSeeking,
Seeking,
CompletedSeeking
} phase = NotSeeking;
bool did_seek = false;
bool seek_failed = false;
// Seeking: persistent state.
enum Phase {
NotSeeking,
Seeking,
CompletedSeeking
} phase = NotSeeking;
bool did_seek = false;
bool seek_failed = false;
// Seeking: transient state.
Cycles::IntType step_rate_counter = 0;
int steps_taken = 0;
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
// Seeking: transient state.
Cycles::IntType step_rate_counter = 0;
int steps_taken = 0;
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
// Head state.
Cycles::IntType head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
// Head state.
Cycles::IntType head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
} drives_[4];
int drives_seeking_ = 0;
} drives_[4];
int drives_seeking_ = 0;
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
bool seek_is_satisfied(int drive);
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
bool seek_is_satisfied(int drive);
// User-supplied parameters; as per the specify command.
int step_rate_time_ = 1;
int head_unload_time_ = 1;
int head_load_time_ = 1;
bool dma_mode_ = false;
bool is_executing_ = false;
// User-supplied parameters; as per the specify command.
int step_rate_time_ = 1;
int head_unload_time_ = 1;
int head_load_time_ = 1;
bool dma_mode_ = false;
bool is_executing_ = false;
// A count of head unload timers currently running.
int head_timers_running_ = 0;
// A count of head unload timers currently running.
int head_timers_running_ = 0;
// Transient storage and counters used while reading the disk.
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
int distance_into_section_ = 0;
int index_hole_count_ = 0, index_hole_limit_ = 0;
// Transient storage and counters used while reading the disk.
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
int distance_into_section_ = 0;
int index_hole_count_ = 0, index_hole_limit_ = 0;
// Keeps track of the drive and head in use during commands.
int active_drive_ = 0;
int active_head_ = 0;
// Keeps track of the drive and head in use during commands.
int active_drive_ = 0;
int active_head_ = 0;
// Internal registers.
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
// Internal registers.
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
// Master switch on not performing any work.
bool is_sleeping_ = false;
// Master switch on not performing any work.
bool is_sleeping_ = false;
};
}

View File

@ -39,7 +39,7 @@ bool z8530::get_interrupt_line() const {
A1 = C/D (i.e. control or data)
*/
std::uint8_t z8530::read(int address) {
std::uint8_t z8530::read(const int address) {
if(address & 2) {
// Read data register for channel.
return channels_[address & 1].read(true, pointer_);
@ -89,7 +89,7 @@ std::uint8_t z8530::read(int address) {
return 0x00;
}
void z8530::write(int address, std::uint8_t value) {
void z8530::write(const int address, const std::uint8_t value) {
if(address & 2) {
// Write data register for channel. This is completely independent
// of whatever is going on over in the control realm.
@ -140,14 +140,14 @@ void z8530::write(int address, std::uint8_t value) {
update_delegate();
}
void z8530::set_dcd(int port, bool level) {
void z8530::set_dcd(const int port, const bool level) {
channels_[port].set_dcd(level);
update_delegate();
}
// MARK: - Channel implementations
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
uint8_t z8530::Channel::read(const bool data, const uint8_t pointer) {
// If this is a data read, just return it.
if(data) {
return data_;
@ -232,7 +232,7 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
return 0x00;
}
void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t value) {
if(data) {
data_ = value;
return;
@ -400,7 +400,7 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
}
}
void z8530::Channel::set_dcd(bool level) {
void z8530::Channel::set_dcd(const bool level) {
if(dcd_ == level) return;
dcd_ = level;

View File

@ -16,95 +16,95 @@ namespace Zilog::SCC {
Models the Zilog 8530 SCC, a serial adaptor.
*/
class z8530 {
public:
/*
**Interface for emulated machine.**
public:
/*
**Interface for emulated machine.**
Notes on addressing below:
Notes on addressing below:
There's no inherent ordering of the two 'address' lines,
A/B and C/D, but the methods below assume:
There's no inherent ordering of the two 'address' lines,
A/B and C/D, but the methods below assume:
A/B = A0
C/D = A1
*/
A/B = A0
C/D = A1
*/
/// Performs a read from the SCC; see above for conventions as to 'address'.
std::uint8_t read(int address);
/// Performs a write to the SCC; see above for conventions as to 'address'.
void write(int address, std::uint8_t value);
/// Resets the SCC.
void reset();
/// Performs a read from the SCC; see above for conventions as to 'address'.
std::uint8_t read(int address);
/// Performs a write to the SCC; see above for conventions as to 'address'.
void write(int address, std::uint8_t value);
/// Resets the SCC.
void reset();
/// @returns The current value of the status output: @c true for active; @c false for inactive.
bool get_interrupt_line() const;
struct Delegate {
/*!
Communicates that @c scc now has the interrupt line status @c new_status.
*/
virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
};
/// @returns The current value of the status output: @c true for active; @c false for inactive.
bool get_interrupt_line() const;
struct Delegate {
/*!
Sets the delegate for this SCC. If this is a new delegate it is sent
an immediate did_change_interrupt_status message, to get it
up to speed.
Communicates that @c scc now has the interrupt line status @c new_status.
*/
void set_delegate(Delegate *delegate) {
if(delegate_ == delegate) return;
delegate_ = delegate;
delegate_->did_change_interrupt_status(this, get_interrupt_line());
}
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
};
/*
**Interface for serial port input.**
*/
void set_dcd(int port, bool level);
/*!
Sets the delegate for this SCC. If this is a new delegate it is sent
an immediate did_change_interrupt_status message, to get it
up to speed.
*/
void set_delegate(Delegate *const delegate) {
if(delegate_ == delegate) return;
delegate_ = delegate;
delegate_->did_change_interrupt_status(this, get_interrupt_line());
}
private:
class Channel {
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line() const;
/*
**Interface for serial port input.**
*/
void set_dcd(int port, bool level);
private:
uint8_t data_ = 0xff;
private:
class Channel {
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line() const;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
private:
uint8_t data_ = 0xff;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
int clock_rate_multiplier_ = 1;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
int clock_rate_multiplier_ = 1;
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
bool dcd_ = false;
} channels_[2];
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
uint8_t pointer_ = 0;
bool dcd_ = false;
} channels_[2];
uint8_t interrupt_vector_ = 0;
uint8_t pointer_ = 0;
uint8_t master_interrupt_control_ = 0;
uint8_t interrupt_vector_ = 0;
bool previous_interrupt_line_ = false;
void update_delegate();
Delegate *delegate_ = nullptr;
uint8_t master_interrupt_control_ = 0;
bool previous_interrupt_line_ = false;
void update_delegate();
Delegate *delegate_ = nullptr;
};
}