mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-01 11:49:58 +00:00
Loosen the assumption of MDA.
This commit is contained in:
parent
6659a1dec5
commit
bdf9c4765b
216
Machines/PCCompatible/MDA.hpp
Normal file
216
Machines/PCCompatible/MDA.hpp
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
//
|
||||||
|
// MDA.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 05/12/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MDA_h
|
||||||
|
#define MDA_h
|
||||||
|
|
||||||
|
#include "../../Components/6845/CRTC6845.hpp"
|
||||||
|
#include "../../Outputs/CRT/CRT.hpp"
|
||||||
|
|
||||||
|
namespace PCCompatible {
|
||||||
|
|
||||||
|
class MDA {
|
||||||
|
public:
|
||||||
|
MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
||||||
|
|
||||||
|
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
|
||||||
|
outputter_.ram = ram;
|
||||||
|
outputter_.font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_for(Cycles cycles) {
|
||||||
|
// I _think_ the MDA's CRTC is clocked at 14/9ths the PIT clock.
|
||||||
|
// Do that conversion here.
|
||||||
|
full_clock_ += 14 * cycles.as<int>();
|
||||||
|
crtc_.run_for(Cycles(full_clock_ / 9));
|
||||||
|
full_clock_ %= 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int address>
|
||||||
|
void write(uint8_t value) {
|
||||||
|
if constexpr (address & 0x8) {
|
||||||
|
outputter_.set_control(value);
|
||||||
|
} else {
|
||||||
|
if constexpr (address & 0x1) {
|
||||||
|
crtc_.set_register(value);
|
||||||
|
} else {
|
||||||
|
crtc_.select_register(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int address>
|
||||||
|
uint8_t read() {
|
||||||
|
if constexpr (address & 0x8) {
|
||||||
|
return outputter_.control();
|
||||||
|
} else {
|
||||||
|
return crtc_.get_register();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Call-ins for ScanProducer.
|
||||||
|
|
||||||
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||||
|
outputter_.crt.set_scan_target(scan_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||||
|
return outputter_.crt.get_scaled_scan_status() * 9.0f / 14.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CRTCOutputter {
|
||||||
|
CRTCOutputter() :
|
||||||
|
crt(882, 9, 382, 3, Outputs::Display::InputDataType::Red2Green2Blue2)
|
||||||
|
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
|
||||||
|
// consider whether that's worth building into the scan target.
|
||||||
|
{
|
||||||
|
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
|
||||||
|
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_control(uint8_t control) {
|
||||||
|
// b0: 'high resolution' (probably breaks if not 1)
|
||||||
|
// b3: video enable
|
||||||
|
// b5: enable blink
|
||||||
|
control_ = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t control() {
|
||||||
|
return control_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
|
||||||
|
// Determine new output state.
|
||||||
|
const OutputState new_state =
|
||||||
|
(state.hsync | state.vsync) ? OutputState::Sync :
|
||||||
|
((state.display_enable && control_&0x08) ? OutputState::Pixels : OutputState::Border);
|
||||||
|
|
||||||
|
// Upon either a state change or just having accumulated too much local time...
|
||||||
|
if(new_state != output_state || count > 882) {
|
||||||
|
// (1) flush preexisting state.
|
||||||
|
if(count) {
|
||||||
|
switch(output_state) {
|
||||||
|
case OutputState::Sync: crt.output_sync(count); break;
|
||||||
|
case OutputState::Border: crt.output_blank(count); break;
|
||||||
|
case OutputState::Pixels:
|
||||||
|
crt.output_data(count);
|
||||||
|
pixels = pixel_pointer = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (2) adopt new state.
|
||||||
|
output_state = new_state;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect pixels if applicable.
|
||||||
|
if(output_state == OutputState::Pixels) {
|
||||||
|
if(!pixels) {
|
||||||
|
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
|
||||||
|
|
||||||
|
// Flush any period where pixels weren't recorded due to back pressure.
|
||||||
|
if(pixels && count) {
|
||||||
|
crt.output_blank(count);
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pixels) {
|
||||||
|
static constexpr uint8_t high_intensity = 0x0d;
|
||||||
|
static constexpr uint8_t low_intensity = 0x09;
|
||||||
|
static constexpr uint8_t off = 0x00;
|
||||||
|
|
||||||
|
if(state.cursor) {
|
||||||
|
pixel_pointer[0] = pixel_pointer[1] = pixel_pointer[2] = pixel_pointer[3] =
|
||||||
|
pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] =
|
||||||
|
pixel_pointer[8] = low_intensity;
|
||||||
|
} else {
|
||||||
|
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff];
|
||||||
|
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff];
|
||||||
|
uint8_t row = font[(glyph * 14) + state.row_address];
|
||||||
|
|
||||||
|
const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity;
|
||||||
|
uint8_t blank = off;
|
||||||
|
|
||||||
|
// Handle irregular attributes.
|
||||||
|
// Cf. http://www.seasip.info/VintagePC/mda.html#memmap
|
||||||
|
switch(attributes) {
|
||||||
|
case 0x00: case 0x08: case 0x80: case 0x88:
|
||||||
|
row = 0;
|
||||||
|
break;
|
||||||
|
case 0x70: case 0x78: case 0xf0: case 0xf8:
|
||||||
|
row ^= 0xff;
|
||||||
|
blank = intensity;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply blink if enabled.
|
||||||
|
if((control_ & 0x20) && (attributes & 0x80) && (state.field_count & 16)) {
|
||||||
|
row ^= 0xff;
|
||||||
|
blank = (blank == off) ? intensity : off;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(((attributes & 7) == 1) && state.row_address == 13) {
|
||||||
|
// Draw as underline.
|
||||||
|
std::fill(pixel_pointer, pixel_pointer + 9, intensity);
|
||||||
|
} else {
|
||||||
|
// Draw according to ROM contents, possibly duplicating final column.
|
||||||
|
pixel_pointer[0] = (row & 0x80) ? intensity : off;
|
||||||
|
pixel_pointer[1] = (row & 0x40) ? intensity : off;
|
||||||
|
pixel_pointer[2] = (row & 0x20) ? intensity : off;
|
||||||
|
pixel_pointer[3] = (row & 0x10) ? intensity : off;
|
||||||
|
pixel_pointer[4] = (row & 0x08) ? intensity : off;
|
||||||
|
pixel_pointer[5] = (row & 0x04) ? intensity : off;
|
||||||
|
pixel_pointer[6] = (row & 0x02) ? intensity : off;
|
||||||
|
pixel_pointer[7] = (row & 0x01) ? intensity : off;
|
||||||
|
pixel_pointer[8] = (glyph >= 0xc0 && glyph < 0xe0) ? pixel_pointer[7] : blank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixel_pointer += 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance.
|
||||||
|
count += 9;
|
||||||
|
|
||||||
|
// Output pixel row prematurely if storage is exhausted.
|
||||||
|
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
|
||||||
|
crt.output_data(count);
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
pixels = pixel_pointer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
|
||||||
|
|
||||||
|
Outputs::CRT::CRT crt;
|
||||||
|
|
||||||
|
enum class OutputState {
|
||||||
|
Sync, Pixels, Border
|
||||||
|
} output_state = OutputState::Sync;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
uint8_t *pixels = nullptr;
|
||||||
|
uint8_t *pixel_pointer = nullptr;
|
||||||
|
static constexpr size_t DefaultAllocationSize = 720;
|
||||||
|
|
||||||
|
const uint8_t *ram = nullptr;
|
||||||
|
std::vector<uint8_t> font;
|
||||||
|
|
||||||
|
uint8_t control_ = 0;
|
||||||
|
} outputter_;
|
||||||
|
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
||||||
|
|
||||||
|
int full_clock_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MDA_h */
|
@ -13,13 +13,13 @@
|
|||||||
#include "PIT.hpp"
|
#include "PIT.hpp"
|
||||||
#include "DMA.hpp"
|
#include "DMA.hpp"
|
||||||
#include "Memory.hpp"
|
#include "Memory.hpp"
|
||||||
|
#include "MDA.hpp"
|
||||||
|
|
||||||
#include "../../InstructionSets/x86/Decoder.hpp"
|
#include "../../InstructionSets/x86/Decoder.hpp"
|
||||||
#include "../../InstructionSets/x86/Flags.hpp"
|
#include "../../InstructionSets/x86/Flags.hpp"
|
||||||
#include "../../InstructionSets/x86/Instruction.hpp"
|
#include "../../InstructionSets/x86/Instruction.hpp"
|
||||||
#include "../../InstructionSets/x86/Perform.hpp"
|
#include "../../InstructionSets/x86/Perform.hpp"
|
||||||
|
|
||||||
#include "../../Components/6845/CRTC6845.hpp"
|
|
||||||
#include "../../Components/8255/i8255.hpp"
|
#include "../../Components/8255/i8255.hpp"
|
||||||
#include "../../Components/8272/CommandDecoder.hpp"
|
#include "../../Components/8272/CommandDecoder.hpp"
|
||||||
#include "../../Components/8272/Results.hpp"
|
#include "../../Components/8272/Results.hpp"
|
||||||
@ -50,22 +50,24 @@ namespace PCCompatible {
|
|||||||
|
|
||||||
using VideoAdaptor = Analyser::Static::PCCompatible::Target::VideoAdaptor;
|
using VideoAdaptor = Analyser::Static::PCCompatible::Target::VideoAdaptor;
|
||||||
|
|
||||||
//bool log = false;
|
template <VideoAdaptor adaptor> struct Adaptor;
|
||||||
//std::string previous;
|
template <> struct Adaptor<VideoAdaptor::MDA> {
|
||||||
|
using type = MDA;
|
||||||
|
};
|
||||||
|
template <> struct Adaptor<VideoAdaptor::CGA> {
|
||||||
|
using type = MDA;
|
||||||
|
};
|
||||||
|
|
||||||
class FloppyController {
|
class FloppyController {
|
||||||
public:
|
public:
|
||||||
FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {
|
FloppyController(PIC &pic, DMA &dma, int drive_count) : pic_(pic), dma_(dma) {
|
||||||
// Default: one floppy drive only.
|
// Default: one floppy drive only.
|
||||||
drives_[0].exists = true;
|
for(int c = 0; c < 4; c++) {
|
||||||
drives_[1].exists = false;
|
drives_[0].exists = drive_count >= c;
|
||||||
drives_[2].exists = false;
|
}
|
||||||
drives_[3].exists = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_digital_output(uint8_t control) {
|
void set_digital_output(uint8_t control) {
|
||||||
// printf("FDC DOR: %02x\n", control);
|
|
||||||
|
|
||||||
// b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1;
|
// b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1;
|
||||||
// b3: 1 => enable DMA; 0 => disable;
|
// b3: 1 => enable DMA; 0 => disable;
|
||||||
// b2: 1 => enable FDC; 0 => hold at reset;
|
// b2: 1 => enable FDC; 0 => hold at reset;
|
||||||
@ -96,7 +98,6 @@ class FloppyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t status() const {
|
uint8_t status() const {
|
||||||
// printf("FDC: read status %02x\n", status_.main());
|
|
||||||
return status_.main();
|
return status_.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +188,6 @@ class FloppyController {
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::Recalibrate:
|
case Command::Recalibrate:
|
||||||
printf("FDC: Recalibrate\n");
|
|
||||||
drives_[decoder_.target().drive].track = 0;
|
drives_[decoder_.target().drive].track = 0;
|
||||||
|
|
||||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||||
@ -195,7 +195,6 @@ class FloppyController {
|
|||||||
pic_.apply_edge<6>(true);
|
pic_.apply_edge<6>(true);
|
||||||
break;
|
break;
|
||||||
case Command::Seek:
|
case Command::Seek:
|
||||||
printf("FDC: Seek %d:%d to %d\n", decoder_.target().drive, decoder_.target().head, decoder_.seek_target());
|
|
||||||
drives_[decoder_.target().drive].track = decoder_.seek_target();
|
drives_[decoder_.target().drive].track = decoder_.seek_target();
|
||||||
|
|
||||||
drives_[decoder_.target().drive].raised_interrupt = true;
|
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||||
@ -204,8 +203,6 @@ class FloppyController {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::SenseInterruptStatus: {
|
case Command::SenseInterruptStatus: {
|
||||||
printf("FDC: SenseInterruptStatus\n");
|
|
||||||
|
|
||||||
int c = 0;
|
int c = 0;
|
||||||
for(; c < 4; c++) {
|
for(; c < 4; c++) {
|
||||||
if(drives_[c].raised_interrupt) {
|
if(drives_[c].raised_interrupt) {
|
||||||
@ -224,14 +221,12 @@ class FloppyController {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Command::Specify:
|
case Command::Specify:
|
||||||
printf("FDC: Specify\n");
|
|
||||||
specify_specs_ = decoder_.specify_specs();
|
specify_specs_ = decoder_.specify_specs();
|
||||||
break;
|
break;
|
||||||
// case Command::SenseDriveStatus: {
|
// case Command::SenseDriveStatus: {
|
||||||
// } break;
|
// } break;
|
||||||
|
|
||||||
case Command::Invalid:
|
case Command::Invalid:
|
||||||
printf("FDC: Invalid\n");
|
|
||||||
results_.serialise_none();
|
results_.serialise_none();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -256,11 +251,9 @@ class FloppyController {
|
|||||||
status_.set(MainStatus::DataIsToProcessor, false);
|
status_.set(MainStatus::DataIsToProcessor, false);
|
||||||
status_.set(MainStatus::CommandInProgress, false);
|
status_.set(MainStatus::CommandInProgress, false);
|
||||||
}
|
}
|
||||||
// printf("FDC read: %02x\n", result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("FDC read?\n");
|
|
||||||
return 0x80;
|
return 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,203 +456,6 @@ class KeyboardController {
|
|||||||
int reset_delay_ = 0;
|
int reset_delay_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MDA {
|
|
||||||
public:
|
|
||||||
MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
|
||||||
|
|
||||||
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
|
|
||||||
outputter_.ram = ram;
|
|
||||||
outputter_.font = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run_for(Cycles cycles) {
|
|
||||||
// I _think_ the MDA's CRTC is clocked at 14/9ths the PIT clock.
|
|
||||||
// Do that conversion here.
|
|
||||||
full_clock_ += 14 * cycles.as<int>();
|
|
||||||
crtc_.run_for(Cycles(full_clock_ / 9));
|
|
||||||
full_clock_ %= 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int address>
|
|
||||||
void write(uint8_t value) {
|
|
||||||
if constexpr (address & 0x8) {
|
|
||||||
outputter_.set_control(value);
|
|
||||||
} else {
|
|
||||||
if constexpr (address & 0x1) {
|
|
||||||
crtc_.set_register(value);
|
|
||||||
} else {
|
|
||||||
crtc_.select_register(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int address>
|
|
||||||
uint8_t read() {
|
|
||||||
if constexpr (address & 0x8) {
|
|
||||||
return outputter_.control();
|
|
||||||
} else {
|
|
||||||
return crtc_.get_register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Call-ins for ScanProducer.
|
|
||||||
|
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
|
||||||
outputter_.crt.set_scan_target(scan_target);
|
|
||||||
}
|
|
||||||
|
|
||||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
|
||||||
return outputter_.crt.get_scaled_scan_status() * 9.0f / 14.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct CRTCOutputter {
|
|
||||||
CRTCOutputter() :
|
|
||||||
crt(882, 9, 382, 3, Outputs::Display::InputDataType::Red2Green2Blue2)
|
|
||||||
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
|
|
||||||
// consider whether that's worth building into the scan target.
|
|
||||||
{
|
|
||||||
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
|
|
||||||
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_control(uint8_t control) {
|
|
||||||
// b0: 'high resolution' (probably breaks if not 1)
|
|
||||||
// b3: video enable
|
|
||||||
// b5: enable blink
|
|
||||||
control_ = control;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t control() {
|
|
||||||
return control_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
|
|
||||||
// Determine new output state.
|
|
||||||
const OutputState new_state =
|
|
||||||
(state.hsync | state.vsync) ? OutputState::Sync :
|
|
||||||
((state.display_enable && control_&0x08) ? OutputState::Pixels : OutputState::Border);
|
|
||||||
|
|
||||||
// Upon either a state change or just having accumulated too much local time...
|
|
||||||
if(new_state != output_state || count > 882) {
|
|
||||||
// (1) flush preexisting state.
|
|
||||||
if(count) {
|
|
||||||
switch(output_state) {
|
|
||||||
case OutputState::Sync: crt.output_sync(count); break;
|
|
||||||
case OutputState::Border: crt.output_blank(count); break;
|
|
||||||
case OutputState::Pixels:
|
|
||||||
crt.output_data(count);
|
|
||||||
pixels = pixel_pointer = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (2) adopt new state.
|
|
||||||
output_state = new_state;
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect pixels if applicable.
|
|
||||||
if(output_state == OutputState::Pixels) {
|
|
||||||
if(!pixels) {
|
|
||||||
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
|
|
||||||
|
|
||||||
// Flush any period where pixels weren't recorded due to back pressure.
|
|
||||||
if(pixels && count) {
|
|
||||||
crt.output_blank(count);
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pixels) {
|
|
||||||
static constexpr uint8_t high_intensity = 0x0d;
|
|
||||||
static constexpr uint8_t low_intensity = 0x09;
|
|
||||||
static constexpr uint8_t off = 0x00;
|
|
||||||
|
|
||||||
if(state.cursor) {
|
|
||||||
pixel_pointer[0] = pixel_pointer[1] = pixel_pointer[2] = pixel_pointer[3] =
|
|
||||||
pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] =
|
|
||||||
pixel_pointer[8] = low_intensity;
|
|
||||||
} else {
|
|
||||||
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff];
|
|
||||||
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff];
|
|
||||||
uint8_t row = font[(glyph * 14) + state.row_address];
|
|
||||||
|
|
||||||
const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity;
|
|
||||||
uint8_t blank = off;
|
|
||||||
|
|
||||||
// Handle irregular attributes.
|
|
||||||
// Cf. http://www.seasip.info/VintagePC/mda.html#memmap
|
|
||||||
switch(attributes) {
|
|
||||||
case 0x00: case 0x08: case 0x80: case 0x88:
|
|
||||||
row = 0;
|
|
||||||
break;
|
|
||||||
case 0x70: case 0x78: case 0xf0: case 0xf8:
|
|
||||||
row ^= 0xff;
|
|
||||||
blank = intensity;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply blink if enabled.
|
|
||||||
if((control_ & 0x20) && (attributes & 0x80) && (state.field_count & 16)) {
|
|
||||||
row ^= 0xff;
|
|
||||||
blank = (blank == off) ? intensity : off;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(((attributes & 7) == 1) && state.row_address == 13) {
|
|
||||||
// Draw as underline.
|
|
||||||
std::fill(pixel_pointer, pixel_pointer + 9, intensity);
|
|
||||||
} else {
|
|
||||||
// Draw according to ROM contents, possibly duplicating final column.
|
|
||||||
pixel_pointer[0] = (row & 0x80) ? intensity : off;
|
|
||||||
pixel_pointer[1] = (row & 0x40) ? intensity : off;
|
|
||||||
pixel_pointer[2] = (row & 0x20) ? intensity : off;
|
|
||||||
pixel_pointer[3] = (row & 0x10) ? intensity : off;
|
|
||||||
pixel_pointer[4] = (row & 0x08) ? intensity : off;
|
|
||||||
pixel_pointer[5] = (row & 0x04) ? intensity : off;
|
|
||||||
pixel_pointer[6] = (row & 0x02) ? intensity : off;
|
|
||||||
pixel_pointer[7] = (row & 0x01) ? intensity : off;
|
|
||||||
pixel_pointer[8] = (glyph >= 0xc0 && glyph < 0xe0) ? pixel_pointer[7] : blank;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel_pointer += 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance.
|
|
||||||
count += 9;
|
|
||||||
|
|
||||||
// Output pixel row prematurely if storage is exhausted.
|
|
||||||
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
|
|
||||||
crt.output_data(count);
|
|
||||||
count = 0;
|
|
||||||
|
|
||||||
pixels = pixel_pointer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
|
|
||||||
|
|
||||||
Outputs::CRT::CRT crt;
|
|
||||||
|
|
||||||
enum class OutputState {
|
|
||||||
Sync, Pixels, Border
|
|
||||||
} output_state = OutputState::Sync;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
uint8_t *pixels = nullptr;
|
|
||||||
uint8_t *pixel_pointer = nullptr;
|
|
||||||
static constexpr size_t DefaultAllocationSize = 720;
|
|
||||||
|
|
||||||
const uint8_t *ram = nullptr;
|
|
||||||
std::vector<uint8_t> font;
|
|
||||||
|
|
||||||
uint8_t control_ = 0;
|
|
||||||
} outputter_;
|
|
||||||
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
|
||||||
|
|
||||||
int full_clock_;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PCSpeaker {
|
struct PCSpeaker {
|
||||||
PCSpeaker() :
|
PCSpeaker() :
|
||||||
toggle(queue),
|
toggle(queue),
|
||||||
@ -729,10 +525,28 @@ class PITObserver {
|
|||||||
using PIT = i8253<false, PITObserver>;
|
using PIT = i8253<false, PITObserver>;
|
||||||
|
|
||||||
class i8255PortHandler : public Intel::i8255::PortHandler {
|
class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||||
// Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol
|
|
||||||
public:
|
public:
|
||||||
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard) :
|
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, VideoAdaptor adaptor, int drive_count) :
|
||||||
speaker_(speaker), keyboard_(keyboard) {}
|
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;
|
||||||
|
case VideoAdaptor::MDA: high_switches_ |= 0b11; break;
|
||||||
|
case VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns.
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void set_value(int port, uint8_t value) {
|
void set_value(int port, uint8_t value) {
|
||||||
switch(port) {
|
switch(port) {
|
||||||
@ -747,43 +561,35 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
enable_keyboard_ = !(value & 0x80);
|
enable_keyboard_ = !(value & 0x80);
|
||||||
keyboard_.set_mode(value >> 6);
|
keyboard_.set_mode(value >> 6);
|
||||||
|
|
||||||
high_switches_ = value & 0x08;
|
use_high_switches_ = value & 0x08;
|
||||||
speaker_.set_control(value & 0x01, value & 0x02);
|
speaker_.set_control(value & 0x01, value & 0x02);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// printf("PPI: %02x to %d\n", value, port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_value(int port) {
|
uint8_t get_value(int port) {
|
||||||
switch(port) {
|
switch(port) {
|
||||||
case 0:
|
case 0:
|
||||||
// printf("PPI: from keyboard\n");
|
return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_);
|
||||||
return enable_keyboard_ ? keyboard_.read() : 0b0011'1101;
|
|
||||||
// Guesses that switches is high and low combined as below.
|
// Guesses that switches is high and low combined as below.
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
// Common:
|
|
||||||
//
|
|
||||||
// b7: 1 => memory parity error; 0 => none;
|
// b7: 1 => memory parity error; 0 => none;
|
||||||
// b6: 1 => IO channel error; 0 => none;
|
// b6: 1 => IO channel error; 0 => none;
|
||||||
// b5: timer 2 output; [TODO]
|
// b5: timer 2 output; [TODO]
|
||||||
// b4: cassette data input; [TODO]
|
// b4: cassette data input; [TODO]
|
||||||
|
// b3...b0: whichever of the high and low switches is selected.
|
||||||
return
|
return
|
||||||
high_switches_ ?
|
use_high_switches_ ? high_switches_ : low_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'1101;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool high_switches_ = false;
|
uint8_t high_switches_ = 0;
|
||||||
|
uint8_t low_switches_ = 0;
|
||||||
|
|
||||||
|
bool use_high_switches_ = false;
|
||||||
PCSpeaker &speaker_;
|
PCSpeaker &speaker_;
|
||||||
KeyboardController &keyboard_;
|
KeyboardController &keyboard_;
|
||||||
|
|
||||||
@ -794,8 +600,8 @@ using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
|||||||
template <VideoAdaptor video>
|
template <VideoAdaptor video>
|
||||||
class IO {
|
class IO {
|
||||||
public:
|
public:
|
||||||
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) :
|
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc) :
|
||||||
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), mda_(mda), fdc_(fdc) {}
|
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc) {}
|
||||||
|
|
||||||
template <typename IntT> void out(uint16_t port, IntT value) {
|
template <typename IntT> void out(uint16_t port, IntT value) {
|
||||||
switch(port) {
|
switch(port) {
|
||||||
@ -853,33 +659,40 @@ class IO {
|
|||||||
case 0x0086: dma_.pages.set_page<6>(uint8_t(value)); break;
|
case 0x0086: dma_.pages.set_page<6>(uint8_t(value)); break;
|
||||||
case 0x0087: dma_.pages.set_page<7>(uint8_t(value)); break;
|
case 0x0087: dma_.pages.set_page<7>(uint8_t(value)); break;
|
||||||
|
|
||||||
|
//
|
||||||
|
// MDA block, with slightly laboured 16-bit to 8-bit mapping.
|
||||||
|
//
|
||||||
case 0x03b0: case 0x03b2: case 0x03b4: case 0x03b6:
|
case 0x03b0: case 0x03b2: case 0x03b4: case 0x03b6:
|
||||||
|
if constexpr (video == VideoAdaptor::MDA) {
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||||
mda_.write<0>(uint8_t(value));
|
video_.template write<0>(uint8_t(value));
|
||||||
mda_.write<1>(uint8_t(value >> 8));
|
video_.template write<1>(uint8_t(value >> 8));
|
||||||
} else {
|
} else {
|
||||||
mda_.write<0>(value);
|
video_.template write<0>(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03b1: case 0x03b3: case 0x03b5: case 0x03b7:
|
case 0x03b1: case 0x03b3: case 0x03b5: case 0x03b7:
|
||||||
|
if constexpr (video == VideoAdaptor::MDA) {
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||||
mda_.write<1>(uint8_t(value));
|
video_.template write<1>(uint8_t(value));
|
||||||
mda_.write<0>(uint8_t(value >> 8));
|
video_.template write<0>(uint8_t(value >> 8));
|
||||||
} else {
|
} else {
|
||||||
mda_.write<1>(value);
|
video_.template write<1>(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03b8:
|
case 0x03b8:
|
||||||
mda_.write<8>(uint8_t(value));
|
if constexpr (video == VideoAdaptor::MDA) {
|
||||||
|
video_.template write<8>(uint8_t(value));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3:
|
case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3:
|
||||||
case 0x03d4: case 0x03d5: case 0x03d6: case 0x03d7:
|
case 0x03d4: case 0x03d5: case 0x03d6: case 0x03d7:
|
||||||
case 0x03d8: case 0x03d9: case 0x03da: case 0x03db:
|
case 0x03d8: case 0x03d9: case 0x03da: case 0x03db:
|
||||||
case 0x03dc: case 0x03dd: case 0x03de: case 0x03df:
|
case 0x03dc: case 0x03dd: case 0x03de: case 0x03df:
|
||||||
// Ignore CGA accesses.
|
// TODO: CGA accesses.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03f2:
|
case 0x03f2:
|
||||||
@ -965,7 +778,11 @@ class IO {
|
|||||||
case 0x03f4: return fdc_.status();
|
case 0x03f4: return fdc_.status();
|
||||||
case 0x03f5: return fdc_.read();
|
case 0x03f5: return fdc_.read();
|
||||||
|
|
||||||
case 0x03b8: return mda_.read<8>();
|
case 0x03b8:
|
||||||
|
if constexpr (video == VideoAdaptor::MDA) {
|
||||||
|
return video_.template read<8>();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb:
|
case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb:
|
||||||
case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef:
|
case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef:
|
||||||
@ -986,7 +803,7 @@ class IO {
|
|||||||
DMA &dma_;
|
DMA &dma_;
|
||||||
PPI &ppi_;
|
PPI &ppi_;
|
||||||
PIC &pic_;
|
PIC &pic_;
|
||||||
MDA &mda_;
|
typename Adaptor<video>::type &video_;
|
||||||
FloppyController &fdc_;
|
FloppyController &fdc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1049,18 +866,21 @@ class ConcreteMachine:
|
|||||||
public MachineTypes::ScanProducer,
|
public MachineTypes::ScanProducer,
|
||||||
public Activity::Source
|
public Activity::Source
|
||||||
{
|
{
|
||||||
|
static constexpr int DriveCount = 1;
|
||||||
|
using Video = typename Adaptor<video>::type;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ConcreteMachine(
|
ConcreteMachine(
|
||||||
[[maybe_unused]] const Analyser::Static::Target &target,
|
const Analyser::Static::PCCompatible::Target &target,
|
||||||
const ROMMachine::ROMFetcher &rom_fetcher
|
const ROMMachine::ROMFetcher &rom_fetcher
|
||||||
) :
|
) :
|
||||||
keyboard_(pic_),
|
keyboard_(pic_),
|
||||||
fdc_(pic_, dma_),
|
fdc_(pic_, dma_, DriveCount),
|
||||||
pit_observer_(pic_, speaker_),
|
pit_observer_(pic_, speaker_),
|
||||||
ppi_handler_(speaker_, keyboard_),
|
ppi_handler_(speaker_, keyboard_, video, DriveCount),
|
||||||
pit_(pit_observer_),
|
pit_(pit_observer_),
|
||||||
ppi_(ppi_handler_),
|
ppi_(ppi_handler_),
|
||||||
context(pit_, dma_, ppi_, pic_, mda_, fdc_)
|
context(pit_, dma_, ppi_, pic_, video_, fdc_)
|
||||||
{
|
{
|
||||||
// Set up DMA source/target.
|
// Set up DMA source/target.
|
||||||
dma_.set_memory(&context.memory);
|
dma_.set_memory(&context.memory);
|
||||||
@ -1072,7 +892,7 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
// Fetch the BIOS. [8088 only, for now]
|
// Fetch the BIOS. [8088 only, for now]
|
||||||
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
|
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
|
||||||
const auto font = ROM::Name::PCCompatibleMDAFont;
|
const auto font = ROM::Name::PCCompatibleMDAFont; // ... or CGA.
|
||||||
|
|
||||||
ROM::Request request = ROM::Request(bios) && ROM::Request(font);
|
ROM::Request request = ROM::Request(bios) && ROM::Request(font);
|
||||||
auto roms = rom_fetcher(request);
|
auto roms = rom_fetcher(request);
|
||||||
@ -1083,9 +903,9 @@ class ConcreteMachine:
|
|||||||
const auto &bios_contents = roms.find(bios)->second;
|
const auto &bios_contents = roms.find(bios)->second;
|
||||||
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
||||||
|
|
||||||
// Give the MDA something to read from.
|
// Give the video card something to read from.
|
||||||
const auto &font_contents = roms.find(font)->second;
|
const auto &font_contents = roms.find(font)->second;
|
||||||
mda_.set_source(context.memory.at(0xb'0000), font_contents);
|
video_.set_source(context.memory.at(0xb'0000), font_contents); // TODO: get RAM base address from the card.
|
||||||
|
|
||||||
// ... and insert media.
|
// ... and insert media.
|
||||||
insert_media(target.media);
|
insert_media(target.media);
|
||||||
@ -1120,7 +940,7 @@ class ConcreteMachine:
|
|||||||
//
|
//
|
||||||
// Advance CRTC at a more approximate rate.
|
// Advance CRTC at a more approximate rate.
|
||||||
//
|
//
|
||||||
mda_.run_for(Cycles(3));
|
video_.run_for(Cycles(3));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Perform one CPU instruction every three PIT cycles.
|
// Perform one CPU instruction every three PIT cycles.
|
||||||
@ -1199,10 +1019,10 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
// MARK: - ScanProducer.
|
// MARK: - ScanProducer.
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||||
mda_.set_scan_target(scan_target);
|
video_.set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
||||||
return mda_.get_scaled_scan_status();
|
return video_.get_scaled_scan_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AudioProducer.
|
// MARK: - AudioProducer.
|
||||||
@ -1246,7 +1066,7 @@ class ConcreteMachine:
|
|||||||
PIC pic_;
|
PIC pic_;
|
||||||
DMA dma_;
|
DMA dma_;
|
||||||
PCSpeaker speaker_;
|
PCSpeaker speaker_;
|
||||||
MDA mda_;
|
Video video_;
|
||||||
|
|
||||||
KeyboardController keyboard_;
|
KeyboardController keyboard_;
|
||||||
FloppyController fdc_;
|
FloppyController fdc_;
|
||||||
@ -1304,8 +1124,8 @@ Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROM
|
|||||||
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
||||||
|
|
||||||
switch(pc_target->adaptor) {
|
switch(pc_target->adaptor) {
|
||||||
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA>(*target, rom_fetcher);
|
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA>(*pc_target, rom_fetcher);
|
||||||
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA>(*target, rom_fetcher);
|
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA>(*pc_target, rom_fetcher);
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1168,6 +1168,7 @@
|
|||||||
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||||
429B135E2B1F7BDA006BB4CB /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
429B135E2B1F7BDA006BB4CB /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||||
429B135F2B1F7BDA006BB4CB /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
429B135F2B1F7BDA006BB4CB /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||||
|
429B13622B1FCA96006BB4CB /* MDA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MDA.hpp; sourceTree = "<group>"; };
|
||||||
42A5E80B2ABBE04600A0DD5D /* NeskellTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeskellTests.swift; sourceTree = "<group>"; };
|
42A5E80B2ABBE04600A0DD5D /* NeskellTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeskellTests.swift; sourceTree = "<group>"; };
|
||||||
42A5E8332ABBE16F00A0DD5D /* illegal_rmw_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = illegal_rmw_test.bin; sourceTree = "<group>"; };
|
42A5E8332ABBE16F00A0DD5D /* illegal_rmw_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = illegal_rmw_test.bin; sourceTree = "<group>"; };
|
||||||
42A5E8342ABBE16F00A0DD5D /* arr_bcd_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = arr_bcd_test.bin; sourceTree = "<group>"; };
|
42A5E8342ABBE16F00A0DD5D /* arr_bcd_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = arr_bcd_test.bin; sourceTree = "<group>"; };
|
||||||
@ -2396,6 +2397,7 @@
|
|||||||
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
|
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
|
||||||
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
||||||
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
||||||
|
429B13622B1FCA96006BB4CB /* MDA.hpp */,
|
||||||
423820132B1A235200964EFE /* Memory.hpp */,
|
423820132B1A235200964EFE /* Memory.hpp */,
|
||||||
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
||||||
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
||||||
|
Loading…
Reference in New Issue
Block a user