mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-20 14:29:11 +00:00
Merge branch 'master' into Templates
This commit is contained in:
commit
90f16026bc
2
.github/workflows/ccpp.yml
vendored
2
.github/workflows/ccpp.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
|
||||
- name: Make
|
||||
|
@ -9,6 +9,7 @@
|
||||
#ifndef ActivityObserver_h
|
||||
#define ActivityObserver_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Activity {
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a confidence source that calculates its probability by virtual of a history of events.
|
||||
@ -41,7 +40,6 @@ class ConfidenceCounter: public ConfidenceSource {
|
||||
int misses_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceCounter_hpp */
|
||||
|
@ -9,8 +9,7 @@
|
||||
#ifndef ConfidenceSource_hpp
|
||||
#define ConfidenceSource_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides an abstract interface through which objects can declare the probability
|
||||
@ -22,7 +21,6 @@ struct ConfidenceSource {
|
||||
virtual float get_confidence() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSource_hpp */
|
||||
|
@ -13,8 +13,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
@ -40,7 +39,6 @@ class ConfidenceSummary: public ConfidenceSource {
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSummary_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configurable interface to multiple machines.
|
||||
@ -36,7 +35,6 @@ class MultiConfigurable: public Configurable::Device {
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurable_hpp */
|
||||
|
@ -14,8 +14,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the joystick machine interface to multiple machines.
|
||||
@ -34,7 +33,6 @@ class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiJoystickMachine_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the keyboard machine interface to multiple machines.
|
||||
@ -55,7 +54,6 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiKeyboardMachine_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the media target interface to multiple machines.
|
||||
@ -35,7 +34,6 @@ struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMediaTarget_hpp */
|
||||
|
@ -19,8 +19,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
@ -116,7 +115,5 @@ class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, pu
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* MultiProducer_hpp */
|
||||
|
@ -16,8 +16,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
|
||||
@ -55,7 +54,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiSpeaker_hpp */
|
||||
|
@ -22,8 +22,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides the same interface as to a single machine, while multiplexing all
|
||||
@ -80,7 +79,6 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMachine_hpp */
|
||||
|
@ -12,9 +12,7 @@
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||
struct Catalogue {
|
||||
@ -31,8 +29,6 @@ struct Catalogue {
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@ -60,8 +58,6 @@ struct File {
|
||||
std::vector<Chunk> chunks;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AcornAnalyser_hpp */
|
||||
|
@ -20,7 +20,7 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
int shift_register = 0;
|
||||
|
||||
// TODO: move this into the parser
|
||||
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
|
||||
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
|
||||
|
||||
// find next area of high tone
|
||||
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
|
||||
|
@ -14,14 +14,10 @@
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_acorn_adfs = false;
|
||||
@ -37,8 +35,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Acorn_Target_h */
|
||||
|
@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
|
||||
|
@ -12,9 +12,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ChipRAM,
|
||||
@ -41,8 +39,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Amiga_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
|
||||
@ -32,8 +30,5 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@ -51,8 +49,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@ -42,8 +40,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -11,9 +11,7 @@
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class PagingModel {
|
||||
@ -38,8 +36,6 @@ struct Target: public ::Analyser::Static::Target {
|
||||
Target() : Analyser::Static::Target(Machine::Atari2600) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Atari_Target_h */
|
||||
|
@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */
|
||||
|
@ -12,9 +12,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemorySize,
|
||||
@ -31,8 +29,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AtariST_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Coleco {
|
||||
namespace Analyser::Static::Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -14,14 +14,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@ -9,12 +9,11 @@
|
||||
#ifndef File_hpp
|
||||
#define File_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct File {
|
||||
std::wstring name;
|
||||
@ -35,8 +34,6 @@ struct File {
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@ -93,7 +93,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
|
||||
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
@ -188,8 +188,8 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
|
||||
// Unhandled:
|
||||
//
|
||||
// M6: this is a C64 file.
|
||||
// MV: this is a Vic-20 file.
|
||||
// M6: this is a C64 file.
|
||||
// MV: this is a Vic-20 file.
|
||||
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
|
||||
// RO: this disk image should be treated as read-only.
|
||||
}
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CommodoreAnalyser_hpp */
|
||||
|
@ -12,14 +12,10 @@
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class MemoryModel {
|
||||
@ -71,8 +69,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Commodore_Target_h */
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "Kernel.hpp"
|
||||
|
||||
using namespace Analyser::Static::MOS6502;
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
|
@ -16,9 +16,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MOS6502 {
|
||||
namespace Analyser::Static::MOS6502 {
|
||||
|
||||
/*!
|
||||
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
|
||||
@ -95,7 +93,5 @@ Disassembly Disassemble(
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disassembler6502_hpp */
|
||||
|
@ -11,9 +11,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembler {
|
||||
namespace Analyser::Static::Disassembler {
|
||||
|
||||
/*!
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
@ -25,8 +23,6 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AddressMapper_hpp */
|
||||
|
@ -9,9 +9,7 @@
|
||||
#ifndef Kernel_hpp
|
||||
#define Kernel_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembly {
|
||||
namespace Analyser::Static::Disassembly {
|
||||
|
||||
template <typename D, typename S> struct PartialDisassembly {
|
||||
D disassembly;
|
||||
@ -45,8 +43,6 @@ template <typename D, typename S, typename Disassembler> D Disassemble(
|
||||
return partial_disassembly.disassembly;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Kernel_hpp */
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "Kernel.hpp"
|
||||
|
||||
using namespace Analyser::Static::Z80;
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
|
@ -15,9 +15,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Z80 {
|
||||
namespace Analyser::Static::Z80 {
|
||||
|
||||
struct Instruction {
|
||||
/*! The address this instruction starts at. This is a mapped address. */
|
||||
@ -84,7 +82,5 @@ Disassembly Disassemble(
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
|
||||
|
@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace DiskII {
|
||||
namespace Analyser::Static::DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
|
||||
|
@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
|
@ -15,9 +15,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
@ -50,8 +48,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
|
@ -11,9 +11,7 @@
|
||||
|
||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
/*!
|
||||
Extends the base cartridge class by adding a (guess at) the banking scheme.
|
||||
@ -33,8 +31,6 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Cartridge_hpp */
|
||||
|
@ -37,6 +37,11 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
auto target = std::make_unique<Analyser::Static::MSX::Target>();
|
||||
target->confidence = confidence;
|
||||
|
||||
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
|
||||
if(segment.data.size() < 48*1024) {
|
||||
target->model = Analyser::Static::MSX::Target::Model::MSX1;
|
||||
}
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
} else {
|
||||
@ -100,6 +105,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
// Bonus observation: all such ROMs are from the MSX 1 era.
|
||||
if(data_size <= 0xc000) {
|
||||
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||
continue;
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@ -37,8 +35,6 @@ struct File {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_Tape_hpp */
|
||||
|
@ -14,14 +14,19 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_disk_drive = false;
|
||||
bool has_msx_music = true;
|
||||
std::string loading_command;
|
||||
|
||||
ReflectableEnum(Model,
|
||||
MSX1,
|
||||
MSX2
|
||||
);
|
||||
Model model = Model::MSX2;
|
||||
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
@ -32,14 +37,15 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_MSX_Target_h */
|
||||
|
@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
@ -30,8 +28,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@ -33,8 +31,6 @@ struct File {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ROM,
|
||||
@ -56,8 +54,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Oric_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class Model {
|
||||
@ -48,10 +46,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Sega_Target_h */
|
||||
|
@ -59,6 +59,7 @@
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
@ -170,6 +171,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("hdv", result.mass_storage_devices, MassStorage::HDV, TargetPlatform::AppleII) // HDV (Apple II, hard disk, single volume image)
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
@ -183,6 +185,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Disk::DiskImageHolder<Storage::Disk::IPF>,
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("mx2", result.cartridges, Cartridge::BinaryDump, TargetPlatform::MSX) // MX2
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
@ -242,7 +245,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
// Check whether the file directly identifies a target; if so then just return that.
|
||||
#define Format(ext, class) \
|
||||
#define Format(ext, class) \
|
||||
if(extension == ext) { \
|
||||
try { \
|
||||
auto target = Storage::State::class::load(file_name); \
|
||||
|
@ -21,8 +21,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Analyser::Static {
|
||||
|
||||
struct State;
|
||||
|
||||
@ -79,7 +78,6 @@ TargetList GetTargets(const std::string &file_name);
|
||||
*/
|
||||
Media GetMedia(const std::string &file_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemoryModel,
|
||||
@ -40,8 +38,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_ZX8081_Target_h */
|
||||
|
@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@ -38,8 +36,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
/*!
|
||||
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
|
||||
of time since run_for was last called.
|
||||
@ -121,7 +123,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
@ -130,7 +138,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
@ -264,6 +278,10 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -276,7 +294,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
object_(std::forward<Args>(args)...),
|
||||
threshold_(threshold) {}
|
||||
threshold_(threshold) {}
|
||||
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
|
@ -66,7 +66,7 @@ uint8_t WD1770::read(int address) {
|
||||
|
||||
// Per Jean Louis-Guérin's documentation:
|
||||
//
|
||||
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
|
||||
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
|
||||
// read live after a type 1.
|
||||
// * the track 0 bit is captured during a type 1 instruction and lost upon any other type,
|
||||
// it is not live sampled.
|
||||
|
@ -156,7 +156,7 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
return uint8_t(bus_.get_state());
|
||||
|
||||
case 1:
|
||||
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
return
|
||||
// Bits repeated as they were set.
|
||||
(initiator_command_ & ~0x60) |
|
||||
|
@ -14,8 +14,7 @@
|
||||
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
|
||||
|
||||
namespace NCR {
|
||||
namespace NCR5380 {
|
||||
namespace NCR::NCR5380 {
|
||||
|
||||
/*!
|
||||
Models the NCR 5380, a SCSI interface chip.
|
||||
@ -24,7 +23,7 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
public:
|
||||
NCR5380(SCSI::Bus &bus, int clock_rate);
|
||||
|
||||
/*! Writes @c value to @c address. */
|
||||
/*! Writes @c value to @c address. */
|
||||
void write(int address, uint8_t value, bool dma_acknowledge = false);
|
||||
|
||||
/*! Reads from @c address. */
|
||||
@ -86,7 +85,6 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
bool phase_matches() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ncr5380_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
@ -138,7 +137,6 @@ template <class BusHandlerT> class MOS6522: public MOS6522Storage {
|
||||
void evaluate_port_b_output();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
@ -12,8 +12,7 @@
|
||||
//
|
||||
// PB6 count-down mode for timer 2.
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
template <typename T> void MOS6522<T>::access(int address) {
|
||||
switch(address) {
|
||||
@ -272,7 +271,7 @@ template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line li
|
||||
// TODO: and at least one full clock since the shift register was written?
|
||||
if(port == Port::B) {
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
|
||||
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
|
||||
}
|
||||
@ -330,7 +329,7 @@ template <typename T> void MOS6522<T>::do_phase2() {
|
||||
|
||||
// If the shift register is shifting according to the input clock, do a shift.
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderPhase2: shift_in(); break;
|
||||
case ShiftMode::OutUnderPhase2: shift_out(); break;
|
||||
}
|
||||
@ -346,9 +345,9 @@ template <typename T> void MOS6522<T>::do_phase1() {
|
||||
// If the shift register is shifting according to this timer, do a shift.
|
||||
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderT2: shift_in(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
|
||||
}
|
||||
|
||||
@ -494,4 +493,3 @@ template <typename T> void MOS6522<T>::shift_out() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
class MOS6522Storage {
|
||||
protected:
|
||||
@ -107,7 +106,6 @@ class MOS6522Storage {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522Storage_hpp */
|
||||
|
@ -14,8 +14,7 @@
|
||||
#include "Implementation/6526Storage.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
@ -86,7 +85,6 @@ template <typename PortHandlerT, Personality personality> class MOS6526:
|
||||
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6526Implementation.hpp"
|
||||
|
@ -12,8 +12,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
enum Interrupts: uint8_t {
|
||||
TimerA = 1 << 0,
|
||||
@ -238,7 +237,6 @@ bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Implementation_h */
|
||||
|
@ -13,8 +13,7 @@
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
class TODBase {
|
||||
public:
|
||||
@ -333,7 +332,6 @@ struct MOS6526Storage {
|
||||
int pending_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Storage_h */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6560 {
|
||||
namespace MOS::MOS6560 {
|
||||
|
||||
// audio state
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
@ -84,11 +83,11 @@ template <class BusHandler> class MOS6560 {
|
||||
speaker_.set_input_rate(float(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
|
||||
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
|
||||
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
|
||||
void set_high_frequency_cutoff(float cutoff) {
|
||||
speaker_.set_high_frequency_cutoff(cutoff);
|
||||
@ -520,7 +519,6 @@ template <class BusHandler> class MOS6560 {
|
||||
OutputMode output_mode_ = OutputMode::NTSC;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _560_hpp */
|
||||
|
@ -14,8 +14,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Motorola {
|
||||
namespace CRTC {
|
||||
namespace Motorola::CRTC {
|
||||
|
||||
struct BusState {
|
||||
bool display_enable = false;
|
||||
@ -269,7 +268,6 @@ template <class T> class CRTC6845 {
|
||||
unsigned int character_is_visible_shifter_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CRTC6845_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace Motorola {
|
||||
namespace ACIA {
|
||||
namespace Motorola::ACIA {
|
||||
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
|
||||
public:
|
||||
@ -126,7 +125,6 @@ class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelega
|
||||
uint8_t get_status();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Motorola_ACIA_6850_hpp */
|
||||
|
@ -9,12 +9,12 @@
|
||||
#ifndef MFP68901_hpp
|
||||
#define MFP68901_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace Motorola {
|
||||
namespace MFP68901 {
|
||||
#include <cstdint>
|
||||
|
||||
namespace Motorola::MFP68901 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
@ -181,7 +181,6 @@ class MFP68901: public ClockingHint::Source {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MFP68901_hpp */
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8255 {
|
||||
namespace Intel::i8255 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
@ -88,7 +87,6 @@ template <class T> class i8255 {
|
||||
T &port_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8255_hpp */
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8272 {
|
||||
namespace Intel::i8272 {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
@ -130,7 +129,6 @@ class i8272 : public Storage::Disk::MFMController {
|
||||
bool is_sleeping_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8272_hpp */
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Zilog {
|
||||
namespace SCC {
|
||||
namespace Zilog::SCC {
|
||||
|
||||
/*!
|
||||
Models the Zilog 8530 SCC, a serial adaptor.
|
||||
@ -110,7 +109,5 @@ class z8530 {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* z8530_hpp */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,12 +12,36 @@
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
namespace TI::TMS {
|
||||
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
|
||||
// Yamaha extensions.
|
||||
V9938,
|
||||
V9958,
|
||||
|
||||
// Sega extensions.
|
||||
SMSVDP,
|
||||
SMS2VDP,
|
||||
GGVDP,
|
||||
MDVDP,
|
||||
};
|
||||
|
||||
enum class TVStandard {
|
||||
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||
PAL,
|
||||
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||
NTSC
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
@ -30,13 +54,10 @@ namespace TMS {
|
||||
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
|
||||
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||
*/
|
||||
class TMS9918: public Base {
|
||||
template <Personality personality> class TMS9918: private Base<personality> {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
TMS9918(Personality p);
|
||||
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
|
||||
TMS9918();
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
@ -44,18 +65,24 @@ class TMS9918: public Base {
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
|
||||
/// Gets the current scan status.
|
||||
/*! Gets the current scan status. */
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*! Sets the type of display the CRT will request. */
|
||||
/*! Sets the type of CRT display. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/*! Gets the type of display the CRT will request. */
|
||||
/*! Gets the type of CRT display. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
|
||||
|
||||
For everything except the Mega Drive VDP:
|
||||
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
|
||||
|
||||
For the Mega Drive:
|
||||
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
|
||||
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
|
||||
@ -65,11 +92,11 @@ class TMS9918: public Base {
|
||||
/*! Gets a register value. */
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! Gets the current scan line; provided by the Master System only. */
|
||||
uint8_t get_current_line();
|
||||
/*! Gets the current scan line; provided by the Sega VDPs only. */
|
||||
uint8_t get_current_line() const;
|
||||
|
||||
/*! Gets the current latched horizontal counter; provided by the Master System only. */
|
||||
uint8_t get_latched_horizontal_counter();
|
||||
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
|
||||
uint8_t get_latched_horizontal_counter() const;
|
||||
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
@ -81,7 +108,7 @@ class TMS9918: public Base {
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles get_next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
@ -96,10 +123,9 @@ class TMS9918: public Base {
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line();
|
||||
bool get_interrupt_line() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TMS9918_hpp */
|
||||
|
1378
Components/9918/Implementation/9918.cpp
Normal file
1378
Components/9918/Implementation/9918.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
115
Components/9918/Implementation/AccessEnums.hpp
Normal file
115
Components/9918/Implementation/AccessEnums.hpp
Normal file
@ -0,0 +1,115 @@
|
||||
//
|
||||
// AccessEnums.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AccessEnums_hpp
|
||||
#define AccessEnums_hpp
|
||||
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// The screen mode is a necessary predecessor to picking the line mode,
|
||||
// which is the thing latched per line.
|
||||
enum class ScreenMode {
|
||||
// Original TMS modes.
|
||||
Blank,
|
||||
Text,
|
||||
MultiColour,
|
||||
ColouredText,
|
||||
Graphics,
|
||||
|
||||
// 8-bit Sega modes.
|
||||
SMSMode4,
|
||||
|
||||
// New Yamaha V9938 modes.
|
||||
YamahaText80,
|
||||
YamahaGraphics3,
|
||||
YamahaGraphics4,
|
||||
YamahaGraphics5,
|
||||
YamahaGraphics6,
|
||||
YamahaGraphics7,
|
||||
|
||||
// Rebranded Yamaha V9938 modes.
|
||||
YamahaGraphics1 = ColouredText,
|
||||
YamahaGraphics2 = Graphics,
|
||||
};
|
||||
|
||||
constexpr int pixels_per_byte(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 1;
|
||||
case ScreenMode::Text: return 6;
|
||||
case ScreenMode::MultiColour: return 2;
|
||||
case ScreenMode::ColouredText: return 8;
|
||||
case ScreenMode::Graphics: return 8;
|
||||
case ScreenMode::SMSMode4: return 2;
|
||||
case ScreenMode::YamahaText80: return 6;
|
||||
case ScreenMode::YamahaGraphics3: return 8;
|
||||
case ScreenMode::YamahaGraphics4: return 2;
|
||||
case ScreenMode::YamahaGraphics5: return 4;
|
||||
case ScreenMode::YamahaGraphics6: return 2;
|
||||
case ScreenMode::YamahaGraphics7: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int width(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 0;
|
||||
case ScreenMode::Text: return 240;
|
||||
case ScreenMode::MultiColour: return 256;
|
||||
case ScreenMode::ColouredText: return 256;
|
||||
case ScreenMode::Graphics: return 256;
|
||||
case ScreenMode::SMSMode4: return 256;
|
||||
case ScreenMode::YamahaText80: return 480;
|
||||
case ScreenMode::YamahaGraphics3: return 256;
|
||||
case ScreenMode::YamahaGraphics4: return 256;
|
||||
case ScreenMode::YamahaGraphics5: return 512;
|
||||
case ScreenMode::YamahaGraphics6: return 512;
|
||||
case ScreenMode::YamahaGraphics7: return 256;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool interleaves_banks(ScreenMode mode) {
|
||||
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
|
||||
}
|
||||
|
||||
constexpr bool is_text(ScreenMode mode) {
|
||||
return mode == ScreenMode::Text || mode == ScreenMode::YamahaText80;
|
||||
}
|
||||
|
||||
enum class FetchMode {
|
||||
Text,
|
||||
Character,
|
||||
Refresh,
|
||||
SMS,
|
||||
Yamaha,
|
||||
};
|
||||
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
};
|
||||
|
||||
enum class VerticalState {
|
||||
/// Describes any line on which pixels do not appear and no fetching occurs, including
|
||||
/// the border, blanking and sync.
|
||||
Blank,
|
||||
/// A line on which pixels do not appear but fetching occurs.
|
||||
Prefetch,
|
||||
/// A line on which pixels appear and fetching occurs.
|
||||
Pixels,
|
||||
};
|
||||
|
||||
enum class SpriteMode {
|
||||
Mode1,
|
||||
Mode2,
|
||||
MasterSystem,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AccessEnums_hpp */
|
168
Components/9918/Implementation/ClockConverter.hpp
Normal file
168
Components/9918/Implementation/ClockConverter.hpp
Normal file
@ -0,0 +1,168 @@
|
||||
//
|
||||
// ClockConverter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockConverter_hpp
|
||||
#define ClockConverter_hpp
|
||||
|
||||
#include "../9918.hpp"
|
||||
#include "PersonalityTraits.hpp"
|
||||
#include "LineLayout.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
enum class Clock {
|
||||
/// Whatever rate this VDP runs at, with location 0 being "the start" of the line per internal preference.
|
||||
Internal,
|
||||
/// A 342-cycle/line clock with the same start position as ::Internal.
|
||||
TMSPixel,
|
||||
/// A 171-cycle/line clock that begins at the memory window which starts straight after ::Internal = 0.
|
||||
TMSMemoryWindow,
|
||||
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
|
||||
CRT,
|
||||
/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
|
||||
/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
|
||||
FromStartOfSync,
|
||||
};
|
||||
|
||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||
static_assert(
|
||||
is_classic_vdp(personality) ||
|
||||
is_yamaha_vdp(personality) ||
|
||||
(personality == Personality::MDVDP)
|
||||
);
|
||||
|
||||
switch(clk) {
|
||||
case Clock::TMSPixel: return 342;
|
||||
case Clock::TMSMemoryWindow: return 171;
|
||||
case Clock::CRT: return 1368;
|
||||
case Clock::Internal:
|
||||
case Clock::FromStartOfSync:
|
||||
if constexpr (is_classic_vdp(personality)) {
|
||||
return 342;
|
||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||
return 1368;
|
||||
} else if constexpr (personality == Personality::MDVDP) {
|
||||
return 3420;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order.
|
||||
template <Personality personality, Clock head, Clock... tail> constexpr int to_internal(int length) {
|
||||
if constexpr (head == Clock::FromStartOfSync) {
|
||||
length = (length + LineLayout<personality>::StartOfSync) % LineLayout<personality>::CyclesPerLine;
|
||||
} else {
|
||||
length = length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, head>();
|
||||
}
|
||||
|
||||
if constexpr (!sizeof...(tail)) {
|
||||
return length;
|
||||
} else {
|
||||
return to_internal<personality, tail...>(length);
|
||||
}
|
||||
}
|
||||
|
||||
/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
|
||||
template <Personality personality, Clock head, Clock... tail> constexpr int from_internal(int length) {
|
||||
if constexpr (head == Clock::FromStartOfSync) {
|
||||
length =
|
||||
(length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::StartOfSync)
|
||||
% LineLayout<personality>::CyclesPerLine;
|
||||
} else {
|
||||
length = length * clock_rate<personality, head>() / clock_rate<personality, Clock::Internal>();
|
||||
}
|
||||
|
||||
if constexpr (!sizeof...(tail)) {
|
||||
return length;
|
||||
} else {
|
||||
return to_internal<personality, tail...>(length);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
||||
Unlike the other clock conversions, this may be non-integral, requiring that
|
||||
an error term be tracked.
|
||||
*/
|
||||
template <Personality personality> class ClockConverter {
|
||||
public:
|
||||
/*!
|
||||
Given that another @c source external **half-cycles** has occurred,
|
||||
indicates how many complete internal **cycles** have additionally elapsed
|
||||
since the last call to @c to_internal.
|
||||
|
||||
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
|
||||
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
|
||||
*/
|
||||
int to_internal(int source) {
|
||||
switch(personality) {
|
||||
// Default behaviour is to apply a multiplication by 3/4;
|
||||
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
|
||||
default: {
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
|
||||
// The two Yamaha chips have an internal clock that is four times
|
||||
// as fast as the TMS, therefore a stateless translation is possible.
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source * 3;
|
||||
|
||||
// The Mega Drive runs at 3420 master clocks per line, which is then
|
||||
// divided by 4 or 5 depending on other state. That's 7 times the
|
||||
// rate provided to the CPU; given that the input is in half-cycles
|
||||
// the proper multiplier is therefore 3.5.
|
||||
case Personality::MDVDP: {
|
||||
const int result = source * 7 + cycles_error_;
|
||||
cycles_error_ = result & 1;
|
||||
return result >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the number of external cycles that need to begin from now in order to
|
||||
get at least @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
// Logic here correlates with multipliers as per @c to_internal.
|
||||
switch(personality) {
|
||||
default:
|
||||
// Relative to the external clock multiplied by 3, it will definitely take this
|
||||
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
||||
internal_cycles = (internal_cycles - 1) << 2;
|
||||
|
||||
// It will also be necessary to complete the current one.
|
||||
internal_cycles += 4 - cycles_error_;
|
||||
|
||||
// Round up to get the first external cycle after
|
||||
// the number of internal_cycles has elapsed.
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::MDVDP:
|
||||
internal_cycles = (internal_cycles - 1) << 1;
|
||||
internal_cycles += 2 - cycles_error_;
|
||||
return HalfCycles((internal_cycles + 6) / 7);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
int cycles_error_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockConverter_hpp */
|
572
Components/9918/Implementation/Draw.hpp
Normal file
572
Components/9918/Implementation/Draw.hpp
Normal file
@ -0,0 +1,572 @@
|
||||
//
|
||||
// Draw.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Draw_hpp
|
||||
#define Draw_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Sprites, as generalised.
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode mode, bool double_width>
|
||||
void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer) {
|
||||
if(!draw_line_buffer_->sprites) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &buffer = *draw_line_buffer_->sprites;
|
||||
if(!buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
|
||||
// If this is the start of the line clip any part of any sprites that is off to the left.
|
||||
if(!start) {
|
||||
for(int index = 0; index < buffer.active_sprite_slot; ++index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x;
|
||||
}
|
||||
}
|
||||
|
||||
int sprite_buffer[256];
|
||||
int sprite_collision = 0;
|
||||
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
|
||||
|
||||
if constexpr (mode == SpriteMode::MasterSystem) {
|
||||
// Draw all sprites into the sprite buffer.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= 0; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
|
||||
// TODO: it feels like the work below should be simplifiable;
|
||||
// the double shift in particular, and hopefully the variable shift.
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1);
|
||||
const int sprite_colour =
|
||||
(((sprite.image[3] << shift) & 0x80) >> 4) |
|
||||
(((sprite.image[2] << shift) & 0x80) >> 5) |
|
||||
(((sprite.image[1] << shift) & 0x80) >> 6) |
|
||||
(((sprite.image[0] << shift) & 0x80) >> 7);
|
||||
|
||||
if(sprite_colour) {
|
||||
sprite_collision |= sprite_buffer[c];
|
||||
sprite_buffer[c] = sprite_colour | 0x10;
|
||||
}
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have
|
||||
// priority (or is transparent).
|
||||
for(int c = start; c < end; ++c) {
|
||||
if(
|
||||
sprite_buffer[c] &&
|
||||
(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf))
|
||||
) colour_buffer[c] = sprite_buffer[c];
|
||||
}
|
||||
|
||||
if(sprite_collision) {
|
||||
status_ |= StatusSpriteCollision;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
assert(!buffer.is_filling);
|
||||
}
|
||||
|
||||
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
const int sprite_width = sprites_16x16_ ? 16 : 8;
|
||||
const int shifter_target = sprite_width << 1;
|
||||
const int pixel_width = sprites_magnified_ ? sprite_width << 1 : sprite_width;
|
||||
int min_sprite = 0;
|
||||
|
||||
//
|
||||
// Approach taken for Mode 2 sprites:
|
||||
//
|
||||
// (1) precompute full sprite images, at up to 32 pixels wide;
|
||||
// (2) for each sprite that is marked as CC, walk backwards until the
|
||||
// first sprite that is not marked CC, ORing it into the precomputed
|
||||
// image at each step;
|
||||
// (3) subsequently, just draw each sprite image independently.
|
||||
//
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
// Determine the lowest visible sprite; exit early if that leaves no sprites visible.
|
||||
for(; min_sprite < buffer.active_sprite_slot; min_sprite++) {
|
||||
auto &sprite = buffer.active_sprites[min_sprite];
|
||||
if(sprite.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(min_sprite == buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!start) {
|
||||
// Pre-rasterise the sprites one-by-one.
|
||||
if(sprites_magnified_) {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 32; c+= 2) {
|
||||
const int shift = (c >> 1) ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
Storage<personality>::sprite_cache_[index][c + 1] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 16; c++) {
|
||||
const int shift = c ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go backwards compositing any sprites that are set as OR masks onto their parents.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite + 1; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.opaque()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sprite may affect all previous up to and cindlugin the next one that is opaque.
|
||||
for(int previous_index = index - 1; previous_index >= min_sprite; --previous_index) {
|
||||
// Determine region of overlap (if any).
|
||||
auto &previous = buffer.active_sprites[previous_index];
|
||||
const int origin = sprite.x - previous.x;
|
||||
const int x1 = std::max(0, -origin);
|
||||
const int x2 = std::min(pixel_width - origin, pixel_width);
|
||||
|
||||
// Composite sprites.
|
||||
for(int x = x1; x < x2; x++) {
|
||||
Storage<personality>::sprite_cache_[previous_index][x + origin]
|
||||
|= Storage<personality>::sprite_cache_[index][x];
|
||||
}
|
||||
|
||||
// If a previous opaque sprite has been found, stop.
|
||||
if(previous.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
const int x1 = std::max(0, start - sprite.x);
|
||||
const int x2 = std::min(end - sprite.x, pixel_width);
|
||||
|
||||
for(int x = x1; x < x2; x++) {
|
||||
const uint8_t colour = Storage<personality>::sprite_cache_[index][x];
|
||||
|
||||
// Plot colour, if visible.
|
||||
if(colour) {
|
||||
pixel_origin_[sprite.x + x] = palette[colour & 0xf];
|
||||
}
|
||||
|
||||
// TODO: is collision location recorded in mode 1?
|
||||
|
||||
// Check for a new collision.
|
||||
if(!(status_ & StatusSpriteCollision)) {
|
||||
sprite_collision |= sprite_buffer[sprite.x + x];
|
||||
sprite_buffer[sprite.x + x] |= colour;
|
||||
status_ |= sprite_collision & StatusSpriteCollision;
|
||||
|
||||
if(status_ & StatusSpriteCollision) {
|
||||
Storage<personality>::collision_location_[0] = uint16_t(x);
|
||||
Storage<personality>::collision_location_[1] = uint16_t(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode1) {
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= shifter_target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1) ^ 7;
|
||||
int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1;
|
||||
|
||||
// A colision is detected regardless of sprite colour ...
|
||||
sprite_collision |= sprite_buffer[c] & sprite_colour;
|
||||
sprite_buffer[c] |= sprite_colour;
|
||||
|
||||
// ... but a sprite with the transparent colour won't actually be visible.
|
||||
sprite_colour &= colour_masks[sprite.image[2] & 0xf];
|
||||
|
||||
pixel_origin_[c] =
|
||||
(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) |
|
||||
(palette[sprite.image[2] & 0xf] & sprite_colour_selection_masks[sprite_colour]);
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
status_ |= sprite_collision << StatusSpriteCollisionShift;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mode 2 logic, as I currently understand it, as a note for my future self:
|
||||
//
|
||||
// If a sprite is marked as 'CC' then it doesn't collide, but its colour value is
|
||||
// ORd with those of all lower-numbered sprites down to the next one that is visible on
|
||||
// that line and not marked CC.
|
||||
//
|
||||
// If no previous sprite meets that criteria, no pixels are displayed. But if one does
|
||||
// then pixels are displayed even where they don't overlap with the earlier sprites.
|
||||
//
|
||||
// ... so in terms of my loop above, I guess I need temporary storage to accumulate
|
||||
// an OR mask up until I hit a non-CC sprite, at which point I composite everything out?
|
||||
// I'm not immediately sure whether I can appropriately reuse sprite_buffer, but possibly?
|
||||
|
||||
// MARK: - TMS9918
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode sprite_mode>
|
||||
void Base<personality>::draw_tms_character(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Paint the background tiles.
|
||||
const int pixels_left = end - start;
|
||||
if(this->screen_mode_ == ScreenMode::MultiColour) {
|
||||
for(int c = start; c < end; ++c) {
|
||||
pixel_target_[c] = palette()[
|
||||
(line_buffer.tiles.patterns[c >> 3][0] >> (((c & 4)^4))) & 15
|
||||
];
|
||||
}
|
||||
} else {
|
||||
const int shift = start & 7;
|
||||
int byte_column = start >> 3;
|
||||
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
int pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]) >> shift;
|
||||
uint8_t colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
uint32_t colours[2] = {
|
||||
palette()[(colour & 15) ? (colour & 15) : background_colour_],
|
||||
palette()[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||
};
|
||||
|
||||
int background_pixels_left = pixels_left;
|
||||
while(true) {
|
||||
background_pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[pattern&0x01];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!background_pixels_left) break;
|
||||
length = std::min(8, background_pixels_left);
|
||||
byte_column++;
|
||||
|
||||
pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]);
|
||||
colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
colours[0] = palette()[(colour & 15) ? (colour & 15) : background_colour_];
|
||||
colours[1] = palette()[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||
}
|
||||
}
|
||||
|
||||
draw_sprites<sprite_mode, false>(0, start, end, palette()); // TODO: propagate a real 'y' into here.
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template <bool apply_blink>
|
||||
void Base<personality>::draw_tms_text(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
uint32_t colours[2][2] = {
|
||||
{palette()[background_colour_], palette()[text_colour_]},
|
||||
{0, 0}
|
||||
};
|
||||
if constexpr (apply_blink) {
|
||||
colours[1][0] = palette()[Storage<personality>::blink_background_colour_];
|
||||
colours[1][1] = palette()[Storage<personality>::blink_text_colour_];
|
||||
}
|
||||
|
||||
const int shift = start % 6;
|
||||
int byte_column = start / 6;
|
||||
int pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]) >> shift;
|
||||
int pixels_left = end - start;
|
||||
int length = std::min(pixels_left, 6 - shift);
|
||||
int flag = 0;
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
while(true) {
|
||||
pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[flag][(pattern&0x01)];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!pixels_left) break;
|
||||
length = std::min(6, pixels_left);
|
||||
byte_column++;
|
||||
pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]);
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] uint32_t cram_dot) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
int colour_buffer[256];
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
/*
|
||||
Add extra border for any pixels that fall before the fine scroll.
|
||||
*/
|
||||
int tile_start = start, tile_end = end;
|
||||
int tile_offset = start;
|
||||
if(output_pointer_.row >= 16 || !Storage<personality>::horizontal_scroll_lock_) {
|
||||
for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) {
|
||||
colour_buffer[c] = 16 + background_colour_;
|
||||
++tile_offset;
|
||||
}
|
||||
|
||||
// Remove the border area from that to which tiles will be drawn.
|
||||
tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t pattern;
|
||||
uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern);
|
||||
|
||||
/*
|
||||
Add background tiles; these will fill the colour_buffer with values in which
|
||||
the low five bits are a palette index, and bit six is set if this tile has
|
||||
priority over sprites.
|
||||
*/
|
||||
if(tile_start < end) {
|
||||
const int shift = tile_start & 7;
|
||||
int byte_column = tile_start >> 3;
|
||||
int pixels_left = tile_end - tile_start;
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
if(line_buffer.tiles.flags[byte_column]&2)
|
||||
pattern >>= shift;
|
||||
else
|
||||
pattern <<= shift;
|
||||
|
||||
while(true) {
|
||||
const int palette_offset = (line_buffer.tiles.flags[byte_column]&0x18) << 1;
|
||||
if(line_buffer.tiles.flags[byte_column]&2) {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x01) << 3) |
|
||||
((pattern_index[2] & 0x01) << 2) |
|
||||
((pattern_index[1] & 0x01) << 1) |
|
||||
((pattern_index[0] & 0x01) << 0) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern >>= 1;
|
||||
}
|
||||
} else {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x80) >> 4) |
|
||||
((pattern_index[2] & 0x80) >> 5) |
|
||||
((pattern_index[1] & 0x80) >> 6) |
|
||||
((pattern_index[0] & 0x80) >> 7) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pixels_left -= length;
|
||||
if(!pixels_left) break;
|
||||
|
||||
length = std::min(8, pixels_left);
|
||||
byte_column++;
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Apply sprites (if any).
|
||||
*/
|
||||
draw_sprites<SpriteMode::MasterSystem, false>(0, start, end, palette(), colour_buffer); // TODO provide good y, as per elsewhere.
|
||||
|
||||
// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any.
|
||||
pixel_target_[start] = Storage<personality>::colour_ram_[colour_buffer[start] & 0x1f] | cram_dot;
|
||||
for(int c = start+1; c < end; ++c) {
|
||||
pixel_target_[c] = Storage<personality>::colour_ram_[colour_buffer[c] & 0x1f];
|
||||
}
|
||||
|
||||
// If the VDP is set to hide the left column and this is the final call that'll come
|
||||
// this line, hide it.
|
||||
if(end == 256) {
|
||||
if(Storage<personality>::hide_left_column_) {
|
||||
pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] =
|
||||
pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] =
|
||||
Storage<personality>::colour_ram_[16 + background_colour_];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template <ScreenMode mode>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
[[maybe_unused]] const auto active_palette = palette();
|
||||
const int sprite_start = start >> 2;
|
||||
const int sprite_end = end >> 2;
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Observation justifying Duff's device below: it's acceptable to paint too many pixels — to paint
|
||||
// beyond `end` — provided that the overpainting is within normal bitmap bounds, because any
|
||||
// mispainted pixels will be replaced before becoming visible to the user.
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics4 || mode == ScreenMode::YamahaGraphics6) {
|
||||
start >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
end >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
|
||||
int column = start & ~1;
|
||||
const int offset = start & 1;
|
||||
start >>= 1;
|
||||
end = (end + 1) >> 1;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 4]; [[fallthrough]];
|
||||
case 1: pixel_target_[column+1] = active_palette[line_buffer.bitmap[start] & 0xf];
|
||||
++start;
|
||||
column += 2;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics5) {
|
||||
start >>= 1;
|
||||
end >>= 1;
|
||||
|
||||
int column = start & ~3;
|
||||
const int offset = start & 3;
|
||||
start >>= 2;
|
||||
end = (end + 3) >> 2;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 6]; [[fallthrough]];
|
||||
case 1: pixel_target_[column+1] = active_palette[(line_buffer.bitmap[start] >> 4) & 3]; [[fallthrough]];
|
||||
case 2: pixel_target_[column+2] = active_palette[(line_buffer.bitmap[start] >> 2) & 3]; [[fallthrough]];
|
||||
case 3: pixel_target_[column+3] = active_palette[line_buffer.bitmap[start] & 3];
|
||||
++start;
|
||||
column += 4;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics7) {
|
||||
start >>= 2;
|
||||
end >>= 2;
|
||||
|
||||
while(start < end) {
|
||||
pixel_target_[start] =
|
||||
palette_pack(
|
||||
uint8_t((line_buffer.bitmap[start] & 0x1c) + ((line_buffer.bitmap[start] & 0x1c) << 3) + ((line_buffer.bitmap[start] & 0x1c) >> 3)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0xe0) + ((line_buffer.bitmap[start] & 0xe0) >> 3) + ((line_buffer.bitmap[start] & 0xe0) >> 6)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0x03) + ((line_buffer.bitmap[start] & 0x03) << 2) + ((line_buffer.bitmap[start] & 0x03) << 4) + ((line_buffer.bitmap[start] & 0x03) << 6))
|
||||
);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
|
||||
palette_pack(0b00000000, 0b00000000, 0b00000000), palette_pack(0b00000000, 0b00000000, 0b01001001),
|
||||
palette_pack(0b00000000, 0b01101101, 0b00000000), palette_pack(0b00000000, 0b01101101, 0b01001001),
|
||||
palette_pack(0b01101101, 0b00000000, 0b00000000), palette_pack(0b01101101, 0b00000000, 0b01001001),
|
||||
palette_pack(0b01101101, 0b01101101, 0b00000000), palette_pack(0b01101101, 0b01101101, 0b01001001),
|
||||
|
||||
palette_pack(0b10010010, 0b11111111, 0b01001001), palette_pack(0b00000000, 0b00000000, 0b11111111),
|
||||
palette_pack(0b00000000, 0b11111111, 0b00000000), palette_pack(0b00000000, 0b11111111, 0b11111111),
|
||||
palette_pack(0b11111111, 0b00000000, 0b00000000), palette_pack(0b11111111, 0b00000000, 0b11111111),
|
||||
palette_pack(0b11111111, 0b11111111, 0b00000000), palette_pack(0b11111111, 0b11111111, 0b11111111),
|
||||
};
|
||||
|
||||
// Possibly TODO: is the data-sheet trying to allege some sort of colour mixing for sprites in Mode 6?
|
||||
draw_sprites<
|
||||
SpriteMode::Mode2,
|
||||
mode == ScreenMode::YamahaGraphics5 || mode == ScreenMode::YamahaGraphics6
|
||||
>(y, sprite_start, sprite_end, mode == ScreenMode::YamahaGraphics7 ? graphics7_sprite_palette : palette());
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
switch(draw_line_buffer_->screen_mode) {
|
||||
// Modes that are the same (or close enough) to those on the TMS.
|
||||
case ScreenMode::Text: draw_tms_text<false>(start >> 2, end >> 2); break;
|
||||
case ScreenMode::YamahaText80: draw_tms_text<true>(start >> 1, end >> 1); break;
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics: draw_tms_character(start >> 2, end >> 2); break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
draw_tms_character<SpriteMode::Mode2>(start >> 2, end >> 2);
|
||||
break;
|
||||
|
||||
#define Dispatch(x) case ScreenMode::x: draw_yamaha<ScreenMode::x>(y, start, end); break;
|
||||
Dispatch(YamahaGraphics4);
|
||||
Dispatch(YamahaGraphics5);
|
||||
Dispatch(YamahaGraphics6);
|
||||
Dispatch(YamahaGraphics7);
|
||||
#undef Dispatch
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Draw_hpp */
|
796
Components/9918/Implementation/Fetch.hpp
Normal file
796
Components/9918/Implementation/Fetch.hpp
Normal file
@ -0,0 +1,796 @@
|
||||
//
|
||||
// Fetch.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Fetch_hpp
|
||||
#define Fetch_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*
|
||||
Fetching routines follow below; they obey the following rules:
|
||||
|
||||
1) input is a start position and an end position; they should perform the proper
|
||||
operations for the period: start <= time < end.
|
||||
2) times are measured relative to the an appropriate clock — they directly
|
||||
count access windows on the TMS and Master System, and cycles on a Yamaha.
|
||||
3) within each sequencer, cycle are numbered as per Grauw's timing diagrams. The difference
|
||||
between those and internal timing, if there is one, is handled by the dispatcher.
|
||||
4) all of these functions are templated with a `use_end` parameter. That will be true if
|
||||
end is < [cycles per line], false otherwise. So functions can use it to eliminate
|
||||
should-exit-now checks (which is likely to be the more usual path of execution).
|
||||
|
||||
Provided for the benefit of the methods below:
|
||||
|
||||
* the function external_slot(), which will perform any pending VRAM read/write.
|
||||
|
||||
All functions should just spool data to intermediary storage. Fetching and drawing are decoupled.
|
||||
*/
|
||||
|
||||
// MARK: - Address mask helpers.
|
||||
|
||||
/// @returns An instance of @c AddressT with all top bits set down to and including
|
||||
/// bit @c end and all others clear.
|
||||
///
|
||||
/// So e.g. if @c AddressT is @c uint16_t and this VDP has a 15-bit address space then
|
||||
/// @c top_bits<10> will be the address with bits 15 to 10 (inclusive) set and the rest clear.
|
||||
template <typename AddressT, int end> constexpr AddressT top_bits() {
|
||||
return AddressT(~0) - AddressT((1 << end) - 1);
|
||||
}
|
||||
|
||||
/// Modifies and returns @c source so that all bits above position @c n are set; the others are unmodified.
|
||||
template <int n, typename AddressT> constexpr AddressT bits(AddressT source = 0) {
|
||||
return AddressT(source | top_bits<AddressT, n>());
|
||||
}
|
||||
|
||||
// MARK: - 171-window Dispatcher.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end, typename SequencerT> void Base<personality>::dispatch(SequencerT &fetcher, int start, int end) {
|
||||
#define index(n) \
|
||||
if(use_end && end == n) return; \
|
||||
[[fallthrough]]; \
|
||||
case n: fetcher.template fetch<from_internal<personality, Clock::FromStartOfSync>(n)>();
|
||||
|
||||
switch(start) {
|
||||
default: assert(false);
|
||||
index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9);
|
||||
index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19);
|
||||
index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29);
|
||||
index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39);
|
||||
index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49);
|
||||
index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59);
|
||||
index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69);
|
||||
index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79);
|
||||
index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89);
|
||||
index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99);
|
||||
index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109);
|
||||
index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119);
|
||||
index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129);
|
||||
index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139);
|
||||
index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149);
|
||||
index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159);
|
||||
index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169);
|
||||
index(170);
|
||||
}
|
||||
|
||||
#undef index
|
||||
}
|
||||
|
||||
// MARK: - Fetchers.
|
||||
|
||||
template <Personality personality>
|
||||
struct TextFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
TextFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y >> 3) * 40))),
|
||||
row_offset(base->pattern_generator_table_address_ & bits<11>(AddressT(y & 7))) {}
|
||||
|
||||
void fetch_name(AddressT column, int slot = 0) {
|
||||
base->name_[slot] = base->ram_[row_base + column];
|
||||
}
|
||||
|
||||
void fetch_pattern(AddressT column, int slot = 0) {
|
||||
base->fetch_line_buffer_->characters.shapes[column] = base->ram_[row_offset + size_t(base->name_[slot] << 3)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const AddressT row_base;
|
||||
const AddressT row_offset;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
CharacterFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31)))
|
||||
{
|
||||
pattern_base = base->pattern_generator_table_address_;
|
||||
colour_base = base->colour_table_address_;
|
||||
colour_name_shift = 6;
|
||||
|
||||
const ScreenMode mode = base->fetch_line_buffer_->screen_mode;
|
||||
if(mode == ScreenMode::Graphics || mode == ScreenMode::YamahaGraphics3) {
|
||||
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
|
||||
pattern_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
colour_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
|
||||
colour_base += AddressT(y & 7);
|
||||
colour_name_shift = 0;
|
||||
} else {
|
||||
colour_base &= bits<6, AddressT>();
|
||||
pattern_base &= bits<11, AddressT>();
|
||||
}
|
||||
|
||||
if(mode == ScreenMode::MultiColour) {
|
||||
pattern_base += AddressT((y >> 2) & 7);
|
||||
} else {
|
||||
pattern_base += AddressT(y & 7);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_name(int column) {
|
||||
base->tile_offset_ = base->ram_[row_base + AddressT(column)];
|
||||
}
|
||||
|
||||
void fetch_pattern(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)];
|
||||
}
|
||||
|
||||
void fetch_colour(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
const AddressT row_base;
|
||||
AddressT pattern_base;
|
||||
AddressT colour_base;
|
||||
int colour_name_shift;
|
||||
};
|
||||
|
||||
constexpr SpriteMode sprite_mode(ScreenMode screen_mode) {
|
||||
switch(screen_mode) {
|
||||
default:
|
||||
return SpriteMode::Mode2;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics:
|
||||
return SpriteMode::Mode1;
|
||||
|
||||
case ScreenMode::SMSMode4:
|
||||
return SpriteMode::MasterSystem;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this be extended to include Master System sprites?
|
||||
template <Personality personality, SpriteMode mode>
|
||||
class SpriteFetcher {
|
||||
public:
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
|
||||
// table, which is intended to fill the 512 bytes before the programmer-located sprite
|
||||
// attribute table.
|
||||
//
|
||||
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
|
||||
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
|
||||
//
|
||||
// AttributeAddressMask is used to enable or disable that behaviour.
|
||||
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
|
||||
|
||||
SpriteFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y) {}
|
||||
|
||||
void fetch_location(int slot) {
|
||||
fetch_xy(slot);
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
fetch_xy(slot + 1);
|
||||
|
||||
base->name_[0] = name(slot);
|
||||
base->name_[1] = name(slot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_pattern(int slot) {
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
fetch_image(slot, name(slot));
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2:
|
||||
fetch_image(slot, base->name_[0]);
|
||||
fetch_image(slot + 1, base->name_[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_y(int sprite) {
|
||||
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
|
||||
const uint8_t sprite_y = base->ram_[address];
|
||||
base->posit_sprite(sprite, sprite_y, y);
|
||||
}
|
||||
|
||||
private:
|
||||
void fetch_xy(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
buffer.active_sprites[slot].x =
|
||||
base->ram_[
|
||||
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
|
||||
];
|
||||
}
|
||||
|
||||
uint8_t name(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
const AddressT address =
|
||||
base->sprite_attribute_table_address_ &
|
||||
AttributeAddressMask &
|
||||
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
|
||||
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
|
||||
return name;
|
||||
}
|
||||
|
||||
void fetch_image(int slot, uint8_t name) {
|
||||
uint8_t colour = 0;
|
||||
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
// Fetch colour from the attribute table, per this sprite's slot.
|
||||
colour = base->ram_[
|
||||
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
|
||||
];
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2: {
|
||||
// Fetch colour from the colour table, per this sprite's slot and row.
|
||||
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
|
||||
colour = base->ram_[
|
||||
colour_table_address &
|
||||
bits<9>(
|
||||
AddressT(sprite.index << 4) |
|
||||
AddressT(sprite.row)
|
||||
)
|
||||
];
|
||||
} break;
|
||||
}
|
||||
sprite.image[2] = colour;
|
||||
sprite.x -= sprite.early_clock();
|
||||
|
||||
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
|
||||
sprite.image[0] = base->ram_[graphic_location];
|
||||
sprite.image[1] = base->ram_[graphic_location+16];
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
|
||||
base->fetch_sprite_buffer_->is_filling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
struct RowInfo {
|
||||
AddressT pattern_address_base;
|
||||
AddressT sub_row[2];
|
||||
};
|
||||
|
||||
SMSFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
storage(static_cast<Storage<personality> *>(base)),
|
||||
y(y),
|
||||
horizontal_offset((y >= 16 || !storage->horizontal_scroll_lock_) ? (base->fetch_line_buffer_->latched_horizontal_scroll >> 3) : 0)
|
||||
{
|
||||
// Limit address bits in use if this is a SMS2 mode.
|
||||
const bool is_tall_mode = base->mode_timing_.pixel_lines != 192;
|
||||
const AddressT pattern_name_address = storage->pattern_name_address_ | (is_tall_mode ? 0x800 : 0);
|
||||
const AddressT pattern_name_offset = is_tall_mode ? 0x100 : 0;
|
||||
|
||||
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
|
||||
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
|
||||
const int scrolled_row = (y + storage->latched_vertical_scroll_) % (is_tall_mode ? 256 : 224);
|
||||
scrolled_row_info.pattern_address_base = (pattern_name_address & bits<11>(AddressT((scrolled_row & ~7) << 3))) - pattern_name_offset;
|
||||
scrolled_row_info.sub_row[0] = AddressT((scrolled_row & 7) << 2);
|
||||
scrolled_row_info.sub_row[1] = AddressT(28 ^ ((scrolled_row & 7) << 2));
|
||||
if(storage->vertical_scroll_lock_) {
|
||||
static_row_info.pattern_address_base = bits<11>(AddressT(pattern_name_address & ((y & ~7) << 3))) - pattern_name_offset;
|
||||
static_row_info.sub_row[0] = AddressT((y & 7) << 2);
|
||||
static_row_info.sub_row[1] = 28 ^ AddressT((y & 7) << 2);
|
||||
} else static_row_info = scrolled_row_info;
|
||||
}
|
||||
|
||||
void fetch_sprite(int sprite) {
|
||||
auto &sprite_buffer = *base->fetch_sprite_buffer_;
|
||||
sprite_buffer.active_sprites[sprite].x =
|
||||
base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 0)
|
||||
] - (storage->shift_sprites_8px_left_ ? 8 : 0);
|
||||
const uint8_t name = base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 1)
|
||||
] & (base->sprites_16x16_ ? ~1 : ~0);
|
||||
|
||||
const AddressT graphic_location =
|
||||
storage->sprite_generator_table_address_ &
|
||||
bits<13>(AddressT((name << 5) | (sprite_buffer.active_sprites[sprite].row << 2)));
|
||||
sprite_buffer.active_sprites[sprite].image[0] = base->ram_[graphic_location];
|
||||
sprite_buffer.active_sprites[sprite].image[1] = base->ram_[graphic_location+1];
|
||||
sprite_buffer.active_sprites[sprite].image[2] = base->ram_[graphic_location+2];
|
||||
sprite_buffer.active_sprites[sprite].image[3] = base->ram_[graphic_location+3];
|
||||
}
|
||||
|
||||
void fetch_tile_name(int column) {
|
||||
const RowInfo &row_info = column < 24 ? scrolled_row_info : static_row_info;
|
||||
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;
|
||||
const size_t address = row_info.pattern_address_base + (scrolled_column << 1);
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
|
||||
line_buffer.tiles.flags[column] = base->ram_[address+1];
|
||||
base->tile_offset_ = AddressT(
|
||||
(((line_buffer.tiles.flags[column]&1) << 8) | base->ram_[address]) << 5
|
||||
) + row_info.sub_row[(line_buffer.tiles.flags[column]&4) >> 2];
|
||||
}
|
||||
|
||||
void fetch_tile_pattern(int column) {
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
line_buffer.tiles.patterns[column][0] = base->ram_[base->tile_offset_];
|
||||
line_buffer.tiles.patterns[column][1] = base->ram_[base->tile_offset_+1];
|
||||
line_buffer.tiles.patterns[column][2] = base->ram_[base->tile_offset_+2];
|
||||
line_buffer.tiles.patterns[column][3] = base->ram_[base->tile_offset_+3];
|
||||
}
|
||||
|
||||
void posit_sprite(int sprite) {
|
||||
base->posit_sprite(sprite, base->ram_[storage->sprite_attribute_table_address_ & bits<8>(AddressT(sprite))], y);
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const Storage<personality> *const storage;
|
||||
const uint8_t y;
|
||||
const int horizontal_offset;
|
||||
RowInfo scrolled_row_info, static_row_info;
|
||||
};
|
||||
|
||||
// MARK: - TMS Sequencers.
|
||||
|
||||
template <Personality personality>
|
||||
struct RefreshSequencer {
|
||||
RefreshSequencer(Base<personality> *base) : base(base) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
||||
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct TextSequencer {
|
||||
template <typename... Args> TextSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
// The first 30 and the final 4 slots are external.
|
||||
if constexpr (cycle < 30 || cycle >= 150) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
return;
|
||||
} else {
|
||||
// For the 120 slots in between follow a three-step pattern of:
|
||||
constexpr int offset = cycle - 30;
|
||||
constexpr auto column = AddressT(offset / 3);
|
||||
switch(offset % 3) {
|
||||
case 0: // (1) fetch tile name.
|
||||
fetcher.fetch_name(column);
|
||||
break;
|
||||
case 1: // (2) external slot.
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
break;
|
||||
case 2: // (3) fetch tile pattern.
|
||||
fetcher.fetch_pattern(column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
TextFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterSequencer {
|
||||
template <typename... Args> CharacterSequencer(Args&&... args) :
|
||||
character_fetcher(std::forward<Args>(args)...),
|
||||
sprite_fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 5) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 5) {
|
||||
// Fetch: n1, c2, pat2a, pat2b, y3, x3, n3, c3, pat3a, pat3b.
|
||||
sprite_fetcher.fetch_pattern(2);
|
||||
sprite_fetcher.fetch_location(3);
|
||||
sprite_fetcher.fetch_pattern(3);
|
||||
}
|
||||
|
||||
if(cycle > 14 && cycle < 19) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
||||
if(cycle == 19) {
|
||||
sprite_fetcher.fetch_y(0); sprite_fetcher.fetch_y(1); sprite_fetcher.fetch_y(2); sprite_fetcher.fetch_y(3);
|
||||
sprite_fetcher.fetch_y(4); sprite_fetcher.fetch_y(5); sprite_fetcher.fetch_y(6); sprite_fetcher.fetch_y(7);
|
||||
}
|
||||
|
||||
// Body of line: tiles themselves, plus some additional potential sprites.
|
||||
if(cycle >= 27 && cycle < 155) {
|
||||
constexpr int offset = cycle - 27;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
switch(sub_block) {
|
||||
case 0: character_fetcher.fetch_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||
sprite_fetcher.fetch_y(sprite);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
character_fetcher.fetch_pattern(block);
|
||||
character_fetcher.fetch_colour(block);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 155 && cycle < 157) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
// Fetch: y0, x0, n0, c0, pat0a, pat0b, y1, x1, n1, c1, pat1a, pat1b, y2, x2.
|
||||
sprite_fetcher.fetch_location(0);
|
||||
sprite_fetcher.fetch_pattern(0);
|
||||
sprite_fetcher.fetch_location(1);
|
||||
sprite_fetcher.fetch_pattern(1);
|
||||
sprite_fetcher.fetch_location(2);
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
CharacterFetcher<personality> character_fetcher;
|
||||
SpriteFetcher<personality, SpriteMode::Mode1> sprite_fetcher;
|
||||
};
|
||||
|
||||
// MARK: - TMS fetch routines.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_refresh(uint8_t, int start, int end) {
|
||||
RefreshSequencer sequencer(this);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_text(uint8_t y, int start, int end) {
|
||||
TextSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_character(uint8_t y, int start, int end) {
|
||||
CharacterSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSSequencer {
|
||||
template <typename... Args> SMSSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
// Cf. https://www.smspower.org/forums/16485-GenesisMode4VRAMTiming with this implementation pegging
|
||||
// window 0 to HSYNC low.
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 3) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 3) {
|
||||
fetcher.fetch_sprite(4);
|
||||
fetcher.fetch_sprite(5);
|
||||
fetcher.fetch_sprite(6);
|
||||
fetcher.fetch_sprite(7);
|
||||
}
|
||||
|
||||
if(cycle == 15 || cycle == 16) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 17) {
|
||||
fetcher.posit_sprite(0); fetcher.posit_sprite(1); fetcher.posit_sprite(2); fetcher.posit_sprite(3);
|
||||
fetcher.posit_sprite(4); fetcher.posit_sprite(5); fetcher.posit_sprite(6); fetcher.posit_sprite(7);
|
||||
fetcher.posit_sprite(8); fetcher.posit_sprite(9); fetcher.posit_sprite(10); fetcher.posit_sprite(11);
|
||||
fetcher.posit_sprite(12); fetcher.posit_sprite(13); fetcher.posit_sprite(14); fetcher.posit_sprite(15);
|
||||
}
|
||||
|
||||
if(cycle >= 25 && cycle < 153) {
|
||||
constexpr int offset = cycle - 25;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
|
||||
case 0: fetcher.fetch_tile_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||
fetcher.posit_sprite(sprite);
|
||||
fetcher.posit_sprite(sprite+1);
|
||||
}
|
||||
break;
|
||||
case 2: fetcher.fetch_tile_pattern(block); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 153 && cycle < 157) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
fetcher.fetch_sprite(0);
|
||||
fetcher.fetch_sprite(1);
|
||||
fetcher.fetch_sprite(2);
|
||||
fetcher.fetch_sprite(3);
|
||||
}
|
||||
|
||||
if(cycle >= 169) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
SMSFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_sms([[maybe_unused]] uint8_t y, [[maybe_unused]] int start, [[maybe_unused]] int end) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
SMSSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template<ScreenMode mode> void Base<personality>::fetch_yamaha(uint8_t y, int end) {
|
||||
CharacterFetcher character_fetcher(this, y);
|
||||
TextFetcher text_fetcher(this, y);
|
||||
SpriteFetcher<personality, sprite_mode(mode)> sprite_fetcher(this, y);
|
||||
|
||||
using Type = typename Storage<personality>::Event::Type;
|
||||
while(Storage<personality>::next_event_->offset < end) {
|
||||
switch(Storage<personality>::next_event_->type) {
|
||||
case Type::External:
|
||||
do_external_slot(Storage<personality>::next_event_->offset);
|
||||
break;
|
||||
|
||||
case Type::Name:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_name(column, 0);
|
||||
text_fetcher.fetch_name(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 2);
|
||||
const auto start = pattern_name_address_ & bits<12>(AddressT((y >> 3) * 80));
|
||||
|
||||
name_[0] = ram_[start + column + 0];
|
||||
name_[1] = ram_[start + column + 1];
|
||||
name_[2] = ram_[start + column + 2];
|
||||
name_[3] = ram_[start + column + 3];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Colour:
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id);
|
||||
const auto address = colour_table_address_ & bits<9>(AddressT((y >> 3) * 10));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
line_buffer.characters.flags[column] = ram_[address + column];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Pattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_pattern(column, 0);
|
||||
text_fetcher.fetch_pattern(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = pattern_generator_table_address_ & bits<11>(AddressT(y & 7));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.characters.shapes[column + 0] = ram_[start + AddressT(name_[0] << 3)];
|
||||
line_buffer.characters.shapes[column + 1] = ram_[start + AddressT(name_[1] << 3)];
|
||||
line_buffer.characters.shapes[column + 2] = ram_[start + AddressT(name_[2] << 3)];
|
||||
line_buffer.characters.shapes[column + 3] = ram_[start + AddressT(name_[3] << 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
// As per comment elsewhere; my _guess_ is that G3 is slotted as if it were
|
||||
// a bitmap mode, with the three bytes that describe each column fitting into
|
||||
// the relevant windows.
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics4:
|
||||
case ScreenMode::YamahaGraphics5: {
|
||||
const int column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = bits<15>((y << 7) | column);
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0)];
|
||||
line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & AddressT(start + 1)];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 2)];
|
||||
line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & AddressT(start + 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaGraphics6:
|
||||
case ScreenMode::YamahaGraphics7: {
|
||||
const uint8_t *const ram2 = &ram_[65536];
|
||||
const int column = Storage<personality>::next_event_->id << 3;
|
||||
const auto start = bits<15>((y << 7) | (column >> 1));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
// Fetch from alternate banks.
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 1] = ram2[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 3] = ram2[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 4] = ram_[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 5] = ram2[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 6] = ram_[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
line_buffer.bitmap[column + 7] = ram2[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteY:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_y(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteLocation:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_location(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpritePattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
++Storage<personality>::next_event_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_yamaha(uint8_t y, int, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
// Dispatch according to [supported] screen mode.
|
||||
#define Dispatch(mode) case mode: fetch_yamaha<mode>(y, end); break;
|
||||
switch(fetch_line_buffer_->screen_mode) {
|
||||
default: break;
|
||||
Dispatch(ScreenMode::Blank);
|
||||
Dispatch(ScreenMode::Text);
|
||||
Dispatch(ScreenMode::MultiColour);
|
||||
Dispatch(ScreenMode::ColouredText);
|
||||
Dispatch(ScreenMode::Graphics);
|
||||
Dispatch(ScreenMode::YamahaText80);
|
||||
Dispatch(ScreenMode::YamahaGraphics3);
|
||||
Dispatch(ScreenMode::YamahaGraphics4);
|
||||
Dispatch(ScreenMode::YamahaGraphics5);
|
||||
Dispatch(ScreenMode::YamahaGraphics6);
|
||||
Dispatch(ScreenMode::YamahaGraphics7);
|
||||
}
|
||||
#undef Dispatch
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Fetch_hpp */
|
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
@ -0,0 +1,132 @@
|
||||
//
|
||||
// LineBuffer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LineBuffer_hpp
|
||||
#define LineBuffer_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Temporary buffers collect a representation of each line prior to pixel serialisation.
|
||||
|
||||
struct SpriteBuffer {
|
||||
// An active sprite is one that has been selected for composition onto
|
||||
// _this_ line.
|
||||
struct ActiveSprite {
|
||||
int index = 0; // The original in-table index of this sprite.
|
||||
int row = 0; // The row of the sprite that should be drawn.
|
||||
int x = 0; // The sprite's x position on screen.
|
||||
|
||||
uint8_t image[4]; // Up to four bytes of image information.
|
||||
//
|
||||
// In practice:
|
||||
//
|
||||
// Master System mode: the four bytes of this 8x8 sprite;
|
||||
// TMS and Yamaha: [0] = the left half of this sprite; [1] = the right side (if 16x16 sprites enabled); [2] = colour, early-clock bit, etc.
|
||||
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
|
||||
|
||||
// Yamaha helpers.
|
||||
bool opaque() const {
|
||||
return !(image[2] & 0x40);
|
||||
}
|
||||
|
||||
/// @returns @c 0x20 if this sprite should generate collisions; @c 0x00 otherwise.
|
||||
int collision_bit() const {
|
||||
return ((image[2] & 0x20) | ((image[2] & 0x40) >> 1)) ^ 0x20;
|
||||
}
|
||||
|
||||
// Yamaha and TMS helpers.
|
||||
int early_clock() const {
|
||||
return (image[2] & 0x80) >> 2;
|
||||
}
|
||||
} active_sprites[8];
|
||||
|
||||
int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
|
||||
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
|
||||
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
|
||||
uint8_t sprite_terminator = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
static constexpr bool test_is_filling = true;
|
||||
#else
|
||||
static constexpr bool test_is_filling = false;
|
||||
#endif
|
||||
bool is_filling = false;
|
||||
|
||||
void reset_sprite_collection();
|
||||
};
|
||||
|
||||
struct LineBuffer {
|
||||
LineBuffer() {}
|
||||
|
||||
// The fetch mode describes the proper timing diagram for this line;
|
||||
// screen mode captures proper output mode.
|
||||
FetchMode fetch_mode = FetchMode::Text;
|
||||
ScreenMode screen_mode = ScreenMode::Text;
|
||||
VerticalState vertical_state = VerticalState::Blank;
|
||||
SpriteBuffer *sprites = nullptr;
|
||||
|
||||
// Holds the horizontal scroll position to apply to this line;
|
||||
// of those VDPs currently implemented, affects the Master System only.
|
||||
uint8_t latched_horizontal_scroll = 0;
|
||||
|
||||
// The names array holds pattern names, as an offset into memory, and
|
||||
// potentially flags also.
|
||||
union {
|
||||
// This struct captures maximal potential detail across the TMS9918
|
||||
// and Sega VDP for tiled modes (plus multicolour).
|
||||
struct {
|
||||
uint8_t flags[32]{};
|
||||
|
||||
// The patterns array holds tile patterns, corresponding 1:1 with names.
|
||||
// Four bytes per pattern is the maximum required by any
|
||||
// currently-implemented VDP.
|
||||
uint8_t patterns[32][4]{};
|
||||
} tiles;
|
||||
|
||||
// The Yamaha and TMS both have text modes, with the former going up to
|
||||
// 80 columns plus 10 bytes of colour-esque flags.
|
||||
struct {
|
||||
uint8_t shapes[80];
|
||||
uint8_t flags[10];
|
||||
} characters;
|
||||
|
||||
// The Yamaha VDP also has a variety of bitmap modes,
|
||||
// the widest of which is 512px @ 4bpp.
|
||||
uint8_t bitmap[256];
|
||||
};
|
||||
|
||||
/*
|
||||
Horizontal layout (on a 342-cycle clock):
|
||||
|
||||
15 cycles right border
|
||||
58 cycles blanking & sync
|
||||
13 cycles left border
|
||||
|
||||
... i.e. to cycle 86, then:
|
||||
|
||||
border up to first_pixel_output_column;
|
||||
pixels up to next_border_column;
|
||||
border up to the end.
|
||||
|
||||
e.g. standard 256-pixel modes will want to set
|
||||
first_pixel_output_column = 86, next_border_column = 342.
|
||||
*/
|
||||
int first_pixel_output_column = 94;
|
||||
int next_border_column = 334;
|
||||
int pixel_count = 256;
|
||||
};
|
||||
|
||||
struct LineBufferPointer {
|
||||
int row = 0, column = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LineBuffer_hpp */
|
80
Components/9918/Implementation/LineLayout.hpp
Normal file
80
Components/9918/Implementation/LineLayout.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
//
|
||||
// LineLayout.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/05/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LineLayout_h
|
||||
#define LineLayout_h
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
template <Personality personality, typename Enable = void> struct LineLayout;
|
||||
|
||||
// Line layout is:
|
||||
//
|
||||
// [0, EndOfSync] sync
|
||||
// (EndOfSync, StartOfColourBurst] blank
|
||||
// (StartOfColourBurst, EndOfColourBurst] colour burst
|
||||
// (EndOfColourBurst, EndOfLeftErase] blank
|
||||
// (EndOfLeftErase, EndOfLeftBorder] border colour
|
||||
// (EndOfLeftBorder, EndOfPixels] pixel content
|
||||
// (EndOfPixels, EndOfRightBorder] border colour
|
||||
// [EndOfRightBorder, <end of line>] blank
|
||||
//
|
||||
// ... with minor caveats:
|
||||
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
||||
// * the Sega VDPs may programatically extend the left border; and
|
||||
// * text mode on all VDPs adjusts border width.
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 26;
|
||||
constexpr static int StartOfColourBurst = 29;
|
||||
constexpr static int EndOfColourBurst = 43;
|
||||
constexpr static int EndOfLeftErase = 50;
|
||||
constexpr static int EndOfLeftBorder = 63;
|
||||
constexpr static int EndOfPixels = 319;
|
||||
constexpr static int EndOfRightBorder = 334;
|
||||
|
||||
constexpr static int CyclesPerLine = 342;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||
constexpr static int TextModeEndOfPixels = 309;
|
||||
|
||||
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
||||
// and falls into the collection gap between the final sprite
|
||||
// graphics and the initial tiles or pixels.
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 100;
|
||||
constexpr static int StartOfColourBurst = 113;
|
||||
constexpr static int EndOfColourBurst = 167;
|
||||
constexpr static int EndOfLeftErase = 202;
|
||||
constexpr static int EndOfLeftBorder = 258;
|
||||
constexpr static int EndOfPixels = 1282;
|
||||
constexpr static int EndOfRightBorder = 1341;
|
||||
|
||||
constexpr static int CyclesPerLine = 1368;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||
constexpr static int TextModeEndOfPixels = 1254;
|
||||
|
||||
constexpr static int ModeLatchCycle = 144;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 16;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LineLayout_h */
|
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// PersonalityTraits.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PersonalityTraits_hpp
|
||||
#define PersonalityTraits_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Genus determinants for the various personalityes.
|
||||
constexpr bool is_sega_vdp(Personality p) {
|
||||
return p >= Personality::SMSVDP;
|
||||
}
|
||||
|
||||
constexpr bool is_yamaha_vdp(Personality p) {
|
||||
return p == Personality::V9938 || p == Personality::V9958;
|
||||
}
|
||||
|
||||
// i.e. one with the original internal timings.
|
||||
constexpr bool is_classic_vdp(Personality p) {
|
||||
return
|
||||
p == Personality::TMS9918A ||
|
||||
p == Personality::SMSVDP ||
|
||||
p == Personality::SMS2VDP ||
|
||||
p == Personality::GGVDP;
|
||||
}
|
||||
|
||||
constexpr size_t memory_size(Personality p) {
|
||||
switch(p) {
|
||||
case TI::TMS::TMS9918A:
|
||||
case TI::TMS::SMSVDP:
|
||||
case TI::TMS::SMS2VDP:
|
||||
case TI::TMS::GGVDP: return 16 * 1024;
|
||||
case TI::TMS::MDVDP: return 64 * 1024;
|
||||
case TI::TMS::V9938: return 128 * 1024;
|
||||
case TI::TMS::V9958: return 192 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t memory_mask(Personality p) {
|
||||
return memory_size(p) - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* PersonalityTraits_hpp */
|
490
Components/9918/Implementation/Storage.hpp
Normal file
490
Components/9918/Implementation/Storage.hpp
Normal file
@ -0,0 +1,490 @@
|
||||
//
|
||||
//
|
||||
// Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_h
|
||||
#define Storage_h
|
||||
|
||||
#include "LineBuffer.hpp"
|
||||
#include "YamahaCommands.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/// A container for personality-specific storage; see specific instances below.
|
||||
template <Personality personality, typename Enable = void> struct Storage {
|
||||
};
|
||||
|
||||
template <> struct Storage<Personality::TMS9918A> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
struct YamahaFetcher {
|
||||
public:
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
|
||||
/// external data must be ready by in order to take part in those slots.
|
||||
uint16_t offset = 1368;
|
||||
enum class Type: uint8_t {
|
||||
/// A slot for reading or writing data on behalf of the CPU or the command engine.
|
||||
External,
|
||||
|
||||
//
|
||||
// Sprites.
|
||||
//
|
||||
SpriteY,
|
||||
SpriteLocation,
|
||||
SpritePattern,
|
||||
|
||||
//
|
||||
// Backgrounds.
|
||||
//
|
||||
Name,
|
||||
Colour,
|
||||
Pattern,
|
||||
} type = Type::External;
|
||||
uint8_t id = 0;
|
||||
|
||||
constexpr Event(Type type, uint8_t id = 0) noexcept :
|
||||
type(type),
|
||||
id(id) {}
|
||||
|
||||
constexpr Event() noexcept {}
|
||||
};
|
||||
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
|
||||
protected:
|
||||
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event_type = GeneratorT::event(c);
|
||||
size += event_type.has_value();
|
||||
}
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
/// @return An array of all events generated by @c GeneratorT in line order.
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
size_t index = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
|
||||
if(!event) {
|
||||
continue;
|
||||
}
|
||||
result[index] = *event;
|
||||
result[index].offset = uint16_t(c);
|
||||
++index;
|
||||
}
|
||||
result[index] = Event();
|
||||
return result;
|
||||
}
|
||||
|
||||
struct StandardGenerators {
|
||||
static constexpr std::optional<Event> external_every_eight(int index) {
|
||||
if(index & 7) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct RefreshGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index < 126) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 0);
|
||||
}
|
||||
|
||||
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
|
||||
// CPU/CMD and the final being refresh.
|
||||
if(grauw_index >= 164 && grauw_index < 1234) {
|
||||
const int offset = grauw_index - 164;
|
||||
if(offset & 7) return std::nullopt;
|
||||
if(((offset >> 3) & 15) == 15) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index >= 1268 && grauw_index < 1330) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1268);
|
||||
}
|
||||
|
||||
// A CPU/CMD at 1334.
|
||||
if(grauw_index == 1334) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
|
||||
if(grauw_index >= 1344 && grauw_index < 1366) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1344);
|
||||
}
|
||||
|
||||
// Otherwise: nothing.
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
if(!include_sprites) {
|
||||
// Various standard zones of one-every-eight external slots.
|
||||
if(grauw_index < 124) {
|
||||
return StandardGenerators::external_every_eight(grauw_index + 2);
|
||||
}
|
||||
if(grauw_index > 1266) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1266);
|
||||
}
|
||||
} else {
|
||||
// This records collection points for all data for selected sprites.
|
||||
// There's only four of them (each site covering two sprites),
|
||||
// so it's clearer just to be explicit.
|
||||
//
|
||||
// There's also a corresponding number of extra external slots to spell out.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1238: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1302: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 2: return Event(Event::Type::SpriteLocation, 4);
|
||||
case 66: return Event(Event::Type::SpriteLocation, 6);
|
||||
case 1270: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1338: return Event(Event::Type::SpritePattern, 2);
|
||||
case 34: return Event(Event::Type::SpritePattern, 4);
|
||||
case 98: return Event(Event::Type::SpritePattern, 6);
|
||||
case 1264: case 1330: case 28: case 92:
|
||||
return Event::Type::External;
|
||||
}
|
||||
}
|
||||
|
||||
if(grauw_index >= 162 && grauw_index < 176) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 162);
|
||||
}
|
||||
|
||||
// Everywhere else the pattern is:
|
||||
//
|
||||
// external or sprite y, external, data block
|
||||
//
|
||||
// Subject to caveats:
|
||||
//
|
||||
// 1) the first data block is just a dummy fetch with no side effects,
|
||||
// so this emulator declines to record it; and
|
||||
// 2) every fourth block, the second external is actually a refresh.
|
||||
//
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
|
||||
switch(sub_block) {
|
||||
default: return std::nullopt;
|
||||
case 0:
|
||||
if(include_sprites) {
|
||||
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
|
||||
if(block < 32) {
|
||||
return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
}
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if(block) {
|
||||
return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Capture various one-in-eight zones.
|
||||
if(grauw_index < 72) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 2);
|
||||
}
|
||||
if(grauw_index >= 166 && grauw_index < 228) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
if(grauw_index >= 1206 && grauw_index < 1332) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1206);
|
||||
}
|
||||
if(grauw_index == 1336) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
if(grauw_index >= 1346) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1346);
|
||||
}
|
||||
|
||||
// Elsewhere...
|
||||
if(grauw_index >= 246) {
|
||||
const int offset = grauw_index - 246;
|
||||
const int block = offset / 48;
|
||||
const int sub_block = offset % 48;
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
case 0: return Event(Event::Type::Name, uint8_t(block));
|
||||
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
|
||||
case 24: return Event(Event::Type::Pattern, uint8_t(block));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Grab sprite events.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1242: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1306: return Event(Event::Type::SpriteLocation, 1);
|
||||
case 6: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 70: return Event(Event::Type::SpriteLocation, 3);
|
||||
case 1274: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1342: return Event(Event::Type::SpritePattern, 1);
|
||||
case 38: return Event(Event::Type::SpritePattern, 2);
|
||||
case 102: return Event(Event::Type::SpritePattern, 3);
|
||||
case 1268: case 1334: case 32: case 96: return Event::Type::External;
|
||||
}
|
||||
|
||||
if(grauw_index >= 166 && grauw_index < 180) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct YamahaCommandState {
|
||||
CommandContext command_context_;
|
||||
ModeDescription mode_description_;
|
||||
std::unique_ptr<Command> command_ = nullptr;
|
||||
|
||||
enum class CommandStep {
|
||||
None,
|
||||
|
||||
CopySourcePixelToStatus,
|
||||
|
||||
ReadSourcePixel,
|
||||
ReadDestinationPixel,
|
||||
WritePixel,
|
||||
|
||||
ReadSourceByte,
|
||||
WriteByte,
|
||||
};
|
||||
CommandStep next_command_step_ = CommandStep::None;
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
if(command_->done()) {
|
||||
command_ = nullptr;
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
|
||||
minimum_command_column_ = current_column + command_->cycles;
|
||||
switch(command_->access) {
|
||||
case Command::AccessType::ReadPoint:
|
||||
next_command_step_ = CommandStep::CopySourcePixelToStatus;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyPoint:
|
||||
next_command_step_ = CommandStep::ReadSourcePixel;
|
||||
break;
|
||||
case Command::AccessType::PlotPoint:
|
||||
next_command_step_ = CommandStep::ReadDestinationPixel;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
// i.e. nothing to do until a colour is received.
|
||||
next_command_step_ = CommandStep::None;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyByte:
|
||||
next_command_step_ = CommandStep::ReadSourceByte;
|
||||
break;
|
||||
case Command::AccessType::WriteByte:
|
||||
next_command_step_ = CommandStep::WriteByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public YamahaFetcher, public YamahaCommandState {
|
||||
using AddressT = uint32_t;
|
||||
|
||||
// The Yamaha's (optional in real hardware) additional 64kb of expansion RAM.
|
||||
// This is a valid target and source for the command engine, but can't be used as a source for current video data.
|
||||
std::array<uint8_t, 65536> expansion_ram_;
|
||||
|
||||
// Register indirections.
|
||||
int selected_status_ = 0;
|
||||
int indirect_register_ = 0;
|
||||
bool increment_indirect_register_ = false;
|
||||
|
||||
// Output horizontal and vertical adjustment, plus the selected vertical offset (i.e. hardware scroll).
|
||||
int adjustment_[2]{};
|
||||
uint8_t vertical_offset_ = 0;
|
||||
|
||||
// The palette, plus a shadow copy in which colour 0 is not the current palette colour 0,
|
||||
// but is rather the current global background colour. This simplifies flow when colour 0
|
||||
// is set as transparent.
|
||||
std::array<uint32_t, 16> palette_{};
|
||||
std::array<uint32_t, 16> background_palette_{};
|
||||
bool solid_background_ = true;
|
||||
|
||||
// Transient state for palette setting.
|
||||
uint8_t new_colour_ = 0;
|
||||
uint8_t palette_entry_ = 0;
|
||||
bool palette_write_phase_ = false;
|
||||
|
||||
// Recepticle for all five bits of the current screen mode.
|
||||
uint8_t mode_ = 0;
|
||||
|
||||
// Used ephemerally during drawing to compound sprites with the 'CC'
|
||||
// (compound colour?) bit set.
|
||||
uint8_t sprite_cache_[8][32]{};
|
||||
|
||||
// Text blink colours.
|
||||
uint8_t blink_text_colour_ = 0;
|
||||
uint8_t blink_background_colour_ = 0;
|
||||
|
||||
// Blink state (which is also affects even/odd page display in applicable modes).
|
||||
int in_blink_ = 1;
|
||||
uint8_t blink_periods_ = 0;
|
||||
uint8_t blink_counter_ = 0;
|
||||
|
||||
// Additional things exposed by status registers.
|
||||
uint8_t colour_status_ = 0;
|
||||
uint16_t colour_location_ = 0;
|
||||
uint16_t collision_location_[2]{};
|
||||
bool line_matches_ = false;
|
||||
|
||||
Storage() noexcept {
|
||||
// Seed to something valid.
|
||||
next_event_ = refresh_events.data();
|
||||
}
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80:
|
||||
case ScreenMode::Text:
|
||||
next_event_ = text_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::YamahaGraphics1:
|
||||
case ScreenMode::YamahaGraphics2:
|
||||
next_event_ = character_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3: // TODO: verify; my guess is that G3 is timed like a bitmap mode
|
||||
// in order to fit the pattern for sprite mode 2. Just a guess.
|
||||
default:
|
||||
next_event_ = sprites_enabled_ ? sprites_events.data() : no_sprites_events.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
};
|
||||
|
||||
// Master System-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_sega_vdp(personality)>> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
|
||||
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
|
||||
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
|
||||
// track of pending collisions, for visual reproduction.
|
||||
struct CRAMDot {
|
||||
LineBufferPointer location;
|
||||
uint32_t value;
|
||||
};
|
||||
std::vector<CRAMDot> upcoming_cram_dots_;
|
||||
|
||||
// The Master System's additional colour RAM.
|
||||
uint32_t colour_ram_[32];
|
||||
bool cram_is_selected_ = false;
|
||||
|
||||
// Programmer-set flags.
|
||||
bool vertical_scroll_lock_ = false;
|
||||
bool horizontal_scroll_lock_ = false;
|
||||
bool hide_left_column_ = false;
|
||||
bool shift_sprites_8px_left_ = false;
|
||||
bool mode4_enable_ = false;
|
||||
uint8_t horizontal_scroll_ = 0;
|
||||
uint8_t vertical_scroll_ = 0;
|
||||
|
||||
// Holds the vertical scroll position for this frame; this is latched
|
||||
// once and cannot dynamically be changed until the next frame.
|
||||
uint8_t latched_vertical_scroll_ = 0;
|
||||
|
||||
// Various resource addresses with VDP-version-specific modifications
|
||||
// built in.
|
||||
AddressT pattern_name_address_;
|
||||
AddressT sprite_attribute_table_address_;
|
||||
AddressT sprite_generator_table_address_;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Storage_h */
|
384
Components/9918/Implementation/YamahaCommands.hpp
Normal file
384
Components/9918/Implementation/YamahaCommands.hpp
Normal file
@ -0,0 +1,384 @@
|
||||
//
|
||||
// YamahaCommands.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef YamahaCommands_hpp
|
||||
#define YamahaCommands_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Generics.
|
||||
|
||||
struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
||||
template <int offset> void add(int amount) {
|
||||
v[offset] += amount;
|
||||
|
||||
if constexpr (offset == 1) {
|
||||
v[offset] &= 0x3ff;
|
||||
} else {
|
||||
v[offset] &= 0x1ff;
|
||||
}
|
||||
}
|
||||
|
||||
Vector & operator += (const Vector &rhs) {
|
||||
add<0>(rhs.v[0]);
|
||||
add<1>(rhs.v[1]);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct Colour {
|
||||
void set(uint8_t value) {
|
||||
colour = value;
|
||||
colour4bpp = uint8_t((value & 0xf) | (value << 4));
|
||||
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
colour = 0x00;
|
||||
colour4bpp = 0xff;
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return (colour & 0xf) == (colour4bpp & 0xf);
|
||||
}
|
||||
|
||||
/// Colour as written by the CPU.
|
||||
uint8_t colour = 0x00;
|
||||
/// The low four bits of the CPU-written colour, repeated twice.
|
||||
uint8_t colour4bpp = 0xff;
|
||||
/// The low two bits of the CPU-written colour, repeated four times.
|
||||
uint8_t colour2bpp = 0xff;
|
||||
};
|
||||
|
||||
struct CommandContext {
|
||||
Vector source;
|
||||
Vector destination;
|
||||
Vector size;
|
||||
|
||||
uint8_t arguments = 0;
|
||||
Colour colour;
|
||||
Colour latched_colour;
|
||||
|
||||
enum class LogicalOperation {
|
||||
Copy = 0b0000,
|
||||
And = 0b0001,
|
||||
Or = 0b0010,
|
||||
Xor = 0b0011,
|
||||
Not = 0b0100,
|
||||
};
|
||||
LogicalOperation pixel_operation;
|
||||
bool test_source;
|
||||
};
|
||||
|
||||
struct ModeDescription {
|
||||
int width = 256;
|
||||
int pixels_per_byte = 4;
|
||||
bool rotate_address = false;
|
||||
int start_cycle = 0;
|
||||
int end_cycle = 0;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
// In net:
|
||||
//
|
||||
// This command is blocked until @c access has been performed, reading
|
||||
// from or writing to @c value. It should not be performed until at least
|
||||
// @c cycles have passed.
|
||||
enum class AccessType {
|
||||
/// Plots a single pixel of the current contextual colour at @c destination,
|
||||
/// which occurs as a read, then a 24-cycle gap, then a write.
|
||||
PlotPoint,
|
||||
|
||||
/// Blocks until the next CPU write to the colour register.
|
||||
WaitForColourReceipt,
|
||||
|
||||
/// Writes an entire byte to the address containing the current @c destination.
|
||||
WriteByte,
|
||||
|
||||
/// Copies a single pixel from @c source location to @c destination,
|
||||
/// being a read, a 32-cycle gap, then a PlotPoint.
|
||||
CopyPoint,
|
||||
|
||||
/// Copies a complete byte from @c source location to @c destination,
|
||||
/// being a read, a 24-cycle gap, then a write.
|
||||
CopyByte,
|
||||
|
||||
/// Copies a single pixel from @c source to the colour status register.
|
||||
ReadPoint,
|
||||
|
||||
// ReadByte,
|
||||
// WaitForColourSend,
|
||||
};
|
||||
AccessType access = AccessType::PlotPoint;
|
||||
int cycles = 0;
|
||||
bool is_cpu_transfer = false;
|
||||
bool y_only = false;
|
||||
|
||||
/// Current command parameters.
|
||||
CommandContext &context;
|
||||
ModeDescription &mode_description;
|
||||
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
|
||||
virtual ~Command() {}
|
||||
|
||||
/// @returns @c true if all output from this command is done; @c false otherwise.
|
||||
virtual bool done() = 0;
|
||||
|
||||
/// Repopulates the fields above with the next action to take, being provided with the
|
||||
/// number of pixels per byte in the current screen mode.
|
||||
virtual void advance() = 0;
|
||||
|
||||
protected:
|
||||
template <int axis, bool include_source> void advance_axis(int offset = 1) {
|
||||
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
if constexpr (include_source) {
|
||||
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace Commands {
|
||||
|
||||
// MARK: - Line drawing.
|
||||
|
||||
/// Implements the LINE command, which is plain-old Bresenham.
|
||||
///
|
||||
/// Per Grauw timing is:
|
||||
///
|
||||
/// * 88 cycles between every pixel plot;
|
||||
/// * plus an additional 32 cycles if a step along the minor axis is taken.
|
||||
struct Line: public Command {
|
||||
public:
|
||||
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
// context.destination = start position;
|
||||
// context.size.v[0] = long side dots;
|
||||
// context.size.v[1] = short side dots;
|
||||
// context.arguments => direction
|
||||
|
||||
position_ = context.size.v[1];
|
||||
numerator_ = position_ << 1;
|
||||
denominator_ = context.size.v[0] << 1;
|
||||
|
||||
cycles = 32;
|
||||
access = AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[0];
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
--context.size.v[0];
|
||||
cycles = 88;
|
||||
|
||||
// b0: 1 => long direction is y;
|
||||
// 0 => long direction is x.
|
||||
//
|
||||
// b2: 1 => x direction is left;
|
||||
// 0 => x direction is right.
|
||||
//
|
||||
// b3: 1 => y direction is up;
|
||||
// 0 => y direction is down.
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<1, false>();
|
||||
} else {
|
||||
advance_axis<0, false>();
|
||||
}
|
||||
|
||||
position_ -= numerator_;
|
||||
if(position_ < 0) {
|
||||
position_ += denominator_;
|
||||
cycles += 32;
|
||||
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<0, false>();
|
||||
} else {
|
||||
advance_axis<1, false>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int position_, numerator_, denominator_, duration_;
|
||||
};
|
||||
|
||||
// MARK: - Single pixel manipulation.
|
||||
|
||||
/// Implements the PSET command, which plots a single pixel and POINT, which reads one.
|
||||
///
|
||||
/// No timings are documented, so this'll output or input as quickly as possible.
|
||||
template <bool is_read> struct Point: public Command {
|
||||
public:
|
||||
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
cycles = 0; // TODO.
|
||||
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return done_;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular base.
|
||||
|
||||
/// Useful base class for anything that does logical work in a rectangle.
|
||||
template <bool logical, bool include_source> struct Rectangle: public Command {
|
||||
public:
|
||||
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
if constexpr (include_source) {
|
||||
start_x_[0] = context.source.v[0];
|
||||
}
|
||||
start_x_[1] = context.destination.v[0];
|
||||
width_ = context.size.v[0];
|
||||
|
||||
if(!width_) {
|
||||
// Width = 0 => maximal width for this mode.
|
||||
// (aside: it's still unclear to me whether commands are
|
||||
// automatically clipped to the display; I think so but
|
||||
// don't want to spend any time on it until I'm certain)
|
||||
// context.size.v[0] = width_ = mode_description.width;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the current destination and, if @c include_source is @c true also the source;
|
||||
/// @returns @c true if a new row was started; @c false otherwise.
|
||||
bool advance_pixel() {
|
||||
if constexpr (logical) {
|
||||
advance_axis<0, include_source>();
|
||||
--context.size.v[0];
|
||||
|
||||
if(context.size.v[0]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
advance_axis<0, include_source>(mode_description.pixels_per_byte);
|
||||
context.size.v[0] -= mode_description.pixels_per_byte;
|
||||
|
||||
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.size.v[0] = width_;
|
||||
if constexpr (include_source) {
|
||||
context.source.v[0] = start_x_[0];
|
||||
}
|
||||
context.destination.v[0] = start_x_[1];
|
||||
|
||||
advance_axis<1, include_source>();
|
||||
--context.size.v[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[1] || !width_;
|
||||
}
|
||||
|
||||
private:
|
||||
int start_x_[2]{}, width_ = 0;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves to/from CPU.
|
||||
|
||||
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
|
||||
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
|
||||
Command::is_cpu_transfer = true;
|
||||
|
||||
// This command is started with the first colour ready to transfer.
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
switch(Command::access) {
|
||||
default: break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WriteByte:
|
||||
case Command::AccessType::PlotPoint:
|
||||
Command::cycles = 0;
|
||||
Command::access = Command::AccessType::WaitForColourReceipt;
|
||||
if(Rectangle<logical, false>::advance_pixel()) {
|
||||
Command::cycles = 64;
|
||||
// TODO: I'm not sure this will be honoured per the outer wrapping.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves within VRAM.
|
||||
|
||||
enum class MoveType {
|
||||
Logical,
|
||||
HighSpeed,
|
||||
YOnly,
|
||||
};
|
||||
|
||||
template <MoveType type> struct Move: public Rectangle<type == MoveType::Logical, true> {
|
||||
static constexpr bool is_logical = type == MoveType::Logical;
|
||||
static constexpr bool is_y_only = type == MoveType::YOnly;
|
||||
using RectangleBase = Rectangle<is_logical, true>;
|
||||
|
||||
Move(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::access = is_logical ? Command::AccessType::CopyPoint : Command::AccessType::CopyByte;
|
||||
Command::cycles = is_y_only ? 0 : 64;
|
||||
Command::y_only = is_y_only;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = is_y_only ? 40 : 64;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += is_y_only ? 0 : 64;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular fills.
|
||||
|
||||
template <bool logical> struct Fill: public Rectangle<logical, false> {
|
||||
using RectangleBase = Rectangle<logical, false>;
|
||||
|
||||
Fill(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::cycles = logical ? 64 : 56;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = logical ? 72 : 48;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += logical ? 64 : 56;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* YamahaCommands_hpp */
|
@ -14,8 +14,7 @@
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
namespace GI::AY38910 {
|
||||
|
||||
/*!
|
||||
A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
|
||||
@ -219,7 +218,6 @@ struct State: public Reflection::StructImpl<State> {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AY_3_8910_hpp */
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Apple {
|
||||
namespace Clock {
|
||||
namespace Apple::Clock {
|
||||
|
||||
/*!
|
||||
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
|
||||
@ -196,7 +195,7 @@ class SerialClock: public ClockStorage {
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(bool clock, bool data) {
|
||||
// The data line is valid when the clock transitions to level 0.
|
||||
// The data line is valid when the clock transitions to level 0.
|
||||
if(clock && !previous_clock_) {
|
||||
// Shift into the command_ register, no matter what.
|
||||
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
||||
@ -293,7 +292,6 @@ class ParallelClock: public ClockStorage {
|
||||
uint8_t control_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Apple_RealTimeClock_hpp */
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
using namespace Apple;
|
||||
|
||||
namespace {
|
||||
namespace {
|
||||
const uint8_t input_command = 0x4; // i.e. Q6
|
||||
const uint8_t input_mode = 0x8; // i.e. Q7
|
||||
const uint8_t input_flux = 0x1;
|
||||
@ -144,7 +144,7 @@ void DiskII::decide_clocking_preference() {
|
||||
// none, given that drives are not running, the shift register has already emptied or stopped and there's no flux about to be received.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
const bool is_stuck_at_nop =
|
||||
!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_ && (state_ &0xf) == 0x8;
|
||||
!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_ && (state_ &0xf) == 0x8;
|
||||
|
||||
clocking_preference_ =
|
||||
(drive_is_sleeping_[0] && drive_is_sleeping_[1] && (!shift_register_ || is_stuck_at_nop) && (inputs_&input_flux))
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Disk {
|
||||
namespace Apple::Disk {
|
||||
|
||||
class DiskIIDrive: public IWMDrive {
|
||||
public:
|
||||
@ -27,7 +26,6 @@ class DiskIIDrive: public IWMDrive {
|
||||
int stepper_position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskIIDrive_hpp */
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
using namespace Apple;
|
||||
|
||||
namespace {
|
||||
namespace {
|
||||
constexpr int CA0 = 1 << 0;
|
||||
constexpr int CA1 = 1 << 1;
|
||||
constexpr int CA2 = 1 << 2;
|
||||
@ -220,7 +220,7 @@ void IWM::set_select(bool enabled) {
|
||||
}
|
||||
|
||||
void IWM::push_drive_state() {
|
||||
if(drives_[active_drive_]) {
|
||||
if(drives_[active_drive_]) {
|
||||
const uint8_t drive_control_lines =
|
||||
((state_ & CA0) ? IWMDrive::CA0 : 0) |
|
||||
((state_ & CA1) ? IWMDrive::CA1 : 0) |
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
namespace Apple::Macintosh {
|
||||
|
||||
class DoubleDensityDrive: public IWMDrive {
|
||||
public:
|
||||
@ -47,7 +46,6 @@ class DoubleDensityDrive: public IWMDrive {
|
||||
int step_direction_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MacintoshDoubleDensityDrive_hpp */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user