1
0
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:
Thomas Harte
2025-03-06 22:33:58 -05:00
parent 7d84d6909e
commit f318bec53c
6 changed files with 1030 additions and 1031 deletions
+376 -377
View File
@@ -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]);
}
// b3b0: 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);
}
// b3b0: 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;
};
}
+98 -98
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
};
}
+28 -28
View File
@@ -17,41 +17,41 @@ namespace PCCompatible {
template <InstructionSet::x86::Model model>
class Segments {
public:
Segments(const Registers<model> &registers) : registers_(registers) {}
public:
Segments(const Registers<model> &registers) : 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> &registers_;
private:
const Registers<model> &registers_;
};
}