2023-11-15 16:01:28 +00:00
|
|
|
|
//
|
|
|
|
|
// PCCompatible.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 15/11/2023.
|
|
|
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "PCCompatible.hpp"
|
2023-11-15 20:58:49 +00:00
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
#include "CGA.hpp"
|
|
|
|
|
#include "DMA.hpp"
|
2023-11-24 18:38:06 +00:00
|
|
|
|
#include "KeyboardMapper.hpp"
|
2023-12-06 03:37:33 +00:00
|
|
|
|
#include "MDA.hpp"
|
|
|
|
|
#include "Memory.hpp"
|
2023-11-21 20:50:38 +00:00
|
|
|
|
#include "PIC.hpp"
|
2023-11-21 00:00:16 +00:00
|
|
|
|
#include "PIT.hpp"
|
2023-12-07 03:56:09 +00:00
|
|
|
|
#include "RTC.hpp"
|
2023-11-21 00:00:16 +00:00
|
|
|
|
|
2023-11-15 20:58:49 +00:00
|
|
|
|
#include "../../InstructionSets/x86/Decoder.hpp"
|
|
|
|
|
#include "../../InstructionSets/x86/Flags.hpp"
|
2023-11-15 16:01:28 +00:00
|
|
|
|
#include "../../InstructionSets/x86/Instruction.hpp"
|
|
|
|
|
#include "../../InstructionSets/x86/Perform.hpp"
|
|
|
|
|
|
2023-11-20 17:13:42 +00:00
|
|
|
|
#include "../../Components/8255/i8255.hpp"
|
2023-11-25 03:19:39 +00:00
|
|
|
|
#include "../../Components/8272/CommandDecoder.hpp"
|
2023-11-28 04:05:37 +00:00
|
|
|
|
#include "../../Components/8272/Results.hpp"
|
2023-11-25 23:10:49 +00:00
|
|
|
|
#include "../../Components/8272/Status.hpp"
|
2023-11-22 17:53:09 +00:00
|
|
|
|
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
2023-11-20 17:13:42 +00:00
|
|
|
|
|
2023-11-20 03:55:29 +00:00
|
|
|
|
#include "../../Numeric/RegisterSizes.hpp"
|
2023-11-22 17:53:09 +00:00
|
|
|
|
|
|
|
|
|
#include "../../Outputs/CRT/CRT.hpp"
|
2023-11-22 03:11:32 +00:00
|
|
|
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
2023-11-20 03:55:29 +00:00
|
|
|
|
|
2023-11-29 20:55:37 +00:00
|
|
|
|
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
|
2023-12-12 00:13:37 +00:00
|
|
|
|
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
2023-11-29 20:55:37 +00:00
|
|
|
|
|
2023-11-22 03:11:32 +00:00
|
|
|
|
#include "../AudioProducer.hpp"
|
2023-11-24 18:38:06 +00:00
|
|
|
|
#include "../KeyboardMachine.hpp"
|
2023-11-29 20:20:14 +00:00
|
|
|
|
#include "../MediaTarget.hpp"
|
2023-11-15 19:30:30 +00:00
|
|
|
|
#include "../ScanProducer.hpp"
|
2023-11-15 16:01:28 +00:00
|
|
|
|
#include "../TimedMachine.hpp"
|
|
|
|
|
|
2023-12-05 20:19:58 +00:00
|
|
|
|
#include "../../Analyser/Static/PCCompatible/Target.hpp"
|
|
|
|
|
|
2023-11-16 20:24:35 +00:00
|
|
|
|
#include <array>
|
2023-11-21 16:19:47 +00:00
|
|
|
|
#include <iostream>
|
2023-11-16 20:24:35 +00:00
|
|
|
|
|
2023-11-15 16:01:28 +00:00
|
|
|
|
namespace PCCompatible {
|
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
using Target = Analyser::Static::PCCompatible::Target;
|
2023-12-05 20:19:58 +00:00
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
// Map from members of the VideoAdaptor enum to concrete class types.
|
|
|
|
|
template <Target::VideoAdaptor adaptor> struct Adaptor;
|
|
|
|
|
template <> struct Adaptor<Target::VideoAdaptor::MDA> { using type = MDA; };
|
|
|
|
|
template <> struct Adaptor<Target::VideoAdaptor::CGA> { using type = CGA; };
|
2023-12-01 18:15:01 +00:00
|
|
|
|
|
2023-11-25 03:19:39 +00:00
|
|
|
|
class FloppyController {
|
|
|
|
|
public:
|
2023-12-05 21:38:09 +00:00
|
|
|
|
FloppyController(PIC &pic, DMA &dma, int drive_count) : pic_(pic), dma_(dma) {
|
2023-11-29 16:35:21 +00:00
|
|
|
|
// Default: one floppy drive only.
|
2023-12-05 21:38:09 +00:00
|
|
|
|
for(int c = 0; c < 4; c++) {
|
2023-12-06 03:02:41 +00:00
|
|
|
|
drives_[c].exists = drive_count > c;
|
2023-12-05 21:38:09 +00:00
|
|
|
|
}
|
2023-11-29 16:35:21 +00:00
|
|
|
|
}
|
2023-11-25 03:19:39 +00:00
|
|
|
|
|
|
|
|
|
void set_digital_output(uint8_t control) {
|
|
|
|
|
// b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1;
|
|
|
|
|
// b3: 1 => enable DMA; 0 => disable;
|
|
|
|
|
// b2: 1 => enable FDC; 0 => hold at reset;
|
2023-11-29 14:52:16 +00:00
|
|
|
|
// b1, b0: drive select (usurps FDC?)
|
|
|
|
|
|
|
|
|
|
drives_[0].motor = control & 0x10;
|
|
|
|
|
drives_[1].motor = control & 0x20;
|
|
|
|
|
drives_[2].motor = control & 0x40;
|
|
|
|
|
drives_[3].motor = control & 0x80;
|
2023-11-25 03:19:39 +00:00
|
|
|
|
|
2023-11-29 16:31:37 +00:00
|
|
|
|
if(observer_) {
|
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
|
if(drives_[c].exists) observer_->set_led_status(drive_name(c), drives_[c].motor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-25 03:19:39 +00:00
|
|
|
|
enable_dma_ = control & 0x08;
|
|
|
|
|
|
|
|
|
|
const bool hold_reset = !(control & 0x04);
|
|
|
|
|
if(!hold_reset && hold_reset_) {
|
2023-11-25 23:10:49 +00:00
|
|
|
|
// TODO: add a delay mechanism.
|
2023-11-25 03:19:39 +00:00
|
|
|
|
reset();
|
|
|
|
|
}
|
|
|
|
|
hold_reset_ = hold_reset;
|
2023-11-25 03:41:33 +00:00
|
|
|
|
if(hold_reset_) {
|
|
|
|
|
pic_.apply_edge<6>(false);
|
|
|
|
|
}
|
2023-11-25 03:19:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-25 23:10:49 +00:00
|
|
|
|
uint8_t status() const {
|
|
|
|
|
return status_.main();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-25 23:15:37 +00:00
|
|
|
|
void write(uint8_t value) {
|
|
|
|
|
decoder_.push_back(value);
|
2023-11-26 02:40:13 +00:00
|
|
|
|
|
|
|
|
|
if(decoder_.has_command()) {
|
2023-11-28 04:05:37 +00:00
|
|
|
|
using Command = Intel::i8272::Command;
|
2023-11-26 02:40:13 +00:00
|
|
|
|
switch(decoder_.command()) {
|
|
|
|
|
default:
|
|
|
|
|
printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command()));
|
|
|
|
|
break;
|
|
|
|
|
|
2023-12-07 21:45:21 +00:00
|
|
|
|
case Command::WriteDeletedData:
|
|
|
|
|
case Command::WriteData: {
|
|
|
|
|
status_.begin(decoder_);
|
|
|
|
|
|
|
|
|
|
// Just decline to write, for now.
|
|
|
|
|
status_.set(Intel::i8272::Status1::NotWriteable);
|
2023-12-07 21:50:12 +00:00
|
|
|
|
status_.set(Intel::i8272::Status0::BecameNotReady);
|
2023-12-07 21:45:21 +00:00
|
|
|
|
|
|
|
|
|
results_.serialise(
|
|
|
|
|
status_,
|
|
|
|
|
decoder_.geometry().cylinder,
|
|
|
|
|
decoder_.geometry().head,
|
|
|
|
|
decoder_.geometry().sector,
|
|
|
|
|
decoder_.geometry().size);
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case Command::ReadDeletedData:
|
2023-11-30 17:47:50 +00:00
|
|
|
|
case Command::ReadData: {
|
2023-12-08 03:11:49 +00:00
|
|
|
|
// printf("FDC: Read from drive %d / head %d / track %d of head %d / track %d / sector %d\n",
|
|
|
|
|
// decoder_.target().drive,
|
|
|
|
|
// decoder_.target().head,
|
|
|
|
|
// drives_[decoder_.target().drive].track,
|
|
|
|
|
// decoder_.geometry().head,
|
|
|
|
|
// decoder_.geometry().cylinder,
|
|
|
|
|
// decoder_.geometry().sector);
|
2023-11-30 17:47:50 +00:00
|
|
|
|
|
2023-12-01 14:37:30 +00:00
|
|
|
|
status_.begin(decoder_);
|
|
|
|
|
|
2023-11-30 17:47:50 +00:00
|
|
|
|
// Search for a matching sector.
|
2023-12-02 23:38:26 +00:00
|
|
|
|
auto target = decoder_.geometry();
|
|
|
|
|
bool complete = false;
|
|
|
|
|
while(!complete) {
|
2023-12-12 00:13:37 +00:00
|
|
|
|
const auto sector = drives_[decoder_.target().drive].sector(target.head, target.sector);
|
|
|
|
|
|
|
|
|
|
if(sector) {
|
2023-12-12 14:35:05 +00:00
|
|
|
|
// TODO: I _think_ I'm supposed to validate the rest of the address here?
|
|
|
|
|
|
2023-12-12 00:13:37 +00:00
|
|
|
|
for(int c = 0; c < 128 << target.size; c++) {
|
|
|
|
|
const auto access_result = dma_.write(2, sector->samples[0].data()[c]);
|
|
|
|
|
switch(access_result) {
|
2023-12-12 14:29:28 +00:00
|
|
|
|
// Default: keep going.
|
|
|
|
|
default: continue;
|
|
|
|
|
|
|
|
|
|
// Anything else: update flags and exit.
|
2023-12-12 00:13:37 +00:00
|
|
|
|
case AccessResult::NotAccepted:
|
|
|
|
|
complete = true;
|
2023-12-12 14:29:28 +00:00
|
|
|
|
status_.set(Intel::i8272::Status1::OverRun);
|
|
|
|
|
status_.set(Intel::i8272::Status0::AbnormalTermination);
|
2023-12-12 00:13:37 +00:00
|
|
|
|
break;
|
|
|
|
|
case AccessResult::AcceptedWithEOP:
|
|
|
|
|
complete = true;
|
|
|
|
|
break;
|
2023-12-01 14:34:31 +00:00
|
|
|
|
}
|
2023-12-02 23:38:26 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2023-12-04 17:19:21 +00:00
|
|
|
|
|
2023-12-12 00:13:37 +00:00
|
|
|
|
++target.sector; // TODO: multitrack?
|
|
|
|
|
} else {
|
2023-12-04 17:19:21 +00:00
|
|
|
|
status_.set(Intel::i8272::Status1::EndOfCylinder);
|
|
|
|
|
status_.set(Intel::i8272::Status0::AbnormalTermination);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-12-01 14:34:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 23:38:26 +00:00
|
|
|
|
results_.serialise(
|
|
|
|
|
status_,
|
|
|
|
|
decoder_.geometry().cylinder,
|
|
|
|
|
decoder_.geometry().head,
|
|
|
|
|
decoder_.geometry().sector,
|
|
|
|
|
decoder_.geometry().size);
|
|
|
|
|
|
|
|
|
|
// TODO: what if head has changed?
|
2023-12-01 14:49:50 +00:00
|
|
|
|
drives_[decoder_.target().drive].status = decoder_.drive_head();
|
|
|
|
|
drives_[decoder_.target().drive].raised_interrupt = true;
|
2023-12-01 14:37:30 +00:00
|
|
|
|
pic_.apply_edge<6>(true);
|
2023-11-30 17:47:50 +00:00
|
|
|
|
} break;
|
2023-11-29 20:30:47 +00:00
|
|
|
|
|
2023-11-29 14:49:15 +00:00
|
|
|
|
case Command::Recalibrate:
|
|
|
|
|
drives_[decoder_.target().drive].track = 0;
|
2023-11-29 20:55:37 +00:00
|
|
|
|
|
2023-11-29 14:49:15 +00:00
|
|
|
|
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);
|
2023-11-28 04:05:37 +00:00
|
|
|
|
break;
|
2023-12-05 02:46:03 +00:00
|
|
|
|
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);
|
|
|
|
|
break;
|
2023-11-28 04:05:37 +00:00
|
|
|
|
|
2023-11-29 14:42:43 +00:00
|
|
|
|
case Command::SenseInterruptStatus: {
|
|
|
|
|
int c = 0;
|
|
|
|
|
for(; c < 4; c++) {
|
|
|
|
|
if(drives_[c].raised_interrupt) {
|
|
|
|
|
drives_[c].raised_interrupt = false;
|
|
|
|
|
status_.set_status0(drives_[c].status);
|
|
|
|
|
results_.serialise(status_, drives_[0].track);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool any_remaining_interrupts = false;
|
|
|
|
|
for(; c < 4; c++) {
|
|
|
|
|
any_remaining_interrupts |= drives_[c].raised_interrupt;
|
|
|
|
|
}
|
|
|
|
|
if(!any_remaining_interrupts) {
|
2023-12-05 02:46:03 +00:00
|
|
|
|
pic_.apply_edge<6>(false);
|
2023-11-29 14:42:43 +00:00
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case Command::Specify:
|
|
|
|
|
specify_specs_ = decoder_.specify_specs();
|
|
|
|
|
break;
|
2023-11-29 14:49:15 +00:00
|
|
|
|
// case Command::SenseDriveStatus: {
|
|
|
|
|
// } break;
|
2023-11-29 14:42:43 +00:00
|
|
|
|
|
2023-11-29 14:49:15 +00:00
|
|
|
|
case Command::Invalid:
|
|
|
|
|
results_.serialise_none();
|
2023-11-28 04:05:37 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-28 04:16:24 +00:00
|
|
|
|
decoder_.clear();
|
|
|
|
|
|
|
|
|
|
// If there are any results to provide, set data direction and data ready.
|
2023-11-28 04:05:37 +00:00
|
|
|
|
if(!results_.empty()) {
|
|
|
|
|
using MainStatus = Intel::i8272::MainStatus;
|
|
|
|
|
status_.set(MainStatus::DataIsToProcessor, true);
|
|
|
|
|
status_.set(MainStatus::DataReady, true);
|
2023-11-29 03:34:34 +00:00
|
|
|
|
status_.set(MainStatus::CommandInProgress, true);
|
2023-11-26 02:40:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-25 23:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 15:27:36 +00:00
|
|
|
|
uint8_t read() {
|
2023-11-28 04:05:37 +00:00
|
|
|
|
using MainStatus = Intel::i8272::MainStatus;
|
|
|
|
|
if(status_.get(MainStatus::DataIsToProcessor)) {
|
|
|
|
|
const uint8_t result = results_.next();
|
|
|
|
|
if(results_.empty()) {
|
|
|
|
|
status_.set(MainStatus::DataIsToProcessor, false);
|
2023-11-29 03:34:34 +00:00
|
|
|
|
status_.set(MainStatus::CommandInProgress, false);
|
2023-11-28 04:05:37 +00:00
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 15:27:36 +00:00
|
|
|
|
return 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 16:31:37 +00:00
|
|
|
|
void set_activity_observer(Activity::Observer *observer) {
|
|
|
|
|
observer_ = observer;
|
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
|
if(drives_[c].exists) {
|
|
|
|
|
observer_->register_led(drive_name(c), 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 20:20:14 +00:00
|
|
|
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
2023-12-04 17:28:29 +00:00
|
|
|
|
// if(drives_[drive].has_disk()) {
|
|
|
|
|
// // TODO: drive should only transition to unready if it was ready in the first place.
|
|
|
|
|
// drives_[drive].status = uint8_t(Intel::i8272::Status0::BecameNotReady);
|
|
|
|
|
// drives_[drive].raised_interrupt = true;
|
|
|
|
|
// pic_.apply_edge<6>(true);
|
|
|
|
|
// }
|
|
|
|
|
drives_[drive].set_disk(disk);
|
2023-11-29 20:20:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-25 03:19:39 +00:00
|
|
|
|
private:
|
|
|
|
|
void reset() {
|
2023-12-08 03:11:49 +00:00
|
|
|
|
// printf("FDC reset\n");
|
2023-11-25 03:19:39 +00:00
|
|
|
|
decoder_.clear();
|
2023-11-25 23:10:49 +00:00
|
|
|
|
status_.reset();
|
2023-11-29 04:18:22 +00:00
|
|
|
|
|
2023-11-29 14:50:08 +00:00
|
|
|
|
// Necessary to pass GlaBIOS' POST test, but: why?
|
2023-11-29 04:18:22 +00:00
|
|
|
|
//
|
|
|
|
|
// Cf. INT_13_0_2 and the CMP AL, 11000000B following a CALL FDC_WAIT_SENSE.
|
2023-11-29 14:42:43 +00:00
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
|
drives_[c].raised_interrupt = true;
|
|
|
|
|
drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady);
|
|
|
|
|
}
|
|
|
|
|
pic_.apply_edge<6>(true);
|
2023-11-28 04:16:24 +00:00
|
|
|
|
|
|
|
|
|
using MainStatus = Intel::i8272::MainStatus;
|
|
|
|
|
status_.set(MainStatus::DataReady, true);
|
|
|
|
|
status_.set(MainStatus::DataIsToProcessor, false);
|
2023-11-25 03:19:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PIC &pic_;
|
|
|
|
|
DMA &dma_;
|
|
|
|
|
|
|
|
|
|
bool hold_reset_ = false;
|
|
|
|
|
bool enable_dma_ = false;
|
2023-11-25 23:10:49 +00:00
|
|
|
|
|
2023-11-25 03:19:39 +00:00
|
|
|
|
Intel::i8272::CommandDecoder decoder_;
|
2023-11-25 23:10:49 +00:00
|
|
|
|
Intel::i8272::Status status_;
|
2023-11-28 04:05:37 +00:00
|
|
|
|
Intel::i8272::Results results_;
|
2023-11-29 14:42:43 +00:00
|
|
|
|
|
|
|
|
|
Intel::i8272::CommandDecoder::SpecifySpecs specify_specs_;
|
|
|
|
|
struct DriveStatus {
|
2023-12-02 04:35:11 +00:00
|
|
|
|
public:
|
|
|
|
|
bool raised_interrupt = false;
|
|
|
|
|
uint8_t status = 0;
|
|
|
|
|
uint8_t track = 0;
|
|
|
|
|
bool motor = false;
|
|
|
|
|
bool exists = true;
|
|
|
|
|
|
2023-12-04 17:28:29 +00:00
|
|
|
|
bool has_disk() const {
|
2023-12-12 00:13:37 +00:00
|
|
|
|
return static_cast<bool>(parser_);
|
2023-12-04 17:28:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> image) {
|
2023-12-12 00:13:37 +00:00
|
|
|
|
parser_ = std::make_unique<Storage::Encodings::MFM::Parser>(image);
|
2023-12-04 17:28:29 +00:00
|
|
|
|
}
|
2023-12-02 04:35:11 +00:00
|
|
|
|
|
2023-12-12 00:13:37 +00:00
|
|
|
|
const Storage::Encodings::MFM::Sector *sector(int head, uint8_t sector) {
|
|
|
|
|
return parser_ ? parser_->sector(head, track, sector) : nullptr;
|
2023-11-29 20:55:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 04:35:11 +00:00
|
|
|
|
private:
|
2023-12-12 00:13:37 +00:00
|
|
|
|
std::unique_ptr<Storage::Encodings::MFM::Parser> parser_;
|
2023-12-02 04:35:11 +00:00
|
|
|
|
|
2023-11-29 14:42:43 +00:00
|
|
|
|
} drives_[4];
|
2023-11-29 16:31:37 +00:00
|
|
|
|
|
2023-11-30 17:47:50 +00:00
|
|
|
|
static std::string drive_name(int c) {
|
2023-11-29 16:35:21 +00:00
|
|
|
|
char name[3] = "A";
|
|
|
|
|
name[0] += c;
|
|
|
|
|
return std::string("Drive ") + name;
|
2023-11-29 16:31:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Activity::Observer *observer_ = nullptr;
|
2023-11-25 03:19:39 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-24 03:10:51 +00:00
|
|
|
|
class KeyboardController {
|
|
|
|
|
public:
|
|
|
|
|
KeyboardController(PIC &pic) : pic_(pic) {}
|
|
|
|
|
|
|
|
|
|
// KB Status Port 61h high bits:
|
|
|
|
|
//; 01 - normal operation. wait for keypress, when one comes in,
|
|
|
|
|
//; force data line low (forcing keyboard to buffer additional
|
|
|
|
|
//; keypresses) and raise IRQ1 high
|
|
|
|
|
//; 11 - stop forcing data line low. lower IRQ1 and don't raise it again.
|
|
|
|
|
//; drop all incoming keypresses on the floor.
|
|
|
|
|
//; 10 - lower IRQ1 and force clock line low, resetting keyboard
|
|
|
|
|
//; 00 - force clock line low, resetting keyboard, but on a 01->00 transition,
|
|
|
|
|
//; IRQ1 would remain high
|
|
|
|
|
void set_mode(uint8_t mode) {
|
2023-12-25 23:43:35 +00:00
|
|
|
|
const auto last_mode = mode_;
|
2023-11-24 03:10:51 +00:00
|
|
|
|
mode_ = Mode(mode);
|
|
|
|
|
switch(mode_) {
|
2024-12-01 14:04:32 +00:00
|
|
|
|
case Mode::NormalOperation: break;
|
2023-11-24 03:10:51 +00:00
|
|
|
|
case Mode::NoIRQsIgnoreInput:
|
|
|
|
|
pic_.apply_edge<1>(false);
|
|
|
|
|
break;
|
2023-12-25 23:43:35 +00:00
|
|
|
|
case Mode::Reset:
|
|
|
|
|
input_.clear();
|
|
|
|
|
[[fallthrough]];
|
2023-11-24 03:10:51 +00:00
|
|
|
|
case Mode::ClearIRQReset:
|
|
|
|
|
pic_.apply_edge<1>(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-12-25 23:43:35 +00:00
|
|
|
|
|
|
|
|
|
// If the reset condition ends, start a counter through until reset is complete.
|
|
|
|
|
if(last_mode == Mode::Reset && mode_ != Mode::Reset) {
|
|
|
|
|
reset_delay_ = 15; // Arbitrarily.
|
|
|
|
|
}
|
2023-11-24 03:10:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void run_for(Cycles cycles) {
|
2023-11-24 03:15:20 +00:00
|
|
|
|
if(reset_delay_ <= 0) {
|
2023-11-24 03:10:51 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
reset_delay_ -= cycles.as<int>();
|
2023-11-24 03:15:20 +00:00
|
|
|
|
if(reset_delay_ <= 0) {
|
2023-12-03 03:29:49 +00:00
|
|
|
|
input_.clear();
|
2023-11-24 03:10:51 +00:00
|
|
|
|
post(0xaa);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t read() {
|
|
|
|
|
pic_.apply_edge<1>(false);
|
2023-12-03 03:29:49 +00:00
|
|
|
|
if(input_.empty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2023-11-24 03:16:08 +00:00
|
|
|
|
|
2023-12-03 03:29:49 +00:00
|
|
|
|
const uint8_t key = input_.front();
|
|
|
|
|
input_.erase(input_.begin());
|
|
|
|
|
if(!input_.empty()) {
|
|
|
|
|
pic_.apply_edge<1>(true);
|
|
|
|
|
}
|
2023-11-24 03:16:08 +00:00
|
|
|
|
return key;
|
2023-11-24 03:10:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void post(uint8_t value) {
|
2023-12-25 23:43:35 +00:00
|
|
|
|
if(mode_ != Mode::NormalOperation || reset_delay_) {
|
2023-11-24 18:38:06 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-03 03:29:49 +00:00
|
|
|
|
input_.push_back(value);
|
2023-11-24 03:10:51 +00:00
|
|
|
|
pic_.apply_edge<1>(true);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 18:38:06 +00:00
|
|
|
|
private:
|
2023-11-24 03:10:51 +00:00
|
|
|
|
enum class Mode {
|
|
|
|
|
NormalOperation = 0b01,
|
|
|
|
|
NoIRQsIgnoreInput = 0b11,
|
|
|
|
|
ClearIRQReset = 0b10,
|
|
|
|
|
Reset = 0b00,
|
|
|
|
|
} mode_;
|
|
|
|
|
|
2023-12-03 03:29:49 +00:00
|
|
|
|
std::vector<uint8_t> input_;
|
2023-11-24 03:10:51 +00:00
|
|
|
|
PIC &pic_;
|
|
|
|
|
|
|
|
|
|
int reset_delay_ = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-22 03:28:33 +00:00
|
|
|
|
struct PCSpeaker {
|
|
|
|
|
PCSpeaker() :
|
|
|
|
|
toggle(queue),
|
|
|
|
|
speaker(toggle) {}
|
|
|
|
|
|
|
|
|
|
void update() {
|
|
|
|
|
speaker.run_for(queue, cycles_since_update);
|
|
|
|
|
cycles_since_update = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_pit(bool pit_input) {
|
|
|
|
|
pit_input_ = pit_input;
|
|
|
|
|
set_level();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_control(bool pit_mask, bool level) {
|
|
|
|
|
pit_mask_ = pit_mask;
|
|
|
|
|
level_ = level;
|
|
|
|
|
set_level();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_level() {
|
2023-11-30 19:37:13 +00:00
|
|
|
|
// TODO: I think pit_mask_ actually acts as the gate input to the PIT.
|
|
|
|
|
const bool new_output = (!pit_mask_ | pit_input_) & level_;
|
2023-11-22 03:36:11 +00:00
|
|
|
|
|
|
|
|
|
if(new_output != output_) {
|
|
|
|
|
update();
|
|
|
|
|
toggle.set_output(new_output);
|
|
|
|
|
output_ = new_output;
|
|
|
|
|
}
|
2023-11-22 03:28:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Concurrency::AsyncTaskQueue<false> queue;
|
|
|
|
|
Audio::Toggle toggle;
|
|
|
|
|
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker;
|
|
|
|
|
Cycles cycles_since_update = 0;
|
|
|
|
|
|
|
|
|
|
bool pit_input_ = false;
|
|
|
|
|
bool pit_mask_ = false;
|
|
|
|
|
bool level_ = false;
|
2023-11-22 03:36:11 +00:00
|
|
|
|
bool output_ = false;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-21 03:36:05 +00:00
|
|
|
|
class PITObserver {
|
|
|
|
|
public:
|
2023-11-22 03:28:33 +00:00
|
|
|
|
PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {}
|
2023-11-21 03:36:05 +00:00
|
|
|
|
|
|
|
|
|
template <int channel>
|
|
|
|
|
void update_output(bool new_level) {
|
|
|
|
|
switch(channel) {
|
|
|
|
|
default: break;
|
|
|
|
|
case 0: pic_.apply_edge<0>(new_level); break;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
case 2: speaker_.set_pit(new_level); break;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
PIC &pic_;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
PCSpeaker &speaker_;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
};
|
2023-11-30 17:47:50 +00:00
|
|
|
|
using PIT = i8253<false, PITObserver>;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
|
2023-11-20 17:13:42 +00:00
|
|
|
|
class i8255PortHandler : public Intel::i8255::PortHandler {
|
|
|
|
|
public:
|
2023-12-27 15:44:51 +00:00
|
|
|
|
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, Target::VideoAdaptor adaptor, int drive_count) :
|
2023-12-05 21:38:09 +00:00
|
|
|
|
speaker_(speaker), keyboard_(keyboard) {
|
|
|
|
|
// High switches:
|
|
|
|
|
//
|
|
|
|
|
// b3, b2: drive count; 00 = 1, 01 = 2, etc
|
|
|
|
|
// b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA)
|
|
|
|
|
switch(adaptor) {
|
|
|
|
|
default: break;
|
2023-12-27 15:44:51 +00:00
|
|
|
|
case Target::VideoAdaptor::MDA: high_switches_ |= 0b11; break;
|
|
|
|
|
case Target::VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns.
|
2023-12-05 21:38:09 +00:00
|
|
|
|
}
|
|
|
|
|
high_switches_ |= uint8_t(drive_count << 2);
|
|
|
|
|
|
|
|
|
|
// Low switches:
|
|
|
|
|
//
|
|
|
|
|
// b3, b2: RAM on motherboard (64 * bit pattern)
|
|
|
|
|
// b1: 1 => FPU present; 0 => absent;
|
|
|
|
|
// b0: 1 => floppy drive present; 0 => absent.
|
|
|
|
|
low_switches_ |= 0b1100; // Assume maximum RAM.
|
|
|
|
|
if(drive_count) low_switches_ |= 0xb0001;
|
|
|
|
|
}
|
2023-11-22 03:28:33 +00:00
|
|
|
|
|
2023-12-07 15:14:06 +00:00
|
|
|
|
/// Supplies a hint about the user's display choice. If the high switches haven't been read yet and this is a CGA device,
|
|
|
|
|
/// this hint will be used to select between 40- and 80-column default display.
|
|
|
|
|
void hint_is_composite(bool composite) {
|
|
|
|
|
if(high_switches_observed_) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(high_switches_ & 3) {
|
|
|
|
|
// Do nothing if a non-CGA card is in use.
|
|
|
|
|
case 0b00: case 0b11:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
high_switches_ &= ~0b11;
|
|
|
|
|
high_switches_ |= composite ? 0b01 : 0b10;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-20 17:13:42 +00:00
|
|
|
|
void set_value(int port, uint8_t value) {
|
2023-11-20 17:21:37 +00:00
|
|
|
|
switch(port) {
|
|
|
|
|
case 1:
|
2023-11-23 19:51:32 +00:00
|
|
|
|
// b7: 0 => enable keyboard read (and IRQ); 1 => don't;
|
2023-11-22 20:21:45 +00:00
|
|
|
|
// b6: 0 => hold keyboard clock low; 1 => don't;
|
|
|
|
|
// b5: 1 => disable IO check; 0 => don't;
|
|
|
|
|
// b4: 1 => disable memory parity check; 0 => don't;
|
|
|
|
|
// b3: [5150] cassette motor control; [5160] high or low switches select;
|
|
|
|
|
// b2: [5150] high or low switches select; [5160] 1 => disable turbo mode;
|
|
|
|
|
// b1, b0: speaker control.
|
2023-11-23 19:51:32 +00:00
|
|
|
|
enable_keyboard_ = !(value & 0x80);
|
2023-11-24 03:10:51 +00:00
|
|
|
|
keyboard_.set_mode(value >> 6);
|
|
|
|
|
|
2023-12-05 21:38:09 +00:00
|
|
|
|
use_high_switches_ = value & 0x08;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
speaker_.set_control(value & 0x01, value & 0x02);
|
2023-11-20 17:21:37 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2023-11-20 17:13:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_value(int port) {
|
2023-11-20 17:21:37 +00:00
|
|
|
|
switch(port) {
|
2023-11-23 19:51:32 +00:00
|
|
|
|
case 0:
|
2023-12-07 15:14:06 +00:00
|
|
|
|
high_switches_observed_ = true;
|
2023-12-05 21:38:09 +00:00
|
|
|
|
return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_);
|
2023-11-24 03:10:51 +00:00
|
|
|
|
// Guesses that switches is high and low combined as below.
|
2023-11-22 20:21:45 +00:00
|
|
|
|
|
2023-11-20 17:21:37 +00:00
|
|
|
|
case 2:
|
|
|
|
|
// b7: 1 => memory parity error; 0 => none;
|
|
|
|
|
// b6: 1 => IO channel error; 0 => none;
|
|
|
|
|
// b5: timer 2 output; [TODO]
|
|
|
|
|
// b4: cassette data input; [TODO]
|
2023-12-05 21:38:09 +00:00
|
|
|
|
// b3...b0: whichever of the high and low switches is selected.
|
2023-12-07 15:14:06 +00:00
|
|
|
|
high_switches_observed_ |= use_high_switches_;
|
2023-11-21 16:25:53 +00:00
|
|
|
|
return
|
2023-12-05 21:38:09 +00:00
|
|
|
|
use_high_switches_ ? high_switches_ : low_switches_;
|
2023-11-20 17:21:37 +00:00
|
|
|
|
}
|
2023-11-20 17:13:42 +00:00
|
|
|
|
return 0;
|
|
|
|
|
};
|
2023-11-20 17:21:37 +00:00
|
|
|
|
|
2023-11-21 16:25:53 +00:00
|
|
|
|
private:
|
2023-12-07 15:14:06 +00:00
|
|
|
|
bool high_switches_observed_ = false;
|
2023-12-05 21:38:09 +00:00
|
|
|
|
uint8_t high_switches_ = 0;
|
|
|
|
|
uint8_t low_switches_ = 0;
|
|
|
|
|
|
|
|
|
|
bool use_high_switches_ = false;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
PCSpeaker &speaker_;
|
2023-11-24 03:10:51 +00:00
|
|
|
|
KeyboardController &keyboard_;
|
2023-11-21 16:25:53 +00:00
|
|
|
|
|
2023-11-23 19:51:32 +00:00
|
|
|
|
bool enable_keyboard_ = false;
|
2023-11-20 17:13:42 +00:00
|
|
|
|
};
|
|
|
|
|
using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
template <Target::VideoAdaptor video>
|
2023-11-16 18:02:35 +00:00
|
|
|
|
class IO {
|
|
|
|
|
public:
|
2023-12-07 03:56:09 +00:00
|
|
|
|
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc, RTC &rtc) :
|
|
|
|
|
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc), rtc_(rtc) {}
|
2023-11-19 12:15:30 +00:00
|
|
|
|
|
2023-11-20 17:13:42 +00:00
|
|
|
|
template <typename IntT> void out(uint16_t port, IntT value) {
|
2023-12-06 04:00:43 +00:00
|
|
|
|
static constexpr uint16_t crtc_base =
|
2023-12-27 15:44:51 +00:00
|
|
|
|
video == Target::VideoAdaptor::MDA ? 0x03b0 : 0x03d0;
|
2023-12-06 04:00:43 +00:00
|
|
|
|
|
2023-11-16 18:02:35 +00:00
|
|
|
|
switch(port) {
|
|
|
|
|
default:
|
|
|
|
|
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
|
|
|
|
printf("Unhandled out: %02x to %04x\n", value, port);
|
|
|
|
|
} else {
|
|
|
|
|
printf("Unhandled out: %04x to %04x\n", value, port);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2023-12-07 03:56:09 +00:00
|
|
|
|
case 0x0070: rtc_.write<0>(uint8_t(value)); break;
|
|
|
|
|
case 0x0071: rtc_.write<1>(uint8_t(value)); break;
|
|
|
|
|
|
2023-11-16 18:02:35 +00:00
|
|
|
|
// On the XT the NMI can be masked by setting bit 7 on I/O port 0xA0.
|
|
|
|
|
case 0x00a0:
|
|
|
|
|
printf("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked");
|
|
|
|
|
break;
|
2023-11-17 22:35:11 +00:00
|
|
|
|
|
2023-12-05 19:52:14 +00:00
|
|
|
|
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;
|
2023-11-20 03:55:29 +00:00
|
|
|
|
|
2023-12-05 19:44:20 +00:00
|
|
|
|
case 0x0020: pic_.write<0>(uint8_t(value)); break;
|
|
|
|
|
case 0x0021: pic_.write<1>(uint8_t(value)); break;
|
2023-11-20 17:21:37 +00:00
|
|
|
|
|
2023-11-24 03:47:31 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2023-11-17 22:35:11 +00:00
|
|
|
|
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:
|
2023-11-30 17:47:50 +00:00
|
|
|
|
ppi_.write(port, uint8_t(value));
|
2023-11-17 22:35:11 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2023-11-30 17:47:50 +00:00
|
|
|
|
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;
|
2023-11-17 22:35:11 +00:00
|
|
|
|
|
2023-12-05 21:38:09 +00:00
|
|
|
|
//
|
2023-12-06 04:00:43 +00:00
|
|
|
|
// CRTC access block, with slightly laboured 16-bit to 8-bit mapping.
|
2023-12-05 21:38:09 +00:00
|
|
|
|
//
|
2023-12-06 04:00:43 +00:00
|
|
|
|
case crtc_base + 0: case crtc_base + 2:
|
|
|
|
|
case crtc_base + 4: case crtc_base + 6:
|
|
|
|
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
|
|
|
|
video_.template write<0>(uint8_t(value));
|
|
|
|
|
video_.template write<1>(uint8_t(value >> 8));
|
|
|
|
|
} else {
|
|
|
|
|
video_.template write<0>(value);
|
2023-11-22 17:53:09 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
2023-12-06 04:00:43 +00:00
|
|
|
|
case crtc_base + 1: case crtc_base + 3:
|
|
|
|
|
case crtc_base + 5: case crtc_base + 7:
|
|
|
|
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
|
|
|
|
video_.template write<1>(uint8_t(value));
|
|
|
|
|
video_.template write<0>(uint8_t(value >> 8));
|
|
|
|
|
} else {
|
|
|
|
|
video_.template write<1>(value);
|
2023-12-05 21:38:09 +00:00
|
|
|
|
}
|
2023-11-17 22:35:11 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2023-12-06 16:19:04 +00:00
|
|
|
|
case crtc_base + 0x8: video_.template write<0x8>(uint8_t(value)); break;
|
|
|
|
|
case crtc_base + 0x9: video_.template write<0x9>(uint8_t(value)); break;
|
2023-11-17 22:35:11 +00:00
|
|
|
|
|
2023-11-25 03:19:39 +00:00
|
|
|
|
case 0x03f2:
|
|
|
|
|
fdc_.set_digital_output(uint8_t(value));
|
|
|
|
|
break;
|
2023-11-25 23:10:49 +00:00
|
|
|
|
case 0x03f4:
|
2023-11-24 03:47:31 +00:00
|
|
|
|
printf("TODO: FDC write of %02x at %04x\n", value, port);
|
|
|
|
|
break;
|
2023-11-27 15:27:36 +00:00
|
|
|
|
case 0x03f5:
|
|
|
|
|
fdc_.write(uint8_t(value));
|
|
|
|
|
break;
|
2023-11-24 03:47:31 +00:00
|
|
|
|
|
|
|
|
|
case 0x0278: case 0x0279: case 0x027a:
|
|
|
|
|
case 0x0378: case 0x0379: case 0x037a:
|
|
|
|
|
case 0x03bc: case 0x03bd: case 0x03be:
|
|
|
|
|
// Ignore parallel port accesses.
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb:
|
|
|
|
|
case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef:
|
|
|
|
|
case 0x02f8: case 0x02f9: case 0x02fa: case 0x02fb:
|
|
|
|
|
case 0x02fc: case 0x02fd: case 0x02fe: case 0x02ff:
|
|
|
|
|
case 0x03e8: case 0x03e9: case 0x03ea: case 0x03eb:
|
|
|
|
|
case 0x03ec: case 0x03ed: case 0x03ee: case 0x03ef:
|
|
|
|
|
case 0x03f8: case 0x03f9: case 0x03fa: case 0x03fb:
|
|
|
|
|
case 0x03fc: case 0x03fd: case 0x03fe: case 0x03ff:
|
|
|
|
|
// Ignore serial port accesses.
|
|
|
|
|
break;
|
2023-11-16 18:02:35 +00:00
|
|
|
|
}
|
2023-11-16 11:48:24 +00:00
|
|
|
|
}
|
2023-11-16 18:02:35 +00:00
|
|
|
|
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) {
|
2023-11-17 22:35:11 +00:00
|
|
|
|
switch(port) {
|
|
|
|
|
default:
|
|
|
|
|
printf("Unhandled in: %04x\n", port);
|
|
|
|
|
break;
|
|
|
|
|
|
2023-12-05 19:52:14 +00:00
|
|
|
|
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 0x0009: case 0x000b:
|
2023-11-24 03:47:31 +00:00
|
|
|
|
case 0x000c: case 0x000f:
|
2023-12-05 19:52:14 +00:00
|
|
|
|
// DMA area, but it doesn't respond.
|
2023-11-24 03:47:31 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2023-11-20 20:11:22 +00:00
|
|
|
|
case 0x0020: return pic_.read<0>();
|
|
|
|
|
case 0x0021: return pic_.read<1>();
|
|
|
|
|
|
2023-11-19 12:15:30 +00:00
|
|
|
|
case 0x0040: return pit_.read<0>();
|
|
|
|
|
case 0x0041: return pit_.read<1>();
|
|
|
|
|
case 0x0042: return pit_.read<2>();
|
2023-11-17 22:35:11 +00:00
|
|
|
|
|
|
|
|
|
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:
|
2023-11-20 17:13:42 +00:00
|
|
|
|
return ppi_.read(port);
|
2023-11-24 03:47:31 +00:00
|
|
|
|
|
2023-12-07 03:56:09 +00:00
|
|
|
|
case 0x0071: return rtc_.read();
|
|
|
|
|
|
2023-11-30 17:47:50 +00:00
|
|
|
|
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>();
|
|
|
|
|
|
2023-11-24 03:47:31 +00:00
|
|
|
|
case 0x0201: break; // Ignore game port.
|
|
|
|
|
|
|
|
|
|
case 0x0278: case 0x0279: case 0x027a:
|
|
|
|
|
case 0x0378: case 0x0379: case 0x037a:
|
|
|
|
|
case 0x03bc: case 0x03bd: case 0x03be:
|
|
|
|
|
// Ignore parallel port accesses.
|
|
|
|
|
break;
|
|
|
|
|
|
2023-11-25 23:10:49 +00:00
|
|
|
|
case 0x03f4: return fdc_.status();
|
2023-11-27 15:27:36 +00:00
|
|
|
|
case 0x03f5: return fdc_.read();
|
|
|
|
|
|
2023-12-05 21:38:09 +00:00
|
|
|
|
case 0x03b8:
|
2023-12-27 15:44:51 +00:00
|
|
|
|
if constexpr (video == Target::VideoAdaptor::MDA) {
|
2023-12-06 04:00:43 +00:00
|
|
|
|
return video_.template read<0x8>();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x3da:
|
2023-12-27 15:44:51 +00:00
|
|
|
|
if constexpr (video == Target::VideoAdaptor::CGA) {
|
2023-12-06 04:00:43 +00:00
|
|
|
|
return video_.template read<0xa>();
|
2023-12-05 21:38:09 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
2023-12-04 21:34:46 +00:00
|
|
|
|
|
2023-11-24 03:47:31 +00:00
|
|
|
|
case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb:
|
|
|
|
|
case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef:
|
|
|
|
|
case 0x02f8: case 0x02f9: case 0x02fa: case 0x02fb:
|
|
|
|
|
case 0x02fc: case 0x02fd: case 0x02fe: case 0x02ff:
|
|
|
|
|
case 0x03e8: case 0x03e9: case 0x03ea: case 0x03eb:
|
|
|
|
|
case 0x03ec: case 0x03ed: case 0x03ee: case 0x03ef:
|
|
|
|
|
case 0x03f8: case 0x03f9: case 0x03fa: case 0x03fb:
|
|
|
|
|
case 0x03fc: case 0x03fd: case 0x03fe: case 0x03ff:
|
|
|
|
|
// Ignore serial port accesses.
|
|
|
|
|
break;
|
2023-11-17 22:35:11 +00:00
|
|
|
|
}
|
2023-12-06 03:02:41 +00:00
|
|
|
|
return 0xff;
|
2023-11-16 18:02:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2023-11-21 03:36:05 +00:00
|
|
|
|
PIT &pit_;
|
2023-11-20 03:55:29 +00:00
|
|
|
|
DMA &dma_;
|
2023-11-20 17:13:42 +00:00
|
|
|
|
PPI &ppi_;
|
2023-11-20 18:53:44 +00:00
|
|
|
|
PIC &pic_;
|
2023-12-05 21:38:09 +00:00
|
|
|
|
typename Adaptor<video>::type &video_;
|
2023-11-25 03:19:39 +00:00
|
|
|
|
FloppyController &fdc_;
|
2023-12-07 03:56:09 +00:00
|
|
|
|
RTC &rtc_;
|
2023-11-15 20:58:49 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class FlowController {
|
|
|
|
|
public:
|
|
|
|
|
FlowController(Registers ®isters, Segments &segments) :
|
|
|
|
|
registers_(registers), segments_(segments) {}
|
|
|
|
|
|
|
|
|
|
// Requirements for perform.
|
2023-12-24 19:11:41 +00:00
|
|
|
|
template <typename AddressT>
|
|
|
|
|
void jump(AddressT address) {
|
|
|
|
|
static_assert(std::is_same_v<AddressT, uint16_t>);
|
2023-11-15 20:58:49 +00:00
|
|
|
|
registers_.ip() = address;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-24 19:11:41 +00:00
|
|
|
|
template <typename AddressT>
|
|
|
|
|
void jump(uint16_t segment, AddressT address) {
|
|
|
|
|
static_assert(std::is_same_v<AddressT, uint16_t>);
|
2023-11-15 20:58:49 +00:00
|
|
|
|
registers_.cs() = segment;
|
|
|
|
|
segments_.did_update(Segments::Source::CS);
|
|
|
|
|
registers_.ip() = address;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 18:15:01 +00:00
|
|
|
|
void halt() {
|
|
|
|
|
halted_ = true;
|
|
|
|
|
}
|
|
|
|
|
void wait() {
|
|
|
|
|
printf("WAIT ????\n");
|
|
|
|
|
}
|
2023-11-15 20:58:49 +00:00
|
|
|
|
|
|
|
|
|
void repeat_last() {
|
|
|
|
|
should_repeat_ = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Other actions.
|
|
|
|
|
void begin_instruction() {
|
|
|
|
|
should_repeat_ = false;
|
|
|
|
|
}
|
|
|
|
|
bool should_repeat() const {
|
|
|
|
|
return should_repeat_;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 18:15:01 +00:00
|
|
|
|
void unhalt() {
|
|
|
|
|
halted_ = false;
|
|
|
|
|
}
|
|
|
|
|
bool halted() const {
|
|
|
|
|
return halted_;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 20:58:49 +00:00
|
|
|
|
private:
|
|
|
|
|
Registers ®isters_;
|
|
|
|
|
Segments &segments_;
|
|
|
|
|
bool should_repeat_ = false;
|
2023-12-01 18:15:01 +00:00
|
|
|
|
bool halted_ = false;
|
2023-11-15 20:58:49 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
template <Target::VideoAdaptor video>
|
2023-11-15 16:01:28 +00:00
|
|
|
|
class ConcreteMachine:
|
|
|
|
|
public Machine,
|
2023-11-15 19:30:30 +00:00
|
|
|
|
public MachineTypes::TimedMachine,
|
2023-11-22 03:11:32 +00:00
|
|
|
|
public MachineTypes::AudioProducer,
|
2023-11-29 16:31:37 +00:00
|
|
|
|
public MachineTypes::MappedKeyboardMachine,
|
2023-11-29 20:20:14 +00:00
|
|
|
|
public MachineTypes::MediaTarget,
|
|
|
|
|
public MachineTypes::ScanProducer,
|
2023-12-06 19:44:59 +00:00
|
|
|
|
public Activity::Source,
|
|
|
|
|
public Configurable::Device
|
2023-11-15 16:01:28 +00:00
|
|
|
|
{
|
2023-12-05 21:38:09 +00:00
|
|
|
|
static constexpr int DriveCount = 1;
|
|
|
|
|
using Video = typename Adaptor<video>::type;
|
|
|
|
|
|
2023-11-15 16:01:28 +00:00
|
|
|
|
public:
|
|
|
|
|
ConcreteMachine(
|
2023-12-05 21:38:09 +00:00
|
|
|
|
const Analyser::Static::PCCompatible::Target &target,
|
2023-11-19 12:15:30 +00:00
|
|
|
|
const ROMMachine::ROMFetcher &rom_fetcher
|
2023-11-22 03:11:32 +00:00
|
|
|
|
) :
|
2023-11-24 03:10:51 +00:00
|
|
|
|
keyboard_(pic_),
|
2023-12-05 21:38:09 +00:00
|
|
|
|
fdc_(pic_, dma_, DriveCount),
|
2023-11-22 03:28:33 +00:00
|
|
|
|
pit_observer_(pic_, speaker_),
|
2023-12-05 21:38:09 +00:00
|
|
|
|
ppi_handler_(speaker_, keyboard_, video, DriveCount),
|
2023-11-22 03:11:32 +00:00
|
|
|
|
pit_(pit_observer_),
|
|
|
|
|
ppi_(ppi_handler_),
|
2023-12-07 03:56:09 +00:00
|
|
|
|
context(pit_, dma_, ppi_, pic_, video_, fdc_, rtc_)
|
2023-11-22 03:11:32 +00:00
|
|
|
|
{
|
2023-12-27 15:44:51 +00:00
|
|
|
|
// Capture speed.
|
|
|
|
|
speed_ = target.speed;
|
|
|
|
|
|
2023-11-30 17:47:50 +00:00
|
|
|
|
// Set up DMA source/target.
|
|
|
|
|
dma_.set_memory(&context.memory);
|
|
|
|
|
|
2023-11-19 20:52:32 +00:00
|
|
|
|
// 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;
|
2023-11-22 03:02:24 +00:00
|
|
|
|
set_clock_rate(double(pit_frequency));
|
2023-11-22 03:28:33 +00:00
|
|
|
|
speaker_.speaker.set_input_rate(double(pit_frequency));
|
2023-11-15 16:32:23 +00:00
|
|
|
|
|
|
|
|
|
// Fetch the BIOS. [8088 only, for now]
|
2023-11-16 03:02:53 +00:00
|
|
|
|
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
|
2023-12-07 03:56:09 +00:00
|
|
|
|
const auto tick = ROM::Name::PCCompatibleGLaTICK;
|
2023-12-05 21:46:39 +00:00
|
|
|
|
const auto font = Video::FontROM;
|
2023-11-16 03:02:53 +00:00
|
|
|
|
|
2023-12-27 15:10:42 +00:00
|
|
|
|
ROM::Request request = ROM::Request(bios) && ROM::Request(tick, true) && ROM::Request(font);
|
2023-11-15 16:32:23 +00:00
|
|
|
|
auto roms = rom_fetcher(request);
|
|
|
|
|
if(!request.validate(roms)) {
|
|
|
|
|
throw ROMMachine::Error::MissingROMs;
|
|
|
|
|
}
|
2023-11-16 03:02:53 +00:00
|
|
|
|
|
2023-12-27 15:10:42 +00:00
|
|
|
|
// A BIOS is mandatory.
|
2023-11-16 03:02:53 +00:00
|
|
|
|
const auto &bios_contents = roms.find(bios)->second;
|
|
|
|
|
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
2023-11-22 18:52:28 +00:00
|
|
|
|
|
2023-12-27 15:10:42 +00:00
|
|
|
|
// 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());
|
|
|
|
|
}
|
2023-12-07 03:56:09 +00:00
|
|
|
|
|
2023-12-05 21:38:09 +00:00
|
|
|
|
// Give the video card something to read from.
|
2023-11-22 19:11:22 +00:00
|
|
|
|
const auto &font_contents = roms.find(font)->second;
|
2023-12-05 21:46:39 +00:00
|
|
|
|
video_.set_source(context.memory.at(Video::BaseAddress), font_contents);
|
2023-11-29 20:20:14 +00:00
|
|
|
|
|
|
|
|
|
// ... and insert media.
|
|
|
|
|
insert_media(target.media);
|
2023-11-15 16:01:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 03:11:32 +00:00
|
|
|
|
~ConcreteMachine() {
|
2023-11-22 03:28:33 +00:00
|
|
|
|
speaker_.queue.flush();
|
2023-11-22 03:11:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 19:30:30 +00:00
|
|
|
|
// MARK: - TimedMachine.
|
2023-11-22 03:02:24 +00:00
|
|
|
|
void run_for(const Cycles duration) override {
|
2023-12-27 15:44:51 +00:00
|
|
|
|
switch(speed_) {
|
|
|
|
|
case Target::Speed::ApproximatelyOriginal: run_for<Target::Speed::ApproximatelyOriginal>(duration); break;
|
2024-12-01 14:04:32 +00:00
|
|
|
|
case Target::Speed::Fast: run_for<Target::Speed::Fast>(duration); break;
|
2023-12-27 15:44:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <Target::Speed speed>
|
|
|
|
|
void run_for(const Cycles duration) {
|
2023-12-24 19:16:12 +00:00
|
|
|
|
const auto pit_ticks = duration.as<int>();
|
2023-12-08 14:38:55 +00:00
|
|
|
|
|
|
|
|
|
int ticks;
|
2023-12-27 15:44:51 +00:00
|
|
|
|
if constexpr (speed == Target::Speed::Fast) {
|
|
|
|
|
ticks = pit_ticks;
|
|
|
|
|
} else {
|
2023-12-08 14:38:55 +00:00
|
|
|
|
cpu_divisor_ += pit_ticks;
|
|
|
|
|
ticks = cpu_divisor_ / 3;
|
|
|
|
|
cpu_divisor_ %= 3;
|
|
|
|
|
}
|
2023-11-22 03:42:53 +00:00
|
|
|
|
|
|
|
|
|
while(ticks--) {
|
2023-11-21 03:52:20 +00:00
|
|
|
|
//
|
2023-11-22 03:02:24 +00:00
|
|
|
|
// First draft: all hardware runs in lockstep, as a multiple or divisor of the PIT frequency.
|
2023-11-21 03:52:20 +00:00
|
|
|
|
//
|
|
|
|
|
|
2023-11-22 03:42:53 +00:00
|
|
|
|
//
|
|
|
|
|
// Advance the PIT and audio.
|
|
|
|
|
//
|
|
|
|
|
pit_.run_for(1);
|
|
|
|
|
++speaker_.cycles_since_update;
|
2023-12-08 14:38:55 +00:00
|
|
|
|
|
2023-12-27 15:52:43 +00:00
|
|
|
|
// For original speed, the CPU performs instructions at a 1/3rd divider of the PIT clock,
|
|
|
|
|
// so run the PIT three times per 'tick'.
|
2023-12-27 15:44:51 +00:00
|
|
|
|
if constexpr (speed == Target::Speed::ApproximatelyOriginal) {
|
2023-12-08 14:38:55 +00:00
|
|
|
|
pit_.run_for(1);
|
|
|
|
|
++speaker_.cycles_since_update;
|
|
|
|
|
pit_.run_for(1);
|
|
|
|
|
++speaker_.cycles_since_update;
|
|
|
|
|
}
|
2023-11-22 03:42:53 +00:00
|
|
|
|
|
2023-11-22 17:53:09 +00:00
|
|
|
|
//
|
|
|
|
|
// Advance CRTC at a more approximate rate.
|
|
|
|
|
//
|
2023-12-27 15:44:51 +00:00
|
|
|
|
video_.run_for(speed == Target::Speed::Fast ? Cycles(1) : Cycles(3));
|
2023-12-08 14:38:55 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Give the keyboard a notification of passing time; it's very approximately clocked,
|
|
|
|
|
// really just including 'some' delays to avoid being instant.
|
|
|
|
|
//
|
|
|
|
|
keyboard_.run_for(Cycles(1));
|
2023-11-22 17:53:09 +00:00
|
|
|
|
|
2023-11-22 03:42:53 +00:00
|
|
|
|
//
|
|
|
|
|
// Perform one CPU instruction every three PIT cycles.
|
|
|
|
|
// i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS.
|
|
|
|
|
//
|
2023-11-22 03:11:32 +00:00
|
|
|
|
|
2023-11-22 03:42:53 +00:00
|
|
|
|
// Query for interrupts and apply if pending.
|
2023-12-05 20:19:58 +00:00
|
|
|
|
if(pic_.pending() && context.flags.template flag<InstructionSet::x86::Flag::Interrupt>()) {
|
2023-11-22 03:42:53 +00:00
|
|
|
|
// 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();
|
2023-11-21 03:52:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 03:42:53 +00:00
|
|
|
|
// Signal interrupt.
|
2023-12-01 18:15:01 +00:00
|
|
|
|
context.flow_controller.unhalt();
|
2023-11-22 03:42:53 +00:00
|
|
|
|
InstructionSet::x86::interrupt(
|
|
|
|
|
pic_.acknowledge(),
|
|
|
|
|
context
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-11-21 03:52:20 +00:00
|
|
|
|
|
2023-12-08 14:38:55 +00:00
|
|
|
|
// Do nothing if currently halted.
|
2023-12-01 18:15:01 +00:00
|
|
|
|
if(context.flow_controller.halted()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
if constexpr (speed == Target::Speed::Fast) {
|
2023-12-27 15:52:43 +00:00
|
|
|
|
// There's no divider applied, so this makes for 2*PIT = around 2.4 MIPS.
|
2023-12-08 14:38:55 +00:00
|
|
|
|
// That's broadly 80286 speed, if MIPS were a valid measure.
|
|
|
|
|
perform_instruction();
|
|
|
|
|
perform_instruction();
|
2023-11-22 03:42:53 +00:00
|
|
|
|
} else {
|
2023-12-08 14:38:55 +00:00
|
|
|
|
// With the clock divider above, this makes for a net of PIT/3 = around 0.4 MIPS.
|
|
|
|
|
// i.e. a shade more than 8086 speed, if MIPS were meaningful.
|
|
|
|
|
perform_instruction();
|
2023-11-22 03:42:53 +00:00
|
|
|
|
}
|
2023-11-15 20:58:49 +00:00
|
|
|
|
|
2023-12-08 14:38:55 +00:00
|
|
|
|
// Other inevitably broad and fuzzy and inconsistent MIPS counts for my own potential future play:
|
|
|
|
|
//
|
|
|
|
|
// 80386 @ 20Mhz: 4–5 MIPS.
|
|
|
|
|
// 80486 @ 66Mhz: 25 MIPS.
|
|
|
|
|
// Pentium @ 100Mhz: 188 MIPS.
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-02 04:35:11 +00:00
|
|
|
|
|
2023-12-08 14:38:55 +00:00
|
|
|
|
void perform_instruction() {
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
// If that didn't yield a whole instruction then the end of memory must have been hit;
|
|
|
|
|
// continue from the beginning.
|
|
|
|
|
if(decoded.first <= 0) {
|
|
|
|
|
const auto all = context.memory.all();
|
|
|
|
|
decoded = decoder.decode(all.first, all.second);
|
|
|
|
|
}
|
2023-11-21 16:19:47 +00:00
|
|
|
|
|
2023-12-08 14:38:55 +00:00
|
|
|
|
context.registers.ip() += decoded.first;
|
|
|
|
|
} else {
|
|
|
|
|
context.flow_controller.begin_instruction();
|
2023-11-15 20:58:49 +00:00
|
|
|
|
}
|
2023-12-08 14:38:55 +00:00
|
|
|
|
|
|
|
|
|
/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) {
|
|
|
|
|
const auto next = to_string(decoded, InstructionSet::x86::Model::i8086);
|
|
|
|
|
// if(next != previous) {
|
|
|
|
|
std::cout << std::hex << decoded_ip_ << " " << next;
|
|
|
|
|
|
|
|
|
|
if(decoded.second.operation() == InstructionSet::x86::Operation::INT) {
|
|
|
|
|
std::cout << " dl:" << std::hex << +context.registers.dl() << "; ";
|
|
|
|
|
std::cout << "ah:" << std::hex << +context.registers.ah() << "; ";
|
|
|
|
|
std::cout << "ch:" << std::hex << +context.registers.ch() << "; ";
|
|
|
|
|
std::cout << "cl:" << std::hex << +context.registers.cl() << "; ";
|
|
|
|
|
std::cout << "dh:" << std::hex << +context.registers.dh() << "; ";
|
|
|
|
|
std::cout << "es:" << std::hex << +context.registers.es() << "; ";
|
|
|
|
|
std::cout << "bx:" << std::hex << +context.registers.bx();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
// previous = next;
|
|
|
|
|
// }
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
// Execute it.
|
|
|
|
|
InstructionSet::x86::perform(
|
|
|
|
|
decoded.second,
|
|
|
|
|
context
|
|
|
|
|
);
|
2023-11-15 20:58:49 +00:00
|
|
|
|
}
|
2023-11-15 16:32:23 +00:00
|
|
|
|
|
2023-11-15 19:30:30 +00:00
|
|
|
|
// MARK: - ScanProducer.
|
2023-11-22 17:53:09 +00:00
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
2023-12-05 21:38:09 +00:00
|
|
|
|
video_.set_scan_target(scan_target);
|
2023-11-22 17:53:09 +00:00
|
|
|
|
}
|
2023-11-15 19:30:30 +00:00
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
2023-12-05 21:38:09 +00:00
|
|
|
|
return video_.get_scaled_scan_status();
|
2023-11-15 19:30:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 03:11:32 +00:00
|
|
|
|
// MARK: - AudioProducer.
|
|
|
|
|
Outputs::Speaker::Speaker *get_speaker() override {
|
2023-11-22 03:28:33 +00:00
|
|
|
|
return &speaker_.speaker;
|
2023-11-22 03:11:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void flush_output(int outputs) final {
|
|
|
|
|
if(outputs & Output::Audio) {
|
2023-11-22 03:28:33 +00:00
|
|
|
|
speaker_.update();
|
|
|
|
|
speaker_.queue.perform();
|
2023-11-22 03:11:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 20:20:14 +00:00
|
|
|
|
// MARK: - MediaTarget
|
|
|
|
|
bool insert_media(const Analyser::Static::Media &media) override {
|
|
|
|
|
int c = 0;
|
|
|
|
|
for(auto &disk : media.disks) {
|
|
|
|
|
fdc_.set_disk(disk, c);
|
|
|
|
|
c++;
|
|
|
|
|
if(c == 4) break;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 18:38:06 +00:00
|
|
|
|
// MARK: - MappedKeyboardMachine.
|
|
|
|
|
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
|
|
|
|
|
return &keyboard_mapper_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_key_state(uint16_t key, bool is_pressed) override {
|
|
|
|
|
keyboard_.post(uint8_t(key | (is_pressed ? 0x00 : 0x80)));
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 16:31:37 +00:00
|
|
|
|
// MARK: - Activity::Source.
|
|
|
|
|
void set_activity_observer(Activity::Observer *observer) final {
|
|
|
|
|
fdc_.set_activity_observer(observer);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 19:44:59 +00:00
|
|
|
|
// MARK: - Configuration options.
|
|
|
|
|
std::unique_ptr<Reflection::Struct> get_options() override {
|
|
|
|
|
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 {
|
|
|
|
|
const auto options = dynamic_cast<Options *>(str.get());
|
|
|
|
|
set_video_signal_configurable(options->output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
|
|
|
|
video_.set_display_type(display_type);
|
2023-12-07 19:21:09 +00:00
|
|
|
|
|
|
|
|
|
// 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));
|
2023-12-06 19:44:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Outputs::Display::DisplayType get_display_type() const override {
|
|
|
|
|
return video_.get_display_type();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 16:32:23 +00:00
|
|
|
|
private:
|
2023-11-21 03:36:05 +00:00
|
|
|
|
PIC pic_;
|
2023-11-20 03:55:29 +00:00
|
|
|
|
DMA dma_;
|
2023-11-22 03:28:33 +00:00
|
|
|
|
PCSpeaker speaker_;
|
2023-12-05 21:38:09 +00:00
|
|
|
|
Video video_;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
|
2023-11-24 03:10:51 +00:00
|
|
|
|
KeyboardController keyboard_;
|
2023-11-25 03:19:39 +00:00
|
|
|
|
FloppyController fdc_;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
PITObserver pit_observer_;
|
2023-11-20 17:13:42 +00:00
|
|
|
|
i8255PortHandler ppi_handler_;
|
2023-11-21 03:36:05 +00:00
|
|
|
|
|
|
|
|
|
PIT pit_;
|
2023-11-20 17:13:42 +00:00
|
|
|
|
PPI ppi_;
|
2023-12-07 03:56:09 +00:00
|
|
|
|
RTC rtc_;
|
2023-11-19 12:15:30 +00:00
|
|
|
|
|
2023-11-24 18:38:06 +00:00
|
|
|
|
PCCompatible::KeyboardMapper keyboard_mapper_;
|
|
|
|
|
|
2023-11-15 20:58:49 +00:00
|
|
|
|
struct Context {
|
2023-12-07 03:56:09 +00:00
|
|
|
|
Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc, RTC &rtc) :
|
2023-11-15 20:58:49 +00:00
|
|
|
|
segments(registers),
|
|
|
|
|
memory(registers, segments),
|
2023-11-19 12:15:30 +00:00
|
|
|
|
flow_controller(registers, segments),
|
2023-12-07 03:56:09 +00:00
|
|
|
|
io(pit, dma, ppi, pic, card, fdc, rtc)
|
2023-11-15 20:58:49 +00:00
|
|
|
|
{
|
|
|
|
|
reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void reset() {
|
|
|
|
|
registers.reset();
|
|
|
|
|
segments.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InstructionSet::x86::Flags flags;
|
|
|
|
|
Registers registers;
|
|
|
|
|
Segments segments;
|
|
|
|
|
Memory memory;
|
|
|
|
|
FlowController flow_controller;
|
2023-12-05 20:19:58 +00:00
|
|
|
|
IO<video> io;
|
2023-11-15 20:58:49 +00:00
|
|
|
|
static constexpr auto model = InstructionSet::x86::Model::i8086;
|
|
|
|
|
} context;
|
2023-11-17 22:09:20 +00:00
|
|
|
|
|
|
|
|
|
// TODO: eliminate use of Decoder8086 and Decoder8086 in gneral in favour of the templated version, as soon
|
|
|
|
|
// as whatever error is preventing GCC from picking up Decoder's explicit instantiations becomes apparent.
|
2023-11-17 22:02:46 +00:00
|
|
|
|
InstructionSet::x86::Decoder8086 decoder;
|
|
|
|
|
// InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
|
2023-11-17 22:09:20 +00:00
|
|
|
|
|
2023-11-21 03:52:20 +00:00
|
|
|
|
uint16_t decoded_ip_ = 0;
|
2023-11-15 21:10:37 +00:00
|
|
|
|
std::pair<int, InstructionSet::x86::Instruction<false>> decoded;
|
2023-11-22 03:02:24 +00:00
|
|
|
|
|
|
|
|
|
int cpu_divisor_ = 0;
|
2023-12-27 15:44:51 +00:00
|
|
|
|
Target::Speed speed_{};
|
2023-11-15 16:01:28 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace PCCompatible;
|
|
|
|
|
|
2024-01-13 03:03:19 +00:00
|
|
|
|
std::unique_ptr<Machine> Machine::PCCompatible(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
2023-12-05 20:19:58 +00:00
|
|
|
|
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
|
|
|
|
|
2023-12-27 15:44:51 +00:00
|
|
|
|
switch(pc_target->adaptor) {
|
2024-01-13 03:03:19 +00:00
|
|
|
|
case Target::VideoAdaptor::MDA: return std::make_unique<PCCompatible::ConcreteMachine<Target::VideoAdaptor::MDA>>(*pc_target, rom_fetcher);
|
|
|
|
|
case Target::VideoAdaptor::CGA: return std::make_unique<PCCompatible::ConcreteMachine<Target::VideoAdaptor::CGA>>(*pc_target, rom_fetcher);
|
2023-12-27 15:44:51 +00:00
|
|
|
|
default: return nullptr;
|
2023-12-05 20:19:58 +00:00
|
|
|
|
}
|
2023-11-15 16:01:28 +00:00
|
|
|
|
}
|