1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-04 23:17:01 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Thomas Harte
4078baa424 Capture up to eight scans per submission group. 2022-07-03 21:54:28 -04:00
Thomas Harte
3179d0d963 Vend data pointers. 2022-07-02 21:58:23 -04:00
Thomas Harte
6a8c792c63 Add new scan target to SDL build. 2022-07-02 21:57:48 -04:00
Thomas Harte
678e1a38fa Ensure some basic traffic reaches the putative software scan target. 2022-07-02 21:29:59 -04:00
Thomas Harte
f4004baff8 Set out a stall. 2022-07-02 16:31:38 -04:00
Thomas Harte
f04e4faae2 Avoid potential ScanTarget include-guard collisions. 2022-07-02 13:54:16 -04:00
535 changed files with 2840714 additions and 148023 deletions

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
- name: Make

View File

@@ -9,7 +9,6 @@
#ifndef ActivityObserver_h
#define ActivityObserver_h
#include <cstdint>
#include <string>
namespace Activity {

View File

@@ -11,7 +11,8 @@
#include "ConfidenceSource.hpp"
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a confidence source that calculates its probability by virtual of a history of events.
@@ -40,6 +41,7 @@ class ConfidenceCounter: public ConfidenceSource {
int misses_ = 1;
};
}
}
#endif /* ConfidenceCounter_hpp */

View File

@@ -9,7 +9,8 @@
#ifndef ConfidenceSource_hpp
#define ConfidenceSource_hpp
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides an abstract interface through which objects can declare the probability
@@ -21,6 +22,7 @@ struct ConfidenceSource {
virtual float get_confidence() = 0;
};
}
}
#endif /* ConfidenceSource_hpp */

View File

@@ -13,7 +13,8 @@
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Summaries a collection of confidence sources by calculating their weighted sum.
@@ -39,6 +40,7 @@ class ConfidenceSummary: public ConfidenceSource {
float weight_sum_;
};
}
}
#endif /* ConfidenceSummary_hpp */

View File

@@ -15,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configurable interface to multiple machines.
@@ -35,6 +36,7 @@ class MultiConfigurable: public Configurable::Device {
std::vector<Configurable::Device *> devices_;
};
}
}
#endif /* MultiConfigurable_hpp */

View File

@@ -14,7 +14,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the joystick machine interface to multiple machines.
@@ -33,6 +34,7 @@ class MultiJoystickMachine: public MachineTypes::JoystickMachine {
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}
}
#endif /* MultiJoystickMachine_hpp */

View File

@@ -15,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the keyboard machine interface to multiple machines.
@@ -54,6 +55,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
Inputs::Keyboard &get_keyboard() final;
};
}
}
#endif /* MultiKeyboardMachine_hpp */

View File

@@ -15,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the media target interface to multiple machines.
@@ -34,6 +35,7 @@ struct MultiMediaTarget: public MachineTypes::MediaTarget {
std::vector<MachineTypes::MediaTarget *> targets_;
};
}
}
#endif /* MultiMediaTarget_hpp */

View File

@@ -19,7 +19,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
template <typename MachineType> class MultiInterface {
public:
@@ -46,7 +47,7 @@ template <typename MachineType> class MultiInterface {
std::recursive_mutex &machines_mutex_;
private:
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
};
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
@@ -115,5 +116,7 @@ class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, pu
*/
}
}
#endif /* MultiProducer_hpp */

View File

@@ -16,7 +16,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
@@ -54,6 +55,7 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
bool stereo_output_ = false;
};
}
}
#endif /* MultiSpeaker_hpp */

View File

@@ -22,7 +22,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides the same interface as to a single machine, while multiplexing all
@@ -79,6 +80,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::
bool has_picked_ = false;
};
}
}
#endif /* MultiMachine_hpp */

View File

@@ -12,7 +12,9 @@
#include "File.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
struct Catalogue {
@@ -29,6 +31,8 @@ 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 */

View File

@@ -13,7 +13,9 @@
#include <string>
#include <vector>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
struct File {
std::string name;
@@ -58,6 +60,8 @@ struct File {
std::vector<Chunk> chunks;
};
}
}
}
#endif /* File_hpp */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* AcornAnalyser_hpp */

View File

@@ -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)) {

View File

@@ -14,10 +14,14 @@
#include "File.hpp"
#include "../../../Storage/Tape/Tape.hpp"
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -13,7 +13,9 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_acorn_adfs = false;
@@ -35,6 +37,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
}
};
}
}
}
#endif /* Analyser_Static_Acorn_Target_h */

View File

@@ -13,10 +13,15 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Amiga {
namespace Analyser {
namespace Static {
namespace Amiga {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */

View File

@@ -12,7 +12,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Amiga {
namespace Analyser {
namespace Static {
namespace Amiga {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ChipRAM,
@@ -39,6 +41,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AmstradCPC {
namespace Analyser {
namespace Static {
namespace AmstradCPC {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */

View File

@@ -14,7 +14,9 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::AmstradCPC {
namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
@@ -30,5 +32,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_AmstradCPC_Target_h */

View File

@@ -13,17 +13,8 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
auto target = std::make_unique<Target>();
target->media = media;
// If any disks are present, attach a Disk II.
if(!target->media.disks.empty()) {
if(!target->media.disks.empty())
target->disk_controller = Target::DiskController::SixteenSector;
}
// The emulated SCSI card requires a IIe, so upgrade to that if
// any mass storage is present.
if(!target->media.mass_storage_devices.empty()) {
target->model = Target::Model::EnhancedIIe;
target->scsi_controller = Target::SCSIController::AppleSCSI;
}
TargetList targets;
targets.push_back(std::move(target));

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AppleII {
namespace Analyser {
namespace Static {
namespace AppleII {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */

View File

@@ -13,7 +13,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AppleII {
namespace Analyser {
namespace Static {
namespace AppleII {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -27,28 +29,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
SixteenSector,
ThirteenSector
);
ReflectableEnum(SCSIController,
None,
AppleSCSI
);
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
SCSIController scsi_controller = SCSIController::None;
Target() : Analyser::Static::Target(Machine::AppleII) {
if(needs_declare()) {
DeclareField(model);
DeclareField(disk_controller);
DeclareField(scsi_controller);
AnnounceEnum(Model);
AnnounceEnum(DiskController);
AnnounceEnum(SCSIController);
}
}
};
}
}
}
#endif /* Analyser_Static_AppleII_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AppleIIgs {
namespace Analyser {
namespace Static {
namespace AppleIIgs {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */

View File

@@ -13,7 +13,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AppleIIgs {
namespace Analyser {
namespace Static {
namespace AppleIIgs {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -40,6 +42,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_AppleIIgs_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Atari2600 {
namespace Analyser {
namespace Static {
namespace Atari2600 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -11,7 +11,9 @@
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Atari2600 {
namespace Analyser {
namespace Static {
namespace Atari2600 {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
@@ -36,6 +38,8 @@ struct Target: public ::Analyser::Static::Target {
Target() : Analyser::Static::Target(Machine::Atari2600) {}
};
}
}
}
#endif /* Analyser_Static_Atari_Target_h */

View File

@@ -13,10 +13,15 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AtariST {
namespace Analyser {
namespace Static {
namespace AtariST {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */

View File

@@ -12,23 +12,16 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AtariST {
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemorySize,
FiveHundredAndTwelveKilobytes,
OneMegabyte,
FourMegabytes);
MemorySize memory_size = MemorySize::OneMegabyte;
Target() : Analyser::Static::Target(Machine::AtariST) {
if(needs_declare()) {
DeclareField(memory_size);
AnnounceEnum(MemorySize);
}
}
Target() : Analyser::Static::Target(Machine::AtariST) {}
};
}
}
}
#endif /* Analyser_Static_AtariST_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Coleco {
namespace Analyser {
namespace Static {
namespace Coleco {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -14,10 +14,14 @@
#include <vector>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}
#endif /* Disk_hpp */

View File

@@ -9,11 +9,12 @@
#ifndef File_hpp
#define File_hpp
#include <cstdint>
#include <string>
#include <vector>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct File {
std::wstring name;
@@ -34,6 +35,8 @@ struct File {
bool is_basic();
};
}
}
}
#endif /* File_hpp */

View File

@@ -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.
}

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* CommodoreAnalyser_hpp */

View File

@@ -12,10 +12,14 @@
#include "../../../Storage/Tape/Tape.hpp"
#include "File.hpp"
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -14,7 +14,9 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class MemoryModel {
@@ -69,6 +71,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -11,7 +11,7 @@
#include "Kernel.hpp"
using namespace Analyser::Static::MOS6502;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
@@ -236,7 +236,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Relative: {
std::size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
++address;
address++;
instruction.operand = memory[operand_address];
}
@@ -291,34 +291,20 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
// Decide on overall flow control.
// All relative instructions are flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
switch(instruction.operation) {
default: break;
case Instruction::KIL:
case Instruction::RTS:
case Instruction::RTI:
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
disassembly.implicit_entry_points.push_back(address);
return;
case Instruction::JMP:
// Adding a new entry point for relative jumps was handled above.
if(instruction.addressing_mode == Instruction::Absolute) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
return;
case Instruction::JSR:
disassembly.remaining_entry_points.push_back(instruction.operand);
break;
}
}
}
@@ -330,5 +316,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -16,7 +16,9 @@
#include <set>
#include <vector>
namespace Analyser::Static::MOS6502 {
namespace Analyser {
namespace Static {
namespace MOS6502 {
/*!
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
@@ -93,5 +95,7 @@ Disassembly Disassemble(
std::vector<uint16_t> entry_points);
}
}
}
#endif /* Disassembler6502_hpp */

View File

@@ -11,7 +11,9 @@
#include <functional>
namespace Analyser::Static::Disassembler {
namespace Analyser {
namespace Static {
namespace Disassembler {
/*!
Provides an address mapper that relocates a chunk of memory so that it starts at
@@ -23,6 +25,8 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
};
}
}
}
}
#endif /* AddressMapper_hpp */

View File

@@ -9,61 +9,44 @@
#ifndef Kernel_hpp
#define Kernel_hpp
namespace Analyser::Static::Disassembly {
namespace Analyser {
namespace Static {
namespace Disassembly {
template <typename D, typename S> struct PartialDisassembly {
D disassembly;
std::vector<S> remaining_entry_points;
std::vector<S> implicit_entry_points;
};
template <typename D, typename S, typename Disassembler> D Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(S)> &address_mapper,
std::vector<S> entry_points,
bool exhaustive)
{
std::vector<S> entry_points) {
PartialDisassembly<D, S> partial_disassembly;
partial_disassembly.remaining_entry_points = entry_points;
while(!partial_disassembly.remaining_entry_points.empty()) {
// Do a recursive-style disassembly for all current entry points.
while(!partial_disassembly.remaining_entry_points.empty()) {
// Pull the next entry point from the back of the vector.
const S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// pull the next entry point from the back of the vector
S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// If that address has already been visited, forget about it.
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// if that address has already been visited, forget about it
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// If it's outgoing, log it as such and forget about it; otherwise disassemble.
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
// If this is not an exhaustive disassembly, that's your lot.
if(!exhaustive) {
break;
}
// Otherwise, copy in the new 'implicit entry points' (i.e. all locations that are one after
// a disassembled region). There's a test above that'll ignore any which have already been
// disassembled from.
std::move(
partial_disassembly.implicit_entry_points.begin(),
partial_disassembly.implicit_entry_points.end(),
std::back_inserter(partial_disassembly.remaining_entry_points)
);
partial_disassembly.implicit_entry_points.clear();
// if it's outgoing, log it as such and forget about it; otherwise disassemble
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
return partial_disassembly.disassembly;
}
}
}
}
#endif /* Kernel_hpp */

View File

@@ -11,7 +11,7 @@
#include "Kernel.hpp"
using namespace Analyser::Static::Z80;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
@@ -589,7 +589,7 @@ struct Z80Disassembler {
break;
}
// Add any (potentially) newly-discovered entry point.
// Add any (potentially) newly discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
@@ -598,37 +598,22 @@ struct Z80Disassembler {
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
switch(instruction.operation) {
default: break;
if(instruction.condition != Instruction::Condition::None) continue;
case Instruction::Operation::RET:
case Instruction::Operation::RETI:
case Instruction::Operation::RETN:
case Instruction::Operation::JP:
case Instruction::Operation::JR:
if(instruction.condition == Instruction::Condition::None) {
disassembly.implicit_entry_points.push_back(accessor.address());
return;
}
}
if(instruction.operation == Instruction::Operation::RET) return;
if(instruction.operation == Instruction::Operation::RETI) return;
if(instruction.operation == Instruction::Operation::RETN) return;
if(instruction.operation == Instruction::Operation::JP) return;
if(instruction.operation == Instruction::Operation::JR) return;
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach)
{
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
memory,
address_mapper,
entry_points,
approach == Approach::Exhaustive
);
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -15,7 +15,9 @@
#include <set>
#include <vector>
namespace Analyser::Static::Z80 {
namespace Analyser {
namespace Static {
namespace Z80 {
struct Instruction {
/*! The address this instruction starts at. This is a mapped address. */
@@ -76,20 +78,13 @@ struct Disassembly {
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
enum class Approach {
/// Disassemble from the supplied entry points until an indeterminate branch or return only, adding other fully-static
/// entry points as they are observed.
Recursive,
/// Disassemble all supplied bytes, regardless of what nonsense may be encountered by accidental parsing of data areas.
Exhaustive,
};
Disassembly Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach);
std::vector<uint16_t> entry_points);
}
}
}
#endif /* StaticAnalyser_Disassembler_Z80_hpp */

View File

@@ -13,10 +13,15 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::DiskII {
namespace Analyser {
namespace Static {
namespace DiskII {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */

View File

@@ -13,10 +13,15 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Enterprise {
namespace Analyser {
namespace Static {
namespace Enterprise {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */

View File

@@ -15,7 +15,9 @@
#include <string>
namespace Analyser::Static::Enterprise {
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
@@ -48,6 +50,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

View File

@@ -11,7 +11,9 @@
#include "../../../Storage/Cartridge/Cartridge.hpp"
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
/*!
Extends the base cartridge class by adding a (guess at) the banking scheme.
@@ -31,6 +33,8 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
Storage::Cartridge::Cartridge(segments), type(type) {}
};
}
}
}
#endif /* Cartridge_hpp */

View File

@@ -37,11 +37,6 @@ 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 {
@@ -105,7 +100,6 @@ 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;
@@ -115,16 +109,97 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// be at play; disassemble to try to figure it out.
std::vector<uint8_t> first_8k;
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
const Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ init_address },
Analyser::Static::Z80::Approach::Exhaustive
{ init_address }
);
// // Look for a indirect store followed by an unconditional JP or CALL into another
// // segment, that's a fairly explicit sign where found.
using Instruction = Analyser::Static::Z80::Instruction;
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
bool is_ascii = false;
// auto iterator = instructions.begin();
// while(iterator != instructions.end()) {
// auto next_iterator = iterator;
// next_iterator++;
// if(next_iterator == instructions.end()) break;
//
// if( iterator->second.operation == Instruction::Operation::LD &&
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
// (
// iterator->second.operand == 0x5000 ||
// iterator->second.operand == 0x6000 ||
// iterator->second.operand == 0x6800 ||
// iterator->second.operand == 0x7000 ||
// iterator->second.operand == 0x77ff ||
// iterator->second.operand == 0x7800 ||
// iterator->second.operand == 0x8000 ||
// iterator->second.operand == 0x9000 ||
// iterator->second.operand == 0xa000
// ) &&
// (
// next_iterator->second.operation == Instruction::Operation::CALL ||
// next_iterator->second.operation == Instruction::Operation::JP
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x6800:
// if(address >= 0x6000 && address < 0x6800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x7000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// if(address >= 0x7000 && address < 0x7800) {
// is_ascii = true;
// }
// break;
// case 0x77ff:
// if(address >= 0x7000 && address < 0x7800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
// }
// break;
// case 0x7800:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x8000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x9000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0xa000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
// }
// break;
// case 0xb000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// }
// }
//
// iterator = next_iterator;
// Look for LD (nnnn), A instructions, and collate those addresses.
std::map<uint16_t, int> address_counts;
@@ -136,46 +211,49 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
}
}
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
// Weight confidences by number of observed hits.
float total_hits =
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
address_counts[0xa000] + address_counts[0x5000] +
address_counts[0x9000] + address_counts[0xb000]
);
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
const bool is_konami = (konami_total * 5) / (total_hits * 3);
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float(ascii_8kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float(ascii_16kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
float(konami_total) / float(total_hits)));
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
float(konami_with_scc_total) / float(total_hits)));
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */

View File

@@ -14,7 +14,9 @@
#include <string>
#include <vector>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
struct File {
std::string name;
@@ -35,6 +37,8 @@ struct File {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* StaticAnalyser_MSX_Tape_hpp */

View File

@@ -14,19 +14,14 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace 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,
@@ -37,15 +32,14 @@ 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 */

View File

@@ -13,10 +13,15 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Macintosh {
namespace Analyser {
namespace Static {
namespace Macintosh {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */

View File

@@ -13,7 +13,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Macintosh {
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
@@ -28,6 +30,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_Macintosh_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -14,7 +14,9 @@
#include <string>
#include <vector>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
struct File {
std::string name;
@@ -31,6 +33,8 @@ struct File {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -14,7 +14,9 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ROM,
@@ -54,6 +56,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Sega {
namespace Analyser {
namespace Static {
namespace Sega {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -13,7 +13,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Sega {
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class Model {
@@ -46,10 +48,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
}
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
}
}
}
#endif /* Analyser_Static_Sega_Target_h */

View File

@@ -53,13 +53,13 @@
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// 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
@@ -81,8 +81,6 @@
// Target Platform Types
#include "../../Storage/TargetPlatforms.hpp"
template<class> inline constexpr bool always_false_v = false;
using namespace Analyser::Static;
namespace {
@@ -126,21 +124,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
if(extension == "2mg") {
// 2MG uses a factory method; defer to it.
try {
const auto media = Storage::Disk::Disk2MG::open(file_name);
std::visit([&result, &potential_platforms](auto &&arg) {
using Type = typename std::decay<decltype(arg)>::type;
if constexpr (std::is_same<Type, nullptr_t>::value) {
// It's valid for no media to be returned.
} else if constexpr (std::is_same<Type, Storage::Disk::DiskImageHolderBase *>::value) {
InsertInstance(result.disks, arg, TargetPlatform::DiskII);
} else if constexpr (std::is_same<Type, Storage::MassStorage::MassStorageDevice *>::value) {
// TODO: or is it Apple IIgs?
InsertInstance(result.mass_storage_devices, arg, TargetPlatform::AppleII);
} else {
static_assert(always_false_v<Type>, "Unexpected type encountered.");
}
}, media);
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
} catch(...) {}
}
@@ -171,7 +155,6 @@ 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>,
@@ -185,7 +168,6 @@ 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
@@ -217,7 +199,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::AtariST) // ST
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
@@ -245,7 +227,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); \

View File

@@ -21,7 +21,8 @@
#include <string>
#include <vector>
namespace Analyser::Static {
namespace Analyser {
namespace Static {
struct State;
@@ -78,6 +79,7 @@ TargetList GetTargets(const std::string &file_name);
*/
Media GetMedia(const std::string &file_name);
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::ZX8081 {
namespace Analyser {
namespace Static {
namespace ZX8081 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -14,7 +14,9 @@
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::ZX8081 {
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemoryModel,
@@ -38,6 +40,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
}
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

View File

@@ -13,10 +13,14 @@
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::ZXSpectrum {
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -13,7 +13,9 @@
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::ZXSpectrum {
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -36,6 +38,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
}
};
}
}
}
#endif /* Target_h */

View File

@@ -14,8 +14,6 @@
#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.
@@ -26,7 +24,7 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
If the held object implements @c next_sequence_point() then it'll be used to flush implicitly
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
as and when sequence points are hit. Callers can use will_flush() to predict these.
If the held object is a subclass of ClockingHint::Source, this template will register as an
@@ -40,7 +38,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
private:
/*!
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
**Does not delete the object.**
@@ -123,13 +121,7 @@ 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));
}
@@ -138,13 +130,7 @@ 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));
}
@@ -247,9 +233,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.next_sequence_point();
time_until_event_ = object_.get_next_sequence_point();
} else {
const auto time = object_.next_sequence_point();
const auto time = object_.get_next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
@@ -272,16 +258,12 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
bool did_flush_ = false;
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
clocking_preference_ = clocking;
}
#ifndef NDEBUG
std::atomic_flag flush_concurrency_check_{};
#endif
};
/*!
@@ -294,7 +276,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) {
@@ -333,7 +315,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
LocalTimeScale time_since_update_;
TargetTimeScale threshold_;
bool is_flushed_ = true;
Concurrency::AsyncTaskQueue<true> task_queue_;
Concurrency::AsyncTaskQueue task_queue_;
};
#endif /* JustInTime_h */

View File

@@ -20,11 +20,6 @@ inline Nanos nanos_now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
inline Seconds seconds(Nanos nanos) {
return double(nanos) / 1e9;
}
}
#endif /* TimeTypes_h */

View File

@@ -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.

View File

@@ -8,18 +8,8 @@
#include "ncr5380.hpp"
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[5380] "
#include "../../Outputs/Log.hpp"
// TODO:
//
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
using namespace NCR::NCR5380;
using SCSI::Line;
@@ -38,16 +28,20 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
void NCR5380::write(int address, uint8_t value, bool) {
switch(address & 7) {
case 0:
LOG("[0] Set current SCSI bus state to " << PADHEX(2) << int(value));
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
dma_acknowledge(value);
// printf("w %02x\n", value);
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
break;
case 1: {
LOG("[1] Initiator command register set: " << PADHEX(2) << int(value));
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
@@ -63,7 +57,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
} break;
case 2:
LOG("[2] Set mode: " << PADHEX(2) << int(value));
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
@@ -75,7 +69,6 @@ void NCR5380::write(int address, uint8_t value, bool) {
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
arbitration_in_progress_ = false;
phase_mismatch_ = false;
switch(mode_ & 0x3) {
case 0x0:
bus_output_ &= ~SCSI::Line::Busy;
@@ -95,36 +88,31 @@ void NCR5380::write(int address, uint8_t value, bool) {
bus_.update_observers();
break;
}
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
// is reset (0) in the Mode Register".
end_of_dma_ &= bool(value & 0x2);
update_control_output();
break;
case 3: {
LOG("[3] Set target command: " << PADHEX(2) << int(value));
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
target_command_ = value;
update_control_output();
} break;
case 4:
LOG("[4] Set select enabled: " << PADHEX(2) << int(value));
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
break;
case 5:
LOG("[5] Start DMA send: " << PADHEX(2) << int(value));
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::Send;
break;
case 6:
LOG("[6] Start DMA target receive: " << PADHEX(2) << int(value));
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
LOG("[7] Start DMA initiator receive: " << PADHEX(2) << int(value));
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
@@ -148,15 +136,18 @@ void NCR5380::write(int address, uint8_t value, bool) {
uint8_t NCR5380::read(int address, bool) {
switch(address & 7) {
case 0:
LOG("[0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
return dma_acknowledge();
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
return uint8_t(bus_.get_state());
case 1:
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
@@ -168,11 +159,11 @@ uint8_t NCR5380::read(int address, bool) {
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
LOG("[2] Get mode");
// LOG("[SCSI 2] Get mode");
return mode_;
case 3:
LOG("[3] Get target command");
// LOG("[SCSI 3] Get target command");
return target_command_;
case 4: {
@@ -186,38 +177,41 @@ uint8_t NCR5380::read(int address, bool) {
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
LOG("[4] Get current bus state: " << PADHEX(2) << int(result));
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const bool phase_matches =
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
const uint8_t result =
(end_of_dma_ ? 0x80 : 0x00) |
/* b7 = end of DMA */
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
/* b5 = parity error */
(irq_ ? 0x10 : 0x00) |
(phase_matches() ? 0x08 : 0x00) |
/* b4 = IRQ active */
(phase_matches ? 0x08 : 0x00) |
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
LOG("[5] Get bus and status: " << PADHEX(2) << int(result));
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
return result;
}
case 6:
LOG("[6] Get input data");
// LOG("[SCSI 6] Get input data");
return 0xff;
case 7:
LOG("[7] Reset parity/interrupt");
irq_ = false;
// LOG("[SCSI 7] Reset parity/interrupt");
return 0xff;
}
return 0;
}
SCSI::BusState NCR5380::target_output() const {
SCSI::BusState NCR5380::target_output() {
SCSI::BusState output = SCSI::DefaultBusState;
if(target_command_ & 0x08) output |= Line::Request;
if(target_command_ & 0x04) output |= Line::Message;
@@ -242,17 +236,6 @@ void NCR5380::update_control_output() {
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
/*
When connected as an Initiator with DMA Mode True,
if the phase lines I//O, C//D, and /MSG do not match the
phase bits in the Target Command Register, a phase mismatch
interrupt is generated when /REQ goes active.
*/
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
irq_ = true;
phase_mismatch_ = true;
}
switch(state_) {
default: break;
@@ -313,13 +296,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
dma_request_ = false;
break;
case SCSI::Line::Request:
// Don't issue a new DMA request if a phase mismatch has
// been detected and this is an intiator receiving.
// This is a bit of reading between the lines.
// (i.e. guesswork, partly)
dma_request_ =
!phase_mismatch_ ||
(dma_operation_ != DMAOperation::InitiatorReceive);
dma_request_ = true;
break;
case SCSI::Line::Request | SCSI::Line::Acknowledge:
dma_request_ = false;
@@ -339,38 +316,3 @@ void NCR5380::set_execution_state(ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}
size_t NCR5380::scsi_id() {
return device_id_;
}
bool NCR5380::dma_request() {
return dma_request_;
}
uint8_t NCR5380::dma_acknowledge() {
const uint8_t bus_state = uint8_t(bus_.get_state());
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
return bus_state;
}
void NCR5380::dma_acknowledge(uint8_t value) {
data_bus_ = value;
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
bool NCR5380::phase_matches() const {
const auto bus_state = bus_.get_state();
return
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
}

View File

@@ -14,7 +14,8 @@
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
namespace NCR::NCR5380 {
namespace NCR {
namespace NCR5380 {
/*!
Models the NCR 5380, a SCSI interface chip.
@@ -23,24 +24,12 @@ 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. */
uint8_t read(int address, bool dma_acknowledge = false);
/*! @returns The SCSI ID assigned to this device. */
size_t scsi_id();
/*! @return @c true if DMA request is active; @c false otherwise. */
bool dma_request();
/*! Signals DMA acknowledge with a simultaneous read. */
uint8_t dma_acknowledge();
/*! Signals DMA acknowledge with a simultaneous write. */
void dma_acknowledge(uint8_t);
private:
SCSI::Bus &bus_;
@@ -57,10 +46,6 @@ class NCR5380 final: public SCSI::Bus::Observer {
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
bool end_of_dma_ = false;
bool irq_ = false;
bool phase_mismatch_ = false;
enum class ExecutionState {
None,
@@ -78,13 +63,13 @@ class NCR5380 final: public SCSI::Bus::Observer {
void set_execution_state(ExecutionState state);
SCSI::BusState target_output() const;
SCSI::BusState target_output();
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
bool phase_matches() const;
};
}
}
#endif /* ncr5380_hpp */

View File

@@ -15,7 +15,8 @@
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS::MOS6522 {
namespace MOS {
namespace MOS6522 {
enum Port {
A = 0,
@@ -137,6 +138,7 @@ template <class BusHandlerT> class MOS6522: public MOS6522Storage {
void evaluate_port_b_output();
};
}
}
#include "Implementation/6522Implementation.hpp"

View File

@@ -12,7 +12,8 @@
//
// PB6 count-down mode for timer 2.
namespace MOS::MOS6522 {
namespace MOS {
namespace MOS6522 {
template <typename T> void MOS6522<T>::access(int address) {
switch(address) {
@@ -271,7 +272,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.
}
@@ -329,7 +330,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;
}
@@ -345,9 +346,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.
}
@@ -493,3 +494,4 @@ template <typename T> void MOS6522<T>::shift_out() {
}
}
}

View File

@@ -11,7 +11,8 @@
#include <cstdint>
namespace MOS::MOS6522 {
namespace MOS {
namespace MOS6522 {
class MOS6522Storage {
protected:
@@ -106,6 +107,7 @@ class MOS6522Storage {
}
};
}
}
#endif /* _522Storage_hpp */

View File

@@ -14,7 +14,8 @@
#include "Implementation/6526Storage.hpp"
#include "../Serial/Line.hpp"
namespace MOS::MOS6526 {
namespace MOS {
namespace MOS6526 {
enum Port {
A = 0,
@@ -85,6 +86,7 @@ template <typename PortHandlerT, Personality personality> class MOS6526:
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
};
}
}
#include "Implementation/6526Implementation.hpp"

View File

@@ -12,7 +12,8 @@
#include <cassert>
#include <cstdio>
namespace MOS::MOS6526 {
namespace MOS {
namespace MOS6526 {
enum Interrupts: uint8_t {
TimerA = 1 << 0,
@@ -237,6 +238,7 @@ bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line
return true;
}
}
}
#endif /* _526Implementation_h */

View File

@@ -13,7 +13,8 @@
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace MOS::MOS6526 {
namespace MOS {
namespace MOS6526 {
class TODBase {
public:
@@ -332,6 +333,7 @@ struct MOS6526Storage {
int pending_ = 0;
};
}
}
#endif /* _526Storage_h */

View File

@@ -12,18 +12,18 @@
using namespace MOS::MOS6560;
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.enqueue([this, volume]() {
audio_queue_.defer([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
});
}
void AudioGenerator::set_control(int channel, uint8_t value) {
audio_queue_.enqueue([this, channel, value]() {
audio_queue_.defer([this, channel, value]() {
control_registers_[channel] = value;
});
}

View File

@@ -15,12 +15,13 @@
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace MOS::MOS6560 {
namespace MOS {
namespace MOS6560 {
// audio state
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
public:
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
@@ -32,7 +33,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
static constexpr bool get_is_stereo() { return false; }
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
@@ -83,11 +84,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);
@@ -432,7 +433,7 @@ template <class BusHandler> class MOS6560 {
BusHandler &bus_handler_;
Outputs::CRT::CRT crt_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
@@ -519,6 +520,7 @@ template <class BusHandler> class MOS6560 {
OutputMode output_mode_ = OutputMode::NTSC;
};
}
}
#endif /* _560_hpp */

View File

@@ -14,7 +14,8 @@
#include <cstdint>
#include <cstdio>
namespace Motorola::CRTC {
namespace Motorola {
namespace CRTC {
struct BusState {
bool display_enable = false;
@@ -268,6 +269,7 @@ template <class T> class CRTC6845 {
unsigned int character_is_visible_shifter_ = 0;
};
}
}
#endif /* CRTC6845_hpp */

View File

@@ -15,7 +15,8 @@
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../Serial/Line.hpp"
namespace Motorola::ACIA {
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
public:
@@ -125,6 +126,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelega
uint8_t get_status();
};
}
}
#endif /* Motorola_ACIA_6850_hpp */

View File

@@ -207,7 +207,7 @@ void MFP68901::run_for(HalfCycles time) {
}
}
HalfCycles MFP68901::next_sequence_point() {
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles::max();
}

View File

@@ -9,12 +9,12 @@
#ifndef MFP68901_hpp
#define MFP68901_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include <cstdint>
namespace Motorola::MFP68901 {
namespace Motorola {
namespace MFP68901 {
class PortHandler {
public:
@@ -40,7 +40,7 @@ class MFP68901: public ClockingHint::Source {
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles next_sequence_point();
HalfCycles get_next_sequence_point();
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
void set_timer_event_input(int channel, bool value);
@@ -181,6 +181,7 @@ class MFP68901: public ClockingHint::Source {
}
};
}
}
#endif /* MFP68901_hpp */

View File

@@ -11,7 +11,8 @@
#include <cstdint>
namespace Intel::i8255 {
namespace Intel {
namespace i8255 {
class PortHandler {
public:
@@ -87,6 +88,7 @@ template <class T> class i8255 {
T &port_handler_;
};
}
}
#endif /* i8255_hpp */

View File

@@ -15,7 +15,8 @@
#include <memory>
#include <vector>
namespace Intel::i8272 {
namespace Intel {
namespace i8272 {
class BusHandler {
public:
@@ -129,6 +130,7 @@ class i8272 : public Storage::Disk::MFMController {
bool is_sleeping_ = false;
};
}
}
#endif /* i8272_hpp */

View File

@@ -11,7 +11,8 @@
#include <cstdint>
namespace Zilog::SCC {
namespace Zilog {
namespace SCC {
/*!
Models the Zilog 8530 SCC, a serial adaptor.
@@ -109,5 +110,7 @@ class z8530 {
};
}
}
#endif /* z8530_hpp */

1052
Components/9918/9918.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,36 +12,12 @@
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
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 {
#include <cstdint>
namespace TI {
namespace TMS {
/*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
@@ -54,10 +30,13 @@ namespace TI::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.
*/
template <Personality personality> class TMS9918: private Base<personality> {
class TMS9918: public Base {
public:
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
TMS9918();
/*!
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);
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard standard);
@@ -65,24 +44,18 @@ template <Personality personality> class TMS9918: private Base<personality> {
/*! 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 CRT display. */
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);
/*! Gets the type of CRT display. */
/*! Gets the type of display the CRT will request. */
Outputs::Display::DisplayType get_display_type() const;
/*!
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.
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.
*/
void run_for(const HalfCycles cycles);
@@ -92,11 +65,11 @@ template <Personality personality> class TMS9918: private Base<personality> {
/*! Gets a register value. */
uint8_t read(int address);
/*! Gets the current scan line; provided by the Sega VDPs only. */
uint8_t get_current_line() const;
/*! Gets the current scan line; provided by the Master System only. */
uint8_t get_current_line();
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
uint8_t get_latched_horizontal_counter() const;
/*! Gets the current latched horizontal counter; provided by the Master System only. */
uint8_t get_latched_horizontal_counter();
/*! Latches the current horizontal counter. */
void latch_horizontal_counter();
@@ -108,7 +81,7 @@ template <Personality personality> class TMS9918: private Base<personality> {
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles next_sequence_point() const;
HalfCycles get_next_sequence_point();
/*!
Returns the amount of time until the nominated line interrupt position is
@@ -123,9 +96,10 @@ template <Personality personality> class TMS9918: private Base<personality> {
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line() const;
bool get_interrupt_line();
};
}
}
#endif /* TMS9918_hpp */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,115 +0,0 @@
//
// 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 */

View File

@@ -1,168 +0,0 @@
//
// 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 */

View File

@@ -1,572 +0,0 @@
//
// 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 */

View File

@@ -1,796 +0,0 @@
//
// 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 */

View File

@@ -1,132 +0,0 @@
//
// 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 */

View File

@@ -1,80 +0,0 @@
//
// 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 */

View File

@@ -1,50 +0,0 @@
//
// 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 */

View File

@@ -1,490 +0,0 @@
//
//
// 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 */

View File

@@ -1,384 +0,0 @@
//
// 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 */

View File

@@ -16,7 +16,7 @@
using namespace GI::AY38910;
template <bool is_stereo>
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// Don't use the low bit of the envelope position if this is an AY.
envelope_position_mask_ |= personality == Personality::AY38910;
@@ -252,7 +252,7 @@ template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t va
// If this is a register that affects audio output, enqueue a mutation onto the
// audio generation thread.
if(selected_register_ < 14) {
task_queue_.enqueue([this, selected_register = selected_register_, value] () {
task_queue_.defer([this, selected_register = selected_register_, value] () {
// Perform any register-specific mutation to output generation.
uint8_t masked_value = value;
switch(selected_register) {

View File

@@ -14,7 +14,8 @@
#include "../../Reflection/Struct.hpp"
namespace GI::AY38910 {
namespace GI {
namespace AY38910 {
/*!
A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
@@ -70,7 +71,7 @@ enum class Personality {
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910(Personality, Concurrency::AsyncTaskQueue<false> &);
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
@@ -113,7 +114,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
static constexpr bool get_is_stereo() { return is_stereo; }
private:
Concurrency::AsyncTaskQueue<false> &task_queue_;
Concurrency::DeferringAsyncTaskQueue &task_queue_;
int selected_register_ = 0;
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -218,6 +219,7 @@ struct State: public Reflection::StructImpl<State> {
}
};
}
}
#endif /* AY_3_8910_hpp */

Some files were not shown because too many files have changed in this diff Show More