// // 6847.cpp // Clock Signal // // Created by Thomas Harte on 04/05/2026. // Copyright © 2026 Thomas Harte. All rights reserved. // #include "6847.hpp" #include namespace { static constexpr uint8_t font[64][12] = { { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1a, 0x2a, 0x2a, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12, 0x1c, 0x12, 0x12, 0x3c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12, 0x12, 0x12, 0x12, 0x3c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x3e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1e, 0x20, 0x20, 0x26, 0x22, 0x22, 0x1e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x22, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x22, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x24, 0x1a, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x28, 0x24, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x10, 0x08, 0x04, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x0e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x1c, 0x2a, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x3e, 0x10, 0x08, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x14, 0x14, 0x36, 0x00, 0x36, 0x14, 0x14, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x1e, 0x20, 0x1c, 0x02, 0x3c, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x32, 0x32, 0x04, 0x08, 0x10, 0x26, 0x26, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x10, 0x28, 0x28, 0x10, 0x2a, 0x24, 0x1a, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x3e, 0x1c, 0x08, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x10, 0x20, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x24, 0x24, 0x24, 0x18, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x20, 0x3e, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x02, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x04, 0x0c, 0x14, 0x3e, 0x04, 0x04, 0x04, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x1c, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, }, { 0x00, 0x00, 0x00, 0x18, 0x24, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, }, }; namespace Colours { constexpr uint32_t rgb(const uint8_t r, const uint8_t g, const uint8_t b) { uint8_t value[] = { r, g, b, 0 }; return std::bit_cast(value); } // // RGB values below sourced from http://www.hcvgm.org/VDG_Colours.html // constexpr uint32_t text0[] = { rgb(0x10, 0x60, 0x10), rgb(0x28, 0xe0, 0x28) }; constexpr uint32_t text1[] = { rgb(0xf0, 0xb0, 0x40), rgb(0x80, 0x30, 0x10) }; constexpr uint32_t resolution0[] = { rgb(0x10, 0x60, 0x10), rgb(0x28, 0xe0, 0x28) }; constexpr uint32_t resolution1[] = { rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0xf0) }; constexpr uint32_t colour0[] = { rgb(0x28, 0xe0, 0x28), rgb(0xf0, 0xf0, 0x70), rgb(0x20, 0x20, 0xd8), rgb(0xa8, 0x20, 0x20), }; constexpr uint32_t colour1[] = { rgb(0xf0, 0xf0, 0xf0), rgb(0x28, 0xa8, 0xa8), rgb(0xd3, 0x61, 0xfa), rgb(0xf0, 0x88, 0x28), }; constexpr uint32_t semigraphics[][2] = { { rgb(0x00, 0x00, 0x00), rgb(0x28, 0xe0, 0x28), }, { rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0x70), }, { rgb(0x00, 0x00, 0x00), rgb(0x20, 0x20, 0xd8), }, { rgb(0x00, 0x00, 0x00), rgb(0xa8, 0x20, 0x20), }, { rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0xf0), }, { rgb(0x00, 0x00, 0x00), rgb(0x28, 0xa8, 0xa8), }, { rgb(0x00, 0x00, 0x00), rgb(0xd3, 0x61, 0xfa), }, { rgb(0x00, 0x00, 0x00), rgb(0xf0, 0x88, 0x28), }, }; } // Empirical, by eye. static constexpr uint8_t ColourPhase = 215; } using namespace Motorola::MC6847; MC6847Base::MC6847Base(const Outputs::Display::Type display_type) : crt_( 455, 1, display_type, Outputs::Display::InputDataType::Red8Green8Blue8 ), display_type_(display_type) { crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour); } void MC6847Base::pixel_line(const int line_begin, const int line_end) { if(line_begin < LineLayout::EndOfLeftBorder && line_end >= LineLayout::EndOfLeftBorder) [[unlikely]] { crt_.output_sync(LineLayout::EndOfSync); if(display_type_ == Outputs::Display::Type::NTSC60) { crt_.output_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync, ColourPhase); } else { crt_.output_default_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync); } crt_.output_blank(LineLayout::EndOfLeftBorder - LineLayout::EndOfColourBurst); pixels_ = reinterpret_cast(crt_.begin_data(256)); } if(pixels_) [[likely]] { Numeric::clamp( line_begin, line_end, [&](const int begin, const int end) { const int row = address_.row(); const int column_begin = (begin - LineLayout::EndOfLeftBorder) >> 3; const int column_end = (end - LineLayout::EndOfLeftBorder) >> 3; for(int c = column_begin; c < column_end; c++) { const auto mode = line_.columns[c].mode; const auto data = line_.columns[c & ~(mode & Mode::Columns16)].data; if(mode & Mode::Graphics) { switch(mode & (Mode::BPP2 | Mode::Columns16)) { case 0: { // 1bpp, 32-column. const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::resolution1 : Colours::resolution0; pixels_[0] = palette[(data >> 7) & 1]; pixels_[1] = palette[(data >> 6) & 1]; pixels_[2] = palette[(data >> 5) & 1]; pixels_[3] = palette[(data >> 4) & 1]; pixels_[4] = palette[(data >> 3) & 1]; pixels_[5] = palette[(data >> 2) & 1]; pixels_[6] = palette[(data >> 1) & 1]; pixels_[7] = palette[(data >> 0) & 1]; } break; case Mode::Columns16: { // 1bpp, 16-column. const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::resolution1 : Colours::resolution0; if(c & 1) { pixels_[0] = pixels_[1] = palette[(data >> 3) & 1]; pixels_[2] = pixels_[3] = palette[(data >> 2) & 1]; pixels_[4] = pixels_[5] = palette[(data >> 1) & 1]; pixels_[6] = pixels_[7] = palette[(data >> 0) & 1]; } else { pixels_[0] = pixels_[1] = palette[(data >> 7) & 1]; pixels_[2] = pixels_[3] = palette[(data >> 6) & 1]; pixels_[4] = pixels_[5] = palette[(data >> 5) & 1]; pixels_[6] = pixels_[7] = palette[(data >> 4) & 1]; } } break; case Mode::BPP2: { // 2bpp, 32-column. const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::colour1 : Colours::colour0; pixels_[0] = pixels_[1] = palette[(data >> 6) & 3]; pixels_[2] = pixels_[3] = palette[(data >> 4) & 3]; pixels_[4] = pixels_[5] = palette[(data >> 2) & 3]; pixels_[6] = pixels_[7] = palette[(data >> 0) & 3]; } break; case Mode::BPP2 | Mode::Columns16: { // 2bpp, 16-column. const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::colour1 : Colours::colour0; if(c & 1) { pixels_[0] = pixels_[1] = pixels_[2] = pixels_[3] = palette[(data >> 2) & 3]; pixels_[4] = pixels_[5] = pixels_[6] = pixels_[7] = palette[(data >> 0) & 3]; } else { pixels_[0] = pixels_[1] = pixels_[2] = pixels_[3] = palette[(data >> 6) & 3]; pixels_[4] = pixels_[5] = pixels_[6] = pixels_[7] = palette[(data >> 4) & 3]; } } break; default: __builtin_unreachable(); } } else { if(mode & Mode::Semigraphics) { const uint32_t *palette; uint8_t pixels; if(mode & Mode::ExternalROM) { palette = Colours::semigraphics[data >> 6]; pixels = [&]() -> uint8_t { if(row < 4) return data >> 4; if(row < 8) return data >> 2; return data; } (); } else { palette = Colours::semigraphics[(data >> 4) & 7]; pixels = row < 6 ? data >> 2 : data; } pixels_[0] = pixels_[1] = pixels_[2] = pixels_[3] = palette[(pixels >> 1) & 1]; pixels_[4] = pixels_[5] = pixels_[6] = pixels_[7] = palette[(pixels >> 0) & 1]; } else { const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::text1 : Colours::text0; uint8_t pixels = mode & Mode::Invert ? 0xff : 0x00; if(mode & Mode::ExternalROM) { // TODO: external ROM. } else { pixels ^= font[data & 63][row] ^ (data & 64 ? 0xff : 0x00); } pixels_[0] = palette[(pixels >> 7) & 1]; pixels_[1] = palette[(pixels >> 6) & 1]; pixels_[2] = palette[(pixels >> 5) & 1]; pixels_[3] = palette[(pixels >> 4) & 1]; pixels_[4] = palette[(pixels >> 3) & 1]; pixels_[5] = palette[(pixels >> 2) & 1]; pixels_[6] = palette[(pixels >> 1) & 1]; pixels_[7] = palette[(pixels >> 0) & 1]; } } pixels_ += 8; } } ); } if(line_end == LineLayout::EndOfLine) [[unlikely]] { crt_.output_data(256); address_.apply_hsync(); crt_.output_blank(LineLayout::EndOfLine - LineLayout::EndOfPixels); } } void MC6847Base::border_line(const int, const int end) { if(end == LineLayout::EndOfLine) { crt_.output_sync(LineLayout::EndOfSync); crt_.output_blank(LineLayout::EndOfLine - LineLayout::EndOfSync); } } void MC6847Base::porch_line(const int begin, const int end) { border_line(begin, end); } void MC6847Base::sync_line(const int, const int end) { if(end == LineLayout::EndOfLine) { crt_.output_sync(LineLayout::EndOfLine); } } namespace { constexpr auto EndOfHSyncPinOutput = MC6847Base::LineLayout::EndOfBackPorch; } bool MC6847Base::hsync(const int column) const { return column <= EndOfHSyncPinOutput; } Cycles MC6847Base::next_sequence_point(const int column) const { if(column < EndOfHSyncPinOutput) return EndOfHSyncPinOutput - column; return LineLayout::EndOfLine - column; } void MC6847Base::reset() { address_.apply_vertical_preload(); } void MC6847Base::Address::apply_vertical_preload() { row_ = 0; address_ = 0; } void MC6847Base::Address::advance(const int column) { address_ += (column & 1) | increment_mask_; } void MC6847Base::Address::apply_hsync() { ++row_; if(row_ == target_row_) { row_ = 0; } else { --address_; } address_ &= line_mask_; } void MC6847Base::Address::reset_row() { row_ = 0; } void MC6847Base::set_mode( const bool graphics, const bool semigraphics, const bool external_rom, const bool invert, const uint8_t graphics_mode, const bool colour_select ) { // "the font row is reset each time the VDG is switched from graphics to text mode". if((mode_ & Mode::Graphics) && !graphics) { address_.reset_row(); } mode_ = (graphics ? Mode::Graphics : 0) | (semigraphics ? Mode::Semigraphics : 0) | (external_rom ? Mode::ExternalROM : 0) | (invert ? Mode::Invert : 0) | (colour_select ? Mode::ColourSelect : 0);; const auto set_32bytes = [&] { address_.increment_mask_ = 1; address_.line_mask_ = uint16_t(~31); }; const auto set_16bytes = [&] { address_.increment_mask_ = 0; address_.line_mask_ = uint16_t(~15); mode_ |= Mode::Columns16; }; if(graphics) { const auto mode = Mode::GraphicsMode(graphics_mode); if(Mode::is_16column(mode)) { set_16bytes(); } else { set_32bytes(); } address_.target_row_ = repeated_rows(mode); if(is_2bpp(mode)) { mode_ |= Mode::BPP2; } } else { set_32bytes(); address_.target_row_ = 12; } // TODO: apply my net understanding, which is now this: // // Alphanumeric modes works pretty much as already implemented with an access to the character ROM, // though it obeys the top two bits; // // Semigraphic modes fetch much like the alphanumeric modes but use the fetched // byte as colour + telectext-style pixel data; // // The graphics modes break down into either 1bpp or 2bpp output, at either 16 or 32 bytes per row. }