From 03ccc8186ae93273227d17fdcf87a2d4135cfbbe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 May 2026 15:25:40 -0400 Subject: [PATCH] This probably still isn't correct, but restores text. --- Components/6847/6847.cpp | 8 +- Components/6847/6847.hpp | 268 +++++++++++++++++++++------------------ 2 files changed, 150 insertions(+), 126 deletions(-) diff --git a/Components/6847/6847.cpp b/Components/6847/6847.cpp index 2501ca7c4..480944fe3 100644 --- a/Components/6847/6847.cpp +++ b/Components/6847/6847.cpp @@ -116,7 +116,7 @@ void MC6847Base::pixel_line(const int line, const int line_begin, const int line // TODO: obey graphics mode here, use proper colours, more. if(pixels_) { - const int row = line % 12; + const int row = address_.row(); for(int c = line_begin; c < line_end; c++) { const int column = c - LineLayout::EndOfLeftBorder; const int pixels = font[line_.data[column] & 63][row]; @@ -134,6 +134,8 @@ void MC6847Base::pixel_line(const int line, const int line_begin, const int line if(end == LineLayout::EndOfPixels) { crt_.output_data(32 * CRTMultiplier, 256); + address_.apply_hsync(); + printf("\n"); } } ); @@ -160,3 +162,7 @@ void MC6847Base::sync_line(const int, const int end) { crt_.output_sync((LineLayout::EndOfLine) * CRTMultiplier); } } + +void MC6847Base::reset() { + address_.apply_vertical_preload(); +} diff --git a/Components/6847/6847.hpp b/Components/6847/6847.hpp index 56092465a..6817c4af7 100644 --- a/Components/6847/6847.hpp +++ b/Components/6847/6847.hpp @@ -14,129 +14,9 @@ namespace Motorola::MC6847 { -enum class FrameTiming { - /// Official frame timing, as implemented by the chip. - NTSC, - - /// Standard PAL adaptation of adding 25 lines before vsync and 25 aftter, as used independently by - /// the Acorn Atom and Dragon 32. - PALPaddedVsync, -}; -constexpr bool is_ntsc(const FrameTiming timing) { - return timing == FrameTiming::NTSC; -} -template struct FrameLayout { - static constexpr int BorderLength = timing == FrameTiming::NTSC ? 27 : 52; - - static constexpr int EndOfPixels = 192; - static constexpr int EndOfBottomBorder = EndOfPixels + BorderLength; - static constexpr int EndOfFrontPorch = EndOfBottomBorder + 2; - static constexpr int EndOfSync = EndOfFrontPorch + 2; - static constexpr int EndOfBackPorch = EndOfSync + 12; - static constexpr int EndOfField = EndOfBackPorch + BorderLength; -}; -static_assert(FrameLayout::EndOfField == 262, "NTSC frames should be 262 lines long"); -static_assert(FrameLayout::EndOfField == 312, "PAL-padded frames should be 312 lines long"); - -struct MC6847Base { - MC6847Base(Outputs::Display::Type); - - void pixel_line(int line, int begin, int end); - void border_line(int begin, int end); - void porch_line(int begin, int end); - void sync_line(int begin, int end); - - struct LineLayout { - // Complete guesses. Who knows? - static const int EndOfSync = 4; - static const int EndOfLeftBorder = 18; - static const int EndOfPixels = 50; - static const int EndOfLine = 64; - }; - - Outputs::CRT::CRT crt_; - uint16_t *pixels_ = nullptr; - - struct LineCapture { - // TODO: capture mode here. - uint8_t data[32]; - } line_; -}; - -template -class MC6847: private MC6847Base { -public: - MC6847(MemoryAccessT &memory) : - MC6847Base(is_ntsc(timing) ? Outputs::Display::Type::NTSC60 : Outputs::Display::Type::PAL50), - memory_(memory) { - crt_.set_fixed_framing([&] { - run_for(Cycles(10'000)); - }); - } - - // - // Expected input clock: NTSC-style 3.58 Mhz. - // - void run_for(const Cycles cycles) { - // This template binds the drawing logic to whatever was supplied - // as MemoryAccessT; a consequence of that is that frame layout logic is here. - cycles_ += cycles; - position_.advance( - cycles_.divide(2).template as(), - [&] (const int line, const int begin, const int end) { - if(line < FrameLayout::EndOfPixels) { - const int line_start = (line / 12) * 32; - Numeric::clamp( - begin, - end, - [&](const int fetch_begin, const int fetch_end) { - for(int c = fetch_begin; c < fetch_end; c++) { - const int column = c - LineLayout::EndOfLeftBorder; - line_.data[column] = memory_[0x400 + size_t(line_start + column)]; - } - } - ); - pixel_line(line, begin, end); - } else if(line < FrameLayout::EndOfBottomBorder) { - border_line(begin, end); - } else if(line < FrameLayout::EndOfFrontPorch) { - porch_line(begin, end); - } else if(line < FrameLayout::EndOfSync) { - sync_line(begin, end); - } else if(line < FrameLayout::EndOfBackPorch) { - porch_line(begin, end); - } else { - border_line(begin, end); - } - }, - [&] { - // TODO: reset counters, etc. - } - ); - } - - void set_scan_target(Outputs::Display::ScanTarget *const target) { - crt_.set_scan_target(target); - } - - Outputs::Display::ScanStatus get_scaled_scan_status() const { - return crt_.get_scaled_scan_status() * 2; - } - - void set_display_type(const Outputs::Display::DisplayType display_type) { - crt_.set_display_type(display_type); - } - - Outputs::Display::DisplayType get_display_type() const { - return crt_.get_display_type(); - } - -private: - MemoryAccessT &memory_; - Cycles cycles_; - Numeric::DividingAccumulator<64, FrameLayout::EndOfField> position_; -}; - +/*! + Encapsulates the 6847's address-generation logic; +*/ struct AddressGenerator { // MARK: - Events. @@ -150,7 +30,7 @@ struct AddressGenerator { if(y_ == y_divider_ || y_ == 12) { // Complete guess: always wrap at 12 even if an in-frame mode switch // has occurred. y_ = 0; - return; + ++counter_; } // Clear either four or five bits. @@ -167,15 +47,22 @@ struct AddressGenerator { counter_ = preload_; } - uint16_t advance() { + void advance() { ++x_; if(x_ == x_divider_ || x_ == 3) { ++counter_; x_ = 0; } + } + + uint16_t address() const { return counter_; } + int row() const { + return y_; + } + // MARK: - Inputs. void set_hsync(const bool active) { @@ -237,4 +124,135 @@ private: int y_ = 0; }; +enum class FrameTiming { + /// Official frame timing, as implemented by the chip. + NTSC, + + /// Standard PAL adaptation of adding 25 lines before vsync and 25 aftter, as used independently by + /// the Acorn Atom and Dragon 32. + PALPaddedVsync, +}; +constexpr bool is_ntsc(const FrameTiming timing) { + return timing == FrameTiming::NTSC; +} +template struct FrameLayout { + static constexpr int BorderLength = timing == FrameTiming::NTSC ? 27 : 52; + + static constexpr int EndOfPixels = 192; + static constexpr int EndOfBottomBorder = EndOfPixels + BorderLength; + static constexpr int EndOfFrontPorch = EndOfBottomBorder + 2; + static constexpr int EndOfSync = EndOfFrontPorch + 2; + static constexpr int EndOfBackPorch = EndOfSync + 12; + static constexpr int EndOfField = EndOfBackPorch + BorderLength; +}; +static_assert(FrameLayout::EndOfField == 262, "NTSC frames should be 262 lines long"); +static_assert(FrameLayout::EndOfField == 312, "PAL-padded frames should be 312 lines long"); + +struct MC6847Base { + MC6847Base(Outputs::Display::Type); + + void pixel_line(int line, int begin, int end); + void border_line(int begin, int end); + void porch_line(int begin, int end); + void sync_line(int begin, int end); + void reset(); + + struct LineLayout { + // Complete guesses. Who knows? + static const int EndOfSync = 4; + static const int EndOfLeftBorder = 18; + static const int EndOfPixels = 50; + static const int EndOfLine = 64; + }; + + Outputs::CRT::CRT crt_; + uint16_t *pixels_ = nullptr; + + struct LineCapture { + // TODO: capture mode here. + uint8_t data[32]; + } line_; + + AddressGenerator address_; +}; + +template +class MC6847: private MC6847Base { +public: + MC6847(MemoryAccessT &memory) : + MC6847Base(is_ntsc(timing) ? Outputs::Display::Type::NTSC60 : Outputs::Display::Type::PAL50), + memory_(memory) { + // HACK. Allows me to test the address generator locally, prior to integrating it into the SAM. + address_.set_preload(0x400); + address_.set_mode(0); + + crt_.set_fixed_framing([&] { + run_for(Cycles(10'000)); + }); + } + + // + // Expected input clock: NTSC-style 3.58 Mhz. + // + void run_for(const Cycles cycles) { + // This template binds the drawing logic to whatever was supplied + // as MemoryAccessT; a consequence of that is that frame layout logic is here. + cycles_ += cycles; + position_.advance( + cycles_.divide(2).template as(), + [&] (const int line, const int begin, const int end) { + if(line < FrameLayout::EndOfPixels) { + Numeric::clamp( + begin, + end, + [&](const int fetch_begin, const int fetch_end) { + for(int c = fetch_begin; c < fetch_end; c++) { + if(c > LineLayout::EndOfLeftBorder) address_.advance(); + const int column = c - LineLayout::EndOfLeftBorder; + const auto source = address_.address(); + line_.data[column] = memory_[source]; + } + } + ); + pixel_line(line, begin, end); + } else if(line < FrameLayout::EndOfBottomBorder) { + border_line(begin, end); + } else if(line < FrameLayout::EndOfFrontPorch) { + porch_line(begin, end); + } else if(line < FrameLayout::EndOfSync) { + sync_line(begin, end); + } else if(line < FrameLayout::EndOfBackPorch) { + porch_line(begin, end); + } else { + border_line(begin, end); + } + }, + [&] { + reset(); + } + ); + } + + void set_scan_target(Outputs::Display::ScanTarget *const target) { + crt_.set_scan_target(target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const { + return crt_.get_scaled_scan_status() * 2; + } + + void set_display_type(const Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const { + return crt_.get_display_type(); + } + +private: + MemoryAccessT &memory_; + Cycles cycles_; + Numeric::DividingAccumulator<64, FrameLayout::EndOfField> position_; +}; + }