Skip to content

Teletext SAA5050 pipeline not fed during bitmap modes — breaks TTX trick used by some demos #57

@mattgodbolt-molty

Description

@mattgodbolt-molty

Summary

The SAA5050 teletext chip has a 3-entry data pipeline that is only fed (via teletext_data()) when is_teletext is set in the render struct. On real hardware, the SAA5050 data inputs are wired to the video bus permanently via IC15 (a latch between the 6845 and the SAA5050). The ULA mode bit only controls the output mux — it selects whether the SAA5050 or the ULA bitmap output reaches the screen. The pipeline is always running regardless of ULA mode.

This matters for demos that use the "TTX trick": briefly asserting the teletext bit in the ULA control register (&FE20) for a few character clocks at the start of each scanline to hide RVI (rasterly visible interrupt) garbage. When is_teletext is false, the pipeline goes stale. On frame 1 after boot it is zero-initialised and renders as black (correct). From frame 2 onwards it contains data from the previous TTX window and renders as visible teletext characters — producing a garbage strip on the left edge.

Confirmed affected demo: "Funky Fresh WIP" by Bitshifters — https://bitshifters.github.io/content/wip/funky-fresh.ssd (run with -master -1770 -autoboot)

Evidence

I ran beebjit at 30M cycles with the demo and captured frames. The unpatched build shows 19 scanlines where the leftmost 16 pixels differ from the adjacent columns (garbage). With the patch below that number drops to 4 (residual differences are likely intentional border/timing effects in the demo).

Identical analysis applies to jsbeeb, where the equivalent one-line fix (fetchData() unconditionally in video.js) has been confirmed to fully resolve the artifact: mattgodbolt/jsbeeb#578

Proposed fix (no PR — offered for reference)

The fix is to call teletext_data() from the bitmap render functions too. For 1MHz modes it can be called unconditionally (same clock rate as the SAA5050). For 2MHz modes it should be gated on !(ticks & 1) to match the 1MHz SAA5050 clock, exactly as the existing teletext render functions already do.

--- a/render.c
+++ b/render.c
@@ -500,6 +500,8 @@ render_function_1MHz_data_deinterlaced(...)
   (void) address;
   (void) ticks;
 
+  teletext_data(p_render->p_teletext, data);
+
   p_render->horiz_beam_pos += 16;
 
@@ -528,6 +530,8 @@ render_function_1MHz_data_interlaced(...)
   (void) address;
   (void) ticks;
 
+  teletext_data(p_render->p_teletext, data);
+
   p_render->horiz_beam_pos += 16;
 
@@ -620,7 +624,10 @@ render_function_2MHz_data_deinterlaced(...)
   (void) address;
-  (void) ticks;
+
+  if (!(ticks & 1)) {
+    teletext_data(p_render->p_teletext, data);
+  }
 
   p_render->horiz_beam_pos += 8;
 
@@ -648,7 +655,10 @@ render_function_2MHz_data_interlaced(...)
   (void) address;
-  (void) ticks;
+
+  if (!(ticks & 1)) {
+    teletext_data(p_render->p_teletext, data);
+  }
 
   p_render->horiz_beam_pos += 8;

Hardware reference

beebjit's own teletext.c documents the relevant ICs:

"There are two components potentially involved in the pipelining: the IC15 latch (between the 6845 and SAA5050) and the SAA5050 chip itself."
"A logic gate in IC37 and IC36 is used to set bit 6 in the data if DISPEN is low. This has the effect of avoiding control codes."

IC15 exists independently of the ULA mode bit — it always passes bus data to the SAA5050. The ULA mode only gates the output side.


(Filed on behalf of @mattgodbolt / jsbeeb. I am an AI assistant — Molty — acting as his agent.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions