mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-21 18:37:11 +00:00
Merge pull request #1419 from TomHarte/Reformatting
Begin a general reformatting.
This commit is contained in:
commit
f332613922
Activity
Analyser
Dynamic
Static
Acorn
Amiga
AmstradCPC
AppleII
AppleIIgs
Atari2600
AtariST
Coleco
Commodore
Disassembler
DiskII
Enterprise
FAT12
MSX
Macintosh
Oric
PCCompatible
Sega
StaticAnalyser.cppZX8081
ZXSpectrum
ClockReceiver
ClockReceiver.hppClockingHintSource.hppDeferredQueue.hppDeferredValue.hppJustInTime.hppScanSynchroniser.hppTimeTypes.hppVSyncPredictor.hpp
Components
1770
5380
6522
6526
6532
6560
6845
6850
8255
8272
@ -22,38 +22,38 @@ namespace Activity {
|
||||
and/or to show or unshow status indicators.
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
|
||||
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
StepBelowZero,
|
||||
StepBeyondMaximum
|
||||
};
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
StepBelowZero,
|
||||
StepBeyondMaximum
|
||||
};
|
||||
|
||||
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
|
||||
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
|
||||
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,8 +13,8 @@
|
||||
namespace Activity {
|
||||
|
||||
class Source {
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,25 +18,25 @@ namespace Analyser::Dynamic {
|
||||
The initial value of the confidence counter is 0.5.
|
||||
*/
|
||||
class ConfidenceCounter: public ConfidenceSource {
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||
void add_miss();
|
||||
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
/*!
|
||||
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||
down towards 0.5, but will never push it upwards.
|
||||
*/
|
||||
void add_equivocal();
|
||||
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
private:
|
||||
int hits_ = 1;
|
||||
int misses_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,10 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
|
||||
ConfidenceSummary::ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights
|
||||
) :
|
||||
sources_(sources), weights_(weights) {
|
||||
assert(weights.size() == sources.size());
|
||||
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
||||
|
@ -18,24 +18,24 @@ namespace Analyser::Dynamic {
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
*/
|
||||
class ConfidenceSummary: public ConfidenceSource {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
public:
|
||||
/*!
|
||||
Instantiates a summary that will produce the weighted sum of
|
||||
@c sources, each using the corresponding entry of @c weights.
|
||||
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
Requires that @c sources and @c weights are of the same length.
|
||||
*/
|
||||
ConfidenceSummary(
|
||||
const std::vector<ConfidenceSource *> &sources,
|
||||
const std::vector<float> &weights);
|
||||
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiStruct: public Reflection::Struct {
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, const size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -23,15 +23,15 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiConfigurable: public Configurable::Device {
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() final;
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() final;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
|
||||
namespace {
|
||||
|
||||
class MultiJoystick: public Inputs::Joystick {
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() >= index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
public:
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() >= index) {
|
||||
joysticks_.push_back(joysticks[index].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
void set_input(const Input &digital_input, const bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
void set_input(const Input &digital_input, const float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,14 +22,14 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
|
||||
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
|
||||
for(const auto &machine: machines_) {
|
||||
machine->set_key_state(key, is_pressed);
|
||||
}
|
||||
@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::can_type(char c) const {
|
||||
bool MultiKeyboardMachine::can_type(const char c) const {
|
||||
bool can_type = true;
|
||||
for(const auto &machine: machines_) {
|
||||
can_type &= machine->can_type(c);
|
||||
@ -51,12 +51,20 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
|
||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
|
||||
: machines_(machines) {
|
||||
for(const auto &machine: machines_) {
|
||||
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
|
||||
observed_keys_.insert(
|
||||
machine->get_keyboard().observed_keys().begin(),
|
||||
machine->get_keyboard().observed_keys().end()
|
||||
);
|
||||
is_exclusive_ |= machine->get_keyboard().is_exclusive();
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
|
||||
const Key key,
|
||||
const char value,
|
||||
const bool is_pressed,
|
||||
const bool is_repeat
|
||||
) {
|
||||
bool was_consumed = false;
|
||||
for(const auto &machine: machines_) {
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
|
||||
|
@ -23,34 +23,34 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
private:
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
private:
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
std::unique_ptr<MultiKeyboard> keyboard_;
|
||||
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ namespace Analyser::Dynamic {
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &media) final;
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &) final;
|
||||
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
private:
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
|
||||
}
|
||||
|
||||
// MARK: - MultiScanProducer
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
scan_target_ = scan_target;
|
||||
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
@ -80,7 +80,12 @@ void MultiScanProducer::did_change_machine_order() {
|
||||
}
|
||||
|
||||
// MARK: - MultiAudioProducer
|
||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
|
||||
MultiAudioProducer::MultiAudioProducer(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
MultiInterface(machines, machines_mutex)
|
||||
{
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
@ -96,7 +101,7 @@ void MultiAudioProducer::did_change_machine_order() {
|
||||
|
||||
// MARK: - MultiTimedMachine
|
||||
|
||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
|
||||
void MultiTimedMachine::run_for(const Time::Seconds duration) {
|
||||
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
@ -21,88 +21,91 @@
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
public:
|
||||
MultiInterface(
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
|
||||
std::recursive_mutex &machines_mutex
|
||||
) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void run_for(Time::Seconds duration) final;
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
private:
|
||||
void run_for(Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
void set_scan_target(Outputs::Display::ScanTarget *) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
|
||||
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
|
||||
}
|
||||
}
|
||||
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
|
||||
float ideal = 0.0f;
|
||||
for(const auto &speaker: speakers_) {
|
||||
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||
@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
return ideal / float(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
|
||||
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
|
||||
stereo_output_ = stereo;
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
|
||||
@ -54,13 +54,13 @@ bool MultiSpeaker::get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_volume(float volume) {
|
||||
void MultiSpeaker::set_output_volume(const float volume) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const std::vector<int16_t> &buffer) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
@ -70,7 +70,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
@ -80,7 +80,7 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->audio_producer()->get_speaker();
|
||||
|
@ -25,32 +25,32 @@ namespace Analyser::Dynamic {
|
||||
abreast of the current frontmost machine.
|
||||
*/
|
||||
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
public:
|
||||
/*!
|
||||
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||
machines return nullptr as their speaker.
|
||||
*/
|
||||
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
|
||||
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||
void set_new_front_machine(::Machine::DynamicMachine *);
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
|
||||
bool stereo_output_ = false;
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
|
||||
audio_producer_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines_),
|
||||
keyboard_machine_(machines_),
|
||||
media_target_(machines_) {
|
||||
media_target_(machines_)
|
||||
{
|
||||
timed_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
@ -35,13 +36,13 @@ Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
return machines_.front()->name(); \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
}
|
||||
|
||||
Provider(Configurable::Device, configurable_device, configurable_)
|
||||
|
@ -38,44 +38,44 @@ namespace Analyser::Dynamic {
|
||||
the others in the set, that machine stops running.
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
requesting this class as a proxy.
|
||||
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||
@c false otherwise.
|
||||
*/
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
|
||||
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
MultiConfigurable configurable_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -49,27 +49,39 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
char name[10];
|
||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||
new_file.name = name;
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.load_address = uint32_t(
|
||||
details->samples[0][file_offset] |
|
||||
(details->samples[0][file_offset+1] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x0c) << 14)
|
||||
);
|
||||
new_file.execution_address = uint32_t(
|
||||
details->samples[0][file_offset+2] |
|
||||
(details->samples[0][file_offset+3] << 8) |
|
||||
((details->samples[0][file_offset+6]&0xc0) << 10)
|
||||
);
|
||||
if(names->samples[0][file_offset + 7] & 0x80) {
|
||||
// File is locked; it may not be altered or deleted.
|
||||
new_file.flags |= File::Flags::Locked;
|
||||
}
|
||||
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
auto data_length = long(
|
||||
details->samples[0][file_offset+4] |
|
||||
(details->samples[0][file_offset+5] << 8) |
|
||||
((details->samples[0][file_offset+6]&0x30) << 12)
|
||||
);
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
new_file.data.reserve(size_t(data_length));
|
||||
|
||||
if(start_sector < 2) continue;
|
||||
while(data_length > 0) {
|
||||
uint8_t sector = uint8_t(start_sector % 10);
|
||||
uint8_t track = uint8_t(start_sector / 10);
|
||||
start_sector++;
|
||||
const uint8_t sector = uint8_t(start_sector % 10);
|
||||
const uint8_t track = uint8_t(start_sector / 10);
|
||||
++start_sector;
|
||||
|
||||
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
const long length_from_sector = std::min(data_length, 256l);
|
||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||
data_length -= length_from_sector;
|
||||
}
|
||||
@ -133,7 +145,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < (catalogue->has_large_sectors ? 0x7d7 : 0x4cb); file_offset += 0x1a) {
|
||||
const std::size_t directory_extent = catalogue->has_large_sectors ? 0x7d7 : 0x4cb;
|
||||
for(std::size_t file_offset = 0x005; file_offset < directory_extent; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11]{};
|
||||
@ -190,11 +203,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
const Storage::Encodings::MFM::Sector *const sector =
|
||||
parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->samples[0].begin(),
|
||||
sector->samples[0].begin() + ssize_t(length_from_sector)
|
||||
);
|
||||
++start_sector;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ struct Catalogue {
|
||||
} bootOption;
|
||||
};
|
||||
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
@ -62,7 +62,11 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
|
||||
@ -121,7 +125,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,10 @@
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(
|
||||
const std::shared_ptr<Storage::Tape::Tape> &tape,
|
||||
Storage::Tape::Acorn::Parser &parser
|
||||
) {
|
||||
auto new_chunk = std::make_unique<File::Chunk>();
|
||||
int shift_register = 0;
|
||||
|
||||
@ -56,7 +59,7 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
|
||||
|
||||
uint16_t calculated_header_crc = parser.get_crc();
|
||||
const uint16_t calculated_header_crc = parser.get_crc();
|
||||
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
||||
|
@ -15,6 +15,6 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -63,8 +63,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
|
||||
void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
std::vector<const Storage::Disk::CPM::File *> candidate_files;
|
||||
candidate_files.reserve(catalogue.files.size());
|
||||
for(const auto &file : catalogue.files) {
|
||||
@ -158,7 +158,10 @@ void InspectCatalogue(
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
bool CheckBootSector(
|
||||
const std::shared_ptr<Storage::Disk::Disk> &disk,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
|
||||
) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
@ -204,7 +207,11 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
@ -233,7 +240,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
|
||||
for(auto &disk: media.disks) {
|
||||
// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
|
||||
if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*data_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
@ -247,7 +255,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
}
|
||||
|
||||
// Failing that check for a system catalogue.
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
|
||||
Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
|
||||
if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
|
||||
InspectCatalogue(*system_catalogue, target);
|
||||
target->media.disks.push_back(disk);
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -33,11 +33,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||
// large amounts of memory.
|
||||
bool has_wide_area_store = false;
|
||||
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
||||
for(const auto &entry : high_location_disassembly.instructions_by_address) {
|
||||
using Instruction = Analyser::Static::MOS6502::Instruction;
|
||||
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
|
||||
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
|
||||
has_wide_area_store |=
|
||||
entry.second.addressing_mode == Instruction::Indirect ||
|
||||
entry.second.addressing_mode == Instruction::IndexedIndirectX ||
|
||||
entry.second.addressing_mode == Instruction::IndirectIndexedY;
|
||||
|
||||
if(has_wide_area_store) break;
|
||||
}
|
||||
@ -50,13 +52,21 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor8kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &segment,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
||||
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
|
||||
if(
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
|
||||
segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(
|
||||
segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
|
||||
segment.data[8190] != 0x00 || segment.data[8188] != 0x00
|
||||
) &&
|
||||
segment.data[0] == 0x78
|
||||
) {
|
||||
target.paging_model = Target::PagingModel::ActivisionStack;
|
||||
@ -88,7 +98,11 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
|
||||
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor16kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is the Atari paging model.
|
||||
target.paging_model = Target::PagingModel::Atari16k;
|
||||
|
||||
@ -108,7 +122,11 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
|
||||
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
static void DeterminePagingFor64kCartridge(
|
||||
Target &target,
|
||||
const Storage::Cartridge::Cartridge::Segment &,
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly
|
||||
) {
|
||||
// Make an assumption that this is a Tigervision if there is a write to 3F.
|
||||
target.paging_model =
|
||||
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
||||
@ -121,8 +139,12 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
const auto word = [](const uint8_t low, const uint8_t high) {
|
||||
return uint16_t(low | (high << 8));
|
||||
};
|
||||
|
||||
const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
|
||||
const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
|
||||
|
||||
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
||||
if(!(address & 0x1000)) return size_t(-1);
|
||||
@ -130,27 +152,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
|
||||
switch(segment.data.size()) {
|
||||
case 8192:
|
||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 10495:
|
||||
target.paging_model = Target::PagingModel::Pitfall2;
|
||||
break;
|
||||
case 12288:
|
||||
target.paging_model = Target::PagingModel::CBSRamPlus;
|
||||
break;
|
||||
case 16384:
|
||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 32768:
|
||||
target.paging_model = Target::PagingModel::Atari32k;
|
||||
break;
|
||||
case 65536:
|
||||
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 8192: DeterminePagingFor8kCartridge(target, segment, disassembly); break;
|
||||
case 10495: target.paging_model = Target::PagingModel::Pitfall2; break;
|
||||
case 12288: target.paging_model = Target::PagingModel::CBSRamPlus; break;
|
||||
case 16384: DeterminePagingFor16kCartridge(target, segment, disassembly); break;
|
||||
case 32768: target.paging_model = Target::PagingModel::Atari32k; break;
|
||||
case 65536: DeterminePagingFor64kCartridge(target, segment, disassembly); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -177,7 +188,11 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
}
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
@ -52,7 +52,11 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
TargetList targets;
|
||||
auto target = std::make_unique<Target>(Machine::ColecoVision);
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -18,155 +18,155 @@
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
public:
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
struct Sector {
|
||||
uint8_t sector, track;
|
||||
std::array<uint8_t, 256> data;
|
||||
bool header_checksum_matched;
|
||||
bool data_checksum_matched;
|
||||
};
|
||||
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(const uint8_t track, const uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
if(difference) {
|
||||
const int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
for(int c = 0; c < difference; c++) {
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
}
|
||||
|
||||
return get_sector(sector);
|
||||
unsigned int zone = 3;
|
||||
if(track >= 18) zone = 2;
|
||||
else if(track >= 25) zone = 1;
|
||||
else if(track >= 31) zone = 0;
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
return get_sector(sector);
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(const int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(const int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block(int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// find end of lead-in
|
||||
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void proceed_to_shift_value(const unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 10) run_for(Cycles(1));
|
||||
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
|
||||
void process_index_hole() {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(const uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1) {
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
}
|
||||
|
||||
void process_index_hole() {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector) {
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
auto sector = std::make_shared<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_next_sector() {
|
||||
auto sector = std::make_shared<Sector>();
|
||||
const int max_index_count = index_count_ + 2;
|
||||
|
||||
while(index_count_ < max_index_count) {
|
||||
// look for a sector header
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
@ -181,7 +181,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
std::vector<uint8_t> directory;
|
||||
uint8_t next_track = 18;
|
||||
uint8_t next_sector = 1;
|
||||
while(1) {
|
||||
while(true) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
|
||||
@ -216,7 +216,9 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
std::size_t number_of_sectors =
|
||||
size_t(directory[header_pointer + 0x1e]) +
|
||||
(size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
@ -229,9 +231,17 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + (is_first_sector ? 4 : 2),
|
||||
sector->data.end()
|
||||
);
|
||||
else
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + 2,
|
||||
sector->data.begin() + next_sector
|
||||
);
|
||||
|
||||
is_first_sector = false;
|
||||
}
|
||||
|
@ -15,6 +15,6 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &);
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
|
||||
|
||||
for(const auto &cartridge : cartridges) {
|
||||
@ -42,7 +42,11 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
auto target = std::make_unique<Target>();
|
||||
@ -93,7 +97,8 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
|
||||
"Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
@ -142,7 +147,8 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||
// if(starting_address + file_size > 0x2000)
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
// else if(target->memory_model == Target::MemoryModel::Unexpanded &&
|
||||
// !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// }
|
||||
// }
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -55,7 +55,9 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
new_file.starting_address = header->starting_address;
|
||||
new_file.ending_address = header->ending_address;
|
||||
new_file.data.swap(data->data);
|
||||
new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
new_file.type =
|
||||
header->type == Storage::Tape::Commodore::Header::RelocatableProgram
|
||||
? File::RelocatableProgram : File::NonRelocatableProgram;
|
||||
|
||||
file_list.push_back(new_file);
|
||||
}
|
||||
|
@ -13,6 +13,6 @@
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,12 @@ using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Dis
|
||||
|
||||
struct MOS6502Disassembler {
|
||||
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
uint16_t address = entry_point;
|
||||
while(true) {
|
||||
@ -75,23 +80,25 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
|
||||
// Decode operation.
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: \
|
||||
case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
#define URM_INSTRUCTION(base, op) \
|
||||
case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
#define M_INSTRUCTION(base, op) \
|
||||
case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e: \
|
||||
instruction.operation = op; \
|
||||
instruction.operation = op; \
|
||||
break;
|
||||
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
#define IM_INSTRUCTION(base, op) \
|
||||
case base: instruction.operation = op; break;
|
||||
|
||||
switch(operation) {
|
||||
default:
|
||||
instruction.operation = Instruction::KIL;
|
||||
@ -259,7 +266,10 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
|
||||
|
||||
// TODO: something wider-ranging than this
|
||||
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
|
||||
if(
|
||||
instruction.addressing_mode == Instruction::Absolute ||
|
||||
instruction.addressing_mode == Instruction::ZeroPage
|
||||
) {
|
||||
const size_t mapped_address = address_mapper(instruction.operand);
|
||||
const bool is_external = mapped_address >= memory.size();
|
||||
|
||||
@ -272,20 +282,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
case Instruction::ADC: case Instruction::SBC:
|
||||
case Instruction::LAS:
|
||||
case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::STY: case Instruction::STX: case Instruction::STA:
|
||||
case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
|
||||
case Instruction::TAS:
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
|
||||
case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
|
||||
case Instruction::DCP: case Instruction::ISC:
|
||||
case Instruction::INC: case Instruction::DEC:
|
||||
case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
|
||||
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
|
||||
.insert(instruction.operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -330,5 +343,10 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
|
||||
memory,
|
||||
address_mapper,
|
||||
entry_points,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace Analyser::Static::Disassembler {
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
address @c start_address.
|
||||
*/
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
|
||||
return [start_address](T argument) {
|
||||
return size_t(argument - start_address);
|
||||
};
|
||||
|
@ -16,44 +16,48 @@ namespace {
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
class Accessor {
|
||||
public:
|
||||
Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
public:
|
||||
Accessor(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
uint16_t address
|
||||
) :
|
||||
memory_(memory), address_mapper_(address_mapper), address_(address) {}
|
||||
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
address_++;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
uint8_t byte() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
++address_;
|
||||
if(mapped_address >= memory_.size()) {
|
||||
overrun_ = true;
|
||||
return 0xff;
|
||||
}
|
||||
return memory_[mapped_address];
|
||||
}
|
||||
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
|
||||
bool overrun() {
|
||||
return overrun_;
|
||||
}
|
||||
bool overrun() const {
|
||||
return overrun_;
|
||||
}
|
||||
|
||||
bool at_end() {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
bool at_end() const {
|
||||
std::size_t mapped_address = address_mapper_(address_);
|
||||
return mapped_address >= memory_.size();
|
||||
}
|
||||
|
||||
uint16_t address() {
|
||||
return address_;
|
||||
}
|
||||
uint16_t address() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
private:
|
||||
const std::vector<uint8_t> &memory_;
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper_;
|
||||
uint16_t address_;
|
||||
bool overrun_ = false;
|
||||
};
|
||||
|
||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
|
||||
@ -83,8 +87,12 @@ Instruction::Location register_pair_table2[] = {
|
||||
Instruction::Location::AF
|
||||
};
|
||||
|
||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
Instruction::Location register_table[] = {
|
||||
Instruction::Location RegisterTableEntry(
|
||||
const int offset, Accessor &accessor,
|
||||
Instruction &instruction,
|
||||
const bool needs_indirect_offset
|
||||
) {
|
||||
constexpr Instruction::Location register_table[] = {
|
||||
Instruction::Location::B, Instruction::Location::C,
|
||||
Instruction::Location::D, Instruction::Location::E,
|
||||
Instruction::Location::H, Instruction::Location::L,
|
||||
@ -92,7 +100,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
Instruction::Location::A
|
||||
};
|
||||
|
||||
Instruction::Location location = register_table[offset];
|
||||
const Instruction::Location location = register_table[offset];
|
||||
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
|
||||
instruction.offset = accessor.byte() - 128;
|
||||
}
|
||||
@ -100,7 +108,7 @@ Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruc
|
||||
return location;
|
||||
}
|
||||
|
||||
Instruction::Operation alu_table[] = {
|
||||
constexpr Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::ADD,
|
||||
Instruction::Operation::ADC,
|
||||
Instruction::Operation::SUB,
|
||||
@ -111,7 +119,7 @@ Instruction::Operation alu_table[] = {
|
||||
Instruction::Operation::CP
|
||||
};
|
||||
|
||||
Instruction::Operation rotation_table[] = {
|
||||
constexpr Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::RLC,
|
||||
Instruction::Operation::RRC,
|
||||
Instruction::Operation::RL,
|
||||
@ -122,19 +130,32 @@ Instruction::Operation rotation_table[] = {
|
||||
Instruction::Operation::SRL
|
||||
};
|
||||
|
||||
Instruction::Operation block_table[][4] = {
|
||||
{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
|
||||
{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
|
||||
{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
|
||||
{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
|
||||
constexpr Instruction::Operation block_table[][4] = {
|
||||
{
|
||||
Instruction::Operation::LDI, Instruction::Operation::CPI,
|
||||
Instruction::Operation::INI, Instruction::Operation::OUTI
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDD, Instruction::Operation::CPD,
|
||||
Instruction::Operation::IND, Instruction::Operation::OUTD
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDIR, Instruction::Operation::CPIR,
|
||||
Instruction::Operation::INIR, Instruction::Operation::OTIR
|
||||
},
|
||||
{
|
||||
Instruction::Operation::LDDR, Instruction::Operation::CPDR,
|
||||
Instruction::Operation::INDR, Instruction::Operation::OTDR
|
||||
},
|
||||
};
|
||||
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
if(!x(operation)) {
|
||||
instruction.operation = rotation_table[y(operation)];
|
||||
instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
@ -148,7 +169,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
}
|
||||
}
|
||||
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
|
||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
@ -170,7 +191,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.destination = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@ -179,7 +201,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
if(y(operation) == 6) {
|
||||
instruction.source = Instruction::Location::None;
|
||||
} else {
|
||||
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@ -190,11 +213,13 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
case 3:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
if(q(operation)) {
|
||||
instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand_Indirect;
|
||||
} else {
|
||||
instruction.destination = Instruction::Location::Operand_Indirect;
|
||||
instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
instruction.operand = accessor.word();
|
||||
break;
|
||||
@ -202,7 +227,8 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_
|
||||
instruction.operation = Instruction::Operation::NEG;
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
instruction.operation =
|
||||
y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::IM;
|
||||
@ -253,7 +279,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
} hl_substitution = None;
|
||||
|
||||
while(true) {
|
||||
uint8_t operation = accessor.byte();
|
||||
const uint8_t operation = accessor.byte();
|
||||
|
||||
switch(x(operation)) {
|
||||
case 0:
|
||||
@ -343,15 +369,18 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
break;
|
||||
case 4:
|
||||
instruction.operation = Instruction::Operation::INC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 5:
|
||||
instruction.operation = Instruction::Operation::DEC;
|
||||
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
break;
|
||||
case 6:
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source = Instruction::Location::Operand;
|
||||
instruction.operand = accessor.byte();
|
||||
break;
|
||||
@ -374,8 +403,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.operation = Instruction::Operation::HALT;
|
||||
} else {
|
||||
instruction.operation = Instruction::Operation::LD;
|
||||
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.source =
|
||||
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
|
||||
instruction.destination =
|
||||
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@ -517,10 +548,14 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
|
||||
if(instruction.source == Instruction::Location::HL_Indirect) {
|
||||
instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.source =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
if(instruction.destination == Instruction::Location::HL_Indirect) {
|
||||
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
instruction.destination =
|
||||
hl_substitution == IX ?
|
||||
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -542,7 +577,12 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
|
||||
}
|
||||
|
||||
struct Z80Disassembler {
|
||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
|
||||
static void AddToDisassembly(
|
||||
PartialDisassembly &disassembly,
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
const uint16_t entry_point
|
||||
) {
|
||||
disassembly.disassembly.internal_calls.insert(entry_point);
|
||||
Accessor accessor(memory, address_mapper, entry_point);
|
||||
|
||||
|
@ -47,7 +47,11 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@ -62,7 +66,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
}
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
@ -89,7 +94,8 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||
// Otherwise go with the Apple II.
|
||||
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
|
||||
bool did_read_shift_register = false;
|
||||
bool is_oric = false;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,11 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@ -72,7 +76,8 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
target->loading_command =
|
||||
std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,11 @@
|
||||
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType platforms
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
@ -39,7 +43,8 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
|
||||
}
|
||||
|
||||
// Attempt to grab MFM track 0, sector 1: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero =
|
||||
disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(
|
||||
*track_zero,
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::FAT12 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
};
|
||||
const Type type;
|
||||
|
||||
Cartridge(const std::vector<Segment> &segments, Type type) :
|
||||
Cartridge(const std::vector<Segment> &segments, const Type type) :
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,8 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
if(segment.data.size() & 0x1fff) {
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
const auto truncated_size =
|
||||
std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(start_address, truncated_data);
|
||||
} else {
|
||||
@ -82,7 +83,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
|
||||
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const size_t data_size = segment.data.size();
|
||||
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
|
||||
|
||||
@ -101,7 +102,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// Reject cartridge if the ROM header wasn't found.
|
||||
if(!found_start) continue;
|
||||
|
||||
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
@ -137,10 +138,12 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_8kb_total =
|
||||
address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
const auto konami_with_scc_total =
|
||||
address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
|
||||
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
|
||||
|
||||
@ -182,7 +185,11 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
return targets;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
@ -194,7 +201,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
|
||||
// Check tapes for loadable files.
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> files_on_tape = GetFiles(tape);
|
||||
const std::vector<File> files_on_tape = GetFiles(tape);
|
||||
if(!files_on_tape.empty()) {
|
||||
switch(files_on_tape.front().type) {
|
||||
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,6 @@ struct File {
|
||||
File();
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -22,12 +22,22 @@ using namespace Analyser::Static::Oric;
|
||||
|
||||
namespace {
|
||||
|
||||
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||
int score(
|
||||
const Analyser::Static::MOS6502::Disassembly &disassembly,
|
||||
const std::set<uint16_t> &rom_functions,
|
||||
const std::set<uint16_t> &variable_locations
|
||||
) {
|
||||
int score = 0;
|
||||
|
||||
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
for(const auto address : disassembly.outward_calls) {
|
||||
score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_stores) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
for(const auto address : disassembly.external_loads) {
|
||||
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
@ -35,19 +45,32 @@ int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::
|
||||
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0228, 0x022b,
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f, 0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0, 0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f, 0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e, 0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563, 0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974, 0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf, 0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524,
|
||||
0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f,
|
||||
0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f,
|
||||
0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
|
||||
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0,
|
||||
0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
|
||||
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a,
|
||||
0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
|
||||
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f,
|
||||
0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
|
||||
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f,
|
||||
0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
|
||||
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e,
|
||||
0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
|
||||
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563,
|
||||
0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
|
||||
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974,
|
||||
0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
|
||||
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf,
|
||||
0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
|
||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a,
|
||||
0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7,
|
||||
0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||
@ -59,19 +82,32 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
const std::set<uint16_t> rom_functions = {
|
||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c, 0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c, 0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de, 0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f, 0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1, 0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab, 0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561, 0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524,
|
||||
0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915,
|
||||
0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c,
|
||||
0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
|
||||
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c,
|
||||
0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
|
||||
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767,
|
||||
0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
|
||||
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de,
|
||||
0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
|
||||
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4,
|
||||
0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
|
||||
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f,
|
||||
0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
|
||||
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab,
|
||||
0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
|
||||
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1,
|
||||
0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
|
||||
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab,
|
||||
0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
|
||||
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561,
|
||||
0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
|
||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14,
|
||||
0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||
0xfc18
|
||||
};
|
||||
const std::set<uint16_t> variable_locations = {
|
||||
@ -102,7 +138,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||
}
|
||||
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
|
||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
|
||||
/*
|
||||
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
|
||||
use disassembly to test for likely matches.
|
||||
@ -120,8 +156,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
|
||||
}
|
||||
|
||||
// Grab a disassembly.
|
||||
const auto disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
|
||||
first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
|
||||
|
||||
// Check for references to the Jasmin registers.
|
||||
int register_hits = 0;
|
||||
@ -145,7 +181,11 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
||||
@ -160,7 +200,11 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
if(file.data_type == File::MachineCode) {
|
||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||
const Analyser::Static::MOS6502::Disassembly disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||
Analyser::Static::MOS6502::Disassemble(
|
||||
file.data,
|
||||
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
|
||||
entry_points
|
||||
);
|
||||
|
||||
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
|
||||
}
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,6 @@ struct File {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,11 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,11 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &file_name,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
if(media.cartridges.empty())
|
||||
return {};
|
||||
|
||||
@ -54,7 +58,8 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
if(lowercase_name.find("(jp)") == std::string::npos) {
|
||||
target->region =
|
||||
(lowercase_name.find("(us)") == std::string::npos &&
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ?
|
||||
Target::Region::Europe : Target::Region::USA;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
@ -63,9 +68,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
// If one is found, set the paging scheme appropriately.
|
||||
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
|
||||
if(
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
(inverse_checksum&0xff) == data[0x7fe8] &&
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present.
|
||||
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
|
||||
) {
|
||||
target->paging_scheme = Target::PagingScheme::Codemasters;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
|
@ -104,14 +104,14 @@ std::string get_extension(const std::string &name) {
|
||||
}
|
||||
|
||||
class MediaAccumulator {
|
||||
public:
|
||||
public:
|
||||
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
|
||||
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
|
||||
|
||||
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
|
||||
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
|
||||
template <typename InstanceT>
|
||||
void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
|
||||
media.disks.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
|
||||
@ -134,13 +134,13 @@ class MediaAccumulator {
|
||||
|
||||
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
|
||||
try {
|
||||
insert<InstanceT>(platforms, std::forward<Args>(args)...);
|
||||
} catch(...) {}
|
||||
@ -149,22 +149,22 @@ class MediaAccumulator {
|
||||
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
|
||||
/// providing the file name as the only construction argument.
|
||||
template <typename InstanceT>
|
||||
void try_standard(TargetPlatform::IntType platforms, const char *extension) {
|
||||
void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
|
||||
if(name_matches(extension)) {
|
||||
try_insert<InstanceT>(platforms, file_name_);
|
||||
}
|
||||
}
|
||||
|
||||
bool name_matches(const char *extension) {
|
||||
bool name_matches(const char *const extension) {
|
||||
return extension_ == extension;
|
||||
}
|
||||
|
||||
Media media;
|
||||
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
};
|
||||
|
||||
}
|
||||
@ -227,7 +227,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
|
||||
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
|
||||
TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
|
||||
|
@ -28,7 +28,11 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
|
||||
return files;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType potential_platforms
|
||||
) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -103,7 +103,11 @@ bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
|
||||
const Media &media,
|
||||
const std::string &,
|
||||
TargetPlatform::IntType
|
||||
) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
|
@ -56,218 +56,225 @@
|
||||
Boolean operators, but forcing callers and receivers to be explicit as to usage.
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
using IntType = int64_t;
|
||||
public:
|
||||
using IntType = int64_t;
|
||||
|
||||
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int,
|
||||
// which is prone silently to permit misuse.
|
||||
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
if constexpr (sizeof(Type) == sizeof(IntType)) {
|
||||
if constexpr (std::is_same_v<Type, IntType>) {
|
||||
return length_;
|
||||
} else if constexpr (std::is_signed_v<Type>) {
|
||||
// Both integers are the same size, but a signed result is being asked for
|
||||
// from an unsigned original.
|
||||
return length_ > Type(std::numeric_limits<Type>::max()) ? Type(std::numeric_limits<Type>::max()) : Type(length_);
|
||||
} else {
|
||||
// An unsigned result is being asked for from a signed original.
|
||||
return length_ < 0 ? 0 : Type(length_);
|
||||
}
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
if constexpr (sizeof(Type) == sizeof(IntType)) {
|
||||
if constexpr (std::is_same_v<Type, IntType>) {
|
||||
return length_;
|
||||
} else if constexpr (std::is_signed_v<Type>) {
|
||||
// Both integers are the same size, but a signed result is being asked for
|
||||
// from an unsigned original.
|
||||
return length_ > Type(std::numeric_limits<Type>::max()) ?
|
||||
Type(std::numeric_limits<Type>::max()) : Type(length_);
|
||||
} else {
|
||||
// An unsigned result is being asked for from a signed original.
|
||||
return length_ < 0 ? 0 : Type(length_);
|
||||
}
|
||||
|
||||
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
const auto clamped = std::clamp(
|
||||
length_,
|
||||
IntType(std::numeric_limits<Type>::min()),
|
||||
IntType(std::numeric_limits<Type>::max())
|
||||
);
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
template <typename Result = T> forceinline Result divide(const T &divisor) {
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r, divisor);
|
||||
return r;
|
||||
}
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
}
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
template <typename Result = T> forceinline Result divide(const T &divisor) {
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r, divisor);
|
||||
return r;
|
||||
}
|
||||
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
protected:
|
||||
IntType length_;
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
|
||||
protected:
|
||||
IntType length_;
|
||||
};
|
||||
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const Cycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
void fill(Cycles &result, const Cycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept :
|
||||
WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const HalfCycles &divisor) {
|
||||
result = Cycles(length_ / (divisor.length_ << 1));
|
||||
length_ %= (divisor.length_ << 1);
|
||||
}
|
||||
void fill(Cycles &result, const HalfCycles &divisor) {
|
||||
result = Cycles(length_ / (divisor.length_ << 1));
|
||||
length_ %= (divisor.length_ << 1);
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result, const HalfCycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
void fill(HalfCycles &result, const HalfCycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
|
||||
|
@ -58,28 +58,28 @@ struct Observer {
|
||||
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
|
||||
*/
|
||||
class Source {
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() const = 0;
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() const = 0;
|
||||
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -15,78 +15,79 @@
|
||||
Provides the logic to insert into and traverse a list of future scheduled items.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueue {
|
||||
public:
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= TimeUnit(0)) {
|
||||
action();
|
||||
return;
|
||||
public:
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= TimeUnit(0)) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!pending_actions_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_actions_.begin();
|
||||
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_actions_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
|
||||
if(!pending_actions_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_actions_.begin();
|
||||
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_actions_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
pending_actions_.emplace(insertion_point, delay, action);
|
||||
} else {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
}
|
||||
|
||||
pending_actions_.emplace(insertion_point, delay, action);
|
||||
/*!
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() const {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the queue the specified amount of time, performing any actions it reaches.
|
||||
*/
|
||||
void advance(TimeUnit time) {
|
||||
auto erase_iterator = pending_actions_.begin();
|
||||
while(erase_iterator != pending_actions_.end()) {
|
||||
erase_iterator->delay -= time;
|
||||
if(erase_iterator->delay <= TimeUnit(0)) {
|
||||
time = -erase_iterator->delay;
|
||||
erase_iterator->action();
|
||||
++erase_iterator;
|
||||
} else {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() const {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
if(erase_iterator != pending_actions_.begin()) {
|
||||
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the queue the specified amount of time, performing any actions it reaches.
|
||||
*/
|
||||
void advance(TimeUnit time) {
|
||||
auto erase_iterator = pending_actions_.begin();
|
||||
while(erase_iterator != pending_actions_.end()) {
|
||||
erase_iterator->delay -= time;
|
||||
if(erase_iterator->delay <= TimeUnit(0)) {
|
||||
time = -erase_iterator->delay;
|
||||
erase_iterator->action();
|
||||
++erase_iterator;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(erase_iterator != pending_actions_.begin()) {
|
||||
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
/// @returns @c true if no actions are enqueued; @c false otherwise.
|
||||
bool empty() const {
|
||||
return pending_actions_.empty();
|
||||
}
|
||||
|
||||
/// @returns @c true if no actions are enqueued; @c false otherwise.
|
||||
bool empty() const {
|
||||
return pending_actions_.empty();
|
||||
}
|
||||
private:
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
|
||||
private:
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) :
|
||||
delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -117,8 +118,6 @@ template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<
|
||||
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
|
||||
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -13,33 +13,33 @@
|
||||
of future values.
|
||||
*/
|
||||
template <int DeferredDepth, typename ValueT> class DeferredValue {
|
||||
private:
|
||||
static_assert(sizeof(ValueT) <= 4);
|
||||
private:
|
||||
static_assert(sizeof(ValueT) <= 4);
|
||||
|
||||
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
|
||||
constexpr int unit_shift = sizeof(ValueT) * 8;
|
||||
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
|
||||
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
|
||||
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
|
||||
constexpr int unit_shift = sizeof(ValueT) * 8;
|
||||
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
|
||||
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
|
||||
|
||||
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
|
||||
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
|
||||
|
||||
public:
|
||||
/// @returns the current value.
|
||||
ValueT value() const {
|
||||
return uint8_t(backlog[0]);
|
||||
public:
|
||||
/// @returns the current value.
|
||||
ValueT value() const {
|
||||
return uint8_t(backlog[0]);
|
||||
}
|
||||
|
||||
/// Advances to the next enqueued value.
|
||||
void advance() {
|
||||
for(size_t c = 0; c < backlog.size() - 1; c--) {
|
||||
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
|
||||
}
|
||||
backlog[backlog.size() - 1] >>= unit_shift;
|
||||
}
|
||||
|
||||
/// Advances to the next enqueued value.
|
||||
void advance() {
|
||||
for(size_t c = 0; c < backlog.size() - 1; c--) {
|
||||
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
|
||||
}
|
||||
backlog[backlog.size() - 1] >>= unit_shift;
|
||||
}
|
||||
|
||||
/// Inserts a new value, replacing whatever is currently at the end of the queue.
|
||||
void insert(ValueT value) {
|
||||
backlog[DeferredDepth / elements_per_uint32] =
|
||||
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
|
||||
}
|
||||
/// Inserts a new value, replacing whatever is currently at the end of the queue.
|
||||
void insert(const ValueT value) {
|
||||
backlog[DeferredDepth / elements_per_uint32] =
|
||||
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
|
||||
}
|
||||
};
|
||||
|
@ -35,251 +35,254 @@
|
||||
TODO: incorporate and codify AsyncJustInTimeActor.
|
||||
*/
|
||||
template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int divider = 1> class JustInTimeActor:
|
||||
public ClockingHint::Observer {
|
||||
private:
|
||||
/*!
|
||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
||||
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
|
||||
public ClockingHint::Observer
|
||||
{
|
||||
private:
|
||||
/*!
|
||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
||||
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
|
||||
|
||||
**Does not delete the object.**
|
||||
|
||||
This is used by the -> operators below, which provide a unique pointer to the enclosed object and
|
||||
update their sequence points upon its destruction — i.e. after the caller has made whatever call
|
||||
or calls as were relevant to the enclosed object.
|
||||
*/
|
||||
class SequencePointAwareDeleter {
|
||||
public:
|
||||
explicit SequencePointAwareDeleter(JustInTimeActor<T, LocalTimeScale, multiplier, divider> *actor) noexcept
|
||||
: actor_(actor) {}
|
||||
|
||||
forceinline void operator ()(const T *const) const {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
actor_->update_sequence_point();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
|
||||
};
|
||||
|
||||
// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
|
||||
using HalfRunFor = void (T::*const)(HalfCycles);
|
||||
static uint8_t half_sig(...);
|
||||
static uint16_t half_sig(HalfRunFor);
|
||||
using TargetTimeScale =
|
||||
std::conditional_t<
|
||||
sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
|
||||
HalfCycles,
|
||||
Cycles>;
|
||||
**Does not delete the object.**
|
||||
|
||||
This is used by the -> operators below, which provide a unique pointer to the enclosed object and
|
||||
update their sequence points upon its destruction — i.e. after the caller has made whatever call
|
||||
or calls as were relevant to the enclosed object.
|
||||
*/
|
||||
class SequencePointAwareDeleter {
|
||||
public:
|
||||
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
object_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds time to the actor.
|
||||
///
|
||||
/// @returns @c true if adding time caused a flush; @c false otherwise.
|
||||
forceinline bool operator += (LocalTimeScale rhs) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if(clocking_preference_ == ClockingHint::Preference::None) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (multiplier != 1) {
|
||||
time_since_update_ += rhs * multiplier;
|
||||
} else {
|
||||
time_since_update_ += rhs;
|
||||
}
|
||||
is_flushed_ = false;
|
||||
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if (clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
explicit SequencePointAwareDeleter(
|
||||
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor) noexcept
|
||||
: actor_(actor) {}
|
||||
|
||||
forceinline void operator ()(const T *const) const {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ -= rhs * multiplier;
|
||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||
time_overrun_ = time_until_event_ / divider;
|
||||
flush();
|
||||
update_sequence_point();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
///
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
/// Acts exactly as per the standard ->, but preserves constness.
|
||||
///
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
/// @returns a pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// @returns a const pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline const T *last_valid() const {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, in the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_;
|
||||
}
|
||||
return TargetTimeScale(time_since_update_.as_integral() / divider);
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
|
||||
/// converted to the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_ + offset;
|
||||
}
|
||||
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
///
|
||||
/// This does not affect this actor's record of when the next sequence point will occur.
|
||||
forceinline void flush() {
|
||||
if(!is_flushed_) {
|
||||
did_flush_ = is_flushed_ = true;
|
||||
if constexpr (divider == 1) {
|
||||
const auto duration = time_since_update_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
} else {
|
||||
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
|
||||
if(duration > TargetTimeScale(0))
|
||||
object_.run_for(duration);
|
||||
}
|
||||
actor_->update_sequence_point();
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether a flush has occurred since the last call to did_flush().
|
||||
[[nodiscard]] forceinline bool did_flush() {
|
||||
const bool did_flush = did_flush_;
|
||||
did_flush_ = false;
|
||||
return did_flush;
|
||||
}
|
||||
private:
|
||||
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
|
||||
};
|
||||
|
||||
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
|
||||
/// point from the final time at the end of the += that triggered the sequence point.
|
||||
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
|
||||
return time_overrun_;
|
||||
}
|
||||
// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
|
||||
using HalfRunFor = void (T::*const)(HalfCycles);
|
||||
static uint8_t half_sig(...);
|
||||
static uint16_t half_sig(HalfRunFor);
|
||||
using TargetTimeScale =
|
||||
std::conditional_t<
|
||||
sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
|
||||
HalfCycles,
|
||||
Cycles>;
|
||||
|
||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||
return time_until_event_ / divider;
|
||||
public:
|
||||
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
object_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
|
||||
[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
|
||||
if constexpr (!has_sequence_points<T>::value) {
|
||||
/// Adds time to the actor.
|
||||
///
|
||||
/// @returns @c true if adding time caused a flush; @c false otherwise.
|
||||
forceinline bool operator += (LocalTimeScale rhs) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if(clocking_preference_ == ClockingHint::Preference::None) {
|
||||
return false;
|
||||
}
|
||||
return rhs >= time_until_event_;
|
||||
}
|
||||
|
||||
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
|
||||
/// after @c duration, if that delay were to occur in @c offset units of time from now.
|
||||
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
|
||||
// A 1:1 mapping is easy.
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
return duration;
|
||||
if constexpr (multiplier != 1) {
|
||||
time_since_update_ += rhs * multiplier;
|
||||
} else {
|
||||
time_since_update_ += rhs;
|
||||
}
|
||||
is_flushed_ = false;
|
||||
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if (clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Work out when this query is placed, and the time to which it relates
|
||||
const auto base = time_since_update_ + offset * divider;
|
||||
const auto target = base + duration * divider;
|
||||
|
||||
// Figure out the number of whole input steps that is required to get
|
||||
// past target, and subtract the number of whole input steps necessary
|
||||
// to get to base.
|
||||
const auto steps_to_base = base.as_integral() / multiplier;
|
||||
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
|
||||
|
||||
return LocalTimeScale(steps_to_target - steps_to_base);
|
||||
}
|
||||
|
||||
/// Updates this template's record of the next sequence point.
|
||||
void update_sequence_point() {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
// Keep a fast path where no conversions will be applied; if conversions are
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.next_sequence_point();
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ -= rhs * multiplier;
|
||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||
time_overrun_ = time_until_event_ / divider;
|
||||
flush();
|
||||
update_sequence_point();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
///
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
/// Acts exactly as per the standard ->, but preserves constness.
|
||||
///
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
/// @returns a pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// @returns a const pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline const T *last_valid() const {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, in the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_;
|
||||
}
|
||||
return TargetTimeScale(time_since_update_.as_integral() / divider);
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
|
||||
/// converted to the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_ + offset;
|
||||
}
|
||||
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
///
|
||||
/// This does not affect this actor's record of when the next sequence point will occur.
|
||||
forceinline void flush() {
|
||||
if(!is_flushed_) {
|
||||
did_flush_ = is_flushed_ = true;
|
||||
if constexpr (divider == 1) {
|
||||
const auto duration = time_since_update_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
} else {
|
||||
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
|
||||
if(duration > TargetTimeScale(0))
|
||||
object_.run_for(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether a flush has occurred since the last call to did_flush().
|
||||
[[nodiscard]] forceinline bool did_flush() {
|
||||
const bool did_flush = did_flush_;
|
||||
did_flush_ = false;
|
||||
return did_flush;
|
||||
}
|
||||
|
||||
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
|
||||
/// point from the final time at the end of the += that triggered the sequence point.
|
||||
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
|
||||
return time_overrun_;
|
||||
}
|
||||
|
||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||
return time_until_event_ / divider;
|
||||
}
|
||||
|
||||
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
|
||||
[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
|
||||
if constexpr (!has_sequence_points<T>::value) {
|
||||
return false;
|
||||
}
|
||||
return rhs >= time_until_event_;
|
||||
}
|
||||
|
||||
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
|
||||
/// after @c duration, if that delay were to occur in @c offset units of time from now.
|
||||
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
|
||||
// A 1:1 mapping is easy.
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
return duration;
|
||||
}
|
||||
|
||||
// Work out when this query is placed, and the time to which it relates
|
||||
const auto base = time_since_update_ + offset * divider;
|
||||
const auto target = base + duration * divider;
|
||||
|
||||
// Figure out the number of whole input steps that is required to get
|
||||
// past target, and subtract the number of whole input steps necessary
|
||||
// to get to base.
|
||||
const auto steps_to_base = base.as_integral() / multiplier;
|
||||
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
|
||||
|
||||
return LocalTimeScale(steps_to_target - steps_to_base);
|
||||
}
|
||||
|
||||
/// Updates this template's record of the next sequence point.
|
||||
void update_sequence_point() {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
// Keep a fast path where no conversions will be applied; if conversions are
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
const auto time = object_.next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
time_until_event_ = time * divider;
|
||||
}
|
||||
time_until_event_ = time * divider;
|
||||
}
|
||||
assert(time_until_event_ > LocalTimeScale(0));
|
||||
}
|
||||
assert(time_until_event_ > LocalTimeScale(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns A cached copy of the object's clocking preference.
|
||||
ClockingHint::Preference clocking_preference() const {
|
||||
return clocking_preference_;
|
||||
}
|
||||
/// @returns A cached copy of the object's clocking preference.
|
||||
ClockingHint::Preference clocking_preference() const {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
|
||||
bool is_flushed_ = true;
|
||||
bool did_flush_ = false;
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
|
||||
bool is_flushed_ = true;
|
||||
bool did_flush_ = false;
|
||||
|
||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
|
||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
||||
template <typename S>
|
||||
struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
|
||||
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -288,49 +291,50 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
Any time the amount of accumulated time crosses a threshold provided at construction time,
|
||||
the object will be updated on the AsyncTaskQueue.
|
||||
*/
|
||||
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
object_(std::forward<Args>(args)...),
|
||||
threshold_(threshold) {}
|
||||
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale>
|
||||
class AsyncJustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
object_(std::forward<Args>(args)...),
|
||||
threshold_(threshold) {}
|
||||
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
time_since_update_ += rhs;
|
||||
if(time_since_update_ >= threshold_) {
|
||||
time_since_update_ -= threshold_;
|
||||
task_queue_.enqueue([this] () {
|
||||
object_.run_for(threshold_);
|
||||
});
|
||||
}
|
||||
is_flushed_ = false;
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
time_since_update_ += rhs;
|
||||
if(time_since_update_ >= threshold_) {
|
||||
time_since_update_ -= threshold_;
|
||||
task_queue_.enqueue([this] () {
|
||||
object_.run_for(threshold_);
|
||||
});
|
||||
}
|
||||
is_flushed_ = false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
inline T *operator->() {
|
||||
flush();
|
||||
return &object_;
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
inline T *operator->() {
|
||||
flush();
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
inline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
inline void flush() {
|
||||
if(!is_flushed_) {
|
||||
task_queue_.flush();
|
||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
is_flushed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
inline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
inline void flush() {
|
||||
if(!is_flushed_) {
|
||||
task_queue_.flush();
|
||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
is_flushed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue<true> task_queue_;
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue<true> task_queue_;
|
||||
};
|
||||
|
@ -21,65 +21,65 @@ namespace Time {
|
||||
of time, to bring it into phase.
|
||||
*/
|
||||
class ScanSynchroniser {
|
||||
public:
|
||||
/*!
|
||||
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
|
||||
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
|
||||
*/
|
||||
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
|
||||
ratio_ = 1.0;
|
||||
if(scan_status.field_duration_gradient < 0.00001) {
|
||||
// Check out the machine's current frame time.
|
||||
// If it's within 3% of a non-zero integer multiple of the
|
||||
// display rate, mark this time window to be split over the sync.
|
||||
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
|
||||
const double integer_ratio = round(ratio_);
|
||||
if(integer_ratio > 0.0) {
|
||||
ratio_ /= integer_ratio;
|
||||
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
|
||||
}
|
||||
public:
|
||||
/*!
|
||||
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
|
||||
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
|
||||
*/
|
||||
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, const double frame_duration) {
|
||||
ratio_ = 1.0;
|
||||
if(scan_status.field_duration_gradient < 0.00001) {
|
||||
// Check out the machine's current frame time.
|
||||
// If it's within 3% of a non-zero integer multiple of the
|
||||
// display rate, mark this time window to be split over the sync.
|
||||
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
|
||||
const double integer_ratio = round(ratio_);
|
||||
if(integer_ratio > 0.0) {
|
||||
ratio_ /= integer_ratio;
|
||||
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
|
||||
Results are undefined if @c can_synchroise returned @c false.
|
||||
*/
|
||||
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
|
||||
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
|
||||
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
|
||||
// no benefit to second guessing it here — just take it to be correct.
|
||||
//
|
||||
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
|
||||
// So the set speed multiplier may be adjusted slightly to aim for that.
|
||||
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
|
||||
if(scan_status.current_position > 0.0) {
|
||||
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
|
||||
else speed_multiplier *= phase_adjustment_ratio;
|
||||
}
|
||||
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
|
||||
return speed_multiplier_ * base_multiplier_;
|
||||
/*!
|
||||
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
|
||||
Results are undefined if @c can_synchroise returned @c false.
|
||||
*/
|
||||
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
|
||||
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
|
||||
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
|
||||
// no benefit to second guessing it here — just take it to be correct.
|
||||
//
|
||||
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
|
||||
// So the set speed multiplier may be adjusted slightly to aim for that.
|
||||
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
|
||||
if(scan_status.current_position > 0.0) {
|
||||
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
|
||||
else speed_multiplier *= phase_adjustment_ratio;
|
||||
}
|
||||
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
|
||||
return speed_multiplier_ * base_multiplier_;
|
||||
}
|
||||
|
||||
void set_base_speed_multiplier(double multiplier) {
|
||||
base_multiplier_ = multiplier;
|
||||
}
|
||||
void set_base_speed_multiplier(const double multiplier) {
|
||||
base_multiplier_ = multiplier;
|
||||
}
|
||||
|
||||
double get_base_speed_multiplier() {
|
||||
return base_multiplier_;
|
||||
}
|
||||
double get_base_speed_multiplier() const {
|
||||
return base_multiplier_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double maximum_rate_adjustment = 1.03;
|
||||
static constexpr double phase_adjustment_ratio = 1.005;
|
||||
private:
|
||||
static constexpr double maximum_rate_adjustment = 1.03;
|
||||
static constexpr double phase_adjustment_ratio = 1.005;
|
||||
|
||||
// Managed local state.
|
||||
double speed_multiplier_ = 1.0;
|
||||
double base_multiplier_ = 1.0;
|
||||
// Managed local state.
|
||||
double speed_multiplier_ = 1.0;
|
||||
double base_multiplier_ = 1.0;
|
||||
|
||||
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
|
||||
double ratio_ = 1.0;
|
||||
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
|
||||
double ratio_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ typedef double Seconds;
|
||||
typedef int64_t Nanos;
|
||||
|
||||
inline Nanos nanos_now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch()
|
||||
).count();
|
||||
}
|
||||
|
||||
inline Seconds seconds(Nanos nanos) {
|
||||
|
@ -21,132 +21,132 @@ namespace Time {
|
||||
(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
|
||||
*/
|
||||
class VSyncPredictor {
|
||||
public:
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has begun.
|
||||
*/
|
||||
void begin_redraw() {
|
||||
redraw_begin_time_ = nanos_now();
|
||||
public:
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has begun.
|
||||
*/
|
||||
void begin_redraw() {
|
||||
redraw_begin_time_ = nanos_now();
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has ended;
|
||||
the predictor will use the amount of time between each begin/end pair to modify
|
||||
its expectations as to how long it takes to draw a frame.
|
||||
*/
|
||||
void end_redraw() {
|
||||
redraw_period_.post(nanos_now() - redraw_begin_time_);
|
||||
}
|
||||
|
||||
/*!
|
||||
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
|
||||
machine calls retrace is now. The predictor uses these notifications to estimate output
|
||||
frame rate.
|
||||
*/
|
||||
void announce_vsync() {
|
||||
const auto now = nanos_now();
|
||||
|
||||
if(last_vsync_) {
|
||||
last_vsync_ += frame_duration_;
|
||||
vsync_jitter_.post(last_vsync_ - now);
|
||||
last_vsync_ = (last_vsync_ + now) >> 1;
|
||||
} else {
|
||||
last_vsync_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has ended;
|
||||
the predictor will use the amount of time between each begin/end pair to modify
|
||||
its expectations as to how long it takes to draw a frame.
|
||||
*/
|
||||
void end_redraw() {
|
||||
redraw_period_.post(nanos_now() - redraw_begin_time_);
|
||||
}
|
||||
/*!
|
||||
Sets the frame rate for the target display.
|
||||
*/
|
||||
void set_frame_rate(float rate) {
|
||||
frame_duration_ = Nanos(1'000'000'000.0f / rate);
|
||||
}
|
||||
|
||||
/*!
|
||||
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
|
||||
machine calls retrace is now. The predictor uses these notifications to estimate output
|
||||
frame rate.
|
||||
*/
|
||||
void announce_vsync() {
|
||||
const auto now = nanos_now();
|
||||
/*!
|
||||
@returns The time this class currently believes a whole frame occupies.
|
||||
*/
|
||||
Time::Nanos frame_duration() {
|
||||
return frame_duration_;
|
||||
}
|
||||
|
||||
if(last_vsync_) {
|
||||
last_vsync_ += frame_duration_;
|
||||
vsync_jitter_.post(last_vsync_ - now);
|
||||
last_vsync_ = (last_vsync_ + now) >> 1;
|
||||
} else {
|
||||
last_vsync_ = now;
|
||||
/*!
|
||||
Adds a record of how much jitter was experienced in scheduling; these values will be
|
||||
factored into the @c suggested_draw_time if supplied.
|
||||
|
||||
A positive number means the timer occurred late. A negative number means it occurred early.
|
||||
*/
|
||||
void add_timer_jitter(Time::Nanos jitter) {
|
||||
timer_jitter_.post(jitter);
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the vsync predictor that output is now paused. This ends frame period
|
||||
calculations until the next announce_vsync() restarts frame-length counting.
|
||||
*/
|
||||
void pause() {
|
||||
last_vsync_ = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@return The time at which redrawing should begin, given the predicted frame period, how
|
||||
long it appears to take to draw a frame and how much jitter there is in scheduling
|
||||
(if those figures are being supplied).
|
||||
*/
|
||||
Nanos suggested_draw_time() {
|
||||
const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
|
||||
const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
|
||||
|
||||
// Permit three standard deviations from the mean, to cover 99.9% of cases.
|
||||
const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
|
||||
|
||||
return last_vsync_ + frame_duration_ - period;
|
||||
}
|
||||
|
||||
private:
|
||||
class VarianceCollector {
|
||||
public:
|
||||
VarianceCollector(Time::Nanos default_value) {
|
||||
sum_ = default_value * 128;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
history_[c] = default_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the frame rate for the target display.
|
||||
*/
|
||||
void set_frame_rate(float rate) {
|
||||
frame_duration_ = Nanos(1'000'000'000.0f / rate);
|
||||
}
|
||||
void post(Time::Nanos value) {
|
||||
sum_ -= history_[write_pointer_];
|
||||
sum_ += value;
|
||||
history_[write_pointer_] = value;
|
||||
write_pointer_ = (write_pointer_ + 1) & 127;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The time this class currently believes a whole frame occupies.
|
||||
*/
|
||||
Time::Nanos frame_duration() {
|
||||
return frame_duration_;
|
||||
}
|
||||
Time::Nanos mean() {
|
||||
return sum_ / 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds a record of how much jitter was experienced in scheduling; these values will be
|
||||
factored into the @c suggested_draw_time if supplied.
|
||||
|
||||
A positive number means the timer occurred late. A negative number means it occurred early.
|
||||
*/
|
||||
void add_timer_jitter(Time::Nanos jitter) {
|
||||
timer_jitter_.post(jitter);
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the vsync predictor that output is now paused. This ends frame period
|
||||
calculations until the next announce_vsync() restarts frame-length counting.
|
||||
*/
|
||||
void pause() {
|
||||
last_vsync_ = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@return The time at which redrawing should begin, given the predicted frame period, how
|
||||
long it appears to take to draw a frame and how much jitter there is in scheduling
|
||||
(if those figures are being supplied).
|
||||
*/
|
||||
Nanos suggested_draw_time() {
|
||||
const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
|
||||
const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
|
||||
|
||||
// Permit three standard deviations from the mean, to cover 99.9% of cases.
|
||||
const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
|
||||
|
||||
return last_vsync_ + frame_duration_ - period;
|
||||
}
|
||||
|
||||
private:
|
||||
class VarianceCollector {
|
||||
public:
|
||||
VarianceCollector(Time::Nanos default_value) {
|
||||
sum_ = default_value * 128;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
history_[c] = default_value;
|
||||
}
|
||||
Time::Nanos variance() {
|
||||
// I haven't yet come up with a better solution that calculating this
|
||||
// in whole every time, given the way that the mean mutates.
|
||||
Time::Nanos variance = 0;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const auto difference = ((history_[c] * 128) - sum_) / 128;
|
||||
variance += (difference * difference);
|
||||
}
|
||||
return variance / 128;
|
||||
}
|
||||
|
||||
void post(Time::Nanos value) {
|
||||
sum_ -= history_[write_pointer_];
|
||||
sum_ += value;
|
||||
history_[write_pointer_] = value;
|
||||
write_pointer_ = (write_pointer_ + 1) & 127;
|
||||
}
|
||||
private:
|
||||
Time::Nanos sum_;
|
||||
Time::Nanos history_[128];
|
||||
size_t write_pointer_ = 0;
|
||||
};
|
||||
|
||||
Time::Nanos mean() {
|
||||
return sum_ / 128;
|
||||
}
|
||||
Nanos redraw_begin_time_ = 0;
|
||||
Nanos last_vsync_ = 0;
|
||||
Nanos frame_duration_ = 1'000'000'000 / 60;
|
||||
|
||||
Time::Nanos variance() {
|
||||
// I haven't yet come up with a better solution that calculating this
|
||||
// in whole every time, given the way that the mean mutates.
|
||||
Time::Nanos variance = 0;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const auto difference = ((history_[c] * 128) - sum_) / 128;
|
||||
variance += (difference * difference);
|
||||
}
|
||||
return variance / 128;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Nanos sum_;
|
||||
Time::Nanos history_[128];
|
||||
size_t write_pointer_ = 0;
|
||||
};
|
||||
|
||||
Nanos redraw_begin_time_ = 0;
|
||||
Nanos last_vsync_ = 0;
|
||||
Nanos frame_duration_ = 1'000'000'000 / 60;
|
||||
|
||||
VarianceCollector vsync_jitter_{0};
|
||||
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
|
||||
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
|
||||
VarianceCollector vsync_jitter_{0};
|
||||
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
|
||||
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user