mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-21 02:37:44 +00:00
Merge pull request #1484 from TomHarte/286Decoding
Repair 286 decoding and `perform`.
This commit is contained in:
commit
0eef2c0d04
@ -13,16 +13,19 @@
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
ReflectableEnum(Model,
|
||||
XT,
|
||||
TurboXT,
|
||||
AT
|
||||
);
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(VideoAdaptor,
|
||||
MDA,
|
||||
CGA);
|
||||
CGA,
|
||||
);
|
||||
VideoAdaptor adaptor = VideoAdaptor::CGA;
|
||||
|
||||
ReflectableEnum(ModelApproximation,
|
||||
XT,
|
||||
TurboXT);
|
||||
ModelApproximation model = ModelApproximation::TurboXT;
|
||||
Model model = Model::TurboXT;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {}
|
||||
|
||||
@ -30,7 +33,7 @@ private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(ModelApproximation);
|
||||
AnnounceEnum(Model);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(model);
|
||||
}
|
||||
|
@ -240,11 +240,11 @@ void into(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT, typename InstructionT, typename ContextT>
|
||||
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
|
||||
void bound(
|
||||
const InstructionT &instruction,
|
||||
read_t<IntT> destination,
|
||||
read_t<IntT> source,
|
||||
read_t<AddressT> destination,
|
||||
read_t<AddressT> source,
|
||||
ContextT &context
|
||||
) {
|
||||
using sIntT = typename std::make_signed<IntT>::type;
|
||||
@ -252,10 +252,9 @@ void bound(
|
||||
const auto source_segment = instruction.data_segment();
|
||||
context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT));
|
||||
const auto lower_bound =
|
||||
sIntT(context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source));
|
||||
source += 2;
|
||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, source));
|
||||
const auto upper_bound =
|
||||
sIntT(context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source));
|
||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, IntT(source + 2)));
|
||||
|
||||
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
|
||||
interrupt(Interrupt::BoundRangeExceeded, context);
|
||||
|
@ -223,7 +223,7 @@ template <
|
||||
} else {
|
||||
static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE));
|
||||
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
|
||||
Primitive::leave<IntT>();
|
||||
Primitive::leave<IntT>(context);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -338,7 +338,7 @@ template <
|
||||
break;
|
||||
} else {
|
||||
static_assert(int(Operation::SETMOC) == int(Operation::BOUND));
|
||||
Primitive::bound<IntT>(instruction, destination_r(), source_r(), context);
|
||||
Primitive::bound<IntT, AddressT>(instruction, destination_r(), source_r(), context);
|
||||
}
|
||||
return;
|
||||
|
||||
|
@ -152,33 +152,37 @@ void enter(
|
||||
const auto alloc_size = instruction.dynamic_storage_size();
|
||||
const auto nesting_level = instruction.nesting_level() & 0x1f;
|
||||
|
||||
// Preauthorse contents that'll be fetched via BP.
|
||||
// Preauthorise contents that'll be fetched via BP.
|
||||
const auto copied_pointers = nesting_level - 2;
|
||||
if(copied_pointers > 0) {
|
||||
context.memory.preauthorise_read(
|
||||
Source::SS,
|
||||
context.registers.bp() - copied_pointers * sizeof(uint16_t),
|
||||
copied_pointers * sizeof(uint16_t)
|
||||
uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)),
|
||||
uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit.
|
||||
);
|
||||
}
|
||||
|
||||
// Preauthorse stack activity.
|
||||
context.memory.preauthorise_stack_write((1 + copied_pointers) * sizeof(uint16_t));
|
||||
// Preauthorise writes.
|
||||
context.memory.preauthorise_stack_write(uint32_t(size_t(nesting_level) * sizeof(uint16_t)));
|
||||
|
||||
// Push BP and grab the end of frame.
|
||||
push<uint16_t, true>(context.registers.bp(), context);
|
||||
const auto frame = context.registers.sp();
|
||||
|
||||
// Copy data as per the nesting level.
|
||||
for(int c = 1; c < nesting_level; c++) {
|
||||
context.registers.bp() -= 2;
|
||||
if(nesting_level > 0) {
|
||||
for(int c = 1; c < nesting_level; c++) {
|
||||
context.registers.bp() -= 2;
|
||||
|
||||
const auto value = context.memory.template preauthorised_read<uint16_t>(Source::SS, context.registers.bp());
|
||||
push<uint16_t, true>(value);
|
||||
const auto value = context.memory.template preauthorised_read<uint16_t>(Source::SS, context.registers.bp());
|
||||
push<uint16_t, true>(value, context);
|
||||
}
|
||||
push<uint16_t, true>(frame, context);
|
||||
}
|
||||
|
||||
// Set final BP.
|
||||
context.registers.bp() = frame;
|
||||
context.registers.sp() -= alloc_size;
|
||||
}
|
||||
|
||||
template <typename IntT, typename ContextT>
|
||||
|
@ -850,7 +850,7 @@ public:
|
||||
|
||||
/// @returns The dynamic storage size argument supplied to an ENTER.
|
||||
constexpr ImmediateT dynamic_storage_size() const {
|
||||
return displacement();
|
||||
return offset();
|
||||
}
|
||||
|
||||
// Standard comparison operator.
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "Numeric/RegisterSizes.hpp"
|
||||
|
||||
#include "Memory.hpp"
|
||||
@ -287,13 +288,14 @@ class DMAPages {
|
||||
}
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class DMA {
|
||||
public:
|
||||
i8237 controller;
|
||||
DMAPages pages;
|
||||
|
||||
// Memory is set posthoc to resolve a startup time.
|
||||
void set_memory(Memory *memory) {
|
||||
void set_memory(Memory<model> *memory) {
|
||||
memory_ = memory;
|
||||
}
|
||||
|
||||
@ -310,7 +312,7 @@ class DMA {
|
||||
}
|
||||
|
||||
private:
|
||||
Memory *memory_;
|
||||
Memory<model> *memory_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -8,9 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ProcessorByModel.hpp"
|
||||
#include "Registers.hpp"
|
||||
#include "Segments.hpp"
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "InstructionSets/x86/AccessType.hpp"
|
||||
|
||||
#include <array>
|
||||
@ -18,159 +20,202 @@
|
||||
namespace PCCompatible {
|
||||
|
||||
// TODO: send writes to the ROM area off to nowhere.
|
||||
struct Memory {
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class Memory {
|
||||
static constexpr auto x86_model = processor_model(model);
|
||||
|
||||
// Constructor.
|
||||
Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {}
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
|
||||
//
|
||||
// Preauthorisation call-ins. Since only an 8088 is currently modelled, all accesses are implicitly authorised.
|
||||
//
|
||||
void preauthorise_stack_write([[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_stack_read([[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_read([[maybe_unused]] InstructionSet::x86::Source segment, [[maybe_unused]] uint16_t start, [[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_read([[maybe_unused]] uint32_t start, [[maybe_unused]] uint32_t length) {}
|
||||
// Constructor.
|
||||
Memory(Registers<x86_model> ®isters, const Segments<x86_model> &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
|
||||
//
|
||||
// Access call-ins.
|
||||
//
|
||||
//
|
||||
// Preauthorisation call-ins. Since only an 8088 is currently modelled, all accesses are implicitly authorised.
|
||||
//
|
||||
void preauthorise_stack_write([[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_stack_read([[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_read([[maybe_unused]] InstructionSet::x86::Source segment, [[maybe_unused]] uint16_t start, [[maybe_unused]] uint32_t length) {}
|
||||
void preauthorise_read([[maybe_unused]] uint32_t start, [[maybe_unused]] uint32_t length) {}
|
||||
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
|
||||
const uint32_t physical_address = address(segment, offset);
|
||||
//
|
||||
// Access call-ins.
|
||||
//
|
||||
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(offset == 0xffff) {
|
||||
return split_word<type>(physical_address, address(segment, 0));
|
||||
}
|
||||
}
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(
|
||||
const InstructionSet::x86::Source segment,
|
||||
const uint16_t offset
|
||||
) {
|
||||
const uint32_t physical_address = address(segment, offset);
|
||||
|
||||
return access<IntT, type>(physical_address);
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
|
||||
// Dispense with the single-byte case trivially.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
} else if(address != 0xf'ffff) {
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
} else {
|
||||
return split_word<type>(address, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Direct write.
|
||||
//
|
||||
template <typename IntT>
|
||||
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
|
||||
// Bytes can be written without further ado.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Words that straddle the segment end must be split in two.
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(offset == 0xffff) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
|
||||
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t target = address(segment, offset) & 0xf'ffff;
|
||||
|
||||
// Words that straddle the end of physical RAM must also be split in two.
|
||||
if(target == 0xf'ffff) {
|
||||
memory[0xf'ffff] = value & 0xff;
|
||||
memory[0x0'0000] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
// It's safe just to write then.
|
||||
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Helper for instruction fetch.
|
||||
//
|
||||
std::pair<const uint8_t *, size_t> next_code() const {
|
||||
const uint32_t start = segments_.cs_base_ + registers_.ip();
|
||||
return std::make_pair(&memory[start], 0x10'000 - start);
|
||||
}
|
||||
|
||||
std::pair<const uint8_t *, size_t> all() const {
|
||||
return std::make_pair(memory.data(), 0x10'000);
|
||||
}
|
||||
|
||||
//
|
||||
// External access.
|
||||
//
|
||||
void install(size_t address, const uint8_t *data, size_t length) {
|
||||
std::copy(data, data + length, memory.begin() + std::vector<uint8_t>::difference_type(address));
|
||||
}
|
||||
|
||||
uint8_t *at(uint32_t address) {
|
||||
return &memory[address];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 1024*1024> memory{0xff};
|
||||
Registers ®isters_;
|
||||
const Segments &segments_;
|
||||
|
||||
uint32_t segment_base(const InstructionSet::x86::Source segment) const {
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: return segments_.ds_base_;
|
||||
case Source::ES: return segments_.es_base_;
|
||||
case Source::CS: return segments_.cs_base_;
|
||||
case Source::SS: return segments_.ss_base_;
|
||||
return split_word<type>(physical_address, address(segment, 0));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t address(const InstructionSet::x86::Source segment, const uint16_t offset) const {
|
||||
return (segment_base(segment) + offset) & 0xf'ffff;
|
||||
return access<IntT, type>(physical_address);
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(const uint32_t address) {
|
||||
// Dispense with the single-byte case trivially.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
} else if(address != 0xf'ffff) {
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
} else {
|
||||
return split_word<type>(address, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <AccessType type>
|
||||
typename InstructionSet::x86::Accessor<uint16_t, type>::type
|
||||
split_word(const uint32_t low_address, const uint32_t high_address) {
|
||||
if constexpr (is_writeable(type)) {
|
||||
write_back_address_[0] = low_address;
|
||||
write_back_address_[1] = high_address;
|
||||
|
||||
// Prepopulate only if this is a modify.
|
||||
if constexpr (type == AccessType::ReadModifyWrite) {
|
||||
write_back_value_ = uint16_t(memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8));
|
||||
}
|
||||
|
||||
return write_back_value_;
|
||||
} else {
|
||||
return uint16_t(memory[low_address] | (memory[high_address] << 8));
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
//
|
||||
// Direct read and write.
|
||||
//
|
||||
template <typename IntT>
|
||||
void preauthorised_write(
|
||||
const InstructionSet::x86::Source segment,
|
||||
const uint16_t offset,
|
||||
const IntT value
|
||||
) {
|
||||
// Bytes can be written without further ado.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Words that straddle the segment end must be split in two.
|
||||
if(offset == 0xffff) {
|
||||
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
|
||||
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t target = address(segment, offset) & 0xf'ffff;
|
||||
|
||||
// Words that straddle the end of physical RAM must also be split in two.
|
||||
if(target == 0xf'ffff) {
|
||||
memory[0xf'ffff] = value & 0xff;
|
||||
memory[0x0'0000] = value >> 8;
|
||||
return;
|
||||
}
|
||||
|
||||
// It's safe just to write then.
|
||||
*reinterpret_cast<IntT *>(&memory[target]) = value;
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
IntT preauthorised_read(
|
||||
const InstructionSet::x86::Source segment,
|
||||
const uint16_t offset
|
||||
) {
|
||||
// Bytes can be written without further ado.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address(segment, offset) & 0xf'ffff];
|
||||
}
|
||||
|
||||
// Words that straddle the segment end must be split in two.
|
||||
if(offset == 0xffff) {
|
||||
return IntT(
|
||||
memory[address(segment, offset) & 0xf'ffff] |
|
||||
memory[address(segment, 0x0000) & 0xf'ffff] << 8
|
||||
);
|
||||
}
|
||||
|
||||
const uint32_t target = address(segment, offset) & 0xf'ffff;
|
||||
|
||||
// Words that straddle the end of physical RAM must also be split in two.
|
||||
if(target == 0xf'ffff) {
|
||||
return IntT(
|
||||
memory[0xf'ffff] |
|
||||
memory[0x0'0000] << 8
|
||||
);
|
||||
}
|
||||
|
||||
// It's safe just to write then.
|
||||
return *reinterpret_cast<IntT *>(&memory[target]);
|
||||
}
|
||||
|
||||
//
|
||||
// Helper for instruction fetch.
|
||||
//
|
||||
std::pair<const uint8_t *, size_t> next_code() const {
|
||||
const uint32_t start = segments_.cs_base_ + registers_.ip();
|
||||
return std::make_pair(&memory[start], 0x10'000 - start);
|
||||
}
|
||||
|
||||
std::pair<const uint8_t *, size_t> all() const {
|
||||
return std::make_pair(memory.data(), 0x10'000);
|
||||
}
|
||||
|
||||
//
|
||||
// External access.
|
||||
//
|
||||
void install(size_t address, const uint8_t *data, size_t length) {
|
||||
std::copy(data, data + length, memory.begin() + std::vector<uint8_t>::difference_type(address));
|
||||
}
|
||||
|
||||
uint8_t *at(uint32_t address) {
|
||||
return &memory[address];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 1024*1024> memory{0xff};
|
||||
Registers<x86_model> ®isters_;
|
||||
const Segments<x86_model> &segments_;
|
||||
|
||||
uint32_t segment_base(const InstructionSet::x86::Source segment) const {
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: return segments_.ds_base_;
|
||||
case Source::ES: return segments_.es_base_;
|
||||
case Source::CS: return segments_.cs_base_;
|
||||
case Source::SS: return segments_.ss_base_;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t address(const InstructionSet::x86::Source segment, const uint16_t offset) const {
|
||||
return (segment_base(segment) + offset) & 0xf'ffff;
|
||||
}
|
||||
|
||||
template <AccessType type>
|
||||
typename InstructionSet::x86::Accessor<uint16_t, type>::type
|
||||
split_word(const uint32_t low_address, const uint32_t high_address) {
|
||||
if constexpr (is_writeable(type)) {
|
||||
write_back_address_[0] = low_address;
|
||||
write_back_address_[1] = high_address;
|
||||
|
||||
// Prepopulate only if this is a modify.
|
||||
if constexpr (type == AccessType::ReadModifyWrite) {
|
||||
write_back_value_ = uint16_t(memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8));
|
||||
}
|
||||
|
||||
return write_back_value_;
|
||||
} else {
|
||||
return uint16_t(memory[low_address] | (memory[high_address] << 8));
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "PCCompatible.hpp"
|
||||
|
||||
#include "ProcessorByModel.hpp"
|
||||
#include "CGA.hpp"
|
||||
#include "DMA.hpp"
|
||||
#include "KeyboardMapper.hpp"
|
||||
@ -52,16 +53,7 @@
|
||||
|
||||
namespace PCCompatible {
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::PCCompatible> log;
|
||||
|
||||
using PCModelApproximation = Analyser::Static::PCCompatible::Target::ModelApproximation;
|
||||
constexpr InstructionSet::x86::Model processor_model(PCModelApproximation model) {
|
||||
switch(model) {
|
||||
default: return InstructionSet::x86::Model::i8086;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
@ -71,9 +63,14 @@ template <Target::VideoAdaptor adaptor> struct Adaptor;
|
||||
template <> struct Adaptor<Target::VideoAdaptor::MDA> { using type = MDA; };
|
||||
template <> struct Adaptor<Target::VideoAdaptor::CGA> { using type = CGA; };
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class FloppyController {
|
||||
public:
|
||||
FloppyController(PIC &pic, DMA &dma, int drive_count) : pic_(pic), dma_(dma) {
|
||||
FloppyController(
|
||||
PIC<model> &pic,
|
||||
DMA<model> &dma,
|
||||
int drive_count
|
||||
) : pic_(pic), dma_(dma) {
|
||||
// Default: one floppy drive only.
|
||||
for(int c = 0; c < 4; c++) {
|
||||
drives_[c].exists = drive_count > c;
|
||||
@ -106,7 +103,7 @@ class FloppyController {
|
||||
}
|
||||
hold_reset_ = hold_reset;
|
||||
if(hold_reset_) {
|
||||
pic_.apply_edge<6>(false);
|
||||
pic_.template apply_edge<6>(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +118,7 @@ class FloppyController {
|
||||
using Command = Intel::i8272::Command;
|
||||
switch(decoder_.command()) {
|
||||
default:
|
||||
log.error().append("TODO: implement FDC command %d\n", uint8_t(decoder_.command()));
|
||||
log.error().append("TODO: implement FDC command %d", uint8_t(decoder_.command()));
|
||||
break;
|
||||
|
||||
case Command::WriteDeletedData:
|
||||
@ -142,7 +139,7 @@ class FloppyController {
|
||||
// TODO: what if head has changed?
|
||||
drives_[decoder_.target().drive].status = decoder_.drive_head();
|
||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||
pic_.apply_edge<6>(true);
|
||||
pic_.template apply_edge<6>(true);
|
||||
} break;
|
||||
|
||||
case Command::ReadDeletedData:
|
||||
@ -203,7 +200,7 @@ class FloppyController {
|
||||
// TODO: what if head has changed?
|
||||
drives_[decoder_.target().drive].status = decoder_.drive_head();
|
||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||
pic_.apply_edge<6>(true);
|
||||
pic_.template apply_edge<6>(true);
|
||||
} break;
|
||||
|
||||
case Command::Recalibrate:
|
||||
@ -211,14 +208,14 @@ class FloppyController {
|
||||
|
||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||
drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded);
|
||||
pic_.apply_edge<6>(true);
|
||||
pic_.template apply_edge<6>(true);
|
||||
break;
|
||||
case Command::Seek:
|
||||
drives_[decoder_.target().drive].track = decoder_.seek_target();
|
||||
|
||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||
drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded);
|
||||
pic_.apply_edge<6>(true);
|
||||
pic_.template apply_edge<6>(true);
|
||||
break;
|
||||
|
||||
case Command::SenseInterruptStatus: {
|
||||
@ -236,7 +233,7 @@ class FloppyController {
|
||||
any_remaining_interrupts |= drives_[c].raised_interrupt;
|
||||
}
|
||||
if(!any_remaining_interrupts) {
|
||||
pic_.apply_edge<6>(false);
|
||||
pic_.template apply_edge<6>(false);
|
||||
}
|
||||
} break;
|
||||
case Command::Specify:
|
||||
@ -308,15 +305,15 @@ class FloppyController {
|
||||
drives_[c].raised_interrupt = true;
|
||||
drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady);
|
||||
}
|
||||
pic_.apply_edge<6>(true);
|
||||
pic_.template apply_edge<6>(true);
|
||||
|
||||
using MainStatus = Intel::i8272::MainStatus;
|
||||
status_.set(MainStatus::DataReady, true);
|
||||
status_.set(MainStatus::DataIsToProcessor, false);
|
||||
}
|
||||
|
||||
PIC &pic_;
|
||||
DMA &dma_;
|
||||
PIC<model> &pic_;
|
||||
DMA<model> &dma_;
|
||||
|
||||
bool hold_reset_ = false;
|
||||
bool enable_dma_ = false;
|
||||
@ -360,9 +357,10 @@ class FloppyController {
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController {
|
||||
public:
|
||||
KeyboardController(PIC &pic) : pic_(pic) {}
|
||||
KeyboardController(PIC<model> &pic) : pic_(pic) {}
|
||||
|
||||
// KB Status Port 61h high bits:
|
||||
//; 01 - normal operation. wait for keypress, when one comes in,
|
||||
@ -379,13 +377,13 @@ class KeyboardController {
|
||||
switch(mode_) {
|
||||
case Mode::NormalOperation: break;
|
||||
case Mode::NoIRQsIgnoreInput:
|
||||
pic_.apply_edge<1>(false);
|
||||
pic_.template apply_edge<1>(false);
|
||||
break;
|
||||
case Mode::Reset:
|
||||
input_.clear();
|
||||
[[fallthrough]];
|
||||
case Mode::ClearIRQReset:
|
||||
pic_.apply_edge<1>(false);
|
||||
pic_.template apply_edge<1>(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -407,7 +405,7 @@ class KeyboardController {
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
pic_.apply_edge<1>(false);
|
||||
pic_.template apply_edge<1>(false);
|
||||
if(input_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
@ -415,7 +413,7 @@ class KeyboardController {
|
||||
const uint8_t key = input_.front();
|
||||
input_.erase(input_.begin());
|
||||
if(!input_.empty()) {
|
||||
pic_.apply_edge<1>(true);
|
||||
pic_.template apply_edge<1>(true);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
@ -425,7 +423,7 @@ class KeyboardController {
|
||||
return;
|
||||
}
|
||||
input_.push_back(value);
|
||||
pic_.apply_edge<1>(true);
|
||||
pic_.template apply_edge<1>(true);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -437,7 +435,7 @@ class KeyboardController {
|
||||
} mode_;
|
||||
|
||||
std::vector<uint8_t> input_;
|
||||
PIC &pic_;
|
||||
PIC<model> &pic_;
|
||||
|
||||
int reset_delay_ = 0;
|
||||
};
|
||||
@ -485,21 +483,22 @@ struct PCSpeaker {
|
||||
bool output_ = false;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class PITObserver {
|
||||
public:
|
||||
PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {}
|
||||
PITObserver(PIC<model> &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {}
|
||||
|
||||
template <int channel>
|
||||
void update_output(bool new_level) {
|
||||
switch(channel) {
|
||||
default: break;
|
||||
case 0: pic_.apply_edge<0>(new_level); break;
|
||||
case 2: speaker_.set_pit(new_level); break;
|
||||
case 0: pic_.template apply_edge<0>(new_level); break;
|
||||
case 2: speaker_.set_pit(new_level); break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PIC &pic_;
|
||||
PIC<model> &pic_;
|
||||
PCSpeaker &speaker_;
|
||||
|
||||
// TODO:
|
||||
@ -508,11 +507,19 @@ class PITObserver {
|
||||
// channel 1 is used for DRAM refresh (presumably connected to DMA?);
|
||||
// channel 2 is gated by a PPI output and feeds into the speaker.
|
||||
};
|
||||
using PIT = i8253<false, PITObserver>;
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
using PIT = i8253<false, PITObserver<model>>;
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
public:
|
||||
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, Target::VideoAdaptor adaptor, int drive_count) :
|
||||
i8255PortHandler(
|
||||
PCSpeaker &speaker,
|
||||
KeyboardController<model> &keyboard,
|
||||
const Target::VideoAdaptor adaptor,
|
||||
const int drive_count
|
||||
) :
|
||||
speaker_(speaker), keyboard_(keyboard) {
|
||||
// High switches:
|
||||
//
|
||||
@ -599,16 +606,25 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
|
||||
bool use_high_switches_ = false;
|
||||
PCSpeaker &speaker_;
|
||||
KeyboardController &keyboard_;
|
||||
KeyboardController<model> &keyboard_;
|
||||
|
||||
bool enable_keyboard_ = false;
|
||||
};
|
||||
using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
using PPI = Intel::i8255::i8255<i8255PortHandler<model>>;
|
||||
|
||||
template <Target::VideoAdaptor video>
|
||||
template <Analyser::Static::PCCompatible::Model model, Target::VideoAdaptor video>
|
||||
class IO {
|
||||
public:
|
||||
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc, RTC &rtc) :
|
||||
IO(
|
||||
PIT<model> &pit,
|
||||
DMA<model> &dma,
|
||||
PPI<model> &ppi,
|
||||
PIC<model> &pic,
|
||||
typename Adaptor<video>::type &card,
|
||||
FloppyController<model> &fdc,
|
||||
RTC &rtc
|
||||
) :
|
||||
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc), rtc_(rtc) {}
|
||||
|
||||
template <typename IntT> void out(uint16_t port, IntT value) {
|
||||
@ -618,9 +634,9 @@ class IO {
|
||||
switch(port) {
|
||||
default:
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
log.error().append("Unhandled out: %02x to %04x\n", value, port);
|
||||
log.error().append("Unhandled out: %02x to %04x", value, port);
|
||||
} else {
|
||||
log.error().append("Unhandled out: %04x to %04x\n", value, port);
|
||||
log.error().append("Unhandled out: %04x to %04x", value, port);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -629,33 +645,33 @@ class IO {
|
||||
|
||||
// On the XT the NMI can be masked by setting bit 7 on I/O port 0xA0.
|
||||
case 0x00a0:
|
||||
log.error().append("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked");
|
||||
log.error().append("TODO: NMIs %s", (value & 0x80) ? "masked" : "unmasked");
|
||||
break;
|
||||
|
||||
case 0x0000: dma_.controller.write<0x0>(uint8_t(value)); break;
|
||||
case 0x0001: dma_.controller.write<0x1>(uint8_t(value)); break;
|
||||
case 0x0002: dma_.controller.write<0x2>(uint8_t(value)); break;
|
||||
case 0x0003: dma_.controller.write<0x3>(uint8_t(value)); break;
|
||||
case 0x0004: dma_.controller.write<0x4>(uint8_t(value)); break;
|
||||
case 0x0005: dma_.controller.write<0x5>(uint8_t(value)); break;
|
||||
case 0x0006: dma_.controller.write<0x6>(uint8_t(value)); break;
|
||||
case 0x0007: dma_.controller.write<0x7>(uint8_t(value)); break;
|
||||
case 0x0008: dma_.controller.write<0x8>(uint8_t(value)); break;
|
||||
case 0x0009: dma_.controller.write<0x9>(uint8_t(value)); break;
|
||||
case 0x000a: dma_.controller.write<0xa>(uint8_t(value)); break;
|
||||
case 0x000b: dma_.controller.write<0xb>(uint8_t(value)); break;
|
||||
case 0x000c: dma_.controller.write<0xc>(uint8_t(value)); break;
|
||||
case 0x000d: dma_.controller.write<0xd>(uint8_t(value)); break;
|
||||
case 0x000e: dma_.controller.write<0xe>(uint8_t(value)); break;
|
||||
case 0x000f: dma_.controller.write<0xf>(uint8_t(value)); break;
|
||||
case 0x0000: dma_.controller.template write<0x0>(uint8_t(value)); break;
|
||||
case 0x0001: dma_.controller.template write<0x1>(uint8_t(value)); break;
|
||||
case 0x0002: dma_.controller.template write<0x2>(uint8_t(value)); break;
|
||||
case 0x0003: dma_.controller.template write<0x3>(uint8_t(value)); break;
|
||||
case 0x0004: dma_.controller.template write<0x4>(uint8_t(value)); break;
|
||||
case 0x0005: dma_.controller.template write<0x5>(uint8_t(value)); break;
|
||||
case 0x0006: dma_.controller.template write<0x6>(uint8_t(value)); break;
|
||||
case 0x0007: dma_.controller.template write<0x7>(uint8_t(value)); break;
|
||||
case 0x0008: dma_.controller.template write<0x8>(uint8_t(value)); break;
|
||||
case 0x0009: dma_.controller.template write<0x9>(uint8_t(value)); break;
|
||||
case 0x000a: dma_.controller.template write<0xa>(uint8_t(value)); break;
|
||||
case 0x000b: dma_.controller.template write<0xb>(uint8_t(value)); break;
|
||||
case 0x000c: dma_.controller.template write<0xc>(uint8_t(value)); break;
|
||||
case 0x000d: dma_.controller.template write<0xd>(uint8_t(value)); break;
|
||||
case 0x000e: dma_.controller.template write<0xe>(uint8_t(value)); break;
|
||||
case 0x000f: dma_.controller.template write<0xf>(uint8_t(value)); break;
|
||||
|
||||
case 0x0020: pic_.write<0>(uint8_t(value)); break;
|
||||
case 0x0021: pic_.write<1>(uint8_t(value)); break;
|
||||
case 0x0020: pic_.template write<0>(uint8_t(value)); break;
|
||||
case 0x0021: pic_.template write<1>(uint8_t(value)); break;
|
||||
|
||||
case 0x0040: pit_.write<0>(uint8_t(value)); break;
|
||||
case 0x0041: pit_.write<1>(uint8_t(value)); break;
|
||||
case 0x0042: pit_.write<2>(uint8_t(value)); break;
|
||||
case 0x0043: pit_.set_mode(uint8_t(value)); break;
|
||||
case 0x0040: pit_.template write<0>(uint8_t(value)); break;
|
||||
case 0x0041: pit_.template write<1>(uint8_t(value)); break;
|
||||
case 0x0042: pit_.template write<2>(uint8_t(value)); break;
|
||||
case 0x0043: pit_.set_mode(uint8_t(value)); break;
|
||||
|
||||
case 0x0060: case 0x0061: case 0x0062: case 0x0063:
|
||||
case 0x0064: case 0x0065: case 0x0066: case 0x0067:
|
||||
@ -664,14 +680,14 @@ class IO {
|
||||
ppi_.write(port, uint8_t(value));
|
||||
break;
|
||||
|
||||
case 0x0080: dma_.pages.set_page<0>(uint8_t(value)); break;
|
||||
case 0x0081: dma_.pages.set_page<1>(uint8_t(value)); break;
|
||||
case 0x0082: dma_.pages.set_page<2>(uint8_t(value)); break;
|
||||
case 0x0083: dma_.pages.set_page<3>(uint8_t(value)); break;
|
||||
case 0x0084: dma_.pages.set_page<4>(uint8_t(value)); break;
|
||||
case 0x0085: dma_.pages.set_page<5>(uint8_t(value)); break;
|
||||
case 0x0086: dma_.pages.set_page<6>(uint8_t(value)); break;
|
||||
case 0x0087: dma_.pages.set_page<7>(uint8_t(value)); break;
|
||||
case 0x0080: dma_.pages.template set_page<0>(uint8_t(value)); break;
|
||||
case 0x0081: dma_.pages.template set_page<1>(uint8_t(value)); break;
|
||||
case 0x0082: dma_.pages.template set_page<2>(uint8_t(value)); break;
|
||||
case 0x0083: dma_.pages.template set_page<3>(uint8_t(value)); break;
|
||||
case 0x0084: dma_.pages.template set_page<4>(uint8_t(value)); break;
|
||||
case 0x0085: dma_.pages.template set_page<5>(uint8_t(value)); break;
|
||||
case 0x0086: dma_.pages.template set_page<6>(uint8_t(value)); break;
|
||||
case 0x0087: dma_.pages.template set_page<7>(uint8_t(value)); break;
|
||||
|
||||
//
|
||||
// CRTC access block, with slightly laboured 16-bit to 8-bit mapping.
|
||||
@ -702,7 +718,7 @@ class IO {
|
||||
fdc_.set_digital_output(uint8_t(value));
|
||||
break;
|
||||
case 0x03f4:
|
||||
log.error().append("TODO: FDC write of %02x at %04x\n", value, port);
|
||||
log.error().append("TODO: FDC write of %02x at %04x", value, port);
|
||||
break;
|
||||
case 0x03f5:
|
||||
fdc_.write(uint8_t(value));
|
||||
@ -729,31 +745,31 @@ class IO {
|
||||
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) {
|
||||
switch(port) {
|
||||
default:
|
||||
log.error().append("Unhandled in: %04x\n", port);
|
||||
log.error().append("Unhandled in: %04x", port);
|
||||
break;
|
||||
|
||||
case 0x0000: return dma_.controller.read<0x0>();
|
||||
case 0x0001: return dma_.controller.read<0x1>();
|
||||
case 0x0002: return dma_.controller.read<0x2>();
|
||||
case 0x0003: return dma_.controller.read<0x3>();
|
||||
case 0x0004: return dma_.controller.read<0x4>();
|
||||
case 0x0005: return dma_.controller.read<0x5>();
|
||||
case 0x0006: return dma_.controller.read<0x6>();
|
||||
case 0x0007: return dma_.controller.read<0x7>();
|
||||
case 0x0008: return dma_.controller.read<0x8>();
|
||||
case 0x000d: return dma_.controller.read<0xd>();
|
||||
case 0x0000: return dma_.controller.template read<0x0>();
|
||||
case 0x0001: return dma_.controller.template read<0x1>();
|
||||
case 0x0002: return dma_.controller.template read<0x2>();
|
||||
case 0x0003: return dma_.controller.template read<0x3>();
|
||||
case 0x0004: return dma_.controller.template read<0x4>();
|
||||
case 0x0005: return dma_.controller.template read<0x5>();
|
||||
case 0x0006: return dma_.controller.template read<0x6>();
|
||||
case 0x0007: return dma_.controller.template read<0x7>();
|
||||
case 0x0008: return dma_.controller.template read<0x8>();
|
||||
case 0x000d: return dma_.controller.template read<0xd>();
|
||||
|
||||
case 0x0009: case 0x000b:
|
||||
case 0x000c: case 0x000f:
|
||||
// DMA area, but it doesn't respond.
|
||||
break;
|
||||
|
||||
case 0x0020: return pic_.read<0>();
|
||||
case 0x0021: return pic_.read<1>();
|
||||
case 0x0020: return pic_.template read<0>();
|
||||
case 0x0021: return pic_.template read<1>();
|
||||
|
||||
case 0x0040: return pit_.read<0>();
|
||||
case 0x0041: return pit_.read<1>();
|
||||
case 0x0042: return pit_.read<2>();
|
||||
case 0x0040: return pit_.template read<0>();
|
||||
case 0x0041: return pit_.template read<1>();
|
||||
case 0x0042: return pit_.template read<2>();
|
||||
|
||||
case 0x0060: case 0x0061: case 0x0062: case 0x0063:
|
||||
case 0x0064: case 0x0065: case 0x0066: case 0x0067:
|
||||
@ -763,14 +779,14 @@ class IO {
|
||||
|
||||
case 0x0071: return rtc_.read();
|
||||
|
||||
case 0x0080: return dma_.pages.page<0>();
|
||||
case 0x0081: return dma_.pages.page<1>();
|
||||
case 0x0082: return dma_.pages.page<2>();
|
||||
case 0x0083: return dma_.pages.page<3>();
|
||||
case 0x0084: return dma_.pages.page<4>();
|
||||
case 0x0085: return dma_.pages.page<5>();
|
||||
case 0x0086: return dma_.pages.page<6>();
|
||||
case 0x0087: return dma_.pages.page<7>();
|
||||
case 0x0080: return dma_.pages.template page<0>();
|
||||
case 0x0081: return dma_.pages.template page<1>();
|
||||
case 0x0082: return dma_.pages.template page<2>();
|
||||
case 0x0083: return dma_.pages.template page<3>();
|
||||
case 0x0084: return dma_.pages.template page<4>();
|
||||
case 0x0085: return dma_.pages.template page<5>();
|
||||
case 0x0086: return dma_.pages.template page<6>();
|
||||
case 0x0087: return dma_.pages.template page<7>();
|
||||
|
||||
case 0x0201: break; // Ignore game port.
|
||||
|
||||
@ -810,69 +826,72 @@ class IO {
|
||||
}
|
||||
|
||||
private:
|
||||
PIT &pit_;
|
||||
DMA &dma_;
|
||||
PPI &ppi_;
|
||||
PIC &pic_;
|
||||
PIT<model> &pit_;
|
||||
DMA<model> &dma_;
|
||||
PPI<model> &ppi_;
|
||||
PIC<model> &pic_;
|
||||
typename Adaptor<video>::type &video_;
|
||||
FloppyController &fdc_;
|
||||
FloppyController<model> &fdc_;
|
||||
RTC &rtc_;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class FlowController {
|
||||
public:
|
||||
FlowController(Registers ®isters, Segments &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
static constexpr auto x86_model = processor_model(model);
|
||||
|
||||
// Requirements for perform.
|
||||
template <typename AddressT>
|
||||
void jump(AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
public:
|
||||
FlowController(Registers<x86_model> ®isters, Segments<x86_model> &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
|
||||
template <typename AddressT>
|
||||
void jump(uint16_t segment, AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.cs() = segment;
|
||||
segments_.did_update(Segments::Source::CS);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
// Requirements for perform.
|
||||
template <typename AddressT>
|
||||
void jump(AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
|
||||
void halt() {
|
||||
halted_ = true;
|
||||
}
|
||||
void wait() {
|
||||
log.error().append("WAIT ????\n");
|
||||
}
|
||||
template <typename AddressT>
|
||||
void jump(const uint16_t segment, const AddressT address) {
|
||||
static_assert(std::is_same_v<AddressT, uint16_t>);
|
||||
registers_.cs() = segment;
|
||||
segments_.did_update(InstructionSet::x86::Source::CS);
|
||||
registers_.ip() = address;
|
||||
}
|
||||
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
void halt() {
|
||||
halted_ = true;
|
||||
}
|
||||
void wait() {
|
||||
log.error().append("WAIT ????");
|
||||
}
|
||||
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
|
||||
void unhalt() {
|
||||
halted_ = false;
|
||||
}
|
||||
bool halted() const {
|
||||
return halted_;
|
||||
}
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
|
||||
private:
|
||||
Registers ®isters_;
|
||||
Segments &segments_;
|
||||
bool should_repeat_ = false;
|
||||
bool halted_ = false;
|
||||
void unhalt() {
|
||||
halted_ = false;
|
||||
}
|
||||
bool halted() const {
|
||||
return halted_;
|
||||
}
|
||||
|
||||
private:
|
||||
Registers<x86_model> ®isters_;
|
||||
Segments<x86_model> &segments_;
|
||||
bool should_repeat_ = false;
|
||||
bool halted_ = false;
|
||||
};
|
||||
|
||||
template <Target::VideoAdaptor video, Target::ModelApproximation pc_model>
|
||||
template <Analyser::Static::PCCompatible::Model pc_model, Target::VideoAdaptor video>
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public MachineTypes::TimedMachine,
|
||||
@ -907,25 +926,45 @@ class ConcreteMachine:
|
||||
set_clock_rate(double(pit_frequency));
|
||||
speaker_.speaker.set_input_rate(double(pit_frequency));
|
||||
|
||||
// Fetch the BIOS. [8088 only, for now]
|
||||
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
|
||||
const auto tick = ROM::Name::PCCompatibleGLaTICK;
|
||||
// Fetch the BIOS.
|
||||
const auto font = Video::FontROM;
|
||||
|
||||
ROM::Request request = ROM::Request(bios) && ROM::Request(tick, true) && ROM::Request(font);
|
||||
constexpr auto biosXT = ROM::Name::PCCompatibleGLaBIOS;
|
||||
constexpr auto tickXT = ROM::Name::PCCompatibleGLaTICK;
|
||||
|
||||
constexpr auto biosAT = ROM::Name::PCCompatiblePhoenix80286BIOS;
|
||||
|
||||
ROM::Request request = ROM::Request(font);
|
||||
switch(pc_model) {
|
||||
default:
|
||||
request = request && ROM::Request(biosXT) && ROM::Request(tickXT);
|
||||
break;
|
||||
case Analyser::Static::PCCompatible::Model::AT:
|
||||
request = request && ROM::Request(biosAT);
|
||||
break;
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
// A BIOS is mandatory.
|
||||
const auto &bios_contents = roms.find(bios)->second;
|
||||
context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
||||
switch(pc_model) {
|
||||
default: {
|
||||
const auto &bios_contents = roms.find(biosXT)->second;
|
||||
context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
||||
|
||||
// If found, install GlaTICK at 0xd'0000.
|
||||
auto tick_contents = roms.find(tick);
|
||||
if(tick_contents != roms.end()) {
|
||||
context_.memory.install(0xd'0000, tick_contents->second.data(), tick_contents->second.size());
|
||||
// If found, install GlaTICK at 0xd'0000.
|
||||
auto tick_contents = roms.find(tickXT);
|
||||
if(tick_contents != roms.end()) {
|
||||
context_.memory.install(0xd'0000, tick_contents->second.data(), tick_contents->second.size());
|
||||
}
|
||||
} break;
|
||||
|
||||
case Analyser::Static::PCCompatible::Model::AT:
|
||||
const auto &bios_contents = roms.find(biosAT)->second;
|
||||
context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
||||
break;
|
||||
}
|
||||
|
||||
// Give the video card something to read from.
|
||||
@ -941,18 +980,9 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - TimedMachine.
|
||||
void run_for(const Cycles duration) override {
|
||||
using Model = Target::ModelApproximation;
|
||||
switch(pc_model) {
|
||||
case Model::XT: run_for<Model::XT>(duration); break;
|
||||
case Model::TurboXT: run_for<Model::TurboXT>(duration); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Target::ModelApproximation model>
|
||||
void run_for(const Cycles duration) {
|
||||
void run_for(const Cycles duration) final {
|
||||
const auto pit_ticks = duration.as<int>();
|
||||
constexpr bool is_fast = model == Target::ModelApproximation::TurboXT;
|
||||
constexpr bool is_fast = pc_model >= Analyser::Static::PCCompatible::Model::TurboXT;
|
||||
|
||||
int ticks;
|
||||
if constexpr (is_fast) {
|
||||
@ -1079,6 +1109,10 @@ class ConcreteMachine:
|
||||
// }
|
||||
}*/
|
||||
|
||||
if(decoded_.second.operation() == InstructionSet::x86::Operation::Invalid) {
|
||||
log.error().append("Invalid operation");
|
||||
}
|
||||
|
||||
// Execute it.
|
||||
InstructionSet::x86::perform(
|
||||
decoded_.second,
|
||||
@ -1087,15 +1121,15 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - ScanProducer.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
// MARK: - AudioProducer.
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_.speaker;
|
||||
}
|
||||
|
||||
@ -1107,7 +1141,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - MediaTarget
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
int c = 0;
|
||||
for(auto &disk : media.disks) {
|
||||
fdc_.set_disk(disk, c);
|
||||
@ -1118,11 +1152,11 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - MappedKeyboardMachine.
|
||||
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
|
||||
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
keyboard_.post(uint8_t(key | (is_pressed ? 0x00 : 0x80)));
|
||||
}
|
||||
|
||||
@ -1132,53 +1166,56 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::unique_ptr<Reflection::Struct> get_options() const override {
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
return options;
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) override {
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_.set_display_type(display_type);
|
||||
|
||||
// Give the PPI a shout-out in case it isn't too late to switch to CGA40.
|
||||
ppi_handler_.hint_is_composite(Outputs::Display::is_composite(display_type));
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const override {
|
||||
Outputs::Display::DisplayType get_display_type() const final {
|
||||
return video_.get_display_type();
|
||||
}
|
||||
|
||||
private:
|
||||
PIC pic_;
|
||||
DMA dma_;
|
||||
static constexpr auto x86_model = processor_model(pc_model);
|
||||
|
||||
PIC<pc_model> pic_;
|
||||
DMA<pc_model> dma_;
|
||||
PCSpeaker speaker_;
|
||||
Video video_;
|
||||
|
||||
KeyboardController keyboard_;
|
||||
FloppyController fdc_;
|
||||
PITObserver pit_observer_;
|
||||
i8255PortHandler ppi_handler_;
|
||||
KeyboardController<pc_model> keyboard_;
|
||||
FloppyController<pc_model> fdc_;
|
||||
PITObserver<pc_model> pit_observer_;
|
||||
i8255PortHandler<pc_model> ppi_handler_;
|
||||
|
||||
PIT pit_;
|
||||
PPI ppi_;
|
||||
PIT<pc_model> pit_;
|
||||
PPI<pc_model> ppi_;
|
||||
RTC rtc_;
|
||||
|
||||
PCCompatible::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
struct Context {
|
||||
Context(
|
||||
PIT &pit,
|
||||
DMA &dma,
|
||||
PPI &ppi,
|
||||
PIC &pic,
|
||||
PIT<pc_model> &pit,
|
||||
DMA<pc_model> &dma,
|
||||
PPI<pc_model> &ppi,
|
||||
PIC<pc_model> &pic,
|
||||
typename Adaptor<video>::type &card,
|
||||
FloppyController &fdc, RTC &rtc
|
||||
FloppyController<pc_model> &fdc,
|
||||
RTC &rtc
|
||||
) :
|
||||
segments(registers),
|
||||
memory(registers, segments),
|
||||
@ -1194,11 +1231,11 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
InstructionSet::x86::Flags flags;
|
||||
Registers registers;
|
||||
Segments segments;
|
||||
Memory memory;
|
||||
FlowController flow_controller;
|
||||
IO<video> io;
|
||||
Registers<x86_model> registers;
|
||||
Segments<x86_model> segments;
|
||||
Memory<pc_model> memory;
|
||||
FlowController<pc_model> flow_controller;
|
||||
IO<pc_model, video> io;
|
||||
static constexpr auto model = processor_model(pc_model);
|
||||
} context_;
|
||||
|
||||
@ -1217,17 +1254,25 @@ class ConcreteMachine:
|
||||
using namespace PCCompatible;
|
||||
|
||||
namespace {
|
||||
static constexpr bool ForceAT = false;
|
||||
|
||||
template <Target::VideoAdaptor video>
|
||||
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
switch(target.model) {
|
||||
case PCModelApproximation::XT:
|
||||
return std::make_unique<PCCompatible::ConcreteMachine<video, PCModelApproximation::XT>>
|
||||
switch(ForceAT ? Analyser::Static::PCCompatible::Model::AT : target.model) {
|
||||
case Analyser::Static::PCCompatible::Model::XT:
|
||||
return std::make_unique<PCCompatible::ConcreteMachine<Analyser::Static::PCCompatible::Model::XT, video>>
|
||||
(target, rom_fetcher);
|
||||
|
||||
case PCModelApproximation::TurboXT:
|
||||
return std::make_unique<PCCompatible::ConcreteMachine<video, PCModelApproximation::TurboXT>>
|
||||
case Analyser::Static::PCCompatible::Model::TurboXT:
|
||||
return std::make_unique<PCCompatible::ConcreteMachine<Analyser::Static::PCCompatible::Model::TurboXT, video>>
|
||||
(target, rom_fetcher);
|
||||
|
||||
case Analyser::Static::PCCompatible::Model::AT:
|
||||
return std::make_unique<PCCompatible::ConcreteMachine<Analyser::Static::PCCompatible::Model::AT, video>>
|
||||
(target, rom_fetcher);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
// Cf. https://helppc.netcore2k.net/hardware/pic
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class PIC {
|
||||
public:
|
||||
template <int address>
|
||||
|
23
Machines/PCCompatible/ProcessorByModel.hpp
Normal file
23
Machines/PCCompatible/ProcessorByModel.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// ProcessorByModel.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/03/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "InstructionSets/x86/Model.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
constexpr InstructionSet::x86::Model processor_model(Analyser::Static::PCCompatible::Model model) {
|
||||
switch(model) {
|
||||
default: return InstructionSet::x86::Model::i8086;
|
||||
case Analyser::Static::PCCompatible::Model::AT: return InstructionSet::x86::Model::i80286;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,9 +8,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "InstructionSets/x86/Model.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
struct Registers {
|
||||
template <InstructionSet::x86::Model>
|
||||
struct Registers;
|
||||
|
||||
template <>
|
||||
struct Registers<InstructionSet::x86::Model::i8086> {
|
||||
public:
|
||||
static constexpr bool is_32bit = false;
|
||||
|
||||
@ -67,4 +73,12 @@ struct Registers {
|
||||
uint16_t ip_;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Registers<InstructionSet::x86::Model::i80186>: public Registers<InstructionSet::x86::Model::i8086> {
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Registers<InstructionSet::x86::Model::i80286>: public Registers<InstructionSet::x86::Model::i80186> {
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,12 +11,14 @@
|
||||
#include "Registers.hpp"
|
||||
|
||||
#include "InstructionSets/x86/Instruction.hpp"
|
||||
#include "InstructionSets/x86/Model.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
template <InstructionSet::x86::Model model>
|
||||
class Segments {
|
||||
public:
|
||||
Segments(const Registers ®isters) : registers_(registers) {}
|
||||
Segments(const Registers<model> ®isters) : registers_(registers) {}
|
||||
|
||||
using Source = InstructionSet::x86::Source;
|
||||
|
||||
@ -49,7 +51,7 @@ class Segments {
|
||||
}
|
||||
|
||||
private:
|
||||
const Registers ®isters_;
|
||||
const Registers<model> ®isters_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -290,6 +290,8 @@
|
||||
4B1EC716255398B000A1F44B /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1EC714255398B000A1F44B /* Sound.cpp */; };
|
||||
4B1EC717255398B000A1F44B /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1EC714255398B000A1F44B /* Sound.cpp */; };
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
|
||||
4B1FBE202D77AAC500BAC888 /* ProcessorByModel.hpp in Resources */ = {isa = PBXBuildFile; fileRef = 4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */; };
|
||||
4B1FBE212D77AAC500BAC888 /* ProcessorByModel.hpp in Resources */ = {isa = PBXBuildFile; fileRef = 4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */; };
|
||||
4B2005432B804D6400420C5C /* ARMDecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2005422B804D6400420C5C /* ARMDecoderTests.mm */; };
|
||||
4B2130E2273A7A0A008A77B4 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2130E0273A7A0A008A77B4 /* Audio.cpp */; };
|
||||
4B2130E3273A7A0A008A77B4 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2130E0273A7A0A008A77B4 /* Audio.cpp */; };
|
||||
@ -1451,6 +1453,7 @@
|
||||
4B1EC714255398B000A1F44B /* Sound.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = "<group>"; };
|
||||
4B1EC715255398B000A1F44B /* Sound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sound.hpp; sourceTree = "<group>"; };
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||
4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ProcessorByModel.hpp; sourceTree = "<group>"; };
|
||||
4B2005402B804AA300420C5C /* OperationMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OperationMapper.hpp; sourceTree = "<group>"; };
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ARMDecoderTests.mm; sourceTree = "<group>"; };
|
||||
4B2005462B8BD7A500420C5C /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
@ -2561,6 +2564,7 @@
|
||||
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
||||
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
||||
4267A9C72B0C26FA008A59BB /* PIT.hpp */,
|
||||
4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */,
|
||||
423820142B1A23C200964EFE /* Registers.hpp */,
|
||||
42EB81252B21788200429AF4 /* RTC.hpp */,
|
||||
423820152B1A23E100964EFE /* Segments.hpp */,
|
||||
@ -5585,6 +5589,7 @@
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
|
||||
4B1FBE202D77AAC500BAC888 /* ProcessorByModel.hpp in Resources */,
|
||||
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */,
|
||||
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
@ -5809,6 +5814,7 @@
|
||||
4BB299F01B587D8400A49093 /* trap4 in Resources */,
|
||||
4B8DF6722550D91600F3433C /* CPUBIT.sfc in Resources */,
|
||||
4BB299451B587D8400A49093 /* dcmay in Resources */,
|
||||
4B1FBE212D77AAC500BAC888 /* ProcessorByModel.hpp in Resources */,
|
||||
4BB299081B587D8400A49093 /* asln in Resources */,
|
||||
4BB2996E1B587D8400A49093 /* laxa in Resources */,
|
||||
4BB2990A1B587D8400A49093 /* aslzx in Resources */,
|
||||
|
@ -302,8 +302,8 @@
|
||||
case CSPCCompatibleVideoAdaptorCGA: target->adaptor = Target::VideoAdaptor::CGA; break;
|
||||
}
|
||||
switch(speed) {
|
||||
case CSPCCompatibleSpeedOriginal: target->model = Target::ModelApproximation::XT; break;
|
||||
case CSPCCompatibleSpeedTurbo: target->model = Target::ModelApproximation::TurboXT; break;
|
||||
case CSPCCompatibleSpeedOriginal: target->model = Analyser::Static::PCCompatible::Model::XT; break;
|
||||
case CSPCCompatibleSpeedTurbo: target->model = Analyser::Static::PCCompatible::Model::TurboXT; break;
|
||||
}
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
|
@ -1232,8 +1232,8 @@ void MainWindow::start_pc() {
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
switch(ui->pcSpeedComboBox->currentIndex()) {
|
||||
default: target->model = Target::ModelApproximation::XT; break;
|
||||
case 1: target->model = Target::ModelApproximation::TurboXT; break;
|
||||
default: target->model = Analyser::Static::PCCompatible::Model::XT; break;
|
||||
case 1: target->model = Analyser::Static::PCCompatible::Model::TurboXT; break;
|
||||
}
|
||||
|
||||
switch(ui->pcVideoAdaptorComboBox->currentIndex()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user