diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index ef8e9deb0..0d7c3f759 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -18,7 +18,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl::type; - destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); + destination_high = IntT((sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT))); destination_low = IntT(sIntT(destination_low) * sIntT(source)); const auto sign_extension = (destination_low & Numeric::top_bit()) ? IntT(~0) : 0; @@ -216,7 +216,7 @@ void div( } // TEMPORARY HACK. Will not work with DWords. - const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low; + const uint32_t dividend = uint32_t((destination_high << (8 * sizeof(IntT))) + destination_low); const auto result = dividend / source; if(IntT(result) != result) { interrupt(Interrupt::DivideError, context); @@ -293,7 +293,7 @@ void idiv( } destination_low = IntT(result); - destination_high = dividend % sIntT(source); + destination_high = IntT(dividend % sIntT(source)); } template diff --git a/InstructionSets/x86/Implementation/ShiftRoll.hpp b/InstructionSets/x86/Implementation/ShiftRoll.hpp index abf57ca82..578641a92 100644 --- a/InstructionSets/x86/Implementation/ShiftRoll.hpp +++ b/InstructionSets/x86/Implementation/ShiftRoll.hpp @@ -54,15 +54,16 @@ void rcl( case 0: break; case Numeric::bit_size(): { const IntT temp_carry = destination & 1; - destination = (destination >> 1) | (carry << (Numeric::bit_size() - 1)); + destination = IntT((destination >> 1) | (carry << (Numeric::bit_size() - 1))); carry = temp_carry; } break; default: { const IntT temp_carry = destination & (Numeric::top_bit() >> (temp_count - 1)); - destination = + destination = IntT( (destination << temp_count) | (destination >> (Numeric::bit_size() + 1 - temp_count)) | - (carry << (temp_count - 1)); + (carry << (temp_count - 1)) + ); carry = temp_carry ? 1 : 0; } break; } @@ -103,15 +104,16 @@ void rcr( case 0: break; case Numeric::bit_size(): { const IntT temp_carry = destination & Numeric::top_bit(); - destination = (destination << 1) | carry; + destination = IntT((destination << 1) | carry); carry = temp_carry; } break; default: { const IntT temp_carry = destination & (1 << (temp_count - 1)); - destination = + destination = IntT( (destination >> temp_count) | (destination << (Numeric::bit_size() + 1 - temp_count)) | - (carry << (Numeric::bit_size() - temp_count)); + (carry << (Numeric::bit_size() - temp_count)) + ); carry = temp_carry; } break; } @@ -159,9 +161,10 @@ void rol( return; } if(temp_count) { - destination = + destination = IntT( (destination << temp_count) | - (destination >> (Numeric::bit_size() - temp_count)); + (destination >> (Numeric::bit_size() - temp_count)) + ); } context.flags.template set_from(destination & 1); @@ -210,9 +213,10 @@ void ror( return; } if(temp_count) { - destination = + destination = IntT( (destination >> temp_count) | - (destination << (Numeric::bit_size() - temp_count)); + (destination << (Numeric::bit_size() - temp_count)) + ); } context.flags.template set_from(destination & Numeric::top_bit()); @@ -298,9 +302,9 @@ void sal( context.flags.template set_from( destination & mask ); - context.flags.template set_from( + context.flags.template set_from(IntT( (destination ^ (destination << 1)) & mask - ); + )); destination <<= count; } break; @@ -323,7 +327,7 @@ void sar( destination = sign ? IntT(~0) : IntT(0); context.flags.template set_from(sign); } else { - const IntT mask = 1 << (count - 1); + const auto mask = IntT(1 << (count - 1)); context.flags.template set_from(destination & mask); destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); } @@ -349,7 +353,7 @@ void shr( context.flags.template set_from(0); destination = 0; } else { - const IntT mask = 1 << (count - 1); + const auto mask = IntT(1 << (count - 1)); context.flags.template set_from(destination & mask); destination >>= count; } diff --git a/Machines/PCCompatible/CGA.hpp b/Machines/PCCompatible/CGA.hpp new file mode 100644 index 000000000..547cb24cd --- /dev/null +++ b/Machines/PCCompatible/CGA.hpp @@ -0,0 +1,377 @@ +// +// CGA.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/12/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef CGA_h +#define CGA_h + +#include "../../Components/6845/CRTC6845.hpp" +#include "../../Outputs/CRT/CRT.hpp" +#include "../../Machines/Utility/ROMCatalogue.hpp" + +namespace PCCompatible { + +class CGA { + public: + CGA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {} + + static constexpr uint32_t BaseAddress = 0xb'8000; + static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont; + + void set_source(const uint8_t *ram, std::vector font) { + outputter_.ram = ram; + outputter_.font = font; + } + + void run_for(Cycles cycles) { + // Input rate is the PIT rate of 1,193,182 Hz. + // CGA is clocked at the real oscillator rate of 14 times that. + // But there's also an internal divide by 8 to align to the fetch clock. + full_clock_ += 7 * cycles.as(); + + const int modulo = 4 * outputter_.clock_divider; + crtc_.run_for(Cycles(full_clock_ / modulo)); + full_clock_ %= modulo; + } + + template + void write(uint8_t value) { + switch(address) { + case 0: case 2: case 4: case 6: + crtc_.select_register(value); + break; + case 1: case 3: case 5: case 7: + crtc_.set_register(value); + break; + + case 0x8: outputter_.set_mode(value); break; + case 0x9: outputter_.set_colours(value); break; + } + } + + template + uint8_t read() { + switch(address) { + case 1: case 3: case 5: case 7: + return crtc_.get_register(); + + case 0xa: + return + // b3: 1 => in vsync; 0 => not; + // b2: 1 => light pen switch is off; + // b1: 1 => positive edge from light pen has set trigger; + // b0: 1 => safe to write to VRAM now without causing snow. + (crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) | + (crtc_.get_bus_state().hsync ? 0b0001 : 0b0000) | + 0b0100; + + default: return 0xff; + } + } + + // MARK: - Display type configuration. + + void set_display_type(Outputs::Display::DisplayType display_type) { + outputter_.crt.set_display_type(display_type); + } + Outputs::Display::DisplayType get_display_type() const { + return outputter_.crt.get_display_type(); + } + + // 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() * 4.0f / (7.0f * 8.0f); + } + + private: + struct CRTCOutputter { + enum class OutputState { + Sync, Pixels, Border, ColourBurst + }; + + CRTCOutputter() : + crt(910, 8, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red2Green2Blue2) + { + crt.set_visible_area(Outputs::Display::Rect(0.097f, 0.095f, 0.82f, 0.82f)); +// crt.set_display_type(Outputs::Display::DisplayType::CompositeColour); // TODO: needs to be a user option. + crt.set_display_type(Outputs::Display::DisplayType::RGB); // TODO: needs to be a user option. + } + + void set_mode(uint8_t control) { + // b5: enable blink + // b4: 1 => 640x200 graphics + // b3: video enable + // b2: 1 => monochrome + // b1: 1 => 320x200 graphics; 0 => text + // b0: 1 => 80-column text; 0 => 40 + + control_ = control; // To capture blink, monochrome and video enable bits. + + if(control & 0x2) { + mode_ = (control & 0x10) ? Mode::Pixels640 : Mode::Pixels320; + pixels_per_tick = (mode_ == Mode::Pixels640) ? 16 : 8; + } else { + mode_ = Mode::Text; + pixels_per_tick = 8; + } + clock_divider = 1 + !(control & 0x01); + } + + void set_colours(uint8_t value) { + // b5: 320x200 palette. + if(value & 0x20) { + palette320[1] = 0b00'10'10; + palette320[2] = 0b10'00'10; + palette320[3] = 0b10'10'10; + } else { + palette320[1] = 0b00'10'00; + palette320[2] = 0b10'00'00; + palette320[3] = 0b10'10'00; // TODO: brown, here and elsewhere. + } + + // b4: set 320x200 palette into high intensity. + if(value & 0x10) { + palette320[1] |= palette320[1] >> 1; + palette320[2] |= palette320[2] >> 1; + palette320[3] |= palette320[3] >> 1; + } + + // b3–b0: set background, border, monochrome colour. + palette320[0] = uint8_t( + ((value & 0x01) << 1) | + ((value & 0x02) << 2) | + ((value & 0x04) << 3) + ); + if(value & 0x08) { + palette320[0] |= palette320[0] >> 1; + } + palette640[1] = palette320[0]; + } + + uint8_t control() { + return control_; + } + + void update_hsync(bool new_hsync) { + if(new_hsync == previous_hsync) { + cycles_since_hsync += clock_divider; + } else { + cycles_since_hsync = 0; + previous_hsync = new_hsync; + } + } + + OutputState implied_state(const Motorola::CRTC::BusState &state) const { + OutputState new_state; + + if(state.hsync || state.vsync) { + new_state = OutputState::Sync; + } else if(!state.display_enable || !(control_&0x08)) { + new_state = OutputState::Border; + + // TODO: I'm pretty sure this isn't correct for colour burst positioning, though + // it happens to fool the particular CRT I've implemented. + if(!(control_&4) && cycles_since_hsync <= 4) { + new_state = OutputState::ColourBurst; + } + } else { + new_state = OutputState::Pixels; + } + + return new_state; + } + + void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) { + // Determine new output state. + update_hsync(state.hsync); + const OutputState new_state = implied_state(state); + + // Upon either a state change or just having accumulated too much local time... + if(new_state != output_state || active_pixels_per_tick != pixels_per_tick || active_clock_divider != clock_divider || count > 912) { + // (1) flush preexisting state. + if(count) { + switch(output_state) { + case OutputState::Sync: crt.output_sync(count * active_clock_divider); break; + case OutputState::Border: crt.output_blank(count * active_clock_divider); break; + case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, 0); break; + case OutputState::Pixels: flush_pixels(); break; + } + } + + // (2) adopt new state. + output_state = new_state; + active_pixels_per_tick = pixels_per_tick; + active_clock_divider = clock_divider; + 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 * active_clock_divider); + count = 0; + } + } + + if(pixels) { + if(state.cursor) { + std::fill(pixel_pointer, pixel_pointer + pixels_per_tick, 0x3f); // i.e. white. + } else { + if(mode_ == Mode::Text) { + serialise_text(state); + } else { + serialise_pixels(state); + } + } + pixel_pointer += active_pixels_per_tick; + } + } + + // Advance. + count += 8; + + // Output pixel row prematurely if storage is exhausted. + if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) { + flush_pixels(); + count = 0; + } + } + void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {} + + void flush_pixels() { + crt.output_data(count * active_clock_divider, size_t((count * active_pixels_per_tick) / 8)); + pixels = pixel_pointer = nullptr; + } + + void serialise_pixels(const Motorola::CRTC::BusState &state) { + // This is what I think is happenings: + // + // Refresh address is still shifted left one and two bytes are fetched, just as if it were + // character code + attributes except that these are two bytes worth of graphics. + // + // Meanwhile, row address is used to invent a 15th address line. + const auto base_address = ((state.refresh_address << 1) + (state.row_address << 13)) & 0x3fff; + const uint8_t bitmap[] = { + ram[base_address], + ram[base_address + 1], + }; + + if(mode_ == Mode::Pixels320) { + pixel_pointer[0] = palette320[(bitmap[0] & 0xc0) >> 6]; + pixel_pointer[1] = palette320[(bitmap[0] & 0x30) >> 4]; + pixel_pointer[2] = palette320[(bitmap[0] & 0x0c) >> 2]; + pixel_pointer[3] = palette320[(bitmap[0] & 0x03) >> 0]; + pixel_pointer[4] = palette320[(bitmap[1] & 0xc0) >> 6]; + pixel_pointer[5] = palette320[(bitmap[1] & 0x30) >> 4]; + pixel_pointer[6] = palette320[(bitmap[1] & 0x0c) >> 2]; + pixel_pointer[7] = palette320[(bitmap[1] & 0x03) >> 0]; + } else { + pixel_pointer[0x0] = palette640[(bitmap[0] & 0x80) >> 7]; + pixel_pointer[0x1] = palette640[(bitmap[0] & 0x40) >> 6]; + pixel_pointer[0x2] = palette640[(bitmap[0] & 0x20) >> 5]; + pixel_pointer[0x3] = palette640[(bitmap[0] & 0x10) >> 4]; + pixel_pointer[0x4] = palette640[(bitmap[0] & 0x08) >> 3]; + pixel_pointer[0x5] = palette640[(bitmap[0] & 0x04) >> 2]; + pixel_pointer[0x6] = palette640[(bitmap[0] & 0x02) >> 1]; + pixel_pointer[0x7] = palette640[(bitmap[0] & 0x01) >> 0]; + pixel_pointer[0x8] = palette640[(bitmap[1] & 0x80) >> 7]; + pixel_pointer[0x9] = palette640[(bitmap[1] & 0x40) >> 6]; + pixel_pointer[0xa] = palette640[(bitmap[1] & 0x20) >> 5]; + pixel_pointer[0xb] = palette640[(bitmap[1] & 0x10) >> 4]; + pixel_pointer[0xc] = palette640[(bitmap[1] & 0x08) >> 3]; + pixel_pointer[0xd] = palette640[(bitmap[1] & 0x04) >> 2]; + pixel_pointer[0xe] = palette640[(bitmap[1] & 0x02) >> 1]; + pixel_pointer[0xf] = palette640[(bitmap[1] & 0x01) >> 0]; + } + } + + void serialise_text(const Motorola::CRTC::BusState &state) { + const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff]; + const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff]; + const uint8_t row = font[(glyph * 8) + state.row_address]; + + uint8_t colours[2] = { + uint8_t(((attributes & 0x40) >> 1) | ((attributes & 0x20) >> 2) | ((attributes & 0x10) >> 3)), + uint8_t(((attributes & 0x04) << 3) | ((attributes & 0x02) << 2) | ((attributes & 0x01) << 1)), + }; + + // Apply foreground intensity. + if(attributes & 0x08) { + colours[1] |= colours[1] >> 1; + } + + // Apply blink or background intensity. + if(control_ & 0x20) { + if((attributes & 0x80) && (state.field_count & 16)) { + std::swap(colours[0], colours[1]); + } + } else { + if(attributes & 0x80) { + colours[0] |= colours[0] >> 1; + } + } + + // Draw according to ROM contents. + pixel_pointer[0] = (row & 0x80) ? colours[1] : colours[0]; + pixel_pointer[1] = (row & 0x40) ? colours[1] : colours[0]; + pixel_pointer[2] = (row & 0x20) ? colours[1] : colours[0]; + pixel_pointer[3] = (row & 0x10) ? colours[1] : colours[0]; + pixel_pointer[4] = (row & 0x08) ? colours[1] : colours[0]; + pixel_pointer[5] = (row & 0x04) ? colours[1] : colours[0]; + pixel_pointer[6] = (row & 0x02) ? colours[1] : colours[0]; + pixel_pointer[7] = (row & 0x01) ? colours[1] : colours[0]; + } + + Outputs::CRT::CRT crt; + static constexpr size_t DefaultAllocationSize = 320; + + // Current output stream. + uint8_t *pixels = nullptr; + uint8_t *pixel_pointer = nullptr; + int active_pixels_per_tick = 8; + int active_clock_divider = 1; + + // Source data. + const uint8_t *ram = nullptr; + std::vector font; + + // CRTC state tracking, for CRT serialisation. + OutputState output_state = OutputState::Sync; + int count = 0; + + bool previous_hsync = false; + int cycles_since_hsync = 0; + + // Current Programmer-set parameters. + int clock_divider = 1; + int pixels_per_tick = 8; + uint8_t control_ = 0; + enum class Mode { + Pixels640, Pixels320, Text, + } mode_ = Mode::Text; + + uint8_t palette320[4]{}; + uint8_t palette640[2]{}; + + } outputter_; + Motorola::CRTC::CRTC6845 crtc_; + + int full_clock_ = 0; +}; + +} + +#endif /* CGA_h */ diff --git a/Machines/PCCompatible/MDA.hpp b/Machines/PCCompatible/MDA.hpp new file mode 100644 index 000000000..ff18706ce --- /dev/null +++ b/Machines/PCCompatible/MDA.hpp @@ -0,0 +1,236 @@ +// +// 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" +#include "../../Machines/Utility/ROMCatalogue.hpp" + +namespace PCCompatible { + +class MDA { + public: + MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {} + + static constexpr uint32_t BaseAddress = 0xb'0000; + static constexpr auto FontROM = ROM::Name::PCCompatibleMDAFont; + + void set_source(const uint8_t *ram, std::vector font) { + outputter_.ram = ram; + outputter_.font = font; + } + + void run_for(Cycles cycles) { + // Input rate is the PIT rate of 1,193,182 Hz. + // MDA is actually clocked at 16.257 MHz; which is treated internally as 1,806,333.333333333333333... Hz + // + // The GCD of those two numbers is... 2. Oh well. + full_clock_ += 8'128'500 * cycles.as(); + crtc_.run_for(Cycles(full_clock_ / (596'591 * 9))); + full_clock_ %= (596'591 * 9); + } + + template + void write(uint8_t value) { + switch(address) { + case 0: case 2: case 4: case 6: + crtc_.select_register(value); + break; + case 1: case 3: case 5: case 7: + crtc_.set_register(value); + break; + + case 0x8: + outputter_.set_control(value); + break; + + default: break; + } + } + + template + uint8_t read() { + switch(address) { + case 1: case 3: case 5: case 7: + return crtc_.get_register(); + + case 0x8: return outputter_.control(); + + default: return 0xff; + } + } + + // 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() * 596591.0f / 8128500.0f; + } + + // MARK: - Display type configuration. + + void set_display_type(Outputs::Display::DisplayType) {} + Outputs::Display::DisplayType get_display_type() const { + return outputter_.crt.get_display_type(); + } + + private: + struct CRTCOutputter { + CRTCOutputter() : + crt(882, 9, 370, 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 font; + + uint8_t control_ = 0; + } outputter_; + Motorola::CRTC::CRTC6845 crtc_; + + int full_clock_ = 0; +}; + +} + +#endif /* MDA_h */ diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 46dd450b1..a0e57a8dd 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -8,18 +8,19 @@ #include "PCCompatible.hpp" +#include "CGA.hpp" +#include "DMA.hpp" #include "KeyboardMapper.hpp" +#include "MDA.hpp" +#include "Memory.hpp" #include "PIC.hpp" #include "PIT.hpp" -#include "DMA.hpp" -#include "Memory.hpp" #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" #include "../../InstructionSets/x86/Instruction.hpp" #include "../../InstructionSets/x86/Perform.hpp" -#include "../../Components/6845/CRTC6845.hpp" #include "../../Components/8255/i8255.hpp" #include "../../Components/8272/CommandDecoder.hpp" #include "../../Components/8272/Results.hpp" @@ -41,27 +42,33 @@ #include "../ScanProducer.hpp" #include "../TimedMachine.hpp" +#include "../../Analyser/Static/PCCompatible/Target.hpp" + #include #include namespace PCCompatible { -//bool log = false; -//std::string previous; +using VideoAdaptor = Analyser::Static::PCCompatible::Target::VideoAdaptor; + +template struct Adaptor; +template <> struct Adaptor { + using type = MDA; +}; +template <> struct Adaptor { + using type = CGA; +}; class FloppyController { 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. - drives_[0].exists = true; - drives_[1].exists = false; - drives_[2].exists = false; - drives_[3].exists = false; + for(int c = 0; c < 4; c++) { + drives_[c].exists = drive_count > c; + } } 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; // b3: 1 => enable DMA; 0 => disable; // b2: 1 => enable FDC; 0 => hold at reset; @@ -92,7 +99,6 @@ class FloppyController { } uint8_t status() const { -// printf("FDC: read status %02x\n", status_.main()); return status_.main(); } @@ -138,6 +144,7 @@ class FloppyController { switch(access_result) { default: break; case AccessResult::NotAccepted: + complete = true; wrote_in_full = false; break; case AccessResult::AcceptedWithEOP: @@ -182,7 +189,6 @@ class FloppyController { } break; case Command::Recalibrate: - printf("FDC: Recalibrate\n"); drives_[decoder_.target().drive].track = 0; drives_[decoder_.target().drive].raised_interrupt = true; @@ -190,7 +196,6 @@ class FloppyController { pic_.apply_edge<6>(true); break; 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].raised_interrupt = true; @@ -199,8 +204,6 @@ class FloppyController { break; case Command::SenseInterruptStatus: { - printf("FDC: SenseInterruptStatus\n"); - int c = 0; for(; c < 4; c++) { if(drives_[c].raised_interrupt) { @@ -219,14 +222,12 @@ class FloppyController { } } break; case Command::Specify: - printf("FDC: Specify\n"); specify_specs_ = decoder_.specify_specs(); break; // case Command::SenseDriveStatus: { // } break; case Command::Invalid: - printf("FDC: Invalid\n"); results_.serialise_none(); break; } @@ -251,11 +252,9 @@ class FloppyController { status_.set(MainStatus::DataIsToProcessor, false); status_.set(MainStatus::CommandInProgress, false); } -// printf("FDC read: %02x\n", result); return result; } - printf("FDC read?\n"); return 0x80; } @@ -458,203 +457,6 @@ class KeyboardController { int reset_delay_ = 0; }; -class MDA { - public: - MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {} - - void set_source(const uint8_t *ram, std::vector 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(); - crtc_.run_for(Cycles(full_clock_ / 9)); - full_clock_ %= 9; - } - - template - 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 - 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 font; - - uint8_t control_ = 0; - } outputter_; - Motorola::CRTC::CRTC6845 crtc_; - - int full_clock_; -}; - struct PCSpeaker { PCSpeaker() : toggle(queue), @@ -724,10 +526,28 @@ class PITObserver { using PIT = i8253; class i8255PortHandler : public Intel::i8255::PortHandler { - // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol public: - i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard) : - speaker_(speaker), keyboard_(keyboard) {} + i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, VideoAdaptor adaptor, int drive_count) : + 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) { switch(port) { @@ -742,43 +562,35 @@ class i8255PortHandler : public Intel::i8255::PortHandler { enable_keyboard_ = !(value & 0x80); keyboard_.set_mode(value >> 6); - high_switches_ = value & 0x08; + use_high_switches_ = value & 0x08; speaker_.set_control(value & 0x01, value & 0x02); break; } -// printf("PPI: %02x to %d\n", value, port); } uint8_t get_value(int port) { switch(port) { case 0: -// printf("PPI: from keyboard\n"); - return enable_keyboard_ ? keyboard_.read() : 0b0011'1101; + return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_); // Guesses that switches is high and low combined as below. case 2: - // Common: - // // b7: 1 => memory parity error; 0 => none; // b6: 1 => IO channel error; 0 => none; // b5: timer 2 output; [TODO] // b4: cassette data input; [TODO] + // b3...b0: whichever of the high and low switches is selected. return - high_switches_ ? - // b3, b2: drive count; 00 = 1, 01 = 2, etc - // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) - 0b0000'0011 - : - // b3, b2: RAM on motherboard (64 * bit pattern) - // b1: 1 => FPU present; 0 => absent; - // b0: 1 => floppy drive present; 0 => absent. - 0b0000'1101; + use_high_switches_ ? high_switches_ : low_switches_; } return 0; }; private: - bool high_switches_ = false; + uint8_t high_switches_ = 0; + uint8_t low_switches_ = 0; + + bool use_high_switches_ = false; PCSpeaker &speaker_; KeyboardController &keyboard_; @@ -786,12 +598,16 @@ class i8255PortHandler : public Intel::i8255::PortHandler { }; using PPI = Intel::i8255::i8255; +template class IO { public: - IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) : - pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), mda_(mda), fdc_(fdc) {} + IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor