diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp new file mode 100644 index 000000000..719bc19f0 --- /dev/null +++ b/Machines/PCCompatible/DMA.hpp @@ -0,0 +1,83 @@ +// +// DMA.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef DMA_hpp +#define DMA_hpp + +#include "../../Numeric/RegisterSizes.hpp" + +namespace PCCompatible { + +class DMA { + public: + void flip_flop_reset() { + next_access_low = true; + } + + void mask_reset() { + // TODO: set all mask bits off. + } + + void master_reset() { + flip_flop_reset(); + // TODO: clear status, set all mask bits on. + } + + template + void write(uint8_t value) { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + channels_[channel].count.halves.high = value; + } else { + channels_[channel].address.halves.high = value; + } + } else { + if constexpr (is_count) { + channels_[channel].count.halves.low = value; + } else { + channels_[channel].address.halves.low = value; + } + } + } + + template + uint8_t read() { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + return channels_[channel].count.halves.high; + } else { + return channels_[channel].address.halves.high; + } + } else { + if constexpr (is_count) { + return channels_[channel].count.halves.low; + } else { + return channels_[channel].address.halves.low; + } + } + } + + private: + bool next_access_low = true; + + struct Channel { + CPU::RegisterPair16 address, count; + } channels_[4]; +}; + +} + +#endif /* DMA_hpp */ diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 53030ad27..46eb59e81 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -8,18 +8,96 @@ #include "PCCompatible.hpp" +#include "DMA.hpp" +#include "PIC.hpp" +#include "PIT.hpp" + #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" #include "../../InstructionSets/x86/Instruction.hpp" #include "../../InstructionSets/x86/Perform.hpp" +#include "../../Components/8255/i8255.hpp" + +#include "../../Numeric/RegisterSizes.hpp" + #include "../ScanProducer.hpp" #include "../TimedMachine.hpp" #include +#include namespace PCCompatible { +class PITObserver { + public: + PITObserver(PIC &pic) : pic_(pic) {} + + template + void update_output(bool new_level) { + switch(channel) { + default: break; + case 0: pic_.apply_edge<0>(new_level); break; + } + } + + private: + PIC &pic_; + + // TODO: + // + // channel 0 is connected to IRQ 0; + // 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 = i8237; + +class i8255PortHandler : public Intel::i8255::PortHandler { + // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol + public: + void set_value(int port, uint8_t value) { + switch(port) { + case 1: + high_switches_ = value & 0x08; + break; + } + printf("PPI: %02x to %d\n", value, port); + } + + uint8_t get_value(int port) { + switch(port) { + case 2: + // Common: + // + // b7: 1 => memory parity error; 0 => none; + // b6: 1 => IO channel error; 0 => none; + // b5: timer 2 output; [TODO] + // b4: cassette data input; [TODO] + return + high_switches_ ? + // b3, b2: drive count; 00 = 1, 01 = 2, etc + // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) + 0b0000'0011 + : + // b3, b2: RAM on motherboard (64 * bit pattern) + // b1: 1 => FPU present; 0 => absent; + // b0: 1 => floppy drive present; 0 => absent. + 0b0000'1100; + } + printf("PPI: from %d\n", port); + return 0; + }; + + private: + bool high_switches_ = false; + + // Provisionally, possibly: + // + // port 0 = keyboard data output buffer; + // +}; +using PPI = Intel::i8255::i8255; + struct Registers { public: static constexpr bool is_32bit = false; @@ -152,8 +230,21 @@ struct Memory { } // Accesses an address based on physical location. +// int mda_delay = -1; // HACK. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { + + // TEMPORARY HACK. +// if(mda_delay > 0) { +// --mda_delay; +// if(!mda_delay) { +// print_mda(); +// } +// } +// if(address >= 0xb'0000 && is_writeable(type)) { +// mda_delay = 100; +// } + // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { return memory[address]; @@ -225,6 +316,19 @@ struct Memory { std::copy(data, data + length, memory.begin() + std::vector::difference_type(address)); } + + // TEMPORARY HACK. +// void print_mda() { +// uint32_t pointer = 0xb'0000; +// for(int y = 0; y < 25; y++) { +// for(int x = 0; x < 80; x++) { +// printf("%c", memory[pointer]); +// pointer += 2; // MDA goes [character, attributes]...; skip the attributes. +// } +// printf("\n"); +// } +// } + private: std::array memory{0xff}; Registers ®isters_; @@ -269,7 +373,9 @@ struct Memory { class IO { public: - template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) { + IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : pit_(pit), dma_(dma), ppi_(ppi), pic_(pic) {} + + template void out(uint16_t port, IntT value) { switch(port) { default: if constexpr (std::is_same_v) { @@ -283,15 +389,97 @@ class IO { case 0x00a0: printf("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked"); break; + + case 0x0000: dma_.write<0>(value); break; + case 0x0001: dma_.write<1>(value); break; + case 0x0002: dma_.write<2>(value); break; + case 0x0003: dma_.write<3>(value); break; + case 0x0004: dma_.write<4>(value); break; + case 0x0005: dma_.write<5>(value); break; + case 0x0006: dma_.write<6>(value); break; + case 0x0007: dma_.write<7>(value); break; + + case 0x0008: case 0x0009: case 0x000a: case 0x000b: + case 0x000c: case 0x000f: + printf("TODO: DMA write of %02x at %04x\n", value, port); + break; + + case 0x000d: dma_.master_reset(); break; + case 0x000e: dma_.mask_reset(); break; + + case 0x0020: pic_.write<0>(value); break; + case 0x0021: pic_.write<1>(value); break; + + case 0x0060: case 0x0061: case 0x0062: case 0x0063: + case 0x0064: case 0x0065: case 0x0066: case 0x0067: + case 0x0068: case 0x0069: case 0x006a: case 0x006b: + case 0x006c: case 0x006d: case 0x006e: case 0x006f: + ppi_.write(port, value); + break; + + case 0x0080: case 0x0081: case 0x0082: case 0x0083: + case 0x0084: case 0x0085: case 0x0086: case 0x0087: + case 0x0088: case 0x0089: case 0x008a: case 0x008b: + case 0x008c: case 0x008d: case 0x008e: case 0x008f: + printf("TODO: DMA page write of %02x at %04x\n", value, port); + break; + + case 0x03b0: case 0x03b1: case 0x03b2: case 0x03b3: + case 0x03b4: case 0x03b5: case 0x03b6: case 0x03b7: + case 0x03b8: case 0x03b9: case 0x03ba: case 0x03bb: + case 0x03bc: case 0x03bd: case 0x03be: case 0x03bf: + printf("TODO: MDA write of %02x at %04x\n", value, port); + break; + + case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3: + case 0x03d4: case 0x03d5: case 0x03d6: case 0x03d7: + case 0x03d8: case 0x03d9: case 0x03da: case 0x03db: + case 0x03dc: case 0x03dd: case 0x03de: case 0x03df: + printf("TODO: CGA write of %02x at %04x\n", value, port); + 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; } } template IntT in([[maybe_unused]] uint16_t port) { - printf("Unhandled in: %04x\n", port); + switch(port) { + default: + printf("Unhandled in: %04x\n", port); + break; + + case 0x0000: return dma_.read<0>(); + case 0x0001: return dma_.read<1>(); + case 0x0002: return dma_.read<2>(); + case 0x0003: return dma_.read<3>(); + case 0x0004: return dma_.read<4>(); + case 0x0005: return dma_.read<5>(); + case 0x0006: return dma_.read<6>(); + case 0x0007: return dma_.read<7>(); + + case 0x0020: return pic_.read<0>(); + case 0x0021: return pic_.read<1>(); + + case 0x0040: return pit_.read<0>(); + case 0x0041: return pit_.read<1>(); + case 0x0042: return pit_.read<2>(); + + case 0x0060: case 0x0061: case 0x0062: case 0x0063: + case 0x0064: case 0x0065: case 0x0066: case 0x0067: + case 0x0068: case 0x0069: case 0x006a: case 0x006b: + case 0x006c: case 0x006d: case 0x006e: case 0x006f: + return ppi_.read(port); + } return IntT(~0); } private: - + PIT &pit_; + DMA &dma_; + PPI &ppi_; + PIC &pic_; }; class FlowController { @@ -337,12 +525,16 @@ class ConcreteMachine: public MachineTypes::ScanProducer { public: + static constexpr int PitMultiplier = 1; + static constexpr int PitDivisor = 3; + ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, - [[maybe_unused]] const ROMMachine::ROMFetcher &rom_fetcher - ) { - // This is actually a MIPS count; try 3 million. - set_clock_rate(3'000'000); + const ROMMachine::ROMFetcher &rom_fetcher + ) : pit_observer_(pic_), pit_(pit_observer_), ppi_(ppi_handler_), context(pit_, dma_, ppi_, pic_) { + // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. + static constexpr int pit_frequency = 1'193'182; + set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. // Fetch the BIOS. [8088 only, for now] const auto bios = ROM::Name::PCCompatibleGLaBIOS; @@ -358,12 +550,37 @@ class ConcreteMachine: } // MARK: - TimedMachine. - void run_for([[maybe_unused]] const Cycles cycles) override { +// bool log = false; +// std::string previous; + void run_for(const Cycles cycles) override { auto instructions = cycles.as_integral(); while(instructions--) { - // Get the next thing to execute into decoded. + // + // First draft: all hardware runs in lockstep. + // + + // Advance the PIT. + pit_.run_for(PitDivisor / PitMultiplier); + + // Query for interrupts and apply if pending. + if(pic_.pending() && context.flags.flag()) { + // Regress the IP if a REP is in-progress so as to resume it later. + if(context.flow_controller.should_repeat()) { + context.registers.ip() = decoded_ip_; + context.flow_controller.begin_instruction(); + } + + // Signal interrupt. + InstructionSet::x86::interrupt( + pic_.acknowledge(), + context + ); + } + + // Get the next thing to execute. if(!context.flow_controller.should_repeat()) { // Decode from the current IP. + decoded_ip_ = context.registers.ip(); const auto remainder = context.memory.next_code(); decoded = decoder.decode(remainder.first, remainder.second); @@ -375,10 +592,20 @@ class ConcreteMachine: } context.registers.ip() += decoded.first; + +// log |= decoded.second.operation() == InstructionSet::x86::Operation::STI; } else { context.flow_controller.begin_instruction(); } +// if(log) { +// const auto next = to_string(decoded, InstructionSet::x86::Model::i8086); +// if(next != previous) { +// std::cout << next << std::endl; +// previous = next; +// } +// } + // Execute it. InstructionSet::x86::perform( decoded.second, @@ -394,11 +621,21 @@ class ConcreteMachine: } private: + PIC pic_; + DMA dma_; + + PITObserver pit_observer_; + i8255PortHandler ppi_handler_; + + PIT pit_; + PPI ppi_; + struct Context { - Context() : + Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : segments(registers), memory(registers, segments), - flow_controller(registers, segments) + flow_controller(registers, segments), + io(pit, dma, ppi, pic) { reset(); } @@ -422,6 +659,7 @@ class ConcreteMachine: InstructionSet::x86::Decoder8086 decoder; // InstructionSet::x86::Decoder decoder; + uint16_t decoded_ip_ = 0; std::pair> decoded; }; diff --git a/Machines/PCCompatible/PIC.hpp b/Machines/PCCompatible/PIC.hpp new file mode 100644 index 000000000..c89d4a157 --- /dev/null +++ b/Machines/PCCompatible/PIC.hpp @@ -0,0 +1,156 @@ +// +// PIC.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PIC_hpp +#define PIC_hpp + +namespace PCCompatible { + +// Cf. https://helppc.netcore2k.net/hardware/pic +class PIC { + public: + template + void write(uint8_t value) { + if(address) { + if(config_.word >= 0) { + switch(config_.word) { + case 0: + vector_base_ = value; + break; + case 1: + if(config_.has_fourth_word) { + // TODO: + // + // (1) slave mask if this is a master; + // (2) master interrupt attachment if this is a slave. + } + [[fallthrough]]; + break; + case 2: + auto_eoi_ = value & 2; + break; + } + + ++config_.word; + if(config_.word == (config_.has_fourth_word ? 3 : 2)) { + config_.word = -1; + } + } else { + mask_ = value; + } + } else { + if(value & 0x10) { + // + // Initialisation Command Word 1. + // + + config_.word = 0; + config_.has_fourth_word = value & 1; + + if(!config_.has_fourth_word) { + auto_eoi_ = false; + } + + single_pic_ = value & 2; + four_byte_vectors_ = value & 4; + level_triggered_ = value & 8; + } else if(value & 0x08) { + // + // Operation Control Word 3. + // + + // b6: 1 => use b5; 0 => ignore. + // b5: 1 => set special mask; 0 => clear. + // b2: 1 => poll command issued; 0 => not. + // b1: 1 => use b0; 0 => ignore. + // b0: 1 => read IRR on next read; 0 => read ISR. + } else { + // + // Operation Control Word 2. + // + + // b7, b6, b5: EOI type. + // b2, b1, b0: interrupt level to acknowledge. + if((value >> 5) == 0b001) { + // Non-specific EOI. + awaiting_eoi_ = false; + } + } + } + } + + template + uint8_t read() { + if(address) { + return mask_; + } + return 0; + } + + template + void apply_edge(bool final_level) { + const uint8_t input_mask = 1 << input; + + // Guess: level triggered means the request can be forwarded only so long as the + // relevant input is actually high. Whereas edge triggered implies capturing state. + if(level_triggered_) { + requests_ &= ~input_mask; + } + if(final_level) { + requests_ |= input_mask; + } + } + + bool pending() { + // Per the OSDev Wiki, masking is applied after the fact. + return !awaiting_eoi_ && (requests_ & ~mask_); + } + + int acknowledge() { + awaiting_eoi_ = true; + + // TODO: there's bound to be a better solution than this search? + // TODO: is this the right priority order? + in_service_ = 0x80; + int id = 7; + while(!(in_service_ & requests_)) { + in_service_ >>= 1; + --id; + } + + if(in_service_) { + requests_ &= ~in_service_; + return vector_base_ + id; + } + + // Spurious interrupt. + return vector_base_ + 7; + } + + private: + bool single_pic_ = false; + bool four_byte_vectors_ = false; + bool level_triggered_ = false; + bool auto_eoi_ = false; + + uint8_t vector_base_ = 0; + uint8_t mask_ = 0; + bool awaiting_eoi_ = false; + + uint8_t requests_ = 0; + uint8_t in_service_ = 0; + + struct ConfgurationState { + int word; + bool has_fourth_word; + } config_; +}; + +} + +#endif /* PIC_hpp */ diff --git a/Machines/PCCompatible/PIT.hpp b/Machines/PCCompatible/PIT.hpp new file mode 100644 index 000000000..0261d98f7 --- /dev/null +++ b/Machines/PCCompatible/PIT.hpp @@ -0,0 +1,259 @@ +// +// PIT.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PIT_hpp +#define PIT_hpp + +namespace PCCompatible { + +template +class i8237 { + public: + i8237(PITObserver &observer) : observer_(observer) {} + + template uint8_t read() { + return channels_[channel].read(); + } + + template void write(uint8_t value) { + channels_[channel].template write(observer_, value); + } + + void set_mode(uint8_t value) { + const int channel_id = (value >> 6) & 3; + if(channel_id == 3) { + // TODO: decode rest of read-back command. + read_back_ = is_8254; + return; + } + switch(channel_id) { + case 0: channels_[0].template set_mode<0>(observer_, value); break; + case 1: channels_[1].template set_mode<1>(observer_, value); break; + case 2: channels_[2].template set_mode<2>(observer_, value); break; + } + } + + void run_for(Cycles cycles) { + // TODO: be intelligent enough to take ticks outside the loop when appropriate. + auto ticks = cycles.as(); + while(ticks--) { + channels_[0].template advance<0>(observer_, 1); + channels_[1].template advance<1>(observer_, 1); + channels_[2].template advance<2>(observer_, 1); + } + } + + private: + // The target for output changes. + PITObserver &observer_; + + // Supported only on 8254s. + bool read_back_ = false; + + enum class LatchMode { + LowOnly, + HighOnly, + LowHigh, + }; + + enum class OperatingMode { + InterruptOnTerminalCount = 0, + HardwareRetriggerableOneShot = 1, + RateGenerator = 2, + SquareWaveGenerator = 3, + SoftwareTriggeredStrobe = 4, + HardwareTriggeredStrobe = 5, + }; + + struct Channel { + LatchMode latch_mode = LatchMode::LowHigh; + OperatingMode mode = OperatingMode::InterruptOnTerminalCount; + bool is_bcd = false; + + bool gated = false; + bool awaiting_reload = true; + + uint16_t counter = 0; + uint16_t reload = 0; + uint16_t latch = 0; + bool output = false; + + bool next_access_high = false; + + void latch_value() { + latch = counter; + } + + template + void set_mode(PITObserver &observer, uint8_t value) { + switch((value >> 4) & 3) { + default: + latch_value(); + return; + + case 1: latch_mode = LatchMode::LowOnly; break; + case 2: latch_mode = LatchMode::HighOnly; break; + case 3: latch_mode = LatchMode::LowHigh; break; + } + is_bcd = value & 1; + next_access_high = false; + + const auto operating_mode = (value >> 1) & 7; + switch(operating_mode) { + default: mode = OperatingMode(operating_mode); break; + case 6: mode = OperatingMode::RateGenerator; break; + case 7: mode = OperatingMode::SquareWaveGenerator; break; + } + + // Set up operating mode. + switch(mode) { + default: + printf("PIT: unimplemented mode %d\n", int(mode)); + break; + + case OperatingMode::InterruptOnTerminalCount: + case OperatingMode::HardwareRetriggerableOneShot: + set_output(observer, false); + awaiting_reload = true; + break; + + case OperatingMode::RateGenerator: + case OperatingMode::SquareWaveGenerator: + set_output(observer, true); + awaiting_reload = true; + break; + } + } + + template + void advance(PITObserver &observer, int ticks) { + if(gated || awaiting_reload) return; + + // TODO: BCD mode is completely ignored below. Possibly not too important. + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + case OperatingMode::HardwareRetriggerableOneShot: + // Output goes permanently high upon a tick from 1 to 0; reload value is not reused. + set_output(observer, output | (counter <= ticks)); + counter -= ticks; + break; + + case OperatingMode::SquareWaveGenerator: { + ticks <<= 1; + do { + // If there's a step from 1 to 0 within the next batch of ticks, + // toggle output and apply a reload. + if(counter && ticks >= counter) { + set_output(observer, output ^ true); + ticks -= counter; + + const uint16_t reload_mask = output ? 0xffff : 0xfffe; + counter = reload & reload_mask; + + continue; + } + counter -= ticks; + } while(false); + } break; + + case OperatingMode::RateGenerator: + do { + // Check for a step from 2 to 1 within the next batch of ticks, which would cause output + // to go high. + if(counter > 1 && ticks >= counter - 1) { + set_output(observer, true); + ticks -= counter - 1; + counter = 1; + continue; + } + + // If there is a step from 1 to 0, reload and set output back to low. + if(counter && ticks >= counter) { + set_output(observer, false); + ticks -= counter; + counter = reload; + continue; + } + + // Otherwise, just continue. + counter -= ticks; + } while(false); + break; + + default: + // TODO. + break; + } + } + + template + void write([[maybe_unused]] PITObserver &observer, uint8_t value) { + switch(latch_mode) { + case LatchMode::LowOnly: + reload = (reload & 0xff00) | value; + break; + case LatchMode::HighOnly: + reload = uint16_t((reload & 0x00ff) | (value << 8)); + break; + case LatchMode::LowHigh: + next_access_high ^= true; + if(next_access_high) { + reload = (reload & 0xff00) | value; + awaiting_reload = true; + return; + } + + reload = uint16_t((reload & 0x00ff) | (value << 8)); + break; + } + + awaiting_reload = false; + + switch(mode) { + default: + counter = reload; + break; + + case OperatingMode::SquareWaveGenerator: + counter = reload & ~1; + break; + } + } + + uint8_t read() { + switch(latch_mode) { + case LatchMode::LowOnly: return uint8_t(latch); + case LatchMode::HighOnly: return uint8_t(latch >> 8); + default: + case LatchMode::LowHigh: + next_access_high ^= true; + return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8); + break; + } + } + + template + void set_output(PITObserver &observer, bool level) { + if(output == level) { + return; + } + + // TODO: how should time be notified? + observer.template update_output(level); + output = level; + } + } channels_[3]; + + // TODO: + // + // RateGenerator: output goes high if gated. +}; + +} + +#endif /* PIT_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index bb8eb4145..a1fccf3ee 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1142,6 +1142,9 @@ 425739302AFBE47700B7D1E4 /* InOut.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InOut.hpp; sourceTree = ""; }; 425739362B051EA800B7D1E4 /* PCCompatible.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCCompatible.hpp; sourceTree = ""; }; 425739372B051EA800B7D1E4 /* PCCompatible.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCCompatible.cpp; sourceTree = ""; }; + 4267A9C72B0C26FA008A59BB /* PIT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIT.hpp; sourceTree = ""; }; + 4267A9C82B0D4EC2008A59BB /* PIC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIC.hpp; sourceTree = ""; }; + 4267A9C92B0D4F17008A59BB /* DMA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMA.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2360,8 +2363,11 @@ 425739352B051EA800B7D1E4 /* PCCompatible */ = { isa = PBXGroup; children = ( - 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, 425739372B051EA800B7D1E4 /* PCCompatible.cpp */, + 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, + 4267A9C72B0C26FA008A59BB /* PIT.hpp */, + 4267A9C82B0D4EC2008A59BB /* PIC.hpp */, + 4267A9C92B0D4F17008A59BB /* DMA.hpp */, ); path = PCCompatible; sourceTree = "";