1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-12 02:24:31 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
Thomas Harte
cad42beef4 Roll in some random style improvements. 2025-09-08 20:38:50 -04:00
Thomas Harte
96fd0b7892 Merge pull request #1553 from TomHarte/IndentationSomeMore
Further reduce indentation.
2025-09-05 23:20:19 -04:00
Thomas Harte
6f1db15d7c Further reduce indentation. 2025-09-05 23:07:45 -04:00
Thomas Harte
1854296ee8 Merge pull request #1552 from TomHarte/ElectronIDE
Reduce code duplication within the ROM catalogue.
2025-09-05 22:45:55 -04:00
Thomas Harte
515cc5f326 Correct spelling. 2025-09-05 22:09:38 -04:00
Thomas Harte
091be7eafe Remove unused header. 2025-09-05 22:03:45 -04:00
Thomas Harte
27a19ea417 Eliminate line-length violations. 2025-09-05 22:03:19 -04:00
Thomas Harte
9a5e9af67c Standardise layout. 2025-09-05 22:00:42 -04:00
Thomas Harte
3a493f2428 Merge pull request #1551 from TomHarte/LogLevels
Allow logging of errors but not info.
2025-09-05 21:04:56 -04:00
Thomas Harte
ca6e34f4b4 Fix dangling OpenGL accesses. 2025-09-05 19:30:33 -04:00
Thomas Harte
e1e68312c4 Transcribe remaining catalogue entries. 2025-09-05 17:23:38 -04:00
Thomas Harte
c7ff2cece4 Head in search of a more-compact form. 2025-09-05 16:55:00 -04:00
Thomas Harte
8e6f4fa36f Fix NDEBUG route. 2025-09-05 14:34:08 -04:00
31 changed files with 4762 additions and 3928 deletions

View File

@@ -23,10 +23,10 @@ struct Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> { class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>; friend Configurable::DisplayOption<Options>;
public: public:
Options(Configurable::OptionsType type) : Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour) {} Configurable::Display::SVideo : Configurable::Display::CompositeColour) {}
private: private:
Options() : Options(Configurable::OptionsType::UserFriendly) {} Options() : Options(Configurable::OptionsType::UserFriendly) {}

View File

@@ -44,37 +44,37 @@ namespace {
Log::Logger<Log::Source::Plus4> logger; Log::Logger<Log::Source::Plus4> logger;
class Joystick: public Inputs::ConcreteJoystick { class Joystick: public Inputs::ConcreteJoystick {
public: public:
Joystick() : Joystick() :
ConcreteJoystick({ ConcreteJoystick({
Input(Input::Up), Input(Input::Up),
Input(Input::Down), Input(Input::Down),
Input(Input::Left), Input(Input::Left),
Input(Input::Right), Input(Input::Right),
Input(Input::Fire) Input(Input::Fire)
}) {} }) {}
void did_set_input(const Input &digital_input, bool is_active) final { void did_set_input(const Input &digital_input, bool is_active) final {
const auto apply = [&](uint8_t mask) { const auto apply = [&](uint8_t mask) {
if(is_active) mask_ &= ~mask; else mask_ |= mask; if(is_active) mask_ &= ~mask; else mask_ |= mask;
}; };
switch(digital_input.type) { switch(digital_input.type) {
default: return; default: return;
case Input::Right: apply(0x08); break; case Input::Right: apply(0x08); break;
case Input::Left: apply(0x04); break; case Input::Left: apply(0x04); break;
case Input::Down: apply(0x02); break; case Input::Down: apply(0x02); break;
case Input::Up: apply(0x01); break; case Input::Up: apply(0x01); break;
case Input::Fire: apply(0xc0); break; case Input::Fire: apply(0xc0); break;
}
} }
}
uint8_t mask() const { uint8_t mask() const {
return mask_; return mask_;
} }
private: private:
uint8_t mask_ = 0xff; uint8_t mask_ = 0xff;
}; };
class Timers { class Timers {

View File

@@ -13,35 +13,35 @@
namespace MSX::Cartridge { namespace MSX::Cartridge {
class ASCII16kbROMSlotHandler: public MemorySlotHandler { class ASCII16kbROMSlotHandler: public MemorySlotHandler {
public: public:
ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) { switch(address >> 11) {
default: default:
if(pc_is_outside_bios) confidence_counter_.add_miss(); if(pc_is_outside_bios) confidence_counter_.add_miss();
break; break;
case 0xc: case 0xc:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x6000);
} }
slot_.map(value * 0x4000, 0x4000, 0x4000); slot_.map(value * 0x4000, 0x4000, 0x4000);
break; break;
case 0xe: case 0xe:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x7000 || address == 0x77ff);
} }
slot_.map(value * 0x4000, 0x8000, 0x4000); slot_.map(value * 0x4000, 0x8000, 0x4000);
break; break;
}
} }
}
virtual std::string debug_type() final { virtual std::string debug_type() final {
return "A16"; return "A16";
} }
private: private:
MSX::MemorySlot &slot_; MSX::MemorySlot &slot_;
}; };
} }

View File

@@ -13,47 +13,47 @@
namespace MSX::Cartridge { namespace MSX::Cartridge {
class ASCII8kbROMSlotHandler: public MemorySlotHandler { class ASCII8kbROMSlotHandler: public MemorySlotHandler {
public: public:
ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) { switch(address >> 11) {
default: default:
if(pc_is_outside_bios) confidence_counter_.add_miss(); if(pc_is_outside_bios) confidence_counter_.add_miss();
break; break;
case 0xc: case 0xc:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x6000 || address == 0x60ff);
} }
slot_.map(value * 0x2000, 0x4000, 0x2000); slot_.map(value * 0x2000, 0x4000, 0x2000);
break; break;
case 0xd: case 0xd:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x6800 || address == 0x68ff);
} }
slot_.map(value * 0x2000, 0x6000, 0x2000); slot_.map(value * 0x2000, 0x6000, 0x2000);
break; break;
case 0xe: case 0xe:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x7000 || address == 0x70ff);
} }
slot_.map(value * 0x2000, 0x8000, 0x2000); slot_.map(value * 0x2000, 0x8000, 0x2000);
break; break;
case 0xf: case 0xf:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x7800 || address == 0x78ff);
} }
slot_.map(value * 0x2000, 0xa000, 0x2000); slot_.map(value * 0x2000, 0xa000, 0x2000);
break; break;
}
} }
}
virtual std::string debug_type() final { virtual std::string debug_type() final {
return "A8"; return "A8";
} }
private: private:
MSX::MemorySlot &slot_; MSX::MemorySlot &slot_;
}; };
} }

View File

@@ -13,39 +13,40 @@
namespace MSX::Cartridge { namespace MSX::Cartridge {
class KonamiROMSlotHandler: public MemorySlotHandler { class KonamiROMSlotHandler: public MemorySlotHandler {
public: public:
KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 13) { switch(address >> 13) {
default: default:
if(pc_is_outside_bios) confidence_counter_.add_miss(); if(pc_is_outside_bios) confidence_counter_.add_miss();
break; break;
case 3: case 3:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x6000);
} }
slot_.map(value * 0x2000, 0x6000, 0x2000); slot_.map(value * 0x2000, 0x6000, 0x2000);
break; break;
case 4: case 4:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0x8000);
} }
slot_.map(value * 0x2000, 0x8000, 0x2000); slot_.map(value * 0x2000, 0x8000, 0x2000);
break; break;
case 5: case 5:
if(pc_is_outside_bios) { if(pc_is_outside_bios) {
if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); hit_or_equivocal(address == 0xa000);
} }
slot_.map(value * 0x2000, 0xa000, 0x2000); slot_.map(value * 0x2000, 0xa000, 0x2000);
break; break;
}
} }
}
virtual std::string debug_type() final { virtual std::string debug_type() final {
return "K"; return "K";
} }
private:
private:
MSX::MemorySlot &slot_; MSX::MemorySlot &slot_;
}; };

View File

@@ -14,73 +14,73 @@
namespace MSX::Cartridge { namespace MSX::Cartridge {
class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { class KonamiWithSCCROMSlotHandler: public MemorySlotHandler {
public: public:
KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) : KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) :
slot_(slot), scc_(scc) {} slot_(slot), scc_(scc) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) { switch(address >> 11) {
default: default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0x0a:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x5000);
}
slot_.map(value * 0x2000, 0x4000, 0x2000);
break;
case 0x0e:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x7000);
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0x12:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x9000);
}
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
slot_.map_handler(0x8000, 0x2000);
} else {
scc_is_visible_ = false;
slot_.map(value * 0x2000, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) {
if(pc_is_outside_bios) confidence_counter_.add_hit();
scc_.write(address, value);
} else {
if(pc_is_outside_bios) confidence_counter_.add_miss(); if(pc_is_outside_bios) confidence_counter_.add_miss();
break; }
case 0x0a: break;
if(pc_is_outside_bios) { case 0x16:
if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); if(pc_is_outside_bios) {
} hit_or_equivocal(address == 0xb000);
slot_.map(value * 0x2000, 0x4000, 0x2000); }
break; slot_.map(value * 0x2000, 0xa000, 0x2000);
case 0x0e: break;
if(pc_is_outside_bios) {
if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0x12:
if(pc_is_outside_bios) {
if(address == 0x9000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
slot_.map_handler(0x8000, 0x2000);
} else {
scc_is_visible_ = false;
slot_.map(value * 0x2000, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) {
if(pc_is_outside_bios) confidence_counter_.add_hit();
scc_.write(address, value);
} else {
if(pc_is_outside_bios) confidence_counter_.add_miss();
}
break;
case 0x16:
if(pc_is_outside_bios) {
if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
} }
}
uint8_t read(uint16_t address) final { uint8_t read(uint16_t address) final {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) { if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
confidence_counter_.add_hit(); confidence_counter_.add_hit();
return scc_.read(address); return scc_.read(address);
}
confidence_counter_.add_miss();
return 0xff;
} }
confidence_counter_.add_miss();
return 0xff;
}
virtual std::string debug_type() final { virtual std::string debug_type() final {
return "KSCC"; return "KSCC";
} }
private: private:
MSX::MemorySlot &slot_; MSX::MemorySlot &slot_;
Konami::SCC &scc_; Konami::SCC &scc_;
bool scc_is_visible_ = false; bool scc_is_visible_ = false;
}; };
} }

View File

@@ -21,22 +21,22 @@
namespace MSX { namespace MSX {
class DiskROM: public MemorySlotHandler, public WD::WD1770 { class DiskROM: public MemorySlotHandler, public WD::WD1770 {
public: public:
DiskROM(MSX::MemorySlot &slot); DiskROM(MSX::MemorySlot &slot);
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final; void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final;
uint8_t read(uint16_t address) final; uint8_t read(uint16_t address) final;
void run_for(HalfCycles half_cycles) final; void run_for(HalfCycles half_cycles) final;
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive); void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_activity_observer(Activity::Observer *observer); void set_activity_observer(Activity::Observer *observer);
private: private:
const std::vector<uint8_t> &rom_; const std::vector<uint8_t> &rom_;
long int controller_cycles_ = 0; long int controller_cycles_ = 0;
void set_head_load_request(bool head_load) final; void set_head_load_request(bool head_load) final;
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@@ -34,115 +34,122 @@ struct MemorySlotChangeHandler {
}; };
class MemorySlot { class MemorySlot {
public: public:
MemorySlot(MemorySlotChangeHandler &); MemorySlot(MemorySlotChangeHandler &);
/// @returns A pointer to the area of memory currently underneath @c address that /// @returns A pointer to the area of memory currently underneath @c address that
/// should be read /// should be read
const uint8_t *read_pointer(int segment) const; const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address. /// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const; uint8_t *write_pointer(int segment) const;
/// Copies an underlying source buffer. /// Copies an underlying source buffer.
void set_source(const std::vector<uint8_t> &source); void set_source(const std::vector<uint8_t> &source);
/// Sets the size of the underlying source buffer. /// Sets the size of the underlying source buffer.
void resize_source(std::size_t); void resize_source(std::size_t);
/// Provides a reference to the internal source storage. /// Provides a reference to the internal source storage.
std::vector<uint8_t> &source(); std::vector<uint8_t> &source();
const std::vector<uint8_t> &source() const; const std::vector<uint8_t> &source() const;
enum AccessType { enum AccessType {
Read, Read,
ReadWrite ReadWrite
}; };
/// Maps the content from @c source_address in the buffer previously /// Maps the content from @c source_address in the buffer previously
/// supplied to @c set_source to the region indicated by /// supplied to @c set_source to the region indicated by
/// @c destination_address and @c length within @c subslot. /// @c destination_address and @c length within @c subslot.
template <AccessType type = AccessType::Read> void map( template <AccessType type = AccessType::Read> void map(
std::size_t source_address, std::size_t source_address,
uint16_t destination_address, uint16_t destination_address,
std::size_t length); std::size_t length);
/// Marks the region indicated by @c destination_address and @c length /// Marks the region indicated by @c destination_address and @c length
/// as requiring calls into this slot's MemorySlotHandler. /// as requiring calls into this slot's MemorySlotHandler.
void map_handler( void map_handler(
uint16_t destination_address, uint16_t destination_address,
std::size_t length); std::size_t length);
/// Marks the region indicated by @c destination_address and @c length /// Marks the region indicated by @c destination_address and @c length
/// as unoccupied. /// as unoccupied.
void unmap( void unmap(
uint16_t destination_address, uint16_t destination_address,
std::size_t length); std::size_t length);
private: private:
std::vector<uint8_t> source_; std::vector<uint8_t> source_;
uint8_t *read_pointers_[8]; uint8_t *read_pointers_[8];
uint8_t *write_pointers_[8]; uint8_t *write_pointers_[8];
MemorySlotChangeHandler &handler_; MemorySlotChangeHandler &handler_;
using MemoryChunk = std::array<uint8_t, 8192>; using MemoryChunk = std::array<uint8_t, 8192>;
inline static MemoryChunk unmapped{0xff}; inline static MemoryChunk unmapped{0xff};
inline static MemoryChunk scratch; inline static MemoryChunk scratch;
}; };
class PrimarySlot { class PrimarySlot {
public: public:
PrimarySlot(MemorySlotChangeHandler &); PrimarySlot(MemorySlotChangeHandler &);
/// @returns A pointer to the area of memory currently underneath @c address that /// @returns A pointer to the area of memory currently underneath @c address that
/// should be read /// should be read
const uint8_t *read_pointer(int segment) const; const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address. /// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const; uint8_t *write_pointer(int segment) const;
/// Attempts to write the argument as the secondary paging selection. /// Attempts to write the argument as the secondary paging selection.
void set_secondary_paging(uint8_t); void set_secondary_paging(uint8_t);
/// @returns The value most recently provided to @c set_secondary_paging. /// @returns The value most recently provided to @c set_secondary_paging.
uint8_t secondary_paging() const; uint8_t secondary_paging() const;
/// Indicates whether this slot supports secondary paging. /// Indicates whether this slot supports secondary paging.
bool supports_secondary_paging = false; bool supports_secondary_paging = false;
/// Provides the subslot at the specified index. /// Provides the subslot at the specified index.
MemorySlot &subslot(int); MemorySlot &subslot(int);
private: private:
MemorySlot subslots_[4]; MemorySlot subslots_[4];
uint8_t secondary_paging_ = 0; uint8_t secondary_paging_ = 0;
}; };
class MemorySlotHandler { class MemorySlotHandler {
public: public:
virtual ~MemorySlotHandler() = default; virtual ~MemorySlotHandler() = default;
/*! Advances time by @c half_cycles. */ /*! Advances time by @c half_cycles. */
virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {} virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {}
/*! Announces an attempt to write @c value to @c address. */ /*! Announces an attempt to write @c value to @c address. */
virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0; virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0;
/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */
virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; }
/*! @returns The probability that this handler is correct for the data it owns. */ /*! @returns The probability that this handler is correct for the data it owns. */
float get_confidence() { float get_confidence() {
return confidence_counter_.confidence(); return confidence_counter_.confidence();
}
virtual std::string debug_type() {
return "";
}
protected:
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
void hit_or_equivocal(const bool is_hit) {
if(is_hit) {
confidence_counter_.add_hit();
} else {
confidence_counter_.add_equivocal();
} }
}
virtual std::string debug_type() {
return "";
}
protected:
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
}; };
} }

View File

@@ -40,42 +40,42 @@ namespace Sega {
namespace MasterSystem { namespace MasterSystem {
class Joystick: public Inputs::ConcreteJoystick { class Joystick: public Inputs::ConcreteJoystick {
public: public:
Joystick() : Joystick() :
ConcreteJoystick({ ConcreteJoystick({
Input(Input::Up), Input(Input::Up),
Input(Input::Down), Input(Input::Down),
Input(Input::Left), Input(Input::Left),
Input(Input::Right), Input(Input::Right),
Input(Input::Fire, 0), Input(Input::Fire, 0),
Input(Input::Fire, 1) Input(Input::Fire, 1)
}) {} }) {}
void did_set_input(const Input &digital_input, bool is_active) final { void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) { switch(digital_input.type) {
default: return; default: return;
case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break; case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break;
case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break; case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break;
case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break; case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break;
case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break; case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break;
case Input::Fire: case Input::Fire:
switch(digital_input.info.control.index) { switch(digital_input.info.control.index) {
default: break; default: break;
case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break; case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break;
case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break; case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break;
} }
break; break;
}
} }
}
uint8_t get_state() { uint8_t get_state() {
return state_; return state_;
} }
private: private:
uint8_t state_ = 0xff; uint8_t state_ = 0xff;
}; };
template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine: template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
@@ -90,282 +90,282 @@ template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
public MachineTypes::ScanProducer, public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine { public MachineTypes::TimedMachine {
public: public:
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
region_(target.region), region_(target.region),
paging_scheme_(target.paging_scheme), paging_scheme_(target.paging_scheme),
z80_(*this), z80_(*this),
sn76489_( sn76489_(
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS, (target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_, audio_queue_,
audio_divider), audio_divider),
opll_(audio_queue_, audio_divider), opll_(audio_queue_, audio_divider),
mixer_(sn76489_, opll_), mixer_(sn76489_, opll_),
speaker_(mixer_), speaker_(mixer_),
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) { keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
// Pick the clock rate based on the region. // Pick the clock rate based on the region.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0; const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
speaker_.set_input_rate(float(clock_rate / audio_divider)); speaker_.set_input_rate(float(clock_rate / audio_divider));
set_clock_rate(clock_rate); set_clock_rate(clock_rate);
// Instantiate the joysticks. // Instantiate the joysticks.
joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick);
// Clear the memory map. // Clear the memory map.
map(read_pointers_, nullptr, 0x10000, 0); map(read_pointers_, nullptr, 0x10000, 0);
map(write_pointers_, nullptr, 0x10000, 0); map(write_pointers_, nullptr, 0x10000, 0);
// Take a copy of the cartridge and place it into memory. // Take a copy of the cartridge and place it into memory.
if(!target.media.cartridges.empty()) { if(!target.media.cartridges.empty()) {
cartridge_ = target.media.cartridges[0]->get_segments()[0].data; cartridge_ = target.media.cartridges[0]->get_segments()[0].data;
} }
if(cartridge_.size() < 48*1024) { if(cartridge_.size() < 48*1024) {
std::size_t new_space = 48*1024 - cartridge_.size(); std::size_t new_space = 48*1024 - cartridge_.size();
cartridge_.resize(48*1024); cartridge_.resize(48*1024);
memset(&cartridge_[48*1024 - new_space], 0xff, new_space); memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
}
if(paging_scheme_ == Target::PagingScheme::Codemasters) {
// The Codemasters cartridges start with pages 0, 1 and 0 again initially visible.
paging_registers_[0] = 0;
paging_registers_[1] = 1;
paging_registers_[2] = 0;
}
// Load the BIOS if available.
//
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
// CRC32 catalogue of those. So far:
//
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();
// Map RAM.
if constexpr (is_master_system(model)) {
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
} else {
map(read_pointers_, ram_, 1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
}
// Apply a relatively low low-pass filter. More guidance needed here.
// TODO: this is disabled for now since it isn't applicable for the FM chip, I think.
// speaker_.set_high_frequency_cutoff(8000);
// Set default mixer levels: FM off, SN full-throttle.
set_mixer_levels(0);
keyboard_.set_delegate(this);
} }
~ConcreteMachine() { if(paging_scheme_ == Target::PagingScheme::Codemasters) {
audio_queue_.flush(); // The Codemasters cartridges start with pages 0, 1 and 0 again initially visible.
paging_registers_[0] = 0;
paging_registers_[1] = 1;
paging_registers_[2] = 0;
} }
ChangeEffect effect_for_file_did_change(const std::string &) const final { // Load the BIOS if available.
return ChangeEffect::RestartMachine; //
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
// CRC32 catalogue of those. So far:
//
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();
// Map RAM.
if constexpr (is_master_system(model)) {
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
} else {
map(read_pointers_, ram_, 1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
} }
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { // Apply a relatively low low-pass filter. More guidance needed here.
vdp_.last_valid()->set_tv_standard( // TODO: this is disabled for now since it isn't applicable for the FM chip, I think.
(region_ == Target::Region::Europe) ? // speaker_.set_high_frequency_cutoff(8000);
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
// Doing the following would be technically correct, but isn't // Set default mixer levels: FM off, SN full-throttle.
// especially thread-safe and won't make a substantial difference. set_mixer_levels(0);
// time_until_debounce_ = vdp_->get_time_until_line(-1);
vdp_.last_valid()->set_scan_target(scan_target); keyboard_.set_delegate(this);
}
~ConcreteMachine() {
audio_queue_.flush();
}
ChangeEffect effect_for_file_did_change(const std::string &) const final {
return ChangeEffect::RestartMachine;
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_.last_valid()->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
// Doing the following would be technically correct, but isn't
// especially thread-safe and won't make a substantial difference.
// time_until_debounce_ = vdp_->get_time_until_line(-1);
vdp_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return vdp_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
} }
if(outputs & Output::Audio) {
Outputs::Display::ScanStatus get_scaled_scan_status() const final { update_audio();
return vdp_.last_valid()->get_scaled_scan_status(); audio_queue_.perform();
} }
}
void set_display_type(Outputs::Display::DisplayType display_type) final { forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
vdp_.last_valid()->set_display_type(display_type); if(vdp_ += cycle.length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
} }
time_since_sn76489_update_ += cycle.length;
Outputs::Display::DisplayType get_display_type() const final { if(cycle.is_terminal()) {
return vdp_.last_valid()->get_display_type(); uint16_t address = cycle.address ? *cycle.address : 0x0000;
} switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff;
break;
Outputs::Speaker::Speaker *get_speaker() final { case CPU::Z80::PartialMachineCycle::Write:
return &speaker_; if(paging_scheme_ == Target::PagingScheme::Sega) {
} if(address >= 0xfffd && cartridge_.size() > 48*1024) {
if(paging_registers_[address - 0xfffd] != *cycle.value) {
void run_for(const Cycles cycles) final { paging_registers_[address - 0xfffd] = *cycle.value;
z80_.run_for(cycles); page_cartridge();
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
}
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(vdp_ += cycle.length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
}
time_since_sn76489_update_ += cycle.length;
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff;
break;
case CPU::Z80::PartialMachineCycle::Write:
if(paging_scheme_ == Target::PagingScheme::Sega) {
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
if(paging_registers_[address - 0xfffd] != *cycle.value) {
paging_registers_[address - 0xfffd] = *cycle.value;
page_cartridge();
}
}
} else {
// i.e. this is the Codemasters paging scheme.
if(!(address&0x3fff) && address < 0xc000) {
if(paging_registers_[address >> 14] != *cycle.value) {
paging_registers_[address >> 14] = *cycle.value;
page_cartridge();
}
} }
} }
} else {
// i.e. this is the Codemasters paging scheme.
if(!(address&0x3fff) && address < 0xc000) {
if(paging_registers_[address >> 14] != *cycle.value) {
paging_registers_[address >> 14] = *cycle.value;
page_cartridge();
}
}
}
if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value;
// else logger.info().append("Ignored write to ROM"); // else logger.info().append("Ignored write to ROM");
break; break;
case CPU::Z80::PartialMachineCycle::Input: case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xc1) { switch(address & 0xc1) {
case 0x00: case 0x00:
logger.error().append("TODO: [input] memory control"); logger.error().append("TODO: [input] memory control");
*cycle.value = 0xff; *cycle.value = 0xff;
break; break;
case 0x01: case 0x01:
logger.error().append("TODO: [input] I/O port control"); logger.error().append("TODO: [input] I/O port control");
*cycle.value = 0xff; *cycle.value = 0xff;
break; break;
case 0x40: case 0x40:
*cycle.value = vdp_->get_current_line(); *cycle.value = vdp_->get_current_line();
break; break;
case 0x41: case 0x41:
*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter(); *cycle.value = vdp_.last_valid()->get_latched_horizontal_counter();
break; break;
case 0x80: case 0x81: case 0x80: case 0x81:
*cycle.value = vdp_->read(address); *cycle.value = vdp_->read(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line()); z80_.set_interrupt_line(vdp_->get_interrupt_line());
break; break;
case 0xc0: { case 0xc0: {
if(memory_control_ & 0x4) { if(memory_control_ & 0x4) {
if(has_fm_audio_ && (address & 0xff) == 0xf2) { if(has_fm_audio_ && (address & 0xff) == 0xf2) {
*cycle.value = opll_detection_word_; *cycle.value = opll_detection_word_;
} else {
*cycle.value = 0xff;
}
} else { } else {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = uint8_t(joypad1->get_state() | (joypad2->get_state() << 6));
}
} break;
case 0xc1: {
if(memory_control_ & 0x4) {
*cycle.value = 0xff; *cycle.value = 0xff;
} else {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value =
(joypad2->get_state() >> 2) |
0x30 |
get_th_values();
} }
} break; } else {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = uint8_t(joypad1->get_state() | (joypad2->get_state() << 6));
}
} break;
case 0xc1: {
if(memory_control_ & 0x4) {
*cycle.value = 0xff;
} else {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
default: *cycle.value =
logger.error().append("[input] Clearly some sort of typo"); (joypad2->get_state() >> 2) |
break; 0x30 |
} get_th_values();
break; }
} break;
case CPU::Z80::PartialMachineCycle::Output: default:
switch(address & 0xc1) { logger.error().append("[input] Clearly some sort of typo");
case 0x00: // i.e. even ports less than 0x40. break;
if constexpr (is_master_system(model)) { }
// TODO: Obey the RAM enable. break;
logger.info().append("Memory control: %02x", memory_control_);
memory_control_ = *cycle.value; case CPU::Z80::PartialMachineCycle::Output:
page_cartridge(); switch(address & 0xc1) {
case 0x00: // i.e. even ports less than 0x40.
if constexpr (is_master_system(model)) {
// TODO: Obey the RAM enable.
logger.info().append("Memory control: %02x", memory_control_);
memory_control_ = *cycle.value;
page_cartridge();
}
break;
case 0x01: { // i.e. odd ports less than 0x40.
// A programmer can force the TH lines to 0 here,
// causing a phoney lightgun latch, so check for any
// discontinuity in TH inputs.
const auto previous_ths = get_th_values();
io_port_control_ = *cycle.value;
const auto new_ths = get_th_values();
// Latch if either TH has newly gone to 1.
if((new_ths^previous_ths)&new_ths) {
vdp_->latch_horizontal_counter();
}
} break;
case 0x40: case 0x41: // i.e. ports 0x400x7f.
update_audio();
sn76489_.write(*cycle.value);
break;
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
switch(address & 0xff) {
case 0xf0: case 0xf1:
update_audio();
opll_.write(address, *cycle.value);
break;
case 0xf2:
opll_detection_word_ = *cycle.value;
set_mixer_levels(opll_detection_word_);
break;
} }
break; }
case 0x01: { // i.e. odd ports less than 0x40. break;
// A programmer can force the TH lines to 0 here,
// causing a phoney lightgun latch, so check for any
// discontinuity in TH inputs.
const auto previous_ths = get_th_values();
io_port_control_ = *cycle.value;
const auto new_ths = get_th_values();
// Latch if either TH has newly gone to 1. default:
if((new_ths^previous_ths)&new_ths) { logger.error().append("[output] Clearly some sort of typo");
vdp_->latch_horizontal_counter(); break;
} }
} break; break;
case 0x40: case 0x41: // i.e. ports 0x400x7f.
update_audio();
sn76489_.write(*cycle.value);
break;
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
switch(address & 0xff) {
case 0xf0: case 0xf1:
update_audio();
opll_.write(address, *cycle.value);
break;
case 0xf2:
opll_detection_word_ = *cycle.value;
set_mixer_levels(opll_detection_word_);
break;
}
}
break;
default:
logger.error().append("[output] Clearly some sort of typo");
break;
}
break;
/* /*
TODO: implementation of the below is incomplete. TODO: implementation of the below is incomplete.
@@ -380,181 +380,181 @@ template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
— Charles MacDonald — Charles MacDonald
*/ */
case CPU::Z80::PartialMachineCycle::Interrupt: case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff; *cycle.value = 0xff;
break; break;
default: break; default: break;
}
}
// The pause button is debounced and takes effect only one line before pixels
// begin; time_until_debounce_ keeps track of the time until then.
time_until_debounce_ -= cycle.length;
if(time_until_debounce_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
return HalfCycles(0);
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() final {
return keyboard_;
}
bool keyboard_did_change_key(Inputs::Keyboard &, Inputs::Keyboard::Key key, bool is_pressed) final {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
return true;
}
if(key == Inputs::Keyboard::Key::Escape) {
reset_is_pressed_ = is_pressed;
return true;
}
return false;
}
void reset_all_keys(Inputs::Keyboard &) final {
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
private:
static constexpr TI::TMS::Personality tms_personality() {
switch(model) {
default:
case Target::Model::SG1000: return TI::TMS::TMS9918A;
case Target::Model::MasterSystem: return TI::TMS::SMSVDP;
case Target::Model::MasterSystem2: return TI::TMS::SMS2VDP;
} }
} }
inline uint8_t get_th_values() { // The pause button is debounced and takes effect only one line before pixels
// Quick not on TH inputs here: if either is setup as an output, then the // begin; time_until_debounce_ keeps track of the time until then.
// currently output level is returned. Otherwise they're fixed at 1. time_until_debounce_ -= cycle.length;
return if(time_until_debounce_ <= HalfCycles(0)) {
uint8_t( z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) | time_until_debounce_ = vdp_->get_time_until_line(-1);
((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80)
);
} }
inline void update_audio() { return HalfCycles(0);
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider))); }
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() final {
return keyboard_;
}
bool keyboard_did_change_key(Inputs::Keyboard &, Inputs::Keyboard::Key key, bool is_pressed) final {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
return true;
} }
void set_mixer_levels(uint8_t mode) { if(key == Inputs::Keyboard::Key::Escape) {
// This is as per the audio control register; reset_is_pressed_ = is_pressed;
// see https://www.smspower.org/Development/AudioControlPort return true;
update_audio();
audio_queue_.enqueue([this, mode] {
switch(mode & 3) {
case 0: // SN76489 only; the default.
mixer_.set_relative_volumes({1.0f, 0.0f});
break;
case 1: // FM only.
mixer_.set_relative_volumes({0.0f, 1.0f});
break;
case 2: // No audio.
mixer_.set_relative_volumes({0.0f, 0.0f});
break;
case 3: // Both FM and SN76489.
mixer_.set_relative_volumes({0.5f, 0.5f});
break;
}
});
} }
using Target = Analyser::Static::Sega::Target; return false;
const Target::Region region_; }
const Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<tms_personality()>> vdp_;
Concurrency::AsyncTaskQueue<false> audio_queue_; void reset_all_keys(Inputs::Keyboard &) final {
TI::SN76489 sn76489_; }
Yamaha::OPL::OPLL opll_;
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
Outputs::Speaker::PullLowpass<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; // MARK: - Configuration options.
Inputs::Keyboard keyboard_; std::unique_ptr<Reflection::Struct> get_options() const final {
bool reset_is_pressed_ = false, pause_is_pressed_ = false; auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
HalfCycles time_since_sn76489_update_; void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
HalfCycles time_until_debounce_; const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
uint8_t ram_[8*1024]; private:
uint8_t bios_[8*1024]; static constexpr TI::TMS::Personality tms_personality() {
std::vector<uint8_t> cartridge_; switch(model) {
default:
case Target::Model::SG1000: return TI::TMS::TMS9918A;
case Target::Model::MasterSystem: return TI::TMS::SMSVDP;
case Target::Model::MasterSystem2: return TI::TMS::SMS2VDP;
}
}
uint8_t io_port_control_ = 0x0f; inline uint8_t get_th_values() {
// Quick not on TH inputs here: if either is setup as an output, then the
// currently output level is returned. Otherwise they're fixed at 1.
return
uint8_t(
((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) |
((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80)
);
// This is a static constexpr for now; I may use it in the future. }
static constexpr bool has_fm_audio_ = true;
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM. inline void update_audio() {
const uint8_t *read_pointers_[64]; speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider)));
uint8_t *write_pointers_[64]; }
template <typename T> void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) {
if(!end_address) end_address = start_address + size; void set_mixer_levels(uint8_t mode) {
for(auto address = start_address; address < end_address; address += 1024) { // This is as per the audio control register;
target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr; // see https://www.smspower.org/Development/AudioControlPort
update_audio();
audio_queue_.enqueue([this, mode] {
switch(mode & 3) {
case 0: // SN76489 only; the default.
mixer_.set_relative_volumes({1.0f, 0.0f});
break;
case 1: // FM only.
mixer_.set_relative_volumes({0.0f, 1.0f});
break;
case 2: // No audio.
mixer_.set_relative_volumes({0.0f, 0.0f});
break;
case 3: // Both FM and SN76489.
mixer_.set_relative_volumes({0.5f, 0.5f});
break;
} }
});
}
using Target = Analyser::Static::Sega::Target;
const Target::Region region_;
const Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<tms_personality()>> vdp_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
Yamaha::OPL::OPLL opll_;
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
Outputs::Speaker::PullLowpass<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Inputs::Keyboard keyboard_;
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_debounce_;
uint8_t ram_[8*1024];
uint8_t bios_[8*1024];
std::vector<uint8_t> cartridge_;
uint8_t io_port_control_ = 0x0f;
// This is a static constexpr for now; I may use it in the future.
static constexpr bool has_fm_audio_ = true;
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM.
const uint8_t *read_pointers_[64];
uint8_t *write_pointers_[64];
template <typename T> void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) {
if(!end_address) end_address = start_address + size;
for(auto address = start_address; address < end_address; address += 1024) {
target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr;
} }
}
uint8_t paging_registers_[3] = {0, 1, 2}; uint8_t paging_registers_[3] = {0, 1, 2};
uint8_t memory_control_ = 0; uint8_t memory_control_ = 0;
void page_cartridge() { void page_cartridge() {
// Either install the cartridge or don't; Japanese machines can't see // Either install the cartridge or don't; Japanese machines can't see
// anything but the cartridge. // anything but the cartridge.
if(!(memory_control_ & 0x40) || region_ == Target::Region::Japan) { if(!(memory_control_ & 0x40) || region_ == Target::Region::Japan) {
for(size_t c = 0; c < 3; ++c) { for(size_t c = 0; c < 3; ++c) {
const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size(); const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size();
map( map(
read_pointers_, read_pointers_,
cartridge_.data() + start_addr, cartridge_.data() + start_addr,
std::min(size_t(0x4000), cartridge_.size() - start_addr), std::min(size_t(0x4000), cartridge_.size() - start_addr),
c * 0x4000); c * 0x4000);
}
// The first 1kb doesn't page though, if this is the Sega paging scheme.
if(paging_scheme_ == Target::PagingScheme::Sega) {
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
}
} else {
map(read_pointers_, nullptr, 0xc000, 0x0000);
} }
// Throw the BIOS on top if this machine has one and it isn't disabled. // The first 1kb doesn't page though, if this is the Sega paging scheme.
if(has_bios_ && !(memory_control_ & 0x08)) { if(paging_scheme_ == Target::PagingScheme::Sega) {
map(read_pointers_, bios_, 8*1024, 0); map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
} }
} else {
map(read_pointers_, nullptr, 0xc000, 0x0000);
} }
bool has_bios_ = true;
// Throw the BIOS on top if this machine has one and it isn't disabled.
if(has_bios_ && !(memory_control_ & 0x08)) {
map(read_pointers_, bios_, 8*1024, 0);
}
}
bool has_bios_ = true;
}; };
} }

View File

@@ -23,33 +23,33 @@ namespace Oric {
see the thread at https://forum.defence-force.org/viewtopic.php?f=25&t=2055 see the thread at https://forum.defence-force.org/viewtopic.php?f=25&t=2055
*/ */
class BD500: public DiskController { class BD500: public DiskController {
public: public:
BD500(); BD500();
void write(int address, uint8_t value); void write(int address, uint8_t value);
uint8_t read(int address); uint8_t read(int address);
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer); void set_activity_observer(Activity::Observer *observer);
private: private:
void set_head_load_request(bool head_load) final; void set_head_load_request(bool head_load) final;
bool is_loading_head_ = false; bool is_loading_head_ = false;
Activity::Observer *observer_ = nullptr; Activity::Observer *observer_ = nullptr;
void access(int address); void access(int address);
void set_head_loaded(bool loaded); void set_head_loaded(bool loaded);
bool enable_overlay_ram_ = false; bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false; bool disable_basic_rom_ = false;
void select_paged_item() { void select_paged_item() {
PagedItem item = PagedItem::RAM; PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) { if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC; item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
} }
set_paged_item(item);
}
}; };
} }

View File

@@ -11,50 +11,50 @@
namespace Oric { namespace Oric {
class DiskController: public WD::WD1770 { class DiskController: public WD::WD1770 {
public: public:
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) : DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) { WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {
emplace_drives(4, clock_rate_, 300, 2, ready_type_); emplace_drives(4, clock_rate_, 300, 2, ready_type_);
// TODO: don't assume four drives? // TODO: don't assume four drives?
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
get_drive(size_t(d)).set_disk(disk);
}
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController &) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(*this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
Delegate *delegate_ = nullptr;
void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(*this);
} }
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) { private:
get_drive(size_t(d)).set_disk(disk); PagedItem paged_item_ = PagedItem::DiskROM;
} int clock_rate_;
Storage::Disk::Drive::ReadyType ready_type_;
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController &) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(*this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
Delegate *delegate_ = nullptr;
void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(*this);
}
}
private:
PagedItem paged_item_ = PagedItem::DiskROM;
int clock_rate_;
Storage::Disk::Drive::ReadyType ready_type_;
}; };
} }

View File

@@ -15,29 +15,29 @@
namespace Oric { namespace Oric {
class Jasmin: public DiskController { class Jasmin: public DiskController {
public: public:
Jasmin(); Jasmin();
void write(int address, uint8_t value); void write(int address, uint8_t value);
void set_activity_observer(Activity::Observer *observer); void set_activity_observer(Activity::Observer *observer);
private: private:
void set_motor_on(bool on) final; void set_motor_on(bool on) final;
bool motor_on_ = false; bool motor_on_ = false;
uint8_t selected_drives_ = 0; uint8_t selected_drives_ = 0;
Activity::Observer *observer_ = nullptr; Activity::Observer *observer_ = nullptr;
bool enable_overlay_ram_ = false; bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false; bool disable_basic_rom_ = false;
void select_paged_item() { void select_paged_item() {
PagedItem item = PagedItem::RAM; PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) { if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC; item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
} }
set_paged_item(item);
}
}; };
} }

View File

@@ -15,30 +15,30 @@
namespace Oric { namespace Oric {
class Microdisc: public DiskController { class Microdisc: public DiskController {
public: public:
Microdisc(); Microdisc();
void set_control_register(uint8_t control); void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register(); uint8_t get_interrupt_request_register();
uint8_t get_data_request_register(); uint8_t get_data_request_register();
bool get_interrupt_request_line(); bool get_interrupt_request_line();
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer); void set_activity_observer(Activity::Observer *observer);
private: private:
void set_head_load_request(bool head_load) final; void set_head_load_request(bool head_load) final;
void set_control_register(uint8_t control, uint8_t changes); void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0; uint8_t last_control_ = 0;
bool irq_enable_ = false; bool irq_enable_ = false;
Cycles::IntType head_load_request_counter_ = -1; Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false; bool head_load_request_ = false;
Activity::Observer *observer_ = nullptr; Activity::Observer *observer_ = nullptr;
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,52 +18,52 @@
namespace Oric { namespace Oric {
class VideoOutput { class VideoOutput {
public: public:
VideoOutput(uint8_t *memory); VideoOutput(uint8_t *memory);
void set_colour_rom(const std::vector<uint8_t> &colour_rom); void set_colour_rom(const std::vector<uint8_t> &colour_rom);
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
Cycles next_sequence_point() const; Cycles next_sequence_point() const;
bool vsync(); bool vsync();
void set_scan_target(Outputs::Display::ScanTarget *scan_target); void set_scan_target(Outputs::Display::ScanTarget *scan_target);
void set_display_type(Outputs::Display::DisplayType display_type); void set_display_type(Outputs::Display::DisplayType display_type);
Outputs::Display::DisplayType get_display_type() const; Outputs::Display::DisplayType get_display_type() const;
Outputs::Display::ScanStatus get_scaled_scan_status() const; Outputs::Display::ScanStatus get_scaled_scan_status() const;
void register_crt_frequency_mismatch(); void register_crt_frequency_mismatch();
private: private:
uint8_t *ram_; uint8_t *ram_;
Outputs::CRT::CRT crt_; Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_; Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false; bool crt_is_60Hz_ = false;
bool has_colour_rom_ = false; bool has_colour_rom_ = false;
void update_crt_frequency(); void update_crt_frequency();
// Counters and limits. // Counters and limits.
int counter_ = 0, frame_counter_ = 0; int counter_ = 0, frame_counter_ = 0;
int v_sync_start_position_, v_sync_end_position_, counter_period_; int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Output target and device. // Output target and device.
uint8_t *rgb_pixel_target_ = nullptr; uint8_t *rgb_pixel_target_ = nullptr;
uint32_t *composite_pixel_target_ = nullptr; uint32_t *composite_pixel_target_ = nullptr;
uint32_t colour_forms_[8]; uint32_t colour_forms_[8];
Outputs::Display::InputDataType data_type_; Outputs::Display::InputDataType data_type_;
// Registers. // Registers.
uint8_t ink_, paper_; uint8_t ink_, paper_;
int character_set_base_address_ = 0xb400; int character_set_base_address_ = 0xb400;
inline void set_character_set_base_address(); inline void set_character_set_base_address();
bool is_graphics_mode_ = false; bool is_graphics_mode_ = false;
bool next_frame_is_sixty_hertz_ = false; bool next_frame_is_sixty_hertz_ = false;
bool use_alternative_character_set_; bool use_alternative_character_set_;
bool use_double_height_characters_; bool use_double_height_characters_;
bool blink_text_; bool blink_text_;
}; };
} }

View File

@@ -37,38 +37,38 @@ enum Key: uint16_t {
}; };
class Keyboard { class Keyboard {
public: public:
Keyboard(Machine machine); Keyboard(Machine machine);
void set_key_state(uint16_t key, bool is_pressed); void set_key_state(uint16_t key, bool is_pressed);
void clear_all_keys(); void clear_all_keys();
uint8_t read(uint16_t address); uint8_t read(uint16_t address);
private: private:
uint8_t key_states_[8]; uint8_t key_states_[8];
const Machine machine_; const Machine machine_;
}; };
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
public: public:
KeyboardMapper(Machine machine); KeyboardMapper(Machine machine);
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override; uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override;
private: private:
const Machine machine_; const Machine machine_;
}; };
class CharacterMapper: public ::Utility::CharacterMapper { class CharacterMapper: public ::Utility::CharacterMapper {
public: public:
CharacterMapper(Machine machine); CharacterMapper(Machine machine);
const uint16_t *sequence_for_character(char character) const override; const uint16_t *sequence_for_character(char character) const override;
bool needs_pause_after_key(uint16_t key) const override; bool needs_pause_after_key(uint16_t key) const override;
private: private:
const Machine machine_; const Machine machine_;
}; };
} }

View File

@@ -61,433 +61,433 @@ template<bool is_zx81> class ConcreteMachine:
public Utility::TypeRecipient<CharacterMapper>, public Utility::TypeRecipient<CharacterMapper>,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public Machine { public Machine {
public: public:
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
Utility::TypeRecipient<CharacterMapper>(keyboard_machine()), Utility::TypeRecipient<CharacterMapper>(keyboard_machine()),
z80_(*this), z80_(*this),
keyboard_(keyboard_machine()), keyboard_(keyboard_machine()),
keyboard_mapper_(keyboard_machine()), keyboard_mapper_(keyboard_machine()),
tape_player_(ZX8081ClockRate), tape_player_(ZX8081ClockRate),
ay_(GI::AY38910::Personality::AY38910, audio_queue_), ay_(GI::AY38910::Personality::AY38910, audio_queue_),
speaker_(ay_) { speaker_(ay_) {
set_clock_rate(ZX8081ClockRate); set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f); speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80; const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
const ROM::Request request(rom_name); const ROM::Request request(rom_name);
auto roms = rom_fetcher(request); auto roms = rom_fetcher(request);
if(!request.validate(roms)) { if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs; throw ROMMachine::Error::MissingROMs;
} }
rom_ = std::move(roms.find(rom_name)->second); rom_ = std::move(roms.find(rom_name)->second);
rom_mask_ = uint16_t(rom_.size() - 1); rom_mask_ = uint16_t(rom_.size() - 1);
switch(target.memory_model) { switch(target.memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024); ram_.resize(1024);
ram_base_ = 16384; ram_base_ = 16384;
ram_mask_ = 1023; ram_mask_ = 1023;
break; break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384); ram_.resize(16384);
ram_base_ = 16384; ram_base_ = 16384;
ram_mask_ = 16383; ram_mask_ = 16383;
break; break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536); ram_.resize(65536);
ram_base_ = 8192; ram_base_ = 8192;
ram_mask_ = 65535; ram_mask_ = 65535;
break; break;
} }
Memory::Fuzz(ram_); Memory::Fuzz(ram_);
// Ensure valid initial key state. // Ensure valid initial key state.
clear_all_keys(); clear_all_keys();
if(!target.loading_command.empty()) { if(!target.loading_command.empty()) {
type_string(target.loading_command); type_string(target.loading_command);
should_autorun_ = true; should_autorun_ = true;
}
insert_media(target.media);
} }
~ConcreteMachine() { insert_media(target.media);
audio_queue_.flush(); }
~ConcreteMachine() {
audio_queue_.flush();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
const HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
time_since_ay_update_ += cycle.length;
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_.run_for(vsync_start_ - previous_counter);
set_hsync(true);
line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(true);
}
video_.run_for(horizontal_counter_ - vsync_start_);
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
video_.run_for(vsync_end_ - previous_counter);
set_hsync(false);
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(false);
z80_.set_wait_line(false);
}
video_.run_for(horizontal_counter_ - vsync_end_);
} else {
video_.run_for(cycle.length);
} }
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { if constexpr (is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207));
const HalfCycles previous_counter = horizontal_counter_; if(!tape_advance_delay_) {
horizontal_counter_ += cycle.length; tape_player_.run_for(cycle.length);
time_since_ay_update_ += cycle.length; } else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
}
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
video_.run_for(vsync_start_ - previous_counter); z80_.set_wait_line(true);
set_hsync(true); }
line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) { if(!cycle.is_terminal()) {
z80_.set_non_maskable_interrupt_line(true); return Cycles(0);
}
const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
if(!nmi_is_enabled_) {
line_counter_ = 0;
set_vsync(false);
} }
video_.run_for(horizontal_counter_ - vsync_start_); if(!(address & 2)) nmi_is_enabled_ = false;
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) { if(!(address & 1)) nmi_is_enabled_ = is_zx81;
video_.run_for(vsync_end_ - previous_counter); if(is_zx81 && !nmi_is_enabled_) z80_.set_wait_line(false);
set_hsync(false);
if(nmi_is_enabled_) { // The below emulates the ZonX AY expansion device.
z80_.set_non_maskable_interrupt_line(false); if constexpr (is_zx81) {
z80_.set_wait_line(false); if((address&0xef) == 0xcf) {
ay_set_register(*cycle.value);
} else if((address&0xef) == 0x0f) {
ay_set_data(*cycle.value);
}
} }
video_.run_for(horizontal_counter_ - vsync_end_); break;
} else {
video_.run_for(cycle.length);
}
if constexpr (is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207)); case CPU::Z80::PartialMachineCycle::Input: {
if(!tape_advance_delay_) { uint8_t value = 0xff;
tape_player_.run_for(cycle.length); if(!(address&1)) {
} else { if(!nmi_is_enabled_) set_vsync(true);
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
}
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) { value &= keyboard_.read(address);
z80_.set_wait_line(true); value &= ~(tape_player_.input() ? 0x00 : 0x80);
} }
if(!cycle.is_terminal()) { // The below emulates the ZonX AY expansion device.
return Cycles(0); if constexpr (is_zx81) {
} if((address&0xef) == 0xcf) {
value &= ay_read_data();
const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
if(!nmi_is_enabled_) {
line_counter_ = 0;
set_vsync(false);
} }
if(!(address & 2)) nmi_is_enabled_ = false; }
if(!(address & 1)) nmi_is_enabled_ = is_zx81; *cycle.value = value;
if(is_zx81 && !nmi_is_enabled_) z80_.set_wait_line(false); } break;
// The below emulates the ZonX AY expansion device. case CPU::Z80::PartialMachineCycle::Interrupt:
if constexpr (is_zx81) { // resetting event is M1 and IOREQ both simultaneously having leading edges;
if((address&0xef) == 0xcf) { // that happens 2 cycles before the end of INTACK. So the timer was reset and
ay_set_register(*cycle.value); // now has advanced twice.
} else if((address&0xef) == 0x0f) { horizontal_counter_ = HalfCycles(2);
ay_set_data(*cycle.value);
}
}
break;
case CPU::Z80::PartialMachineCycle::Input: { *cycle.value = 0xff;
uint8_t value = 0xff; break;
if(!(address&1)) {
if(!nmi_is_enabled_) set_vsync(true);
value &= keyboard_.read(address); case CPU::Z80::PartialMachineCycle::Refresh:
value &= ~(tape_player_.input() ? 0x00 : 0x80); // The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
} // address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// The below emulates the ZonX AY expansion device. // of the IRQ line if necessary.
if constexpr (is_zx81) { if(!(address & 0x40)) {
if((address&0xef) == 0xcf) { z80_.set_interrupt_line(true, Cycles(-2));
value &= ay_read_data(); z80_.set_interrupt_line(false);
} }
} if(has_latched_video_byte_) {
*cycle.value = value; std::size_t char_address = size_t((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
} break; const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
case CPU::Z80::PartialMachineCycle::Interrupt: latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
// resetting event is M1 and IOREQ both simultaneously having leading edges;
// that happens 2 cycles before the end of INTACK. So the timer was reset and
// now has advanced twice.
horizontal_counter_ = HalfCycles(2);
*cycle.value = 0xff;
break;
case CPU::Z80::PartialMachineCycle::Refresh:
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
// address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// of the IRQ line if necessary.
if(!(address & 0x40)) {
z80_.set_interrupt_line(true, Cycles(-2));
z80_.set_interrupt_line(false);
}
if(has_latched_video_byte_) {
std::size_t char_address = size_t((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else {
latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
}
video_.output_byte(latched_video_byte_);
has_latched_video_byte_ = false;
}
break;
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
const uint64_t prior_offset = tape_player_.serialiser()->offset();
const int next_byte = parser_.get_next_byte(*tape_player_.serialiser());
if(next_byte != -1) {
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = uint8_t(next_byte);
*cycle.value = 0x00;
z80_.set_value_of(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.serialiser()->set_offset(prior_offset);
}
}
if(should_autorun_ && address == finished_load_address_) {
type_string(is_zx81 ? "r \n" : "r\n "); // Spaces here are not especially scientific; they merely ensure sufficient pauses for both the ZX80 and 81, empirically.
should_autorun_ = false;
}
// Check for automatic tape control.
if(use_automatic_tape_motor_control_) {
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
}
is_opcode_read = true;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else { } else {
const uint8_t value = ram_[address & ram_mask_]; latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
} }
break;
case CPU::Z80::PartialMachineCycle::Write: video_.output_byte(latched_video_byte_);
if(address >= ram_base_) { has_latched_video_byte_ = false;
ram_[address & ram_mask_] = *cycle.value;
}
break;
default: break;
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
video_.flush();
}
if constexpr (is_zx81) {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
} }
break;
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
const uint64_t prior_offset = tape_player_.serialiser()->offset();
const int next_byte = parser_.get_next_byte(*tape_player_.serialiser());
if(next_byte != -1) {
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = uint8_t(next_byte);
*cycle.value = 0x00;
z80_.set_value_of(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.serialiser()->set_offset(prior_offset);
}
}
if(should_autorun_ && address == finished_load_address_) {
type_string(is_zx81 ? "r \n" : "r\n "); // Spaces here are not especially scientific; they merely ensure sufficient pauses for both the ZX80 and 81, empirically.
should_autorun_ = false;
}
// Check for automatic tape control.
if(use_automatic_tape_motor_control_) {
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
}
is_opcode_read = true;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else {
const uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
}
break;
default: break;
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
video_.flush();
}
if constexpr (is_zx81) {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
} }
} }
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target); video_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
Outputs::Speaker::Speaker *get_speaker() final {
return is_zx81 ? &speaker_ : nullptr;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front(), is_zx81 ? TargetPlatform::ZX81 : TargetPlatform::ZX80);
} }
Outputs::Display::ScanStatus get_scaled_scan_status() const final { set_use_fast_tape();
return video_.get_scaled_scan_status(); return !media.tapes.empty();
}
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
// MARK: - Keyboard
void set_key_state(uint16_t key, bool is_pressed) final {
keyboard_.set_key_state(key, is_pressed);
}
void clear_all_keys() final {
keyboard_.clear_all_keys();
}
static constexpr Sinclair::ZX::Keyboard::Machine keyboard_machine() {
return is_zx81 ? Sinclair::ZX::Keyboard::Machine::ZX81 : Sinclair::ZX::Keyboard::Machine::ZX80;
}
// MARK: - Tape control
void set_use_automatic_tape_motor_control(bool enabled) {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) {
tape_player_.set_motor_control(false);
} }
}
Outputs::Speaker::Speaker *get_speaker() final { void set_tape_is_playing(bool is_playing) final {
return is_zx81 ? &speaker_ : nullptr; tape_player_.set_motor_control(is_playing);
} }
void run_for(const Cycles cycles) final { bool get_tape_is_playing() final {
z80_.run_for(cycles); return tape_player_.motor_control();
} }
bool insert_media(const Analyser::Static::Media &media) final { // MARK: - Typer timing
if(!media.tapes.empty()) { HalfCycles get_typer_delay(const std::string &) const final {
tape_player_.set_tape(media.tapes.front(), is_zx81 ? TargetPlatform::ZX81 : TargetPlatform::ZX80); return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
} }
set_use_fast_tape(); HalfCycles get_typer_frequency() const final {
return !media.tapes.empty(); return Cycles(159'961);
} }
void type_string(const std::string &string) final { KeyboardMapper *get_keyboard_mapper() final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string); return &keyboard_mapper_;
} }
bool can_type(char c) const final { // MARK: - Configuration options.
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
// MARK: - Keyboard std::unique_ptr<Reflection::Struct> get_options() const final {
void set_key_state(uint16_t key, bool is_pressed) final { auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
keyboard_.set_key_state(key, is_pressed); options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
} options->quickload = allow_fast_tape_hack_;
return options;
}
void clear_all_keys() final { void set_options(const std::unique_ptr<Reflection::Struct> &str) {
keyboard_.clear_all_keys(); const auto options = dynamic_cast<Options *>(str.get());
} set_use_automatic_tape_motor_control(options->automatic_tape_motor_control);
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape();
}
static constexpr Sinclair::ZX::Keyboard::Machine keyboard_machine() { private:
return is_zx81 ? Sinclair::ZX::Keyboard::Machine::ZX81 : Sinclair::ZX::Keyboard::Machine::ZX80; CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
} Video video_;
// MARK: - Tape control // If fast tape loading is enabled then the PC will be trapped at tape_trap_address_;
// the emulator will then do a high-level reinterpretation of the standard ZX80/81 reading
// of a single byte, and the next thing executed will be at tape_return_address_;
static constexpr uint16_t tape_trap_address_ = is_zx81 ? 0x37c : 0x220;
static constexpr uint16_t tape_return_address_ = is_zx81 ? 0x380 : 0x248;
void set_use_automatic_tape_motor_control(bool enabled) { // If automatic tape motor control is enabled then the tape will be permitted to play any time
use_automatic_tape_motor_control_ = enabled; // the program counter is >= automatic_tape_motor_start_address_ and < automatic_tape_motor_end_address_.
if(!enabled) { static constexpr uint16_t automatic_tape_motor_start_address_ = is_zx81 ? 0x340 : 0x206;
tape_player_.set_motor_control(false); static constexpr uint16_t automatic_tape_motor_end_address_ = is_zx81 ? 0x3c3 : 0x24d;
}
}
void set_tape_is_playing(bool is_playing) final { // When automatically loading, if the PC gets to the finished_load_address_ in order to print 0/0
tape_player_.set_motor_control(is_playing); // (so it's anything that indicates that loading completed, but the program did not autorun) then the
} // emulator will automatically RUN whatever has been loaded.
static constexpr uint16_t finished_load_address_ = is_zx81 ?
0x6d1 : // ZX81: this is the routine that prints 0/0 (i.e. success).
0x203; // ZX80: this is the JR that exits the ZX80's LOAD and returns to MAIN-EXEC.
bool should_autorun_ = false;
bool get_tape_is_playing() final { std::vector<uint8_t> ram_;
return tape_player_.motor_control(); uint16_t ram_mask_, ram_base_;
}
// MARK: - Typer timing std::vector<uint8_t> rom_;
HalfCycles get_typer_delay(const std::string &) const final { uint16_t rom_mask_;
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
}
HalfCycles get_typer_frequency() const final { bool vsync_ = false, hsync_ = false;
return Cycles(159'961); int line_counter_ = 0;
}
KeyboardMapper *get_keyboard_mapper() final { Sinclair::ZX::Keyboard::Keyboard keyboard_;
return &keyboard_mapper_; Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_;
}
// MARK: - Configuration options. HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;
std::unique_ptr<Reflection::Struct> get_options() const final { bool nmi_is_enabled_ = false;
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) { static constexpr auto vsync_start_ = is_zx81 ? HalfCycles(32) : HalfCycles(26);
const auto options = dynamic_cast<Options *>(str.get()); static constexpr auto vsync_end_ = is_zx81 ? HalfCycles(64) : HalfCycles(66);
set_use_automatic_tape_motor_control(options->automatic_tape_motor_control); HalfCycles horizontal_counter_;
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape();
}
private: uint8_t latched_video_byte_ = 0;
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; bool has_latched_video_byte_ = false;
Video video_;
// If fast tape loading is enabled then the PC will be trapped at tape_trap_address_; bool use_fast_tape_hack_ = false;
// the emulator will then do a high-level reinterpretation of the standard ZX80/81 reading bool allow_fast_tape_hack_ = false;
// of a single byte, and the next thing executed will be at tape_return_address_; void set_use_fast_tape() {
static constexpr uint16_t tape_trap_address_ = is_zx81 ? 0x37c : 0x220; use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
static constexpr uint16_t tape_return_address_ = is_zx81 ? 0x380 : 0x248; }
bool use_automatic_tape_motor_control_ = true;
HalfCycles tape_advance_delay_ = 0;
// If automatic tape motor control is enabled then the tape will be permitted to play any time // MARK: - Video
// the program counter is >= automatic_tape_motor_start_address_ and < automatic_tape_motor_end_address_. inline void set_vsync(bool sync) {
static constexpr uint16_t automatic_tape_motor_start_address_ = is_zx81 ? 0x340 : 0x206; vsync_ = sync;
static constexpr uint16_t automatic_tape_motor_end_address_ = is_zx81 ? 0x3c3 : 0x24d; update_sync();
}
// When automatically loading, if the PC gets to the finished_load_address_ in order to print 0/0 inline void set_hsync(bool sync) {
// (so it's anything that indicates that loading completed, but the program did not autorun) then the hsync_ = sync;
// emulator will automatically RUN whatever has been loaded. update_sync();
static constexpr uint16_t finished_load_address_ = is_zx81 ? }
0x6d1 : // ZX81: this is the routine that prints 0/0 (i.e. success).
0x203; // ZX80: this is the JR that exits the ZX80's LOAD and returns to MAIN-EXEC.
bool should_autorun_ = false;
std::vector<uint8_t> ram_; inline void update_sync() {
uint16_t ram_mask_, ram_base_; video_.set_sync(vsync_ || hsync_);
}
std::vector<uint8_t> rom_; // MARK: - Audio
uint16_t rom_mask_; Concurrency::AsyncTaskQueue<false> audio_queue_;
using AY = GI::AY38910::AY38910<false>;
bool vsync_ = false, hsync_ = false; AY ay_;
int line_counter_ = 0; Outputs::Speaker::PullLowpass<AY> speaker_;
HalfCycles time_since_ay_update_;
Sinclair::ZX::Keyboard::Keyboard keyboard_; inline void ay_set_register(uint8_t value) {
Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_; update_audio();
GI::AY38910::Utility::select_register(ay_, value);
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; }
Storage::Tape::ZX8081::Parser parser_; inline void ay_set_data(uint8_t value) {
update_audio();
bool nmi_is_enabled_ = false; GI::AY38910::Utility::write_data(ay_, value);
}
static constexpr auto vsync_start_ = is_zx81 ? HalfCycles(32) : HalfCycles(26); inline uint8_t ay_read_data() {
static constexpr auto vsync_end_ = is_zx81 ? HalfCycles(64) : HalfCycles(66); update_audio();
HalfCycles horizontal_counter_; return GI::AY38910::Utility::read(ay_);
}
uint8_t latched_video_byte_ = 0; inline void update_audio() {
bool has_latched_video_byte_ = false; speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
bool use_fast_tape_hack_ = false;
bool allow_fast_tape_hack_ = false;
void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
}
bool use_automatic_tape_motor_control_ = true;
HalfCycles tape_advance_delay_ = 0;
// MARK: - Video
inline void set_vsync(bool sync) {
vsync_ = sync;
update_sync();
}
inline void set_hsync(bool sync) {
hsync_ = sync;
update_sync();
}
inline void update_sync() {
video_.set_sync(vsync_ || hsync_);
}
// MARK: - Audio
Concurrency::AsyncTaskQueue<false> audio_queue_;
using AY = GI::AY38910::AY38910<false>;
AY ay_;
Outputs::Speaker::PullLowpass<AY> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();
GI::AY38910::Utility::select_register(ay_, value);
}
inline void ay_set_data(uint8_t value) {
update_audio();
GI::AY38910::Utility::write_data(ay_, value);
}
inline uint8_t ay_read_data() {
update_audio();
return GI::AY38910::Utility::read(ay_);
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
}; };
} }

View File

@@ -178,25 +178,25 @@ public:
constexpr uint8_t masks[] = {0, 0xff}; constexpr uint8_t masks[] = {0, 0xff};
#define Output(n) \ #define Output(n) \
{ \ { \
const uint8_t pixels = \ const uint8_t pixels = \
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \ uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
\ \
const uint8_t colours[2] = { \ const uint8_t colours[2] = { \
palette[(last_fetches_[n+1] & 0x78) >> 3], \ palette[(last_fetches_[n+1] & 0x78) >> 3], \
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \ palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
}; \ }; \
\ \
pixel_target_[0] = colours[(pixels >> 7) & 1]; \ pixel_target_[0] = colours[(pixels >> 7) & 1]; \
pixel_target_[1] = colours[(pixels >> 6) & 1]; \ pixel_target_[1] = colours[(pixels >> 6) & 1]; \
pixel_target_[2] = colours[(pixels >> 5) & 1]; \ pixel_target_[2] = colours[(pixels >> 5) & 1]; \
pixel_target_[3] = colours[(pixels >> 4) & 1]; \ pixel_target_[3] = colours[(pixels >> 4) & 1]; \
pixel_target_[4] = colours[(pixels >> 3) & 1]; \ pixel_target_[4] = colours[(pixels >> 3) & 1]; \
pixel_target_[5] = colours[(pixels >> 2) & 1]; \ pixel_target_[5] = colours[(pixels >> 2) & 1]; \
pixel_target_[6] = colours[(pixels >> 1) & 1]; \ pixel_target_[6] = colours[(pixels >> 1) & 1]; \
pixel_target_[7] = colours[(pixels >> 0) & 1]; \ pixel_target_[7] = colours[(pixels >> 0) & 1]; \
pixel_target_ += 8; \ pixel_target_ += 8; \
} }
Output(0); Output(0);
Output(2); Output(2);
@@ -454,15 +454,14 @@ private:
friend struct State; friend struct State;
static constexpr uint8_t RGB(const uint8_t r, const uint8_t g, const uint8_t b) { #define RGB(r, g, b) (r << 4) | (g << 2) | b
return uint8_t((r << 4) | (g << 2) | b);
}
static constexpr uint8_t palette[] = { static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2), RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2),
RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3), RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3),
RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3), RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3),
}; };
#undef RGB
}; };
struct State: public Reflection::StructImpl<State> { struct State: public Reflection::StructImpl<State> {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ enum Name {
AcornADFS, AcornADFS,
PRESAdvancedPlus6, PRESAdvancedPlus6,
Acorn1770DFS, Acorn1770DFS,
AcornIDEADFS103,
// Acorn Archimedes. // Acorn Archimedes.
AcornArthur030, AcornArthur030,
@@ -223,8 +224,20 @@ struct Description {
private: private:
template <typename FileNameT, typename CRC32T> Description( template <typename FileNameT, typename CRC32T> Description(
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0) const Name name,
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} { const char *machine_name,
const char *descriptive_name,
const FileNameT &file_names,
const size_t size,
const CRC32T crc32s = uint32_t(0)
) :
name{name},
machine_name{machine_name},
descriptive_name{descriptive_name},
file_names{file_names},
size{size},
crc32s{crc32s}
{
// Slightly lazy: deal with the case where the constructor wasn't provided with any // Slightly lazy: deal with the case where the constructor wasn't provided with any
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative // CRCs by spotting that the set has exactly one member, which has value 0. The alternative
// would be to provide a partial specialisation that never put anything into the set. // would be to provide a partial specialisation that never put anything into the set.
@@ -232,6 +245,8 @@ private:
this->crc32s.clear(); this->crc32s.clear();
} }
} }
static const std::vector<Description> &all_roms();
}; };
/// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM /// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM

View File

@@ -22,37 +22,37 @@ namespace Utility {
necessary to type that character on a given machine. necessary to type that character on a given machine.
*/ */
class CharacterMapper { class CharacterMapper {
public: public:
virtual ~CharacterMapper() = default; virtual ~CharacterMapper() = default;
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual const uint16_t *sequence_for_character(char character) const = 0; virtual const uint16_t *sequence_for_character(char character) const = 0;
/// The typer will automatically reset all keys in between each sequence that it types. /// The typer will automatically reset all keys in between each sequence that it types.
/// By default it will pause for one key's duration when doing so. Character mappers /// By default it will pause for one key's duration when doing so. Character mappers
/// can eliminate that pause by overriding this method. /// can eliminate that pause by overriding this method.
/// @returns @c true if the typer should pause after performing a reset; @c false otherwise. /// @returns @c true if the typer should pause after performing a reset; @c false otherwise.
virtual bool needs_pause_after_reset_all_keys() const { return true; } virtual bool needs_pause_after_reset_all_keys() const { return true; }
/// The typer will pause between every entry in a keyboard sequence. On some machines /// The typer will pause between every entry in a keyboard sequence. On some machines
/// that may not be necessary — it'll often depends on whether the machine needs time to /// that may not be necessary — it'll often depends on whether the machine needs time to
/// observe a modifier like shift before it sees the actual keypress. /// observe a modifier like shift before it sees the actual keypress.
/// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise. /// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise.
virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; } virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; }
protected: protected:
using KeySequence = std::array<uint16_t, 16>; using KeySequence = std::array<uint16_t, 16>;
/*! /*!
Provided in the base class as a convenience: given the C array of key sequences @c sequences, Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr. returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/ */
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const { template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character); std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr; if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr; if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter].data(); return sequences[ucharacter].data();
} }
}; };
/*! /*!

View File

@@ -16,7 +16,7 @@ namespace Numeric {
/// keeping the least-significant bit in its original position. /// keeping the least-significant bit in its original position.
/// ///
/// i.e. if @c input is abcdefgh then the result is 0a0b0c0d0e0f0g0h /// i.e. if @c input is abcdefgh then the result is 0a0b0c0d0e0f0g0h
constexpr uint16_t spread_bits(uint8_t input) { constexpr uint16_t spread_bits(const uint8_t input) {
uint16_t result = uint16_t(input); // 0000 0000 abcd efgh uint16_t result = uint16_t(input); // 0000 0000 abcd efgh
result = (result | (result << 4)) & 0x0f0f; // 0000 abcd 0000 efgh result = (result | (result << 4)) & 0x0f0f; // 0000 abcd 0000 efgh
result = (result | (result << 2)) & 0x3333; // 00ab 00cd 00ef 00gh result = (result | (result << 2)) & 0x3333; // 00ab 00cd 00ef 00gh
@@ -26,11 +26,12 @@ constexpr uint16_t spread_bits(uint8_t input) {
/// Performs the opposite action to @c spread_bits; given the 16-bit input /// Performs the opposite action to @c spread_bits; given the 16-bit input
/// @c abcd @c efgh @c ijkl @c mnop, returns the byte value @c bdfhjlnp /// @c abcd @c efgh @c ijkl @c mnop, returns the byte value @c bdfhjlnp
/// i.e. every other bit is retained, keeping the least-significant bit in place. /// i.e. every other bit is retained, keeping the least-significant bit in place.
constexpr uint8_t unspread_bits(uint16_t input) { constexpr uint8_t unspread_bits(const uint16_t input) {
input &= 0x5555; // 0a0b 0c0d 0e0f 0g0h uint16_t result = input;
input = (input | (input >> 1)) & 0x3333; // 00ab 00cd 00ef 00gh result &= 0x5555; // 0a0b 0c0d 0e0f 0g0h
input = (input | (input >> 2)) & 0x0f0f; // 0000 abcd 0000 efgh result = (result | (result >> 1)) & 0x3333; // 00ab 00cd 00ef 00gh
return uint8_t(input | (input >> 4)); // 0000 0000 abcd efgh result = (result | (result >> 2)) & 0x0f0f; // 0000 abcd 0000 efgh
return uint8_t(result | (result >> 4)); // 0000 0000 abcd efgh
} }
} }

View File

@@ -61,7 +61,7 @@ public:
An initial value of 0 is invalid. An initial value of 0 is invalid.
*/ */
LFSR(IntType initial_value) : value_(initial_value) {} LFSR(const IntType initial_value) : value_(initial_value) {}
/*! /*!
Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0, Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0,

View File

@@ -30,13 +30,13 @@ namespace Numeric {
template <int... Sizes> class NumericCoder { template <int... Sizes> class NumericCoder {
public: public:
/// Modifies @c target to hold @c value at @c index. /// Modifies @c target to hold @c value at @c index.
template <int index> static void encode(int &target, int value) { template <int index> static void encode(int &target, const int value) {
static_assert(index < sizeof...(Sizes), "Index must be within range"); static_assert(index < sizeof...(Sizes), "Index must be within range");
NumericEncoder<Sizes...>::template encode<index>(target, value); NumericEncoder<Sizes...>::template encode<index>(target, value);
} }
/// @returns The value from @c source at @c index. /// @returns The value from @c source at @c index.
template <int index> static int decode(int source) { template <int index> static int decode(const int source) {
static_assert(index < sizeof...(Sizes), "Index must be within range"); static_assert(index < sizeof...(Sizes), "Index must be within range");
return NumericDecoder<Sizes...>::template decode<index>(source); return NumericDecoder<Sizes...>::template decode<index>(source);
} }
@@ -45,7 +45,7 @@ private:
template <int size, int... Tail> template <int size, int... Tail>
struct NumericEncoder { struct NumericEncoder {
template <int index, int i = 0, int divider = 1> static void encode(int &target, int value) { template <int index, int i = 0, int divider = 1> static void encode(int &target, const int value) {
if constexpr (i == index) { if constexpr (i == index) {
const int suffix = target % divider; const int suffix = target % divider;
target /= divider; target /= divider;
@@ -61,7 +61,7 @@ private:
template <int size, int... Tail> template <int size, int... Tail>
struct NumericDecoder { struct NumericDecoder {
template <int index, int i = 0, int divider = 1> static int decode(int source) { template <int index, int i = 0, int divider = 1> static int decode(const int source) {
if constexpr (i == index) { if constexpr (i == index) {
return (source / divider) % size; return (source / divider) % size;
} else { } else {

View File

@@ -62,7 +62,7 @@
</Testables> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"

View File

@@ -76,7 +76,7 @@ enum class EnabledLevel {
constexpr EnabledLevel enabled_level(const Source source) { constexpr EnabledLevel enabled_level(const Source source) {
#ifdef NDEBUG #ifdef NDEBUG
return false; return EnabledLevel::None;
#endif #endif
// Allow for compile-time source-level enabling and disabling of different sources. // Allow for compile-time source-level enabling and disabling of different sources.

View File

@@ -24,7 +24,7 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type) {
test_gl(glShaderSource, shader, 1, &c_str, NULL); test_gl(glShaderSource, shader, 1, &c_str, NULL);
test_gl(glCompileShader, shader); test_gl(glCompileShader, shader);
if constexpr (logger.enabled) { if constexpr (logger.ErrorsEnabled) {
GLint isCompiled = 0; GLint isCompiled = 0;
test_gl(glGetShaderiv, shader, GL_COMPILE_STATUS, &isCompiled); test_gl(glGetShaderiv, shader, GL_COMPILE_STATUS, &isCompiled);
if(isCompiled == GL_FALSE) { if(isCompiled == GL_FALSE) {
@@ -69,7 +69,7 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_
for(const auto &binding : attribute_bindings) { for(const auto &binding : attribute_bindings) {
test_gl(glBindAttribLocation, shader_program_, binding.index, binding.name.c_str()); test_gl(glBindAttribLocation, shader_program_, binding.index, binding.name.c_str());
if constexpr (logger.enabled) { if constexpr (logger.ErrorsEnabled) {
const auto error = glGetError(); const auto error = glGetError();
switch(error) { switch(error) {
case 0: break; case 0: break;
@@ -88,7 +88,7 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_
test_gl(glLinkProgram, shader_program_); test_gl(glLinkProgram, shader_program_);
if constexpr (logger.enabled) { if constexpr (logger.ErrorsEnabled) {
GLint logLength; GLint logLength;
test_gl(glGetProgramiv, shader_program_, GL_INFO_LOG_LENGTH, &logLength); test_gl(glGetProgramiv, shader_program_, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0) { if(logLength > 0) {

View File

@@ -56,45 +56,45 @@ template <Action action, typename IteratorT, typename SampleT> void fill(Iterato
*/ */
template <typename SourceT, bool stereo> template <typename SourceT, bool stereo>
class BufferSource { class BufferSource {
public: public:
/*! /*!
Indicates whether this component will write stereo samples. Indicates whether this component will write stereo samples.
*/ */
static constexpr bool is_stereo = stereo; static constexpr bool is_stereo = stereo;
/*! /*!
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available). helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
No default implementation is provided. No default implementation is provided.
*/ */
template <Action action> template <Action action>
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target); void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
/*! /*!
@returns @c true if it is trivially true that a call to get_samples would just @returns @c true if it is trivially true that a call to get_samples would just
fill the target with zeroes; @c false if a call might return all zeroes or fill the target with zeroes; @c false if a call might return all zeroes or
might not. might not.
*/ */
// bool is_zero_level() const { return false; } // bool is_zero_level() const { return false; }
/*! /*!
Sets the proper output range for this sample source; it should write values Sets the proper output range for this sample source; it should write values
between 0 and volume. between 0 and volume.
*/ */
// void set_sample_volume_range(std::int16_t volume); // void set_sample_volume_range(std::int16_t volume);
/*! /*!
Permits a sample source to declare that, averaged over time, it will use only Permits a sample source to declare that, averaged over time, it will use only
a certain proportion of the allocated volume range. This commonly happens a certain proportion of the allocated volume range. This commonly happens
in sample sources that use a time-multiplexed sound output — for example, if in sample sources that use a time-multiplexed sound output — for example, if
one were to output only every other sample then it would return 0.5. one were to output only every other sample then it would return 0.5.
This is permitted to vary over time but there is no contract as to when it will be This is permitted to vary over time but there is no contract as to when it will be
used by a speaker. If it varies, it should do so very infrequently and only to used by a speaker. If it varies, it should do so very infrequently and only to
represent changes in hardware configuration. represent changes in hardware configuration.
*/ */
double average_output_peak() const { return 1.0; } double average_output_peak() const { return 1.0; }
}; };
/// ///

View File

@@ -8,7 +8,8 @@ DFS-1770-2.20.rom — used only if the user opens a DFS disk image
ADFS-E00_1.rom — used only if the user opens an ADFS disk image ADFS-E00_1.rom — used only if the user opens an ADFS disk image
ADFS-E00_2.rom ADFS-E00_2.rom
AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below. AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below.
adfs.rom - used only if the user opens a hard disk image adfs.rom used only if the user opens a hard disk image or requests a SCSI interface
ELK103.rom — used only if the user requests an IDE interface
Possibly to be desired in the future: Possibly to be desired in the future:
* os300.rom * os300.rom