1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-04-20 10:17:05 +00:00

Merge pull request #1574 from TomHarte/SAA5050

Add SAA5050 and hence BBC Mode 7.
This commit is contained in:
Thomas Harte
2025-09-25 23:12:28 -04:00
committed by GitHub
7 changed files with 537 additions and 50 deletions
+365
View File
@@ -0,0 +1,365 @@
//
// 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 this being the correct logic is unknown.
uint16_t wide =
((top & 0b00001) ? 0b0000'0000'0011 : 0) |
((top & 0b00010) ? 0b0000'0000'1100 : 0) |
((top & 0b00100) ? 0b0000'0011'0000 : 0) |
((top & 0b01000) ? 0b0000'1100'0000 : 0) |
((top & 0b10000) ? 0b0011'0000'0000 : 0);
if((top & 0b10000) && (bottom & 0b11000) == 0b01000) wide |= 0b0000'1000'0000;
if((top & 0b01000) && (bottom & 0b01100) == 0b00100) wide |= 0b0000'0010'0000;
if((top & 0b00100) && (bottom & 0b00110) == 0b00010) wide |= 0b0000'0000'1000;
if((top & 0b00010) && (bottom & 0b00011) == 0b00001) wide |= 0b0000'0000'0010;
if((top & 0b01000) && (bottom & 0b11000) == 0b10000) wide |= 0b0001'0000'0000;
if((top & 0b00100) && (bottom & 0b01100) == 0b01000) wide |= 0b0000'0100'0000;
if((top & 0b00010) && (bottom & 0b00110) == 0b00100) wide |= 0b0000'0001'0000;
if((top & 0b00001) && (bottom & 0b00011) == 0b00010) wide |= 0b0000'0000'0100;
return wide;
}
enum ControlCode: uint8_t {
RedAlpha = 0x01,
GreenAlpha = 0x02,
YellowAlpha = 0x03,
BlueAlpha = 0x04,
MagentaAlpha = 0x05,
CyanAlpha = 0x06,
WhiteAlpha = 0x07,
Flash = 0x08,
Steady = 0x09,
RedGraphics = 0x11,
GreenGraphics = 0x12,
YellowGraphics = 0x13,
BlueGraphics = 0x14,
MagentaGraphics = 0x15,
CyanGraphics = 0x16,
WhiteGraphics = 0x17,
Conceal = 0x18,
ContinuousGraphics = 0x19,
SeparatedGraphics = 0x1a,
NormalHeight = 0xc,
DoubleHeight = 0xd,
BlackBackground = 0x1c,
NewBackground = 0x1d,
HoldGraphics = 0x1e,
ReleaseGraphics = 0x1f,
};}
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;
++frame_counter_;
}
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_.pixels = 0;
has_output_ = false;
apply_control(ControlCode::WhiteAlpha);
apply_control(ControlCode::Steady);
apply_control(ControlCode::NormalHeight);
apply_control(ControlCode::ContinuousGraphics);
apply_control(ControlCode::BlackBackground);
apply_control(ControlCode::ReleaseGraphics);
}
bool SAA5050Serialiser::has_output() const {
return has_output_;
}
SAA5050Serialiser::Output SAA5050Serialiser::output() {
has_output_ = false;
return output_;
}
void SAA5050Serialiser::apply_control(const uint8_t value) {
const auto set_alpha = [&](const uint8_t colour) {
alpha_mode_ = true;
conceal_ = false;
output_.alpha = colour;
hold_graphics_ = false;
};
const auto set_graphics = [&](const uint8_t colour) {
alpha_mode_ = false;
conceal_ = false;
output_.alpha = colour;
hold_graphics_ = false;
};
switch(value) {
default: break;
case RedAlpha: set_alpha(0b100); break;
case GreenAlpha: set_alpha(0b010); break;
case YellowAlpha: set_alpha(0b110); break;
case BlueAlpha: set_alpha(0b001); break;
case MagentaAlpha: set_alpha(0b101); break;
case CyanAlpha: set_alpha(0b011); break;
case WhiteAlpha: set_alpha(0b111); break;
case Flash: flash_ = true; break;
case Steady: flash_ = false; break;
case RedGraphics: set_graphics(0b100); break;
case GreenGraphics: set_graphics(0b010); break;
case YellowGraphics: set_graphics(0b110); break;
case BlueGraphics: set_graphics(0b001); break;
case MagentaGraphics: set_graphics(0b101); break;
case CyanGraphics: set_graphics(0b011); break;
case WhiteGraphics: set_graphics(0b111); break;
case Conceal: conceal_ = true; 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;
case HoldGraphics: hold_graphics_ = true; break;
case ReleaseGraphics: hold_graphics_ = false; last_graphic_ = 32; break;
}
}
void SAA5050Serialiser::set_reveal(const bool reveal) {
reveal_ = reveal;
}
void SAA5050Serialiser::add(const Numeric::SizedCounter<7> c) {
has_output_ = true;
if(c.get() < 32) {
output_.pixels = hold_graphics_ ? pixels(last_graphic_) : 0;
apply_control(c.get());
return;
}
output_.pixels = pixels(c.get());
}
uint16_t SAA5050Serialiser:: pixels(const uint8_t c) {
if(flash_ && frame_counter_&16) {
return 0;
}
if(conceal_ && !reveal_) {
return 0;
}
// Divert into graphics only if both the mode and the character code allows it.
if(!alpha_mode_ && (c & (1 << 5))) {
last_graphic_ = c;
// Graphics layout:
//
// |----|----|
// | | |
// | b0 | b1 |
// | | |
// |----|----|
// | | |
// | b2 | b3 |
// | | |
// |----|----|
// | | |
// | b4 | b6 |
// | | |
// |----|----|
if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
return 0;
}
uint16_t pixels;
if(line_ < 6) {
pixels =
((c & 1) ? 0b1111'1100'0000 : 0) |
((c & 2) ? 0b0000'0011'1111 : 0);
} else if(line_ < 12) {
pixels =
((c & 4) ? 0b1111'1100'0000 : 0) |
((c & 8) ? 0b0000'0011'1111 : 0);
} else {
pixels =
((c & 16) ? 0b1111'1100'0000 : 0) |
((c & 64) ? 0b0000'0011'1111 : 0);
}
if(separated_graphics_) {
pixels &= 0b0111'1101'1111;
}
return pixels;
}
if(double_height_) {
const auto top_address = (line_ >> 2) + double_height_offset_;
const uint8_t top = font[c - 32][top_address];
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
if(line_ & 2) {
return scale(bottom, top);
} else {
return scale(top, bottom);
}
} else {
if(double_height_offset_) {
return 0;
} else {
const auto top_address = line_ >> 1;
const uint8_t top = font[c - 32][top_address];
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
if(odd_frame_) {
return scale(bottom, top);
} else {
return scale(top, bottom);
}
}
}
}
+63
View File
@@ -0,0 +1,63 @@
//
// 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();
void set_reveal(bool);
private:
Output output_;
bool has_output_ = false;
int row_, line_;
bool odd_frame_;
bool flash_ = false;
int frame_counter_ = 0;
bool reveal_ = false;
bool conceal_ = false;
bool alpha_mode_ = true;
bool separated_graphics_ = false;
bool double_height_ = false;
bool row_has_double_height_ = false;
int double_height_offset_ = 0;
bool hold_graphics_ = false;
uint8_t last_graphic_ = 0;
uint16_t pixels(const uint8_t);
void apply_control(const uint8_t);
};
}
+87 -50
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,15 +324,13 @@ 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.
if(state.hsync) {
++cycles_into_hsync_;
} else {
cycles_into_hsync_ = 0;
}
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;
// TODO: this is copy/pasted from the CPC. How does the BBC do it?
// if(state.hsync) {
// ++cycles_into_hsync_;
// } else {
// cycles_into_hsync_ = 0;
// }
// const bool is_colour_burst = cycles_into_hsync_ >= 5 && cycles_into_hsync_ < 9;
// Check for a cursor leading edge.
cursor_shifter_ >>= 4;
@@ -349,17 +344,64 @@ 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;
// 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;
// Grab 5050 output, if any.
bool has_5050_output_ = saa5050_serialiser_.has_output();
const auto saa_50505_output_ = saa5050_serialiser_.output();
// Fetch, possibly.
const bool should_fetch = state.display_enable && (active_collation_.is_teletext || !(state.line.get() & 8));
if(should_fetch) {
const uint16_t address = [&] {
// Teletext address generation.
if(state.refresh.get() & (1 << 13)) {
return uint16_t(
0x3c00 |
((state.refresh.get() & 0x800) << 3) |
(state.refresh.get() & 0x3ff)
);
}
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 = ram_[address];
pixel_shifter_ = fetched;
saa5050_serialiser_.add(fetched);
}
// Pick new output mode.
const OutputMode output_mode = [&] {
if(state.hsync || state.vsync) {
return OutputMode::Sync;
}
// if(is_colour_burst) {
// return OutputMode::ColourBurst;
// }
if(
(should_fetch && !active_collation_.is_teletext) ||
(has_5050_output_ && active_collation_.is_teletext) ||
cursor_shifter_
) {
return OutputMode::Pixels;
}
return OutputMode::Blank;
} ();
// 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
@@ -379,11 +421,8 @@ public:
previous_output_mode_ = output_mode;
}
// Increment cycles since state changed.
cycles_ += crtc_clock_multiplier_ << 3;
// Collect some more pixels if output is ongoing.
if(previous_output_mode_ == OutputMode::Pixels) {
if(output_mode == OutputMode::Pixels) {
// Flush the current buffer pixel if full; the CRTC allows many different display
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour.
@@ -396,30 +435,20 @@ public:
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_.begin_data(PixelAllocationUnit, 8);
}
if(pixel_pointer_) {
uint16_t address;
if(state.refresh.get() & (1 << 13)) {
// Teletext address generation mode.
address = 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;
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(has_5050_output_) {
uint16_t pixels = saa_50505_output_.pixels;
for(int c = 0; c < 12; c++) {
*pixel_pointer_++ =
((pixels & 0b1000'0000'0000) ? saa_50505_output_.alpha : saa_50505_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) {
@@ -433,6 +462,9 @@ public:
}
}
}
// Increment cycles since state changed.
cycles_ += crtc_clock_multiplier_ << 3;
}
/// Sets the destination for output.
@@ -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 */,
+2
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 \
+1
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')
+1
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