mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-26 01:16:29 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
26ccd930c3 | ||
|
82211c7312 | ||
|
2015c154fe | ||
|
ef17d116a8 | ||
|
46fddc44bf | ||
|
0214a77cd7 | ||
|
425ed658f1 | ||
|
a53adb561e | ||
|
3c3c55090a | ||
|
ebc04c6520 | ||
|
8b0e8f5b13 | ||
|
16132a007e | ||
|
b6e41ceea7 | ||
|
7015e46227 | ||
|
cce2607c80 |
318
Components/SAA5050/SAA5050.cpp
Normal file
318
Components/SAA5050/SAA5050.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
//
|
||||
// SAA5050.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SAA5050.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
// SAA5050 font, padded out to one byte per row. The least-significant five bits of each byte
|
||||
// are the meaningful pixels for that row, with the LSB being on the right.
|
||||
constexpr uint8_t font[][10] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // Character 32.
|
||||
{0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x15, 0x14, 0x0e, 0x05, 0x15, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x08, 0x1f, 0x08, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x16, 0x01, 0x02, 0x04, 0x07, },
|
||||
{0x00, 0x00, 0x04, 0x02, 0x1f, 0x02, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x0e, 0x15, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
||||
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, },
|
||||
{0x00, 0x08, 0x08, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00, 0x00, },
|
||||
{0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1a, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, },
|
||||
{0x00, 0x00, 0x00, 0x0b, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
||||
{0x00, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x03, 0x05, 0x07, 0x01, },
|
||||
{0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x18, 0x04, 0x18, 0x04, 0x19, 0x03, 0x05, 0x07, 0x01, },
|
||||
{0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, },
|
||||
};
|
||||
|
||||
constexpr uint16_t scale(const uint8_t top, const uint8_t bottom) {
|
||||
// Adapted from old ElectrEm source; my original provenance for the logic is unknown.
|
||||
uint16_t wide =
|
||||
((top & 0x01) ? 0b0000'0000'0011 : 0) |
|
||||
((top & 0x02) ? 0b0000'0000'1100 : 0) |
|
||||
((top & 0x04) ? 0b0000'0011'0000 : 0) |
|
||||
((top & 0x08) ? 0b0000'1100'0000 : 0) |
|
||||
((top & 0x10) ? 0b0011'0000'0000 : 0);
|
||||
|
||||
if((top & 0x10) && (bottom & 0x08) && !(bottom & 0x10)) wide |= 0b0000'1000'0000;
|
||||
if((top & 0x08) && (bottom & 0x04) && !(bottom & 0x08)) wide |= 0b0000'0010'0000;
|
||||
if((top & 0x04) && (bottom & 0x02) && !(bottom & 0x04)) wide |= 0b0000'0000'1000;
|
||||
if((top & 0x02) && (bottom & 0x01) && !(bottom & 0x02)) wide |= 0b0000'0000'0010;
|
||||
|
||||
if((top & 0x08) && (bottom & 0x10) && !(bottom & 0x08)) wide |= 0b0001'0000'0000;
|
||||
if((top & 0x04) && (bottom & 0x08) && !(bottom & 0x04)) wide |= 0b0000'0100'0000;
|
||||
if((top & 0x02) && (bottom & 0x04) && !(bottom & 0x02)) wide |= 0b0000'0001'0000;
|
||||
if((top & 0x01) && (bottom & 0x02) && !(bottom & 0x01)) wide |= 0b0000'0000'0100;
|
||||
|
||||
return wide;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Mullard;
|
||||
|
||||
void SAA5050Serialiser::begin_frame(const bool is_odd) {
|
||||
line_ = -2;
|
||||
row_ = 0;
|
||||
odd_frame_ = is_odd;
|
||||
|
||||
row_has_double_height_ = false;
|
||||
double_height_offset_ = 0;
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::begin_line() {
|
||||
line_ += 2;
|
||||
if(line_ == 20) {
|
||||
line_ = 0;
|
||||
++row_;
|
||||
|
||||
if(row_has_double_height_) {
|
||||
double_height_offset_ = (double_height_offset_ + 5) % 10;
|
||||
}
|
||||
row_has_double_height_ = false;
|
||||
}
|
||||
|
||||
output_.alpha = 0x7;
|
||||
output_.background = 0x0;
|
||||
output_.pixels = 0;
|
||||
has_output_ = false;
|
||||
next_control_ = 0;
|
||||
|
||||
alpha_mode_ = true;
|
||||
separated_graphics_ = false;
|
||||
double_height_ = false;
|
||||
}
|
||||
|
||||
bool SAA5050Serialiser::has_output() const {
|
||||
return has_output_;
|
||||
}
|
||||
|
||||
SAA5050Serialiser::Output SAA5050Serialiser::output() {
|
||||
has_output_ = false;
|
||||
return output_;
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::add(const Numeric::SizedCounter<7> c) {
|
||||
// Apply any enqueued control code.
|
||||
enum ControlCode: uint8_t {
|
||||
RedAlpha = 0x01,
|
||||
GreenAlpha = 0x02,
|
||||
YellowAlpha = 0x03,
|
||||
BlueAlpha = 0x04,
|
||||
MagentaAlpha = 0x05,
|
||||
CyanAlpha = 0x06,
|
||||
WhiteAlpha = 0x07,
|
||||
|
||||
RedGraphics = 0x11,
|
||||
GreenGraphics = 0x12,
|
||||
YellowGraphics = 0x13,
|
||||
BlueGraphics = 0x14,
|
||||
MagentaGraphics = 0x15,
|
||||
CyanGraphics = 0x16,
|
||||
WhiteGraphics = 0x17,
|
||||
|
||||
ContinuousGraphics = 0x19,
|
||||
SeparatedGraphics = 0x1a,
|
||||
|
||||
NormalHeight = 0xc,
|
||||
DoubleHeight = 0xd,
|
||||
|
||||
BlackBackground = 0x1c,
|
||||
NewBackground = 0x1d,
|
||||
};
|
||||
switch(next_control_) {
|
||||
default: break;
|
||||
|
||||
case RedAlpha: alpha_mode_ = true; output_.alpha = 0b100; break;
|
||||
case GreenAlpha: alpha_mode_ = true; output_.alpha = 0b010; break;
|
||||
case YellowAlpha: alpha_mode_ = true; output_.alpha = 0b110; break;
|
||||
case BlueAlpha: alpha_mode_ = true; output_.alpha = 0b001; break;
|
||||
case MagentaAlpha: alpha_mode_ = true; output_.alpha = 0b101; break;
|
||||
case CyanAlpha: alpha_mode_ = true; output_.alpha = 0b011; break;
|
||||
case WhiteAlpha: alpha_mode_ = true; output_.alpha = 0b111; break;
|
||||
|
||||
case RedGraphics: alpha_mode_ = false; output_.alpha = 0b100; break;
|
||||
case GreenGraphics: alpha_mode_ = false; output_.alpha = 0b010; break;
|
||||
case YellowGraphics: alpha_mode_ = false; output_.alpha = 0b110; break;
|
||||
case BlueGraphics: alpha_mode_ = false; output_.alpha = 0b001; break;
|
||||
case MagentaGraphics: alpha_mode_ = false; output_.alpha = 0b101; break;
|
||||
case CyanGraphics: alpha_mode_ = false; output_.alpha = 0b011; break;
|
||||
case WhiteGraphics: alpha_mode_ = false; output_.alpha = 0b111; break;
|
||||
|
||||
case ContinuousGraphics: separated_graphics_ = false; break;
|
||||
case SeparatedGraphics: separated_graphics_ = true; break;
|
||||
|
||||
case NormalHeight: double_height_ = false; break;
|
||||
case DoubleHeight: double_height_ = row_has_double_height_ = true; break;
|
||||
|
||||
case BlackBackground: output_.background = 0; break;
|
||||
case NewBackground: output_.background = output_.alpha; break;
|
||||
}
|
||||
next_control_ = 0;
|
||||
|
||||
has_output_ = true;
|
||||
if(c.get() < 32) {
|
||||
// Defer any [some?] control characters until next time through.
|
||||
next_control_ = c.get();
|
||||
output_.pixels = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Divert into graphics only if both the mode and the character code allows it.
|
||||
if(!alpha_mode_ && (c.get() & (1 << 5))) {
|
||||
// Graphics layout:
|
||||
//
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b0 | b1 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b2 | b3 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b4 | b6 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
|
||||
const auto bits = c.get();
|
||||
if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
|
||||
output_.pixels = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(line_ < 6) {
|
||||
output_.pixels =
|
||||
((bits & 1) ? 0b1111'1100'0000 : 0) |
|
||||
((bits & 2) ? 0b0000'0011'1111 : 0);
|
||||
} else if(line_ < 12) {
|
||||
output_.pixels =
|
||||
((bits & 4) ? 0b1111'1100'0000 : 0) |
|
||||
((bits & 8) ? 0b0000'0011'1111 : 0);
|
||||
} else {
|
||||
output_.pixels =
|
||||
((bits & 16) ? 0b1111'1100'0000 : 0) |
|
||||
((bits & 64) ? 0b0000'0011'1111 : 0);
|
||||
}
|
||||
|
||||
if(separated_graphics_) {
|
||||
output_.pixels &= 0b0111'1101'1111;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(double_height_) {
|
||||
const auto top_address = (line_ >> 2) + double_height_offset_;
|
||||
const uint8_t top = font[c.get() - 32][top_address];
|
||||
const uint8_t bottom = font[c.get() - 32][std::min(9, top_address + 1)];
|
||||
|
||||
if(line_ & 2) {
|
||||
output_.pixels = scale(bottom, top);
|
||||
} else {
|
||||
output_.pixels = scale(top, bottom);
|
||||
}
|
||||
} else {
|
||||
if(double_height_offset_) {
|
||||
output_.pixels = 0;
|
||||
} else {
|
||||
const auto top_address = line_ >> 1;
|
||||
const uint8_t top = font[c.get() - 32][top_address];
|
||||
const uint8_t bottom = font[c.get() - 32][std::min(9, top_address + 1)];
|
||||
|
||||
if(odd_frame_) {
|
||||
output_.pixels = scale(bottom, top);
|
||||
} else {
|
||||
output_.pixels = scale(top, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
Components/SAA5050/SAA5050.hpp
Normal file
50
Components/SAA5050/SAA5050.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// SAA5050.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "Numeric/SizedCounter.hpp"
|
||||
|
||||
namespace Mullard {
|
||||
|
||||
struct SAA5050Serialiser {
|
||||
public:
|
||||
void begin_frame(bool is_odd);
|
||||
void begin_line();
|
||||
|
||||
void add(Numeric::SizedCounter<7>);
|
||||
|
||||
struct Output {
|
||||
// The low twelve bits of this word provide 1bpp pixels.
|
||||
uint16_t pixels;
|
||||
|
||||
// Colours for set and background pixels.
|
||||
uint8_t alpha;
|
||||
uint8_t background;
|
||||
};
|
||||
bool has_output() const;
|
||||
Output output();
|
||||
|
||||
private:
|
||||
uint8_t next_control_ = 0;
|
||||
Output output_;
|
||||
bool has_output_ = false;
|
||||
|
||||
int row_, line_;
|
||||
bool odd_frame_;
|
||||
|
||||
bool alpha_mode_ = true;
|
||||
bool separated_graphics_ = false;
|
||||
|
||||
bool double_height_ = false;
|
||||
bool row_has_double_height_ = false;
|
||||
int double_height_offset_ = 0;
|
||||
};
|
||||
|
||||
}
|
@@ -17,8 +17,9 @@
|
||||
|
||||
#include "Components/6522/6522.hpp"
|
||||
#include "Components/6845/CRTC6845.hpp"
|
||||
#include "Components/SN76489/SN76489.hpp"
|
||||
#include "Components/6850/6850.hpp"
|
||||
#include "Components/SAA5050/SAA5050.hpp"
|
||||
#include "Components/SN76489/SN76489.hpp"
|
||||
#include "Components/uPD7002/uPD7002.hpp"
|
||||
|
||||
// TODO: factor this more appropriately.
|
||||
@@ -306,10 +307,6 @@ public:
|
||||
|
||||
active_collation_.pixels_per_clock = 1 << ((value >> 2) & 0x03);
|
||||
active_collation_.is_teletext = value & 0x02;
|
||||
if(active_collation_.is_teletext) {
|
||||
Logger::error().append("TODO: video control => teletext %d", bool(value & 0x02));
|
||||
}
|
||||
|
||||
flash_mask_ = value & 0x01 ? 7 : 0;
|
||||
cursor_mask_ = value & 0b1110'0000;
|
||||
}
|
||||
@@ -327,6 +324,7 @@ public:
|
||||
system_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(state.vsync);
|
||||
|
||||
// Count cycles since horizontal sync to insert a colour burst.
|
||||
// TODO: this is copy/pasted from the CPC. How does the BBC do it?
|
||||
if(state.hsync) {
|
||||
++cycles_into_hsync_;
|
||||
} else {
|
||||
@@ -334,9 +332,6 @@ public:
|
||||
}
|
||||
const bool is_colour_burst = cycles_into_hsync_ >= 5 && cycles_into_hsync_ < 9;
|
||||
|
||||
// Sync is taken to override pixels, and is combined as a simple OR.
|
||||
const bool is_sync = state.hsync || state.vsync;
|
||||
|
||||
// Check for a cursor leading edge.
|
||||
cursor_shifter_ >>= 4;
|
||||
if(state.cursor != previous_cursor_enabled_) {
|
||||
@@ -349,17 +344,32 @@ public:
|
||||
previous_cursor_enabled_ = state.cursor;
|
||||
}
|
||||
|
||||
OutputMode output_mode;
|
||||
const bool should_fetch = state.display_enable && (active_collation_.is_teletext || !(state.line.get() & 8));
|
||||
if(is_sync) {
|
||||
output_mode = OutputMode::Sync;
|
||||
} else if(is_colour_burst) {
|
||||
output_mode = OutputMode::ColourBurst;
|
||||
} else if(should_fetch || cursor_shifter_) {
|
||||
output_mode = OutputMode::Pixels;
|
||||
} else {
|
||||
output_mode = OutputMode::Blank;
|
||||
const OutputMode output_mode = [&] {
|
||||
if(state.hsync || state.vsync) {
|
||||
return OutputMode::Sync;
|
||||
}
|
||||
if(is_colour_burst) {
|
||||
return OutputMode::ColourBurst;;
|
||||
}
|
||||
if(should_fetch || cursor_shifter_ || (active_collation_.is_teletext && saa5050_serialiser_.has_output())) {
|
||||
return OutputMode::Pixels;
|
||||
}
|
||||
return OutputMode::Blank;
|
||||
} ();
|
||||
|
||||
// Consider some SAA5050 signalling.
|
||||
if(!state.vsync && previous_vsync_) {
|
||||
// Complete fiction here; the SAA5050 field flag is set by peeking inside CRTC state.
|
||||
// TODO: what really sets CRS for the SAA5050? Time since hsync maybe?
|
||||
saa5050_serialiser_.begin_frame(state.field_count.bit<0>());
|
||||
}
|
||||
previous_vsync_ = state.vsync;
|
||||
|
||||
if(state.display_enable && !previous_display_enabled_) {
|
||||
saa5050_serialiser_.begin_line();
|
||||
}
|
||||
previous_display_enabled_ = state.display_enable;
|
||||
|
||||
// If a transition between sync/border/pixels just occurred, flush whatever was
|
||||
// in progress to the CRT and reset counting. Also flush if this mode has just been effective
|
||||
@@ -396,30 +406,41 @@ public:
|
||||
if(!pixel_data_) {
|
||||
pixel_pointer_ = pixel_data_ = crt_.begin_data(PixelAllocationUnit, 8);
|
||||
}
|
||||
if(pixel_pointer_) {
|
||||
uint16_t address;
|
||||
|
||||
const uint16_t address = [&] {
|
||||
// Teletext address generation.
|
||||
if(state.refresh.get() & (1 << 13)) {
|
||||
// Teletext address generation mode.
|
||||
address = uint16_t(
|
||||
return uint16_t(
|
||||
0x3c00 |
|
||||
((state.refresh.get() & 0x800) << 3) |
|
||||
(state.refresh.get() & 0x3ff)
|
||||
);
|
||||
// TODO: wraparound? Does that happen on Mode 7?
|
||||
} else {
|
||||
address = uint16_t((state.refresh.get() << 3) | (state.line.get() & 7));
|
||||
if(address & 0x8000) {
|
||||
address = (address + video_base_) & 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
pixel_shifter_ = should_fetch ? ram_[address] : 0;
|
||||
uint16_t address = uint16_t((state.refresh.get() << 3) | (state.line.get() & 7));
|
||||
if(address & 0x8000) {
|
||||
address = (address + video_base_) & 0x7fff;
|
||||
}
|
||||
return address;
|
||||
} ();
|
||||
const uint8_t fetched = should_fetch ? ram_[address] : 0;
|
||||
pixel_shifter_ = fetched;
|
||||
|
||||
if(pixel_data_) {
|
||||
if(active_collation_.is_teletext) {
|
||||
// TODO: output meaningful teletext pixels.
|
||||
for(int c = 0; c < 12; c++) {
|
||||
*pixel_pointer_++ = (pixel_shifter_ & 0x80 ? 0xff : 0x00) ^ cursor_mask_;
|
||||
pixel_shifter_ <<= 1;
|
||||
if(saa5050_serialiser_.has_output()) {
|
||||
const auto output = saa5050_serialiser_.output();
|
||||
|
||||
uint16_t pixels = output.pixels;
|
||||
for(int c = 0; c < 12; c++) {
|
||||
*pixel_pointer_++ =
|
||||
((pixels & 0b1000'0000'0000) ? output.alpha : output.background)
|
||||
^ uint8_t(cursor_shifter_);
|
||||
pixels <<= 1;
|
||||
}
|
||||
} else {
|
||||
std::fill(pixel_pointer_, pixel_pointer_ + 12, 0);
|
||||
pixel_pointer_ += 12;
|
||||
}
|
||||
} else {
|
||||
switch(crtc_clock_multiplier_ * active_collation_.pixels_per_clock) {
|
||||
@@ -431,7 +452,18 @@ public:
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*else {
|
||||
if(active_collation_.is_teletext) {
|
||||
pixel_pointer_ += 12;
|
||||
} else {
|
||||
pixel_pointer_ += crtc_clock_multiplier_ * active_collation_.pixels_per_clock;
|
||||
}
|
||||
} */
|
||||
|
||||
// TODO: why does enabling the below cause alternate lines to be different stretches?
|
||||
// if(should_fetch) {
|
||||
saa5050_serialiser_.add(fetched);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +531,9 @@ private:
|
||||
uint32_t cursor_shifter_ = 0;
|
||||
bool previous_cursor_enabled_ = false;
|
||||
|
||||
bool previous_display_enabled_ = false;
|
||||
bool previous_vsync_ = false;
|
||||
|
||||
template <int count> void shift_pixels(const uint8_t cursor_mask) {
|
||||
for(int c = 0; c < count; c++) {
|
||||
const uint8_t colour =
|
||||
@@ -513,6 +548,8 @@ private:
|
||||
|
||||
const uint8_t *const ram_ = nullptr;
|
||||
SystemVIA &system_via_;
|
||||
|
||||
Mullard::SAA5050Serialiser saa5050_serialiser_;
|
||||
};
|
||||
using CRTC = Motorola::CRTC::CRTC6845<
|
||||
CRTCBusHandler,
|
||||
|
@@ -641,6 +641,9 @@
|
||||
4B882F5B2C2F9C7700D84031 /* Shaker in Resources */ = {isa = PBXBuildFile; fileRef = 4B882F5A2C2F9C7700D84031 /* Shaker */; };
|
||||
4B882F5C2C32199400D84031 /* MachineForTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */; };
|
||||
4B882F5D2C3219A400D84031 /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
|
||||
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
4B8855A62E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
4B8855A72E84D51B00E251DD /* SAA5050.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8855A32E84D51B00E251DD /* SAA5050.cpp */; };
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */; };
|
||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944E6201967B4007DE474 /* ConfidenceCounter.cpp */; };
|
||||
4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944E6201967B4007DE474 /* ConfidenceCounter.cpp */; };
|
||||
@@ -1795,6 +1798,8 @@
|
||||
4B882F582C2F9C6900D84031 /* CPCShakerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CPCShakerTests.mm; sourceTree = "<group>"; };
|
||||
4B882F5A2C2F9C7700D84031 /* Shaker */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaker; sourceTree = "<group>"; };
|
||||
4B88559C2E8185BF00E251DD /* SizedCounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SizedCounter.hpp; sourceTree = "<group>"; };
|
||||
4B8855A22E84D51B00E251DD /* SAA5050.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SAA5050.hpp; sourceTree = "<group>"; };
|
||||
4B8855A32E84D51B00E251DD /* SAA5050.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SAA5050.cpp; sourceTree = "<group>"; };
|
||||
4B89449220194A47007DE474 /* CSStaticAnalyser+TargetVector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "CSStaticAnalyser+TargetVector.h"; path = "StaticAnalyser/CSStaticAnalyser+TargetVector.h"; sourceTree = "<group>"; };
|
||||
4B8944E4201967B4007DE474 /* ConfidenceSummary.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSummary.hpp; sourceTree = "<group>"; };
|
||||
4B8944E5201967B4007DE474 /* ConfidenceSource.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSource.hpp; sourceTree = "<group>"; };
|
||||
@@ -3847,6 +3852,15 @@
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8855A42E84D51B00E251DD /* SAA5050 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8855A22E84D51B00E251DD /* SAA5050.hpp */,
|
||||
4B8855A32E84D51B00E251DD /* SAA5050.cpp */,
|
||||
);
|
||||
path = SAA5050;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8944E2201967B4007DE474 /* Analyser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -5089,6 +5103,7 @@
|
||||
4B4B1A39200198C900A0F866 /* KonamiSCC */,
|
||||
4BC23A212467600E001A6030 /* OPx */,
|
||||
4BF0BC6E2973318E00CCA2B5 /* RP5C01 */,
|
||||
4B8855A42E84D51B00E251DD /* SAA5050 */,
|
||||
4B0ACBFF237756EC008902D0 /* Serial */,
|
||||
4BB0A6582044FD3000FB3688 /* SN76489 */,
|
||||
4B47F3B42E7B9A14005D4DEC /* uPD7002 */,
|
||||
@@ -6301,6 +6316,7 @@
|
||||
4B302185208A550100773308 /* DiskII.cpp in Sources */,
|
||||
42EB81292B23AAC300429AF4 /* IMD.cpp in Sources */,
|
||||
4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */,
|
||||
4B8855A72E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
||||
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */,
|
||||
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
|
||||
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
@@ -6397,6 +6413,7 @@
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||
@@ -6628,6 +6645,7 @@
|
||||
4B778F3123A5F0CB0000D260 /* Keyboard.cpp in Sources */,
|
||||
4B778F4A23A5F1FB0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
4B7752AB28217E560073E2C5 /* SZX.cpp in Sources */,
|
||||
4B8855A62E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
||||
4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */,
|
||||
4B06AAE22C645F970034D014 /* PCBooter.cpp in Sources */,
|
||||
4B1082C32C1A87CA00B07C5D /* CSL.cpp in Sources */,
|
||||
|
@@ -76,6 +76,7 @@ SOURCES += \
|
||||
$$SRC/Components/KonamiSCC/*.cpp \
|
||||
$$SRC/Components/OPx/*.cpp \
|
||||
$$SRC/Components/RP5C01/*.cpp \
|
||||
$$SRC/Components/SAA5050/*.cpp \
|
||||
$$SRC/Components/Serial/*.cpp \
|
||||
$$SRC/Components/SN76489/*.cpp \
|
||||
$$SRC/Components/uPD7002/*.cpp \
|
||||
@@ -212,6 +213,7 @@ HEADERS += \
|
||||
$$SRC/Components/OPx/*.hpp \
|
||||
$$SRC/Components/OPx/Implementation/*.hpp \
|
||||
$$SRC/Components/RP5C01/*.hpp \
|
||||
$$SRC/Components/SAA5050/*.hpp \
|
||||
$$SRC/Components/Serial/*.hpp \
|
||||
$$SRC/Components/SN76489/*.hpp \
|
||||
$$SRC/Components/uPD7002/*.hpp \
|
||||
|
@@ -60,6 +60,7 @@ SOURCES += glob.glob('../../Components/I2C/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/OPx/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/RP5C01/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/SAA5050/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/Serial/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/uPD7002/*.cpp')
|
||||
|
@@ -58,6 +58,7 @@ set(CLK_SOURCES
|
||||
Components/KonamiSCC/KonamiSCC.cpp
|
||||
Components/OPx/OPLL.cpp
|
||||
Components/RP5C01/RP5C01.cpp
|
||||
Components/SAA5050/SAA5050.cpp
|
||||
Components/SN76489/SN76489.cpp
|
||||
Components/Serial/Line.cpp
|
||||
Components/uPD7002/uPD7002.cpp
|
||||
|
Reference in New Issue
Block a user