1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-26 01:16:29 +00:00

Compare commits

...

15 Commits

Author SHA1 Message Date
Thomas Harte
26ccd930c3 Begin tidying. 2025-09-25 17:53:54 -04:00
Thomas Harte
82211c7312 Add some 'graphics' support. 2025-09-25 17:50:26 -04:00
Thomas Harte
2015c154fe Correctly clear double-height flags. 2025-09-25 13:28:22 -04:00
Thomas Harte
ef17d116a8 Don't permit single-height text on a lower double-height row. 2025-09-25 13:22:25 -04:00
Thomas Harte
46fddc44bf Support double-height text. 2025-09-25 13:21:49 -04:00
Thomas Harte
0214a77cd7 Add TODO. 2025-09-25 13:10:52 -04:00
Thomas Harte
425ed658f1 Support colour control codes, clarify SAA5050 signalling. 2025-09-25 13:03:55 -04:00
Thomas Harte
a53adb561e Erase TODO, continue to update state without target. 2025-09-25 09:25:46 -04:00
Thomas Harte
3c3c55090a Port forward ElectrEm's font smoothing. 2025-09-25 09:22:16 -04:00
Thomas Harte
ebc04c6520 Eliminate warning. 2025-09-24 22:58:50 -04:00
Thomas Harte
8b0e8f5b13 Move all work [near] definitively into the SAA5050. 2025-09-24 22:55:49 -04:00
Thomas Harte
16132a007e Remove silly call. 2025-09-24 22:26:37 -04:00
Thomas Harte
b6e41ceea7 Hack in low-resolution Mode 7. 2025-09-24 22:25:43 -04:00
Thomas Harte
7015e46227 Put together enough of an interface to expect to see some pixels. 2025-09-24 22:08:04 -04:00
Thomas Harte
cce2607c80 Add file for SAA5050 logic. 2025-09-24 21:43:25 -04:00
7 changed files with 460 additions and 33 deletions

View 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);
}
}
}
}

View 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;
};
}

View File

@@ -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,

View File

@@ -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 */,

View File

@@ -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 \

View File

@@ -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')

View File

@@ -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