diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp index d873dffaf..d31942752 100644 --- a/Machines/PCCompatible/DMA.hpp +++ b/Machines/PCCompatible/DMA.hpp @@ -127,6 +127,37 @@ class i8237 { return result; } + // + // Interface for reading/writing via DMA. + // + static constexpr auto NotAvailable = uint32_t(~0); + + /// Provides the next target address for @c channel if performing either a write (if @c is_write is @c true) or read (otherwise). + /// + /// @returns Either a 16-bit address or @c NotAvailable if the requested channel isn't set up to perform a read or write at present. + uint32_t access(size_t channel, bool is_write) { + if(channels_[channel].transfer_complete) { + return NotAvailable; + } + if(is_write && channels_[channel].transfer != Channel::Transfer::Write) { + return NotAvailable; + } + if(!is_write && channels_[channel].transfer != Channel::Transfer::Read) { + return NotAvailable; + } + + const auto address = channels_[channel].address.full; + channels_[channel].address.full += channels_[channel].address_decrement ? -1 : 1; + + --channels_[channel].count.full; + channels_[channel].transfer_complete = (channels_[channel].count.full == 0xffff); + if(channels_[channel].transfer_complete) { + // TODO: _something_ with mode. + } + + return address; + } + private: // Low/high byte latch. bool next_access_low_ = true; @@ -173,7 +204,7 @@ class DMAPages { return pages_[page_for_index(index)]; } - uint8_t channel_page(int channel) { + uint8_t channel_page(size_t channel) { return pages_[channel]; } diff --git a/Machines/PCCompatible/Memory.hpp b/Machines/PCCompatible/Memory.hpp new file mode 100644 index 000000000..263617b9e --- /dev/null +++ b/Machines/PCCompatible/Memory.hpp @@ -0,0 +1,177 @@ +// +// Memory.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/12/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Memory_hpp +#define Memory_hpp + +#include "Registers.hpp" +#include "Segments.hpp" + +#include "../../InstructionSets/x86/AccessType.hpp" + +namespace PCCompatible { + +// TODO: send writes to the ROM area off to nowhere. +struct Memory { + public: + using AccessType = InstructionSet::x86::AccessType; + + // Constructor. + Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {} + + // + // 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) {} + + // + // Access call-ins. + // + + // Accesses an address based on segment:offset. + template + typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { + const uint32_t physical_address = address(segment, offset); + + if constexpr (std::is_same_v) { + // 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(physical_address, address(segment, 0)); + } + } + + return access(physical_address); + } + + // Accesses an address based on physical location. + template + typename InstructionSet::x86::Accessor::type access(uint32_t address) { + // Dispense with the single-byte case trivially. + if constexpr (std::is_same_v) { + return memory[address]; + } else if(address != 0xf'ffff) { + return *reinterpret_cast(&memory[address]); + } else { + return split_word(address, 0); + } + } + + template + void write_back() { + if constexpr (std::is_same_v) { + 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 + 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) { + 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(&memory[target]) = value; + } + + // + // Helper for instruction fetch. + // + std::pair next_code() { + const uint32_t start = segments_.cs_base_ + registers_.ip(); + return std::make_pair(&memory[start], 0x10'000 - start); + } + + std::pair all() { + 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::difference_type(address)); + } + + uint8_t *at(uint32_t address) { + return &memory[address]; + } + + private: + std::array memory{0xff}; + Registers ®isters_; + const Segments &segments_; + + uint32_t segment_base(InstructionSet::x86::Source segment) { + 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(InstructionSet::x86::Source segment, uint16_t offset) { + return (segment_base(segment) + offset) & 0xf'ffff; + } + + template + typename InstructionSet::x86::Accessor::type + split_word(uint32_t low_address, 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_; +}; + +} + +#endif /* Memory_hpp */ diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 74e9483d7..85e688928 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -12,6 +12,7 @@ #include "PIC.hpp" #include "PIT.hpp" #include "DMA.hpp" +#include "Memory.hpp" #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" @@ -45,11 +46,6 @@ namespace PCCompatible { -//bool log = false; -//std::string previous; - -struct Memory; - class DMA { public: i8237 controller; @@ -60,6 +56,19 @@ class DMA { memory_ = memory; } + // TODO: this permits only 8-bit DMA. Fix that. + bool write(size_t channel, uint8_t value) { + auto address = controller.access(channel, true); + if(address == i8237::NotAvailable) { + return false; + } + + address |= uint32_t(pages.channel_page(channel) << 16); + *memory_->at(address) = value; + + return true; + } + private: Memory *memory_; }; @@ -127,6 +136,7 @@ class FloppyController { // Search for a matching sector. const auto target = decoder_.geometry(); + bool found_sector = false; for(auto &pair: drives_[decoder_.target().drive].cached_track) { if( (pair.second.address.track == target.cylinder) && @@ -134,9 +144,27 @@ class FloppyController { (pair.second.address.side == target.head) && (pair.second.size == target.size) ) { -// `printf(""); + found_sector = true; + bool wrote_in_full = true; + + for(int c = 0; c < 128 << target.size; c++) { + if(!dma_.write(2, pair.second.samples[0].data()[c])) { + wrote_in_full = false; + break; + } + } + + if(wrote_in_full) { + } else { + } + + break; } } + + if(!found_sector) { + } + } break; case Command::Seek: @@ -682,257 +710,6 @@ class i8255PortHandler : public Intel::i8255::PortHandler { }; using PPI = Intel::i8255::i8255; -struct Registers { - public: - static constexpr bool is_32bit = false; - - uint8_t &al() { return ax_.halves.low; } - uint8_t &ah() { return ax_.halves.high; } - uint16_t &ax() { return ax_.full; } - - CPU::RegisterPair16 &axp() { return ax_; } - - uint8_t &cl() { return cx_.halves.low; } - uint8_t &ch() { return cx_.halves.high; } - uint16_t &cx() { return cx_.full; } - - uint8_t &dl() { return dx_.halves.low; } - uint8_t &dh() { return dx_.halves.high; } - uint16_t &dx() { return dx_.full; } - - uint8_t &bl() { return bx_.halves.low; } - uint8_t &bh() { return bx_.halves.high; } - uint16_t &bx() { return bx_.full; } - - uint16_t &sp() { return sp_; } - uint16_t &bp() { return bp_; } - uint16_t &si() { return si_; } - uint16_t &di() { return di_; } - - uint16_t &ip() { return ip_; } - - uint16_t &es() { return es_; } - uint16_t &cs() { return cs_; } - uint16_t &ds() { return ds_; } - uint16_t &ss() { return ss_; } - uint16_t es() const { return es_; } - uint16_t cs() const { return cs_; } - uint16_t ds() const { return ds_; } - uint16_t ss() const { return ss_; } - - void reset() { - cs_ = 0xffff; - ip_ = 0; - } - - private: - CPU::RegisterPair16 ax_; - CPU::RegisterPair16 cx_; - CPU::RegisterPair16 dx_; - CPU::RegisterPair16 bx_; - - uint16_t sp_; - uint16_t bp_; - uint16_t si_; - uint16_t di_; - uint16_t es_, cs_, ds_, ss_; - uint16_t ip_; -}; - -class Segments { - public: - Segments(const Registers ®isters) : registers_(registers) {} - - using Source = InstructionSet::x86::Source; - - /// Posted by @c perform after any operation which *might* have affected a segment register. - void did_update(Source segment) { - switch(segment) { - default: break; - case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break; - case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break; - case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break; - case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break; - } - } - - void reset() { - did_update(Source::ES); - did_update(Source::CS); - did_update(Source::DS); - did_update(Source::SS); - } - - uint32_t es_base_, cs_base_, ds_base_, ss_base_; - - bool operator ==(const Segments &rhs) const { - return - es_base_ == rhs.es_base_ && - cs_base_ == rhs.cs_base_ && - ds_base_ == rhs.ds_base_ && - ss_base_ == rhs.ss_base_; - } - - private: - const Registers ®isters_; -}; - -// TODO: send writes to the ROM area off to nowhere. -struct Memory { - public: - using AccessType = InstructionSet::x86::AccessType; - - // Constructor. - Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {} - - // - // 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) {} - - // - // Access call-ins. - // - - // Accesses an address based on segment:offset. - template - typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { - const uint32_t physical_address = address(segment, offset); - - if constexpr (std::is_same_v) { - // 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(physical_address, address(segment, 0)); - } - } - - return access(physical_address); - } - - // Accesses an address based on physical location. - template - typename InstructionSet::x86::Accessor::type access(uint32_t address) { - // Dispense with the single-byte case trivially. - if constexpr (std::is_same_v) { - return memory[address]; - } else if(address != 0xf'ffff) { - return *reinterpret_cast(&memory[address]); - } else { - return split_word(address, 0); - } - } - - template - void write_back() { - if constexpr (std::is_same_v) { - 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 - 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) { - 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(&memory[target]) = value; - } - - // - // Helper for instruction fetch. - // - std::pair next_code() { - const uint32_t start = segments_.cs_base_ + registers_.ip(); - return std::make_pair(&memory[start], 0x10'000 - start); - } - - std::pair all() { - 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::difference_type(address)); - } - - const uint8_t *at(uint32_t address) { - return &memory[address]; - } - - private: - std::array memory{0xff}; - Registers ®isters_; - const Segments &segments_; - - uint32_t segment_base(InstructionSet::x86::Source segment) { - 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(InstructionSet::x86::Source segment, uint16_t offset) { - return (segment_base(segment) + offset) & 0xf'ffff; - } - - template - typename InstructionSet::x86::Accessor::type - split_word(uint32_t low_address, 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_; -}; - class IO { public: IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) : diff --git a/Machines/PCCompatible/Registers.hpp b/Machines/PCCompatible/Registers.hpp new file mode 100644 index 000000000..61198fc89 --- /dev/null +++ b/Machines/PCCompatible/Registers.hpp @@ -0,0 +1,73 @@ +// +// Registers.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/12/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Registers_hpp +#define Registers_hpp + +namespace PCCompatible { + +struct Registers { + public: + static constexpr bool is_32bit = false; + + uint8_t &al() { return ax_.halves.low; } + uint8_t &ah() { return ax_.halves.high; } + uint16_t &ax() { return ax_.full; } + + CPU::RegisterPair16 &axp() { return ax_; } + + uint8_t &cl() { return cx_.halves.low; } + uint8_t &ch() { return cx_.halves.high; } + uint16_t &cx() { return cx_.full; } + + uint8_t &dl() { return dx_.halves.low; } + uint8_t &dh() { return dx_.halves.high; } + uint16_t &dx() { return dx_.full; } + + uint8_t &bl() { return bx_.halves.low; } + uint8_t &bh() { return bx_.halves.high; } + uint16_t &bx() { return bx_.full; } + + uint16_t &sp() { return sp_; } + uint16_t &bp() { return bp_; } + uint16_t &si() { return si_; } + uint16_t &di() { return di_; } + + uint16_t &ip() { return ip_; } + + uint16_t &es() { return es_; } + uint16_t &cs() { return cs_; } + uint16_t &ds() { return ds_; } + uint16_t &ss() { return ss_; } + uint16_t es() const { return es_; } + uint16_t cs() const { return cs_; } + uint16_t ds() const { return ds_; } + uint16_t ss() const { return ss_; } + + void reset() { + cs_ = 0xffff; + ip_ = 0; + } + + private: + CPU::RegisterPair16 ax_; + CPU::RegisterPair16 cx_; + CPU::RegisterPair16 dx_; + CPU::RegisterPair16 bx_; + + uint16_t sp_; + uint16_t bp_; + uint16_t si_; + uint16_t di_; + uint16_t es_, cs_, ds_, ss_; + uint16_t ip_; +}; + +} + +#endif /* Registers_hpp */ diff --git a/Machines/PCCompatible/Segments.hpp b/Machines/PCCompatible/Segments.hpp new file mode 100644 index 000000000..2e69038ff --- /dev/null +++ b/Machines/PCCompatible/Segments.hpp @@ -0,0 +1,58 @@ +// +// Segments.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/12/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Segments_hpp +#define Segments_hpp + +#include "Registers.hpp" + +#include "../../InstructionSets/x86/Instruction.hpp" + +namespace PCCompatible { + +class Segments { + public: + Segments(const Registers ®isters) : registers_(registers) {} + + using Source = InstructionSet::x86::Source; + + /// Posted by @c perform after any operation which *might* have affected a segment register. + void did_update(Source segment) { + switch(segment) { + default: break; + case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break; + case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break; + case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break; + case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break; + } + } + + void reset() { + did_update(Source::ES); + did_update(Source::CS); + did_update(Source::DS); + did_update(Source::SS); + } + + uint32_t es_base_, cs_base_, ds_base_, ss_base_; + + bool operator ==(const Segments &rhs) const { + return + es_base_ == rhs.es_base_ && + cs_base_ == rhs.cs_base_ && + ds_base_ == rhs.ds_base_ && + ss_base_ == rhs.ss_base_; + } + + private: + const Registers ®isters_; +}; + +} + +#endif /* Segments_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6a77713d5..95bd725c0 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1133,6 +1133,9 @@ 4238200E2B17CBC800964EFE /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4238200F2B17CBC800964EFE /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 423820102B17CBC800964EFE /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 423820132B1A235200964EFE /* Memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Memory.hpp; sourceTree = ""; }; + 423820142B1A23C200964EFE /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = ""; }; + 423820152B1A23E100964EFE /* Segments.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Segments.hpp; sourceTree = ""; }; 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; 42437B342ACF02A9006DFED1 /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = ""; }; 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; @@ -2383,11 +2386,14 @@ isa = PBXGroup; children = ( 425739372B051EA800B7D1E4 /* PCCompatible.cpp */, - 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, - 4267A9C72B0C26FA008A59BB /* PIT.hpp */, - 4267A9C82B0D4EC2008A59BB /* PIC.hpp */, 4267A9C92B0D4F17008A59BB /* DMA.hpp */, 4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */, + 423820132B1A235200964EFE /* Memory.hpp */, + 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, + 4267A9C82B0D4EC2008A59BB /* PIC.hpp */, + 4267A9C72B0C26FA008A59BB /* PIT.hpp */, + 423820142B1A23C200964EFE /* Registers.hpp */, + 423820152B1A23E100964EFE /* Segments.hpp */, ); path = PCCompatible; sourceTree = "";