mirror of
https://github.com/TomHarte/CLK.git
synced 2026-04-20 10:17:05 +00:00
Reduce indentation, improve constness.
This commit is contained in:
+376
-377
@@ -15,433 +15,432 @@
|
||||
namespace PCCompatible {
|
||||
|
||||
class CGA {
|
||||
public:
|
||||
CGA() : crtc_(outputter_) {}
|
||||
public:
|
||||
CGA() : crtc_(outputter_) {}
|
||||
|
||||
static constexpr uint32_t BaseAddress = 0xb'8000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont;
|
||||
static constexpr uint32_t BaseAddress = 0xb'8000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont;
|
||||
|
||||
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
|
||||
outputter_.ram = ram;
|
||||
outputter_.font = font;
|
||||
void set_source(const uint8_t *const ram, const std::vector<uint8_t> font) {
|
||||
outputter_.ram = ram;
|
||||
outputter_.font = font;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
// Input rate is the PIT rate of 1,193,182 Hz.
|
||||
// CGA is clocked at the real oscillator rate of 12 times that.
|
||||
// But there's also an internal divide by 8 to align to the 80-cfetch clock.
|
||||
// ... and 12/8 = 3/2.
|
||||
full_clock_ += 3 * cycles.as<int>();
|
||||
|
||||
const int modulo = 2 * outputter_.clock_divider;
|
||||
crtc_.run_for(Cycles(full_clock_ / modulo));
|
||||
full_clock_ %= modulo;
|
||||
}
|
||||
|
||||
template <int address>
|
||||
void write(const 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 <int address>
|
||||
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().display_enable ? 0b0000 : 0b0001) |
|
||||
0b0100;
|
||||
|
||||
default: return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Display type configuration.
|
||||
|
||||
void set_display_type(const Outputs::Display::DisplayType display_type) {
|
||||
outputter_.crt.set_display_type(display_type);
|
||||
outputter_.set_is_composite(Outputs::Display::is_composite(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 *const scan_target) {
|
||||
outputter_.crt.set_scan_target(scan_target);
|
||||
}
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
// The CRT is always handed data at the full CGA pixel clock rate, so just
|
||||
// divide by 12 to get back to the rate that run_for is being called at.
|
||||
return outputter_.crt.get_scaled_scan_status() / 12.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::RGB);
|
||||
}
|
||||
|
||||
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 12 times that.
|
||||
// But there's also an internal divide by 8 to align to the 80-cfetch clock.
|
||||
// ... and 12/8 = 3/2.
|
||||
full_clock_ += 3 * cycles.as<int>();
|
||||
void set_mode(const 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
|
||||
|
||||
const int modulo = 2 * outputter_.clock_divider;
|
||||
crtc_.run_for(Cycles(full_clock_ / modulo));
|
||||
full_clock_ %= modulo;
|
||||
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);
|
||||
|
||||
// Both graphics mode and monochrome/colour may have changed, so update the palette.
|
||||
update_palette();
|
||||
}
|
||||
|
||||
template <int address>
|
||||
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;
|
||||
void set_is_composite(const bool is_composite) {
|
||||
is_composite_ = is_composite;
|
||||
update_palette();
|
||||
}
|
||||
|
||||
case 0x8: outputter_.set_mode(value); break;
|
||||
case 0x9: outputter_.set_colours(value); break;
|
||||
void set_colours(uint8_t value) {
|
||||
colours_ = value;
|
||||
update_palette();
|
||||
}
|
||||
|
||||
uint8_t control() const {
|
||||
return control_;
|
||||
}
|
||||
|
||||
void update_hsync(const bool new_hsync) {
|
||||
if(new_hsync == previous_hsync) {
|
||||
cycles_since_hsync += clock_divider;
|
||||
} else {
|
||||
cycles_since_hsync = 0;
|
||||
previous_hsync = new_hsync;
|
||||
}
|
||||
}
|
||||
|
||||
template <int address>
|
||||
uint8_t read() {
|
||||
switch(address) {
|
||||
case 1: case 3: case 5: case 7:
|
||||
return crtc_.get_register();
|
||||
OutputState implied_state(const Motorola::CRTC::BusState &state) const {
|
||||
OutputState new_state;
|
||||
|
||||
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().display_enable ? 0b0000 : 0b0001) |
|
||||
0b0100;
|
||||
if(state.hsync || state.vsync) {
|
||||
new_state = OutputState::Sync;
|
||||
} else if(!state.display_enable || !(control_&0x08)) {
|
||||
new_state = OutputState::Border;
|
||||
|
||||
default: return 0xff;
|
||||
// TODO: 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 <= 6) {
|
||||
new_state = OutputState::ColourBurst;
|
||||
}
|
||||
} else {
|
||||
new_state = OutputState::Pixels;
|
||||
}
|
||||
|
||||
return new_state;
|
||||
}
|
||||
|
||||
void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
|
||||
// Determine new output state.
|
||||
update_hsync(state.hsync);
|
||||
const OutputState new_state = implied_state(state);
|
||||
static constexpr uint8_t colour_phase = 200;
|
||||
|
||||
// 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 ||
|
||||
active_border_colour != border_colour ||
|
||||
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:
|
||||
if(active_border_colour) {
|
||||
crt.output_blank(count * active_clock_divider);
|
||||
} else {
|
||||
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
|
||||
}
|
||||
break;
|
||||
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); 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;
|
||||
active_border_colour = border_colour;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Display type configuration.
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
outputter_.crt.set_display_type(display_type);
|
||||
outputter_.set_is_composite(Outputs::Display::is_composite(display_type));
|
||||
}
|
||||
Outputs::Display::DisplayType get_display_type() const {
|
||||
return outputter_.crt.get_display_type();
|
||||
void flush_pixels() {
|
||||
crt.output_data(count * active_clock_divider, size_t((count * active_pixels_per_tick) / 8));
|
||||
pixels = pixel_pointer = nullptr;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// The CRT is always handed data at the full CGA pixel clock rate, so just
|
||||
// divide by 12 to get back to the rate that run_for is being called at.
|
||||
return outputter_.crt.get_scaled_scan_status() / 12.0f;
|
||||
}
|
||||
|
||||
private:
|
||||
struct CRTCOutputter {
|
||||
enum class OutputState {
|
||||
Sync, Pixels, Border, ColourBurst
|
||||
void serialise_pixels(const Motorola::CRTC::BusState &state) {
|
||||
// Refresh address is shifted left and two bytes are fetched, just as if the fetch were for
|
||||
// character code + attributes, but producing two bytes worth of graphics.
|
||||
//
|
||||
// Meanwhile, row address is used as a substitute 14th address line.
|
||||
const auto base_address =
|
||||
((state.refresh_address & 0xfff) << 1) +
|
||||
((state.row_address & 1) << 13);
|
||||
const uint8_t bitmap[] = {
|
||||
ram[base_address],
|
||||
ram[base_address + 1],
|
||||
};
|
||||
|
||||
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::RGB);
|
||||
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 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
|
||||
void serialise_text(const Motorola::CRTC::BusState &state) {
|
||||
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0x3fff];
|
||||
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0x3fff];
|
||||
const uint8_t row = font[(glyph * 8) + state.row_address];
|
||||
|
||||
control_ = control; // To capture blink, monochrome and video enable bits.
|
||||
uint8_t colours[2] = { rgb(attributes >> 4), rgbi(attributes) };
|
||||
|
||||
if(control & 0x2) {
|
||||
mode_ = (control & 0x10) ? Mode::Pixels640 : Mode::Pixels320;
|
||||
pixels_per_tick = (mode_ == Mode::Pixels640) ? 16 : 8;
|
||||
// Apply blink or background intensity.
|
||||
if(control_ & 0x20) {
|
||||
// Set both colours to black if within a blink; otherwise consider a yellow-to-brown conversion.
|
||||
if((attributes & 0x80) && (state.field_count & 16)) {
|
||||
colours[0] = colours[1] = 0;
|
||||
} else {
|
||||
mode_ = Mode::Text;
|
||||
pixels_per_tick = 8;
|
||||
colours[0] = yellow_to_brown(colours[0]);
|
||||
}
|
||||
clock_divider = 1 + !(control & 0x01);
|
||||
|
||||
// Both graphics mode and monochrome/colour may have changed, so update the palette.
|
||||
update_palette();
|
||||
}
|
||||
|
||||
void set_is_composite(bool is_composite) {
|
||||
is_composite_ = is_composite;
|
||||
update_palette();
|
||||
}
|
||||
|
||||
void set_colours(uint8_t value) {
|
||||
colours_ = value;
|
||||
update_palette();
|
||||
}
|
||||
|
||||
uint8_t control() {
|
||||
return control_;
|
||||
}
|
||||
|
||||
void update_hsync(bool new_hsync) {
|
||||
if(new_hsync == previous_hsync) {
|
||||
cycles_since_hsync += clock_divider;
|
||||
} else {
|
||||
if(attributes & 0x80) {
|
||||
colours[0] = bright(colours[0]);
|
||||
} else {
|
||||
cycles_since_hsync = 0;
|
||||
previous_hsync = new_hsync;
|
||||
// Yellow to brown definitely doesn't apply if the colour has been brightened.
|
||||
colours[0] = yellow_to_brown(colours[0]);
|
||||
}
|
||||
}
|
||||
|
||||
OutputState implied_state(const Motorola::CRTC::BusState &state) const {
|
||||
OutputState new_state;
|
||||
// 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];
|
||||
}
|
||||
|
||||
if(state.hsync || state.vsync) {
|
||||
new_state = OutputState::Sync;
|
||||
} else if(!state.display_enable || !(control_&0x08)) {
|
||||
new_state = OutputState::Border;
|
||||
Outputs::CRT::CRT crt;
|
||||
static constexpr size_t DefaultAllocationSize = 320;
|
||||
|
||||
// TODO: 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 <= 6) {
|
||||
new_state = OutputState::ColourBurst;
|
||||
}
|
||||
} else {
|
||||
new_state = OutputState::Pixels;
|
||||
}
|
||||
// Current output stream.
|
||||
uint8_t *pixels = nullptr;
|
||||
uint8_t *pixel_pointer = nullptr;
|
||||
int active_pixels_per_tick = 8;
|
||||
int active_clock_divider = 1;
|
||||
uint8_t active_border_colour = 0;
|
||||
|
||||
return new_state;
|
||||
}
|
||||
// Source data.
|
||||
const uint8_t *ram = nullptr;
|
||||
std::vector<uint8_t> font;
|
||||
|
||||
void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
|
||||
// Determine new output state.
|
||||
update_hsync(state.hsync);
|
||||
const OutputState new_state = implied_state(state);
|
||||
static constexpr uint8_t colour_phase = 200;
|
||||
// CRTC state tracking, for CRT serialisation.
|
||||
OutputState output_state = OutputState::Sync;
|
||||
int count = 0;
|
||||
|
||||
// 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 ||
|
||||
active_border_colour != border_colour ||
|
||||
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:
|
||||
if(active_border_colour) {
|
||||
crt.output_blank(count * active_clock_divider);
|
||||
} else {
|
||||
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
|
||||
}
|
||||
break;
|
||||
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); break;
|
||||
case OutputState::Pixels: flush_pixels(); break;
|
||||
}
|
||||
}
|
||||
bool previous_hsync = false;
|
||||
int cycles_since_hsync = 0;
|
||||
|
||||
// (2) adopt new state.
|
||||
output_state = new_state;
|
||||
active_pixels_per_tick = pixels_per_tick;
|
||||
active_clock_divider = clock_divider;
|
||||
active_border_colour = border_colour;
|
||||
count = 0;
|
||||
}
|
||||
// Current Programmer-set parameters.
|
||||
int clock_divider = 1;
|
||||
int pixels_per_tick = 8;
|
||||
uint8_t colours_ = 0;
|
||||
uint8_t control_ = 0;
|
||||
bool is_composite_ = false;
|
||||
enum class Mode {
|
||||
Pixels640, Pixels320, Text,
|
||||
} mode_ = Mode::Text;
|
||||
|
||||
// Collect pixels if applicable.
|
||||
if(output_state == OutputState::Pixels) {
|
||||
if(!pixels) {
|
||||
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
|
||||
uint8_t palette320[4]{};
|
||||
uint8_t palette640[2]{};
|
||||
uint8_t border_colour;
|
||||
|
||||
// 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 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) {
|
||||
// Refresh address is shifted left and two bytes are fetched, just as if the fetch were for
|
||||
// character code + attributes, but producing two bytes worth of graphics.
|
||||
//
|
||||
// Meanwhile, row address is used as a substitute 14th address line.
|
||||
const auto base_address =
|
||||
((state.refresh_address & 0xfff) << 1) +
|
||||
((state.row_address & 1) << 13);
|
||||
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) & 0x3fff];
|
||||
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0x3fff];
|
||||
const uint8_t row = font[(glyph * 8) + state.row_address];
|
||||
|
||||
uint8_t colours[2] = { rgb(attributes >> 4), rgbi(attributes) };
|
||||
|
||||
// Apply blink or background intensity.
|
||||
if(control_ & 0x20) {
|
||||
// Set both colours to black if within a blink; otherwise consider a yellow-to-brown conversion.
|
||||
if((attributes & 0x80) && (state.field_count & 16)) {
|
||||
colours[0] = colours[1] = 0;
|
||||
} else {
|
||||
colours[0] = yellow_to_brown(colours[0]);
|
||||
}
|
||||
} else {
|
||||
if(attributes & 0x80) {
|
||||
colours[0] = bright(colours[0]);
|
||||
} else {
|
||||
// Yellow to brown definitely doesn't apply if the colour has been brightened.
|
||||
colours[0] = yellow_to_brown(colours[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
uint8_t active_border_colour = 0;
|
||||
|
||||
// Source data.
|
||||
const uint8_t *ram = nullptr;
|
||||
std::vector<uint8_t> 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 colours_ = 0;
|
||||
uint8_t control_ = 0;
|
||||
bool is_composite_ = false;
|
||||
enum class Mode {
|
||||
Pixels640, Pixels320, Text,
|
||||
} mode_ = Mode::Text;
|
||||
|
||||
uint8_t palette320[4]{};
|
||||
uint8_t palette640[2]{};
|
||||
uint8_t border_colour;
|
||||
|
||||
void update_palette() {
|
||||
// b5: 320x200 palette, unless in monochrome mode.
|
||||
if(control_ & 0x04) {
|
||||
void update_palette() {
|
||||
// b5: 320x200 palette, unless in monochrome mode.
|
||||
if(control_ & 0x04) {
|
||||
palette320[1] = DarkCyan;
|
||||
palette320[2] = DarkRed;
|
||||
palette320[3] = DarkGrey;
|
||||
} else {
|
||||
if(colours_ & 0x20) {
|
||||
palette320[1] = DarkCyan;
|
||||
palette320[2] = DarkRed;
|
||||
palette320[2] = DarkMagenta;
|
||||
palette320[3] = DarkGrey;
|
||||
} else {
|
||||
if(colours_ & 0x20) {
|
||||
palette320[1] = DarkCyan;
|
||||
palette320[2] = DarkMagenta;
|
||||
palette320[3] = DarkGrey;
|
||||
} else {
|
||||
palette320[1] = DarkGreen;
|
||||
palette320[2] = DarkRed;
|
||||
palette320[3] = DarkYellow;
|
||||
}
|
||||
palette320[1] = DarkGreen;
|
||||
palette320[2] = DarkRed;
|
||||
palette320[3] = DarkYellow;
|
||||
}
|
||||
|
||||
// b4: set 320x200 palette into high intensity.
|
||||
if(colours_ & 0x10) {
|
||||
palette320[1] = bright(palette320[1]);
|
||||
palette320[2] = bright(palette320[2]);
|
||||
palette320[3] = bright(palette320[3]);
|
||||
} else {
|
||||
// Remap dark yellow to brown if applicable.
|
||||
palette320[3] = yellow_to_brown(palette320[3]);
|
||||
}
|
||||
|
||||
// b3–b0: set background, border, monochrome colour.
|
||||
palette640[1] = palette320[0] = rgbi(colours_);
|
||||
border_colour = (mode_ != Mode::Pixels640) ? palette320[0] : 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Named colours and mapping logic.
|
||||
//
|
||||
static constexpr uint8_t DarkCyan = 0b00'10'10;
|
||||
static constexpr uint8_t DarkMagenta = 0b10'00'10;
|
||||
static constexpr uint8_t DarkGrey = 0b10'10'10;
|
||||
|
||||
static constexpr uint8_t DarkGreen = 0b00'10'00;
|
||||
static constexpr uint8_t DarkRed = 0b10'00'00;
|
||||
static constexpr uint8_t DarkYellow = 0b10'10'00;
|
||||
|
||||
static constexpr uint8_t Brown = 0b10'01'00;
|
||||
|
||||
/// @returns @c Brown if @c source is @c DarkYellow and composite output is not enabled; @c source otherwise.
|
||||
constexpr uint8_t yellow_to_brown(uint8_t source) {
|
||||
return (source == DarkYellow && !is_composite_) ? Brown : source;
|
||||
// b4: set 320x200 palette into high intensity.
|
||||
if(colours_ & 0x10) {
|
||||
palette320[1] = bright(palette320[1]);
|
||||
palette320[2] = bright(palette320[2]);
|
||||
palette320[3] = bright(palette320[3]);
|
||||
} else {
|
||||
// Remap dark yellow to brown if applicable.
|
||||
palette320[3] = yellow_to_brown(palette320[3]);
|
||||
}
|
||||
|
||||
/// @returns The brightened (i.e. high intensity) version of @c source.
|
||||
constexpr uint8_t bright(uint8_t source) {
|
||||
return source | (source >> 1);
|
||||
}
|
||||
// b3–b0: set background, border, monochrome colour.
|
||||
palette640[1] = palette320[0] = rgbi(colours_);
|
||||
border_colour = (mode_ != Mode::Pixels640) ? palette320[0] : 0;
|
||||
}
|
||||
|
||||
/// Maps the RGB TTL triplet @c source to an appropriate output colour.
|
||||
constexpr uint8_t rgb(uint8_t source) {
|
||||
return uint8_t(
|
||||
((source & 0x01) << 1) |
|
||||
((source & 0x02) << 2) |
|
||||
((source & 0x04) << 3)
|
||||
);
|
||||
}
|
||||
//
|
||||
// Named colours and mapping logic.
|
||||
//
|
||||
static constexpr uint8_t DarkCyan = 0b00'10'10;
|
||||
static constexpr uint8_t DarkMagenta = 0b10'00'10;
|
||||
static constexpr uint8_t DarkGrey = 0b10'10'10;
|
||||
|
||||
/// Maps the RGBI value in @c source to an appropriate output colour, including potential yellow-to-brown conversion.
|
||||
constexpr uint8_t rgbi(uint8_t source) {
|
||||
const uint8_t result = rgb(source);
|
||||
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
||||
}
|
||||
} outputter_;
|
||||
Motorola::CRTC::CRTC6845<
|
||||
CRTCOutputter,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
static constexpr uint8_t DarkGreen = 0b00'10'00;
|
||||
static constexpr uint8_t DarkRed = 0b10'00'00;
|
||||
static constexpr uint8_t DarkYellow = 0b10'10'00;
|
||||
|
||||
int full_clock_ = 0;
|
||||
static constexpr uint8_t Brown = 0b10'01'00;
|
||||
|
||||
/// @returns @c Brown if @c source is @c DarkYellow and composite output is not enabled; @c source otherwise.
|
||||
constexpr uint8_t yellow_to_brown(const uint8_t source) {
|
||||
return (source == DarkYellow && !is_composite_) ? Brown : source;
|
||||
}
|
||||
|
||||
/// @returns The brightened (i.e. high intensity) version of @c source.
|
||||
constexpr uint8_t bright(const uint8_t source) {
|
||||
return source | (source >> 1);
|
||||
}
|
||||
|
||||
/// Maps the RGB TTL triplet @c source to an appropriate output colour.
|
||||
constexpr uint8_t rgb(const uint8_t source) {
|
||||
return uint8_t(
|
||||
((source & 0x01) << 1) |
|
||||
((source & 0x02) << 2) |
|
||||
((source & 0x04) << 3)
|
||||
);
|
||||
}
|
||||
|
||||
/// Maps the RGBI value in @c source to an appropriate output colour, including potential yellow-to-brown conversion.
|
||||
constexpr uint8_t rgbi(const uint8_t source) {
|
||||
const uint8_t result = rgb(source);
|
||||
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
||||
}
|
||||
} outputter_;
|
||||
Motorola::CRTC::CRTC6845<
|
||||
CRTCOutputter,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
|
||||
int full_clock_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,125 +13,125 @@
|
||||
namespace PCCompatible {
|
||||
|
||||
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
public:
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override {
|
||||
using k = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
case k::Escape: return 1;
|
||||
public:
|
||||
uint16_t mapped_key_for_key(const Inputs::Keyboard::Key key) const override {
|
||||
using k = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
case k::Escape: return 1;
|
||||
|
||||
case k::k1: return 2;
|
||||
case k::k2: return 3;
|
||||
case k::k3: return 4;
|
||||
case k::k4: return 5;
|
||||
case k::k5: return 6;
|
||||
case k::k6: return 7;
|
||||
case k::k7: return 8;
|
||||
case k::k8: return 9;
|
||||
case k::k9: return 10;
|
||||
case k::k0: return 11;
|
||||
case k::k1: return 2;
|
||||
case k::k2: return 3;
|
||||
case k::k3: return 4;
|
||||
case k::k4: return 5;
|
||||
case k::k5: return 6;
|
||||
case k::k6: return 7;
|
||||
case k::k7: return 8;
|
||||
case k::k8: return 9;
|
||||
case k::k9: return 10;
|
||||
case k::k0: return 11;
|
||||
|
||||
case k::Hyphen: return 12;
|
||||
case k::Equals: return 13;
|
||||
case k::Backspace: return 14;
|
||||
case k::Hyphen: return 12;
|
||||
case k::Equals: return 13;
|
||||
case k::Backspace: return 14;
|
||||
|
||||
case k::Tab: return 15;
|
||||
case k::Q: return 16;
|
||||
case k::W: return 17;
|
||||
case k::E: return 18;
|
||||
case k::R: return 19;
|
||||
case k::T: return 20;
|
||||
case k::Y: return 21;
|
||||
case k::U: return 22;
|
||||
case k::I: return 23;
|
||||
case k::O: return 24;
|
||||
case k::P: return 25;
|
||||
case k::Tab: return 15;
|
||||
case k::Q: return 16;
|
||||
case k::W: return 17;
|
||||
case k::E: return 18;
|
||||
case k::R: return 19;
|
||||
case k::T: return 20;
|
||||
case k::Y: return 21;
|
||||
case k::U: return 22;
|
||||
case k::I: return 23;
|
||||
case k::O: return 24;
|
||||
case k::P: return 25;
|
||||
|
||||
case k::OpenSquareBracket: return 26;
|
||||
case k::CloseSquareBracket: return 27;
|
||||
case k::Enter: return 28;
|
||||
case k::OpenSquareBracket: return 26;
|
||||
case k::CloseSquareBracket: return 27;
|
||||
case k::Enter: return 28;
|
||||
|
||||
case k::LeftControl:
|
||||
case k::RightControl: return 29;
|
||||
case k::LeftControl:
|
||||
case k::RightControl: return 29;
|
||||
|
||||
case k::A: return 30;
|
||||
case k::S: return 31;
|
||||
case k::D: return 32;
|
||||
case k::F: return 33;
|
||||
case k::G: return 34;
|
||||
case k::H: return 35;
|
||||
case k::J: return 36;
|
||||
case k::K: return 37;
|
||||
case k::L: return 38;
|
||||
case k::A: return 30;
|
||||
case k::S: return 31;
|
||||
case k::D: return 32;
|
||||
case k::F: return 33;
|
||||
case k::G: return 34;
|
||||
case k::H: return 35;
|
||||
case k::J: return 36;
|
||||
case k::K: return 37;
|
||||
case k::L: return 38;
|
||||
|
||||
case k::Semicolon: return 39;
|
||||
case k::Quote: return 40;
|
||||
case k::BackTick: return 41;
|
||||
case k::Semicolon: return 39;
|
||||
case k::Quote: return 40;
|
||||
case k::BackTick: return 41;
|
||||
|
||||
case k::LeftShift: return 42;
|
||||
case k::Backslash: return 43;
|
||||
case k::LeftShift: return 42;
|
||||
case k::Backslash: return 43;
|
||||
|
||||
case k::Z: return 44;
|
||||
case k::X: return 45;
|
||||
case k::C: return 46;
|
||||
case k::V: return 47;
|
||||
case k::B: return 48;
|
||||
case k::N: return 49;
|
||||
case k::M: return 50;
|
||||
case k::Z: return 44;
|
||||
case k::X: return 45;
|
||||
case k::C: return 46;
|
||||
case k::V: return 47;
|
||||
case k::B: return 48;
|
||||
case k::N: return 49;
|
||||
case k::M: return 50;
|
||||
|
||||
case k::Comma: return 51;
|
||||
case k::FullStop: return 52;
|
||||
case k::ForwardSlash: return 53;
|
||||
case k::RightShift: return 54;
|
||||
case k::Comma: return 51;
|
||||
case k::FullStop: return 52;
|
||||
case k::ForwardSlash: return 53;
|
||||
case k::RightShift: return 54;
|
||||
|
||||
case k::KeypadAsterisk: return 55;
|
||||
case k::KeypadAsterisk: return 55;
|
||||
|
||||
case k::LeftOption:
|
||||
case k::RightOption: return 56;
|
||||
case k::Space: return 57;
|
||||
case k::CapsLock: return 58;
|
||||
case k::LeftOption:
|
||||
case k::RightOption: return 56;
|
||||
case k::Space: return 57;
|
||||
case k::CapsLock: return 58;
|
||||
|
||||
case k::F1: return 59;
|
||||
case k::F2: return 60;
|
||||
case k::F3: return 61;
|
||||
case k::F4: return 62;
|
||||
case k::F5: return 63;
|
||||
case k::F6: return 64;
|
||||
case k::F7: return 65;
|
||||
case k::F8: return 66;
|
||||
case k::F9: return 67;
|
||||
case k::F10: return 68;
|
||||
case k::F1: return 59;
|
||||
case k::F2: return 60;
|
||||
case k::F3: return 61;
|
||||
case k::F4: return 62;
|
||||
case k::F5: return 63;
|
||||
case k::F6: return 64;
|
||||
case k::F7: return 65;
|
||||
case k::F8: return 66;
|
||||
case k::F9: return 67;
|
||||
case k::F10: return 68;
|
||||
|
||||
case k::NumLock: return 69;
|
||||
case k::ScrollLock: return 70;
|
||||
case k::NumLock: return 69;
|
||||
case k::ScrollLock: return 70;
|
||||
|
||||
case k::Keypad7: return 71;
|
||||
case k::Up:
|
||||
case k::Keypad8: return 72;
|
||||
case k::Keypad9: return 73;
|
||||
case k::KeypadMinus: return 74;
|
||||
case k::Keypad7: return 71;
|
||||
case k::Up:
|
||||
case k::Keypad8: return 72;
|
||||
case k::Keypad9: return 73;
|
||||
case k::KeypadMinus: return 74;
|
||||
|
||||
case k::Left:
|
||||
case k::Keypad4: return 75;
|
||||
case k::Keypad5: return 76;
|
||||
case k::Right:
|
||||
case k::Keypad6: return 77;
|
||||
case k::KeypadPlus: return 78;
|
||||
case k::Left:
|
||||
case k::Keypad4: return 75;
|
||||
case k::Keypad5: return 76;
|
||||
case k::Right:
|
||||
case k::Keypad6: return 77;
|
||||
case k::KeypadPlus: return 78;
|
||||
|
||||
case k::Keypad1: return 79;
|
||||
case k::Down:
|
||||
case k::Keypad2: return 80;
|
||||
case k::Keypad3: return 81;
|
||||
case k::Keypad1: return 79;
|
||||
case k::Down:
|
||||
case k::Keypad2: return 80;
|
||||
case k::Keypad3: return 81;
|
||||
|
||||
case k::Keypad0: return 82;
|
||||
case k::KeypadDecimalPoint: return 83;
|
||||
/* TODO: SysReq = 84 */
|
||||
case k::Keypad0: return 82;
|
||||
case k::KeypadDecimalPoint: return 83;
|
||||
/* TODO: SysReq = 84 */
|
||||
|
||||
case k::F11: return 87;
|
||||
case k::F12: return 88;
|
||||
case k::F11: return 87;
|
||||
case k::F12: return 88;
|
||||
|
||||
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+184
-184
@@ -15,221 +15,221 @@
|
||||
namespace PCCompatible {
|
||||
|
||||
class MDA {
|
||||
public:
|
||||
MDA() : crtc_(outputter_) {}
|
||||
public:
|
||||
MDA() : crtc_(outputter_) {}
|
||||
|
||||
static constexpr uint32_t BaseAddress = 0xb'0000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleMDAFont;
|
||||
static constexpr uint32_t BaseAddress = 0xb'0000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleMDAFont;
|
||||
|
||||
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
|
||||
outputter_.ram = ram;
|
||||
outputter_.font = font;
|
||||
void set_source(const uint8_t *const ram, const std::vector<uint8_t> font) {
|
||||
outputter_.ram = ram;
|
||||
outputter_.font = font;
|
||||
}
|
||||
|
||||
void run_for(const 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<int>();
|
||||
crtc_.run_for(Cycles(full_clock_ / (596'591 * 9)));
|
||||
full_clock_ %= (596'591 * 9);
|
||||
}
|
||||
|
||||
template <int address>
|
||||
void write(const 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 <int address>
|
||||
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 *const 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.028f, 0.025f, 0.98f, 0.98f));
|
||||
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
}
|
||||
|
||||
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<int>();
|
||||
crtc_.run_for(Cycles(full_clock_ / (596'591 * 9)));
|
||||
full_clock_ %= (596'591 * 9);
|
||||
void set_control(const uint8_t control) {
|
||||
// b0: 'high resolution' (probably breaks if not 1)
|
||||
// b3: video enable
|
||||
// b5: enable blink
|
||||
control_ = control;
|
||||
}
|
||||
|
||||
template <int address>
|
||||
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;
|
||||
}
|
||||
uint8_t control() const {
|
||||
return control_;
|
||||
}
|
||||
|
||||
template <int address>
|
||||
uint8_t read() {
|
||||
switch(address) {
|
||||
case 1: case 3: case 5: case 7:
|
||||
return crtc_.get_register();
|
||||
void perform_bus_cycle(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);
|
||||
|
||||
case 0x8: return outputter_.control();
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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.028f, 0.025f, 0.98f, 0.98f));
|
||||
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
// (2) adopt new state.
|
||||
output_state = new_state;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
void set_control(uint8_t control) {
|
||||
// b0: 'high resolution' (probably breaks if not 1)
|
||||
// b3: video enable
|
||||
// b5: enable blink
|
||||
control_ = control;
|
||||
}
|
||||
// Collect pixels if applicable.
|
||||
if(output_state == OutputState::Pixels) {
|
||||
if(!pixels) {
|
||||
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
|
||||
|
||||
uint8_t control() {
|
||||
return control_;
|
||||
}
|
||||
// Flush any period where pixels weren't recorded due to back pressure.
|
||||
if(pixels && count) {
|
||||
crt.output_blank(count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void perform_bus_cycle(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);
|
||||
if(pixels) {
|
||||
static constexpr uint8_t high_intensity = 0x0d;
|
||||
static constexpr uint8_t low_intensity = 0x09;
|
||||
static constexpr uint8_t off = 0x00;
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// (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;
|
||||
// Apply blink if enabled.
|
||||
if((control_ & 0x20) && (attributes & 0x80) && (state.field_count & 16)) {
|
||||
row ^= 0xff;
|
||||
blank = (blank == off) ? intensity : off;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if(((attributes & 7) == 1) && state.row_address == 13) {
|
||||
// Draw as underline.
|
||||
std::fill(pixel_pointer, pixel_pointer + 9, 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;
|
||||
}
|
||||
// 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;
|
||||
pixel_pointer += 9;
|
||||
}
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT crt;
|
||||
// Advance.
|
||||
count += 9;
|
||||
|
||||
enum class OutputState {
|
||||
Sync, Pixels, Border
|
||||
} output_state = OutputState::Sync;
|
||||
int count = 0;
|
||||
// Output pixel row prematurely if storage is exhausted.
|
||||
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
|
||||
crt.output_data(count);
|
||||
count = 0;
|
||||
|
||||
uint8_t *pixels = nullptr;
|
||||
uint8_t *pixel_pointer = nullptr;
|
||||
static constexpr size_t DefaultAllocationSize = 720;
|
||||
pixels = pixel_pointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *ram = nullptr;
|
||||
std::vector<uint8_t> font;
|
||||
Outputs::CRT::CRT crt;
|
||||
|
||||
uint8_t control_ = 0;
|
||||
} outputter_;
|
||||
Motorola::CRTC::CRTC6845<
|
||||
CRTCOutputter,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
enum class OutputState {
|
||||
Sync, Pixels, Border
|
||||
} output_state = OutputState::Sync;
|
||||
int count = 0;
|
||||
|
||||
int full_clock_ = 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::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
|
||||
int full_clock_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+130
-130
@@ -14,159 +14,159 @@ namespace PCCompatible {
|
||||
|
||||
// Cf. https://helppc.netcore2k.net/hardware/pic
|
||||
class PIC {
|
||||
public:
|
||||
template <int address>
|
||||
void write(uint8_t value) {
|
||||
if(address) {
|
||||
if(config_.word >= 0) {
|
||||
switch(config_.word) {
|
||||
case 0:
|
||||
vector_base_ = value;
|
||||
break;
|
||||
case 1:
|
||||
if(config_.has_fourth_word) {
|
||||
// TODO:
|
||||
//
|
||||
// (1) slave mask if this is a master;
|
||||
// (2) master interrupt attachment if this is a slave.
|
||||
}
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
auto_eoi_ = value & 2;
|
||||
break;
|
||||
}
|
||||
public:
|
||||
template <int address>
|
||||
void write(const uint8_t value) {
|
||||
if(address) {
|
||||
if(config_.word >= 0) {
|
||||
switch(config_.word) {
|
||||
case 0:
|
||||
vector_base_ = value;
|
||||
break;
|
||||
case 1:
|
||||
if(config_.has_fourth_word) {
|
||||
// TODO:
|
||||
//
|
||||
// (1) slave mask if this is a master;
|
||||
// (2) master interrupt attachment if this is a slave.
|
||||
}
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
auto_eoi_ = value & 2;
|
||||
break;
|
||||
}
|
||||
|
||||
++config_.word;
|
||||
if(config_.word == (config_.has_fourth_word ? 3 : 2)) {
|
||||
config_.word = -1;
|
||||
}
|
||||
} else {
|
||||
mask_ = value;
|
||||
++config_.word;
|
||||
if(config_.word == (config_.has_fourth_word ? 3 : 2)) {
|
||||
config_.word = -1;
|
||||
}
|
||||
} else {
|
||||
if(value & 0x10) {
|
||||
//
|
||||
// Initialisation Command Word 1.
|
||||
//
|
||||
mask_ = value;
|
||||
}
|
||||
} else {
|
||||
if(value & 0x10) {
|
||||
//
|
||||
// Initialisation Command Word 1.
|
||||
//
|
||||
|
||||
config_.word = 0;
|
||||
config_.has_fourth_word = value & 1;
|
||||
config_.word = 0;
|
||||
config_.has_fourth_word = value & 1;
|
||||
|
||||
if(!config_.has_fourth_word) {
|
||||
auto_eoi_ = false;
|
||||
}
|
||||
if(!config_.has_fourth_word) {
|
||||
auto_eoi_ = false;
|
||||
}
|
||||
|
||||
single_pic_ = value & 2;
|
||||
four_byte_vectors_ = value & 4;
|
||||
level_triggered_ = value & 8;
|
||||
} else if(value & 0x08) {
|
||||
//
|
||||
// Operation Control Word 3.
|
||||
//
|
||||
single_pic_ = value & 2;
|
||||
four_byte_vectors_ = value & 4;
|
||||
level_triggered_ = value & 8;
|
||||
} else if(value & 0x08) {
|
||||
//
|
||||
// Operation Control Word 3.
|
||||
//
|
||||
|
||||
// b6: 1 => use b5; 0 => ignore.
|
||||
// b5: 1 => set special mask; 0 => clear.
|
||||
// b2: 1 => poll command issued; 0 => not.
|
||||
// b1: 1 => use b0; 0 => ignore.
|
||||
// b0: 1 => read IRR on next read; 0 => read ISR.
|
||||
} else {
|
||||
//
|
||||
// Operation Control Word 2.
|
||||
//
|
||||
// b6: 1 => use b5; 0 => ignore.
|
||||
// b5: 1 => set special mask; 0 => clear.
|
||||
// b2: 1 => poll command issued; 0 => not.
|
||||
// b1: 1 => use b0; 0 => ignore.
|
||||
// b0: 1 => read IRR on next read; 0 => read ISR.
|
||||
} else {
|
||||
//
|
||||
// Operation Control Word 2.
|
||||
//
|
||||
|
||||
// b7, b6, b5: EOI type.
|
||||
// b2, b1, b0: interrupt level to acknowledge.
|
||||
switch(value >> 5) {
|
||||
default:
|
||||
printf("PIC: TODO EOI type %d\n", value >> 5);
|
||||
case 0b010: // No-op.
|
||||
break;
|
||||
// b7, b6, b5: EOI type.
|
||||
// b2, b1, b0: interrupt level to acknowledge.
|
||||
switch(value >> 5) {
|
||||
default:
|
||||
printf("PIC: TODO EOI type %d\n", value >> 5);
|
||||
case 0b010: // No-op.
|
||||
break;
|
||||
|
||||
case 0b001: // Non-specific EOI.
|
||||
case 0b001: // Non-specific EOI.
|
||||
awaiting_eoi_ = false;
|
||||
break;
|
||||
|
||||
case 0b011: { // Specific EOI.
|
||||
if((value & 3) == eoi_target_) {
|
||||
awaiting_eoi_ = false;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0b011: { // Specific EOI.
|
||||
if((value & 3) == eoi_target_) {
|
||||
awaiting_eoi_ = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
// TODO:
|
||||
// 0b000 = rotate in auto EOI mode (clear)
|
||||
// 0b100 = rotate in auto EOI mode (set)
|
||||
// 0b101 = rotate on nonspecific EOI command
|
||||
// 0b110 = set primary command
|
||||
// 0b111 = rotate on specific EOI command
|
||||
}
|
||||
// TODO:
|
||||
// 0b000 = rotate in auto EOI mode (clear)
|
||||
// 0b100 = rotate in auto EOI mode (set)
|
||||
// 0b101 = rotate on nonspecific EOI command
|
||||
// 0b110 = set primary command
|
||||
// 0b111 = rotate on specific EOI command
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int address>
|
||||
uint8_t read() {
|
||||
if(address) {
|
||||
return mask_;
|
||||
}
|
||||
return 0;
|
||||
template <int address>
|
||||
uint8_t read() const {
|
||||
if(address) {
|
||||
return mask_;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <int input>
|
||||
void apply_edge(const bool final_level) {
|
||||
constexpr uint8_t input_mask = 1 << input;
|
||||
|
||||
// Guess: level triggered means the request can be forwarded only so long as the
|
||||
// relevant input is actually high. Whereas edge triggered implies capturing state.
|
||||
if(level_triggered_) {
|
||||
requests_ &= ~input_mask;
|
||||
}
|
||||
if(final_level) {
|
||||
requests_ |= input_mask;
|
||||
}
|
||||
}
|
||||
|
||||
bool pending() const {
|
||||
// Per the OSDev Wiki, masking is applied after the fact.
|
||||
return !awaiting_eoi_ && (requests_ & ~mask_);
|
||||
}
|
||||
|
||||
int acknowledge() {
|
||||
in_service_ = 0x01;
|
||||
int id = 0;
|
||||
while(!(in_service_ & requests_) && in_service_) {
|
||||
in_service_ <<= 1;
|
||||
++id;
|
||||
}
|
||||
|
||||
template <int input>
|
||||
void apply_edge(bool final_level) {
|
||||
constexpr uint8_t input_mask = 1 << input;
|
||||
|
||||
// Guess: level triggered means the request can be forwarded only so long as the
|
||||
// relevant input is actually high. Whereas edge triggered implies capturing state.
|
||||
if(level_triggered_) {
|
||||
requests_ &= ~input_mask;
|
||||
}
|
||||
if(final_level) {
|
||||
requests_ |= input_mask;
|
||||
}
|
||||
if(in_service_) {
|
||||
eoi_target_ = id;
|
||||
awaiting_eoi_ = !auto_eoi_;
|
||||
requests_ &= ~in_service_;
|
||||
return vector_base_ + id;
|
||||
}
|
||||
|
||||
bool pending() {
|
||||
// Per the OSDev Wiki, masking is applied after the fact.
|
||||
return !awaiting_eoi_ && (requests_ & ~mask_);
|
||||
}
|
||||
// Spurious interrupt.
|
||||
return vector_base_ + 7;
|
||||
}
|
||||
|
||||
int acknowledge() {
|
||||
in_service_ = 0x01;
|
||||
int id = 0;
|
||||
while(!(in_service_ & requests_) && in_service_) {
|
||||
in_service_ <<= 1;
|
||||
++id;
|
||||
}
|
||||
private:
|
||||
bool single_pic_ = false;
|
||||
bool four_byte_vectors_ = false;
|
||||
bool level_triggered_ = false;
|
||||
bool auto_eoi_ = false;
|
||||
|
||||
if(in_service_) {
|
||||
eoi_target_ = id;
|
||||
awaiting_eoi_ = !auto_eoi_;
|
||||
requests_ &= ~in_service_;
|
||||
return vector_base_ + id;
|
||||
}
|
||||
uint8_t vector_base_ = 0;
|
||||
uint8_t mask_ = 0;
|
||||
bool awaiting_eoi_ = false;
|
||||
int eoi_target_ = 0;
|
||||
|
||||
// Spurious interrupt.
|
||||
return vector_base_ + 7;
|
||||
}
|
||||
uint8_t requests_ = 0;
|
||||
uint8_t in_service_ = 0;
|
||||
|
||||
private:
|
||||
bool single_pic_ = false;
|
||||
bool four_byte_vectors_ = false;
|
||||
bool level_triggered_ = false;
|
||||
bool auto_eoi_ = false;
|
||||
|
||||
uint8_t vector_base_ = 0;
|
||||
uint8_t mask_ = 0;
|
||||
bool awaiting_eoi_ = false;
|
||||
int eoi_target_ = 0;
|
||||
|
||||
uint8_t requests_ = 0;
|
||||
uint8_t in_service_ = 0;
|
||||
|
||||
struct ConfgurationState {
|
||||
int word;
|
||||
bool has_fourth_word;
|
||||
} config_;
|
||||
struct ConfgurationState {
|
||||
int word;
|
||||
bool has_fourth_word;
|
||||
} config_;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
|
||||
+214
-214
@@ -12,245 +12,245 @@ namespace PCCompatible {
|
||||
|
||||
template <bool is_8254, typename PITObserver>
|
||||
class i8253 {
|
||||
public:
|
||||
i8253(PITObserver &observer) : observer_(observer) {}
|
||||
public:
|
||||
i8253(PITObserver &observer) : observer_(observer) {}
|
||||
|
||||
template <int channel> uint8_t read() {
|
||||
return channels_[channel].read();
|
||||
template <int channel> uint8_t read() {
|
||||
return channels_[channel].read();
|
||||
}
|
||||
|
||||
template <int channel> void write(const uint8_t value) {
|
||||
channels_[channel].template write<channel>(observer_, value);
|
||||
}
|
||||
|
||||
void set_mode(const uint8_t value) {
|
||||
const int channel_id = (value >> 6) & 3;
|
||||
if(channel_id == 3) {
|
||||
// TODO: decode rest of read-back command.
|
||||
read_back_ = is_8254;
|
||||
return;
|
||||
}
|
||||
switch(channel_id) {
|
||||
case 0: channels_[0].template set_mode<0>(observer_, value); break;
|
||||
case 1: channels_[1].template set_mode<1>(observer_, value); break;
|
||||
case 2: channels_[2].template set_mode<2>(observer_, value); break;
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
// TODO: be intelligent enough to take ticks outside the loop when appropriate.
|
||||
auto ticks = cycles.as<int>();
|
||||
while(ticks--) {
|
||||
channels_[0].template advance<0>(observer_, 1);
|
||||
channels_[1].template advance<1>(observer_, 1);
|
||||
channels_[2].template advance<2>(observer_, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// The target for output changes.
|
||||
PITObserver &observer_;
|
||||
|
||||
// Supported only on 8254s.
|
||||
bool read_back_ = false;
|
||||
|
||||
enum class LatchMode {
|
||||
LowOnly,
|
||||
HighOnly,
|
||||
LowHigh,
|
||||
};
|
||||
|
||||
enum class OperatingMode {
|
||||
InterruptOnTerminalCount = 0,
|
||||
HardwareRetriggerableOneShot = 1,
|
||||
RateGenerator = 2,
|
||||
SquareWaveGenerator = 3,
|
||||
SoftwareTriggeredStrobe = 4,
|
||||
HardwareTriggeredStrobe = 5,
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
LatchMode latch_mode = LatchMode::LowHigh;
|
||||
OperatingMode mode = OperatingMode::InterruptOnTerminalCount;
|
||||
bool is_bcd = false;
|
||||
|
||||
bool gated = false;
|
||||
bool awaiting_reload = true;
|
||||
|
||||
uint16_t counter = 0;
|
||||
uint16_t reload = 0;
|
||||
uint16_t latch = 0;
|
||||
bool output = false;
|
||||
|
||||
bool next_access_high = false;
|
||||
|
||||
void latch_value() {
|
||||
latch = counter;
|
||||
}
|
||||
|
||||
template <int channel> void write(uint8_t value) {
|
||||
channels_[channel].template write<channel>(observer_, value);
|
||||
}
|
||||
|
||||
void set_mode(uint8_t value) {
|
||||
const int channel_id = (value >> 6) & 3;
|
||||
if(channel_id == 3) {
|
||||
// TODO: decode rest of read-back command.
|
||||
read_back_ = is_8254;
|
||||
template <int channel>
|
||||
void set_mode(PITObserver &observer, const uint8_t value) {
|
||||
switch((value >> 4) & 3) {
|
||||
default:
|
||||
latch_value();
|
||||
return;
|
||||
|
||||
case 1: latch_mode = LatchMode::LowOnly; break;
|
||||
case 2: latch_mode = LatchMode::HighOnly; break;
|
||||
case 3: latch_mode = LatchMode::LowHigh; break;
|
||||
}
|
||||
switch(channel_id) {
|
||||
case 0: channels_[0].template set_mode<0>(observer_, value); break;
|
||||
case 1: channels_[1].template set_mode<1>(observer_, value); break;
|
||||
case 2: channels_[2].template set_mode<2>(observer_, value); break;
|
||||
is_bcd = value & 1;
|
||||
next_access_high = false;
|
||||
|
||||
const auto operating_mode = (value >> 1) & 7;
|
||||
switch(operating_mode) {
|
||||
default: mode = OperatingMode(operating_mode); break;
|
||||
case 6: mode = OperatingMode::RateGenerator; break;
|
||||
case 7: mode = OperatingMode::SquareWaveGenerator; break;
|
||||
}
|
||||
|
||||
// Set up operating mode.
|
||||
switch(mode) {
|
||||
default:
|
||||
printf("PIT: unimplemented mode %d\n", int(mode));
|
||||
break;
|
||||
|
||||
case OperatingMode::InterruptOnTerminalCount:
|
||||
case OperatingMode::HardwareRetriggerableOneShot:
|
||||
set_output<channel>(observer, false);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
|
||||
case OperatingMode::RateGenerator:
|
||||
case OperatingMode::SquareWaveGenerator:
|
||||
set_output<channel>(observer, true);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(Cycles cycles) {
|
||||
// TODO: be intelligent enough to take ticks outside the loop when appropriate.
|
||||
auto ticks = cycles.as<int>();
|
||||
while(ticks--) {
|
||||
channels_[0].template advance<0>(observer_, 1);
|
||||
channels_[1].template advance<1>(observer_, 1);
|
||||
channels_[2].template advance<2>(observer_, 1);
|
||||
}
|
||||
}
|
||||
template <int channel>
|
||||
void advance(PITObserver &observer, int ticks) {
|
||||
if(gated || awaiting_reload) return;
|
||||
|
||||
private:
|
||||
// The target for output changes.
|
||||
PITObserver &observer_;
|
||||
// TODO: BCD mode is completely ignored below. Possibly not too important.
|
||||
switch(mode) {
|
||||
case OperatingMode::InterruptOnTerminalCount:
|
||||
case OperatingMode::HardwareRetriggerableOneShot:
|
||||
// Output goes permanently high upon a tick from 1 to 0; reload value is not reused.
|
||||
set_output<channel>(observer, output | (counter <= ticks));
|
||||
counter -= ticks;
|
||||
break;
|
||||
|
||||
// Supported only on 8254s.
|
||||
bool read_back_ = false;
|
||||
case OperatingMode::SquareWaveGenerator: {
|
||||
ticks <<= 1;
|
||||
do {
|
||||
// If there's a step from 1 to 0 within the next batch of ticks,
|
||||
// toggle output and apply a reload.
|
||||
if(counter && ticks >= counter) {
|
||||
set_output<channel>(observer, output ^ true);
|
||||
ticks -= counter;
|
||||
|
||||
enum class LatchMode {
|
||||
LowOnly,
|
||||
HighOnly,
|
||||
LowHigh,
|
||||
};
|
||||
const uint16_t reload_mask = output ? 0xffff : 0xfffe;
|
||||
counter = reload & reload_mask;
|
||||
|
||||
enum class OperatingMode {
|
||||
InterruptOnTerminalCount = 0,
|
||||
HardwareRetriggerableOneShot = 1,
|
||||
RateGenerator = 2,
|
||||
SquareWaveGenerator = 3,
|
||||
SoftwareTriggeredStrobe = 4,
|
||||
HardwareTriggeredStrobe = 5,
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
LatchMode latch_mode = LatchMode::LowHigh;
|
||||
OperatingMode mode = OperatingMode::InterruptOnTerminalCount;
|
||||
bool is_bcd = false;
|
||||
|
||||
bool gated = false;
|
||||
bool awaiting_reload = true;
|
||||
|
||||
uint16_t counter = 0;
|
||||
uint16_t reload = 0;
|
||||
uint16_t latch = 0;
|
||||
bool output = false;
|
||||
|
||||
bool next_access_high = false;
|
||||
|
||||
void latch_value() {
|
||||
latch = counter;
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void set_mode(PITObserver &observer, uint8_t value) {
|
||||
switch((value >> 4) & 3) {
|
||||
default:
|
||||
latch_value();
|
||||
return;
|
||||
|
||||
case 1: latch_mode = LatchMode::LowOnly; break;
|
||||
case 2: latch_mode = LatchMode::HighOnly; break;
|
||||
case 3: latch_mode = LatchMode::LowHigh; break;
|
||||
}
|
||||
is_bcd = value & 1;
|
||||
next_access_high = false;
|
||||
|
||||
const auto operating_mode = (value >> 1) & 7;
|
||||
switch(operating_mode) {
|
||||
default: mode = OperatingMode(operating_mode); break;
|
||||
case 6: mode = OperatingMode::RateGenerator; break;
|
||||
case 7: mode = OperatingMode::SquareWaveGenerator; break;
|
||||
}
|
||||
|
||||
// Set up operating mode.
|
||||
switch(mode) {
|
||||
default:
|
||||
printf("PIT: unimplemented mode %d\n", int(mode));
|
||||
break;
|
||||
|
||||
case OperatingMode::InterruptOnTerminalCount:
|
||||
case OperatingMode::HardwareRetriggerableOneShot:
|
||||
set_output<channel>(observer, false);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
|
||||
case OperatingMode::RateGenerator:
|
||||
case OperatingMode::SquareWaveGenerator:
|
||||
set_output<channel>(observer, true);
|
||||
awaiting_reload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void advance(PITObserver &observer, int ticks) {
|
||||
if(gated || awaiting_reload) return;
|
||||
|
||||
// TODO: BCD mode is completely ignored below. Possibly not too important.
|
||||
switch(mode) {
|
||||
case OperatingMode::InterruptOnTerminalCount:
|
||||
case OperatingMode::HardwareRetriggerableOneShot:
|
||||
// Output goes permanently high upon a tick from 1 to 0; reload value is not reused.
|
||||
set_output<channel>(observer, output | (counter <= ticks));
|
||||
continue;
|
||||
}
|
||||
counter -= ticks;
|
||||
break;
|
||||
} while(false);
|
||||
} break;
|
||||
|
||||
case OperatingMode::SquareWaveGenerator: {
|
||||
ticks <<= 1;
|
||||
do {
|
||||
// If there's a step from 1 to 0 within the next batch of ticks,
|
||||
// toggle output and apply a reload.
|
||||
if(counter && ticks >= counter) {
|
||||
set_output<channel>(observer, output ^ true);
|
||||
ticks -= counter;
|
||||
|
||||
const uint16_t reload_mask = output ? 0xffff : 0xfffe;
|
||||
counter = reload & reload_mask;
|
||||
|
||||
continue;
|
||||
}
|
||||
counter -= ticks;
|
||||
} while(false);
|
||||
} break;
|
||||
|
||||
case OperatingMode::RateGenerator:
|
||||
do {
|
||||
// Check for a step from 2 to 1 within the next batch of ticks, which would cause output
|
||||
// to go high.
|
||||
if(counter > 1 && ticks >= counter - 1) {
|
||||
set_output<channel>(observer, true);
|
||||
ticks -= counter - 1;
|
||||
counter = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a step from 1 to 0, reload and set output back to low.
|
||||
if(counter && ticks >= counter) {
|
||||
set_output<channel>(observer, false);
|
||||
ticks -= counter;
|
||||
counter = reload;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, just continue.
|
||||
counter -= ticks;
|
||||
} while(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void write([[maybe_unused]] PITObserver &observer, uint8_t value) {
|
||||
switch(latch_mode) {
|
||||
case LatchMode::LowOnly:
|
||||
reload = (reload & 0xff00) | value;
|
||||
break;
|
||||
case LatchMode::HighOnly:
|
||||
reload = uint16_t((reload & 0x00ff) | (value << 8));
|
||||
break;
|
||||
case LatchMode::LowHigh:
|
||||
next_access_high ^= true;
|
||||
if(next_access_high) {
|
||||
reload = (reload & 0xff00) | value;
|
||||
awaiting_reload = true;
|
||||
return;
|
||||
case OperatingMode::RateGenerator:
|
||||
do {
|
||||
// Check for a step from 2 to 1 within the next batch of ticks, which would cause output
|
||||
// to go high.
|
||||
if(counter > 1 && ticks >= counter - 1) {
|
||||
set_output<channel>(observer, true);
|
||||
ticks -= counter - 1;
|
||||
counter = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
reload = uint16_t((reload & 0x00ff) | (value << 8));
|
||||
break;
|
||||
}
|
||||
// If there is a step from 1 to 0, reload and set output back to low.
|
||||
if(counter && ticks >= counter) {
|
||||
set_output<channel>(observer, false);
|
||||
ticks -= counter;
|
||||
counter = reload;
|
||||
continue;
|
||||
}
|
||||
|
||||
awaiting_reload = false;
|
||||
// Otherwise, just continue.
|
||||
counter -= ticks;
|
||||
} while(false);
|
||||
break;
|
||||
|
||||
switch(mode) {
|
||||
default:
|
||||
counter = reload;
|
||||
default:
|
||||
// TODO.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case OperatingMode::SquareWaveGenerator:
|
||||
counter = reload & ~1;
|
||||
break;
|
||||
}
|
||||
template <int channel>
|
||||
void write([[maybe_unused]] PITObserver &observer, const uint8_t value) {
|
||||
switch(latch_mode) {
|
||||
case LatchMode::LowOnly:
|
||||
reload = (reload & 0xff00) | value;
|
||||
break;
|
||||
case LatchMode::HighOnly:
|
||||
reload = uint16_t((reload & 0x00ff) | (value << 8));
|
||||
break;
|
||||
case LatchMode::LowHigh:
|
||||
next_access_high ^= true;
|
||||
if(next_access_high) {
|
||||
reload = (reload & 0xff00) | value;
|
||||
awaiting_reload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
reload = uint16_t((reload & 0x00ff) | (value << 8));
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
switch(latch_mode) {
|
||||
case LatchMode::LowOnly: return uint8_t(latch);
|
||||
case LatchMode::HighOnly: return uint8_t(latch >> 8);
|
||||
default:
|
||||
case LatchMode::LowHigh:
|
||||
next_access_high ^= true;
|
||||
return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8);
|
||||
break;
|
||||
}
|
||||
awaiting_reload = false;
|
||||
|
||||
switch(mode) {
|
||||
default:
|
||||
counter = reload;
|
||||
break;
|
||||
|
||||
case OperatingMode::SquareWaveGenerator:
|
||||
counter = reload & ~1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
switch(latch_mode) {
|
||||
case LatchMode::LowOnly: return uint8_t(latch);
|
||||
case LatchMode::HighOnly: return uint8_t(latch >> 8);
|
||||
default:
|
||||
case LatchMode::LowHigh:
|
||||
next_access_high ^= true;
|
||||
return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void set_output(PITObserver &observer, const bool level) {
|
||||
if(output == level) {
|
||||
return;
|
||||
}
|
||||
|
||||
template <int channel>
|
||||
void set_output(PITObserver &observer, bool level) {
|
||||
if(output == level) {
|
||||
return;
|
||||
}
|
||||
// TODO: how should time be notified?
|
||||
observer.template update_output<channel>(level);
|
||||
output = level;
|
||||
}
|
||||
} channels_[3];
|
||||
|
||||
// TODO: how should time be notified?
|
||||
observer.template update_output<channel>(level);
|
||||
output = level;
|
||||
}
|
||||
} channels_[3];
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// RateGenerator: output goes high if gated.
|
||||
// TODO:
|
||||
//
|
||||
// RateGenerator: output goes high if gated.
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,41 +17,41 @@ namespace PCCompatible {
|
||||
|
||||
template <InstructionSet::x86::Model model>
|
||||
class Segments {
|
||||
public:
|
||||
Segments(const Registers<model> ®isters) : registers_(registers) {}
|
||||
public:
|
||||
Segments(const Registers<model> ®isters) : registers_(registers) {}
|
||||
|
||||
using Source = InstructionSet::x86::Source;
|
||||
using Source = InstructionSet::x86::Source;
|
||||
|
||||
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
||||
void did_update(Source segment) {
|
||||
switch(segment) {
|
||||
default: break;
|
||||
case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break;
|
||||
case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break;
|
||||
case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break;
|
||||
case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break;
|
||||
}
|
||||
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
||||
void did_update(const Source segment) {
|
||||
switch(segment) {
|
||||
default: break;
|
||||
case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break;
|
||||
case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break;
|
||||
case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break;
|
||||
case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
did_update(Source::ES);
|
||||
did_update(Source::CS);
|
||||
did_update(Source::DS);
|
||||
did_update(Source::SS);
|
||||
}
|
||||
void reset() {
|
||||
did_update(Source::ES);
|
||||
did_update(Source::CS);
|
||||
did_update(Source::DS);
|
||||
did_update(Source::SS);
|
||||
}
|
||||
|
||||
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
||||
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
||||
|
||||
bool operator ==(const Segments &rhs) const {
|
||||
return
|
||||
es_base_ == rhs.es_base_ &&
|
||||
cs_base_ == rhs.cs_base_ &&
|
||||
ds_base_ == rhs.ds_base_ &&
|
||||
ss_base_ == rhs.ss_base_;
|
||||
}
|
||||
bool operator ==(const Segments &rhs) const {
|
||||
return
|
||||
es_base_ == rhs.es_base_ &&
|
||||
cs_base_ == rhs.cs_base_ &&
|
||||
ds_base_ == rhs.ds_base_ &&
|
||||
ss_base_ == rhs.ss_base_;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registers<model> ®isters_;
|
||||
private:
|
||||
const Registers<model> ®isters_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user