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:
parent
86fa8da8c5
commit
088bc14b11
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_) {
|
||||
|
@ -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;
|
||||
|
@ -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_);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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: b0–b4: end of horizontal blank;
|
||||
// b5–b6: "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: b0–b4: end of horizontal blank;
|
||||
// b5–b6: "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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user