mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-19 07:31:15 +00:00
Merge pull request #1209 from TomHarte/SupportChipsAplenty
Add various other PC chips into the mix.
This commit is contained in:
commit
c11d3b61d6
83
Machines/PCCompatible/DMA.hpp
Normal file
83
Machines/PCCompatible/DMA.hpp
Normal file
@ -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 <int address>
|
||||
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 <int address>
|
||||
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 */
|
@ -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 <array>
|
||||
#include <iostream>
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
class PITObserver {
|
||||
public:
|
||||
PITObserver(PIC &pic) : pic_(pic) {}
|
||||
|
||||
template <int channel>
|
||||
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<false, PITObserver>;
|
||||
|
||||
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<i8255PortHandler>;
|
||||
|
||||
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 IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::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<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
@ -225,6 +316,19 @@ struct Memory {
|
||||
std::copy(data, data + length, memory.begin() + std::vector<uint8_t>::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<uint8_t, 1024*1024> memory{0xff};
|
||||
Registers ®isters_;
|
||||
@ -269,7 +373,9 @@ struct Memory {
|
||||
|
||||
class IO {
|
||||
public:
|
||||
template <typename IntT> 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 <typename IntT> void out(uint16_t port, IntT value) {
|
||||
switch(port) {
|
||||
default:
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
@ -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 <typename IntT> 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<InstructionSet::x86::Flag::Interrupt>()) {
|
||||
// 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<InstructionSet::x86::Model::i8086> decoder;
|
||||
|
||||
uint16_t decoded_ip_ = 0;
|
||||
std::pair<int, InstructionSet::x86::Instruction<false>> decoded;
|
||||
};
|
||||
|
||||
|
156
Machines/PCCompatible/PIC.hpp
Normal file
156
Machines/PCCompatible/PIC.hpp
Normal file
@ -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 <int address>
|
||||
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 <int address>
|
||||
uint8_t read() {
|
||||
if(address) {
|
||||
return mask_;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <int input>
|
||||
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 */
|
259
Machines/PCCompatible/PIT.hpp
Normal file
259
Machines/PCCompatible/PIT.hpp
Normal file
@ -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 <bool is_8254, typename PITObserver>
|
||||
class i8237 {
|
||||
public:
|
||||
i8237(PITObserver &observer) : observer_(observer) {}
|
||||
|
||||
template <int channel> uint8_t read() {
|
||||
return channels_[channel].read();
|
||||
}
|
||||
|
||||
template <int channel> void write(uint8_t value) {
|
||||
channels_[channel].template write<channel>(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<int>();
|
||||
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 <int channel>
|
||||
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<channel>(observer, false);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
|
||||
case OperatingMode::RateGenerator:
|
||||
case OperatingMode::SquareWaveGenerator:
|
||||
set_output<channel>(observer, true);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
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<channel>(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<channel>(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<channel>(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<channel>(observer, false);
|
||||
ticks -= counter;
|
||||
counter = reload;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, just continue.
|
||||
counter -= ticks;
|
||||
} while(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
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 <int channel>
|
||||
void set_output(PITObserver &observer, bool level) {
|
||||
if(output == level) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: how should time be notified?
|
||||
observer.template update_output<channel>(level);
|
||||
output = level;
|
||||
}
|
||||
} channels_[3];
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// RateGenerator: output goes high if gated.
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* PIT_hpp */
|
@ -1142,6 +1142,9 @@
|
||||
425739302AFBE47700B7D1E4 /* InOut.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InOut.hpp; sourceTree = "<group>"; };
|
||||
425739362B051EA800B7D1E4 /* PCCompatible.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCCompatible.hpp; sourceTree = "<group>"; };
|
||||
425739372B051EA800B7D1E4 /* PCCompatible.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCCompatible.cpp; sourceTree = "<group>"; };
|
||||
4267A9C72B0C26FA008A59BB /* PIT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIT.hpp; sourceTree = "<group>"; };
|
||||
4267A9C82B0D4EC2008A59BB /* PIC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIC.hpp; sourceTree = "<group>"; };
|
||||
4267A9C92B0D4F17008A59BB /* DMA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMA.hpp; sourceTree = "<group>"; };
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
|
||||
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||
@ -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 = "<group>";
|
||||
|
Loading…
x
Reference in New Issue
Block a user