Skip to content

Video Pipeline

sy2002 edited this page Jul 20, 2026 · 4 revisions

Every core you port was born to talk to a screen that no longer sits on anyone's desk. A Commodore 64 speaks to a 1980s television at roughly fifteen thousand lines per second. An Amiga can weave two half-pictures into one tall one. A Game Boy has no video signal at all — just a tiny liquid-crystal panel and a fistful of pixel pulses. Your job, and the framework's, is to take whatever ragged, gloriously imprecise signal your core produces and deliver it — twice, at the same time — to two connectors on the back of a MEGA65: a digital HDMI socket that demands crisp, standards-clean video, and an analog VGA socket that can drive either a modern VGA monitor or — with the right cable — a genuine period CRT at its original 15 kHz.

This is the cover-to-cover guide to how that happens. It starts from the very idea of a video signal — no prior knowledge of scanlines, sync pulses, or pixel clocks assumed — walks through what a MiSTer core actually hands you, and then follows one RGB stream all the way to both connectors, stage by stage, naming every module it passes through. It uses the three reference cores as running examples: the Commodore 64 (C64MEGA65), the Amiga 500 (AExp), and the Game Boy Color (gbc4mega65). Each solves the same problem differently, and between them they exercise almost every feature the pipeline has. By the end you should be able to read — and reproduce — every video choice those three cores make.

It is a concepts article. It teaches you why the pipeline is shaped the way it is and what each piece does, so that the exact porting steps make sense. Those steps — the lines you edit in globals.vhd, mega65.vhd, config.vhd, and main.vhd — live in The Ultimate MiSTer2MEGA65 Porting Guide, and this page points you there. Clocking is its own deep subject with its own article; where video and clocks meet, this page defers to Clocking and Clock Domain Crossing.

The MiSTer2MEGA65 video pipeline takes one raw retro RGB stream from your core and splits it into two independent outputs — an analog VGA path that stays faithful to the core's native timing (scandoubled for a modern VGA monitor, or passed raw at 15 kHz to a genuine retro CRT), and a digital HDMI path that rescales every frame into a fixed, standards-clean mode — and almost everything you do as a video porter is deciding how each of those two paths should behave.


Part 1 — How a retro machine makes a picture

Before we can talk about pipelines we have to agree on what a "video signal" even is. If you have never had to think about it — most programmers haven't — this part builds the vocabulary from zero. Every term the rest of the article uses is defined here, in the order you need it. If some of it is already familiar, skim to §1.4; the retro-specific quirks there are where the framework earns its keep.

1.1 The raster: a picture painted one line at a time

An old cathode-ray-tube (CRT) television or monitor makes a picture with a single moving dot of light. An electron beam sweeps across the inside of the glass from left to right, painting one horizontal stripe of the image, then snaps back to the left and drops down one row to paint the next stripe. Each of those stripes is a scanline. Stack a few hundred scanlines top to bottom and you have one complete image — a frame. Do that fifty or sixty times a second and persistence of vision turns the flicker into a steady picture.

   how a CRT paints a frame

   ──────────────────────────────▶   scan line 1  (beam sweeps left to right)
      then fly back: left, and down one line
   ──────────────────────────────▶   scan line 2
      fly back ...
   ──────────────────────────────▶   scan line 3
                 ⋮
   ──────────────────────────────▶   last line
      then fly back up to the top: the next frame begins

Two rhythms govern this. The fast one is horizontal: how quickly the beam sweeps one line and returns for the next. The slow one is vertical: how quickly it works down the whole screen and jumps back to the top for the next frame. Everything about video timing is just a careful description of those two return trips.

1.2 Sync, blanking, and the anatomy of a line

The beam needs to be told when to snap back. That instruction is the sync pulse — a brief, distinctive electrical signal. A horizontal sync (HSYNC) pulse says "you've reached the right edge; fly back to the left." A vertical sync (VSYNC) pulse says "you've reached the bottom; fly back to the top." A retro machine emits these two pulses continuously, and the display obediently follows them.

The snap-back is not instantaneous, and while it happens the beam must be switched off so it doesn't smear a bright diagonal line across the picture. Switching the beam off is called blanking. So every line is not just "visible pixels then sync" — there is a short blanked gap before the sync pulse and another after it, giving the beam time to settle. Those gaps have names that survive from the CRT era: the front porch (blanked time between the last visible pixel and the start of sync) and the back porch (blanked time between the end of sync and the first visible pixel of the next line).

   the anatomy of one scanline  (time flows left to right)

   |<---------- one line (H total) ---------->|
   |     visible pixels    | FP |  sync  | BP |
   +-----------------------+----+--------+----+
   |  ### the picture ###  | blanked (black)  |
   +-----------------------+----+--------+----+
   ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾________‾‾‾‾‾‾   HSYNC
        (the dip = sync pulse; FP/BP = front/back porch)

   The dip is drawn as the physical CRT pulse (usually active-LOW);
   inside M2M the same sync is carried active-HIGH (see §1.3).

The identical idea, rotated ninety degrees, describes the whole frame: some active lines (the ones carrying picture), then a vertical front porch of blank lines, then the vertical sync pulse, then a vertical back porch of blank lines, then the next frame begins.

   the anatomy of one frame

   +--------------------------------+
   |     active lines (the picture) |   e.g. 576 visible lines
   +--------------------------------+
   |   V front porch  (blank lines) |
   |   V sync pulse                 |
   |   V back porch   (blank lines) |
   +--------------------------------+  ← next frame starts here

Two more definitions and we have the whole language:

  • The pixel clock (also called the dot clock) is the drumbeat that clocks out individual pixels along a line. If a line is 864 pixel-clocks wide (720 visible plus the porches and sync) and there are 625 lines per frame, the frame is 864 × 625 = 540000 pixel-clocks; run the pixel clock at 27 MHz and you get 27000000 / 540000 = 50 frames per second. Every video timing number in this article is ultimately counted in pixel clocks. If the dot clock is a new idea, two short primers repay ten minutes: video timing in Verilog and the XFree86 Video Timings HOWTO.

  • Data enable (DE) is a single signal that is high exactly when the beam is inside the visible picture — i.e. not in any porch, sync, or blanking. It is simply "the opposite of blanking," collapsed into one wire. You will meet it constantly, because a lot of hardware would rather be told "paint now / don't paint now" once than reason about four separate porch and sync signals.

1.3 Two ways to carry the picture, and which one FPGAs use

There are two historical ways to get this signal from a machine to a screen.

Composite video crams everything — the three colors, brightness, and both sync pulses — onto a single wire, encoded as one analog waveform. It is what a TV's yellow RCA jack carries. It is compact and lossy, and untangling it back into clean pixels is real work.

Separate RGB plus sync keeps everything on its own wire: one line each for red, green, and blue, plus (usually) separate horizontal and vertical sync. It is what a SCART cable (the European TV/AV connector), a VGA cable, and — importantly — the inside of an FPGA all use. Because the colors are never blended together, RGB is the clean, native language of a modern core. Every M2M core speaks it: eight bits each of red, green, and blue, plus separate HSYNC and VSYNC.

This is the form the framework works in throughout, so from here on "the video signal" means: three 8-bit color busses, an HSYNC, a VSYNC, and — the framework's preferred way of expressing blanking — a separate HBlank and VBlank signal (each high during horizontal or vertical blanking respectively). A core that produces HBlank/VBlank directly is giving the framework exactly what it wants; a core that only has sync pulses has to have the blanking reconstructed for it, which we get to in Part 2.

A convention worth memorizing now. Inside M2M, sync and blank are active-high: HSYNC is '1' during the sync pulse, HBlank is '1' during blanking, and so on. Polarity is a recurring source of "why is my picture torn / shifted / black" bugs, and the pipeline assumes active-high at the core boundary. (The transmitted HDMI polarity can differ per video mode — the framework fixes that up for you at the very end; see Part 5. But at the boundary where your core hands over its pixels, everything is active-high.)

1.4 15 kHz versus 31 kHz — the great divide

Here is the single most consequential fact about retro video, the one that shapes half of the analog pipeline.

Count how many scanlines a machine draws per second — that is its horizontal frequency, and it is the horizontal sync rate. A 1980s home computer or console draws roughly 15,000 lines per second: about 15 kHz. A Commodore 64 is 15.64 kHz; an Amiga is 15.625 kHz. Old televisions were built for exactly this rate.

A PC VGA monitor cannot sync that slowly. The lowest thing a standard VGA monitor understands is 640×480 at 60 Hz, whose horizontal frequency is about 31.5 kHz — twice as fast. Feed a raw 15 kHz signal into a VGA monitor and it displays nothing, or a rolling mess. This is not a small incompatibility you can trim away; it is a factor-of-two gap baked into the hardware.

There are two ways across the gap, and M2M offers both:

  • Draw every line twice. If you emit each source scanline on two output lines instead of one, you halve the time each line is on screen and thereby double the horizontal frequency: 15 kHz becomes ~31 kHz, and a VGA monitor locks. The machine that does this is a scandoubler, and it is the heart of the analog pipeline (Part 4). This is called "line doubling," and it is deliberately dumb — it does not smooth or interpolate, it just shows each line twice.

  • Rebuild the picture at a completely standard resolution. Instead of nudging the line rate, capture whole frames and re-scan them out at, say, exactly 1280×720 at 50 Hz — a resolution every modern display accepts. That is the job of the HDMI scaler (Part 5), and it is a far heavier machine than a scandoubler.

Keep this pair in mind: the analog path gently doubles the native signal; the digital path completely rebuilds it. Nearly every difference between the two halves of the pipeline flows from that.

1.5 Interlace versus progressive, and why the Amiga is special

So far each frame has been a clean top-to-bottom sweep of every line. That is progressive scanning. But CRT televisions had a trick to squeeze more apparent vertical resolution out of the same 15 kHz line rate: interlace.

An interlaced signal splits each frame into two fields. The first field draws only the odd-numbered lines (1, 3, 5, …); the second field draws only the even-numbered lines (2, 4, 6, …), shifted down half a line so they fall in the gaps. Two fields interleaved — hence "interlaced" — make one full-height frame, but each field is sent separately, at the full refresh rate. So an interlaced 50 Hz signal sends 50 fields per second (25 full frames), each field carrying half the lines.

   progressive               interlaced (two fields, woven)

   line 1  ───────           line 1  ───────  field A (odd)
   line 2  ───────           line 2  ┈┈┈┈┈┈┈  field B (even)
   line 3  ───────           line 3  ───────  field A (odd)
   line 4  ───────           line 4  ┈┈┈┈┈┈┈  field B (even)
   (every line, one pass)    (fields A and B interleave; field B's
                             even lines fall in the gaps, half a line lower)

The Commodore 64 and a standard Game Boy are progressive. The Amiga is the interesting case: its low-resolution modes are progressive, but its high-resolution "laced" modes (like 640×512) are genuinely interlaced. A signal that switches between the two at runtime forces the pipeline to detect interlace and cope with it — which is exactly why the Amiga core, AExp, is our worked example for interlace handling (Part 5). To signal which field is currently being drawn, an interlaced source toggles a one-bit field flag every field; that flag is the thread the whole interlace story hangs on.

1.6 PAL, NTSC, and the tyranny of not-quite-round numbers

The last piece of background is regional. Europe's PAL standard runs about 50 frames per second with 625 lines total; North America's and Japan's NTSC runs about 60 frames per second with 525 lines. A PAL machine and an NTSC machine differ in line count, frame rate, and often pixel clock, and many cores can switch between them. For a first port you pick one — usually the machine's home region — and get a picture; you add the other later.

But the deeper lesson hiding in these numbers is that retro timings are not round. A PAL C64 does not run at 50.000 Hz; it runs at about 50.124 Hz. A Game Boy runs at 59.7275 Hz, not 60. NTSC's "60 Hz" is really 59.94. These machines were clocked by cheap crystals divided down by whatever integer was convenient, and the resulting refresh rate is whatever it is. On a CRT that never mattered — the TV just followed the sync. But a modern HDMI display insists on a standard rate (exactly 50.000 or 60.000 Hz), and the gap between "what the core produces" and "what HDMI demands" is the root cause of the tearing that flicker-free exists to cure (Part 5). Tuck that mismatch away; it is the punchline of the digital pipeline.

There is a second imprecision worth flagging now: the shape of the sync pulses. A retro core's sync pulses are whatever width the original hardware happened to make them, and they were only ever meant to satisfy a forgiving CRT. A picky modern monitor, by contrast, guesses which video mode it is being fed partly from the width and polarity of those pulses. A scandoubled retro signal can therefore be technically valid yet get misclassified by a VGA monitor — the reason the analog pipeline offers an optional sync reshaper (Part 4).

1.7 Aspect ratio, and why retro pixels aren't square

There is one more property of a retro picture that trips up almost everyone, and it unlocks a whole feature of the pipeline: a picture's shape is not the same thing as its pixel count.

The display aspect ratio is the shape of the whole picture as it is meant to appear — width to height. A Commodore 64 picture is meant to fill a roughly 4:3 frame, the shape of every period television. That is a fact about how it should look, and by itself it says nothing about how many pixels the machine draws.

The pixel aspect ratio is the shape of a single pixel — a square, or a rectangle taller or wider than square. Here is the surprise for anyone raised on modern displays: retro pixels are almost never square. A C64 draws, in round numbers, 320 pixels across by 200 down. If those pixels were square the picture would be 320:200 = 8:5 = 1.6 — wider than 4:3 — yet a C64 on a TV shows a ~4:3 (1.33) image. The pixels must therefore be taller than they are wide: the machine spreads its 200 rows over more height than 320 square pixels would imply. PAL and NTSC machines differ here too, because their line counts and timings differ, so the very same core can have a different pixel shape in each region.

State the lesson sharply, because it drives everything downstream: a raster's pixel dimensions tell you almost nothing about the physical shape the picture should take. You always need a second fact — the intended display aspect (equivalently, the pixel aspect) — to present it correctly. A naive port that treats retro pixels as square gets a subtly (or grossly) wrong shape: circles become ovals, playfields squash or stretch.

And it turns one notch more subtle, because the HDMI standard itself uses non-square pixels. The CEA/HDMI mode the framework calls "576p" is 720×576 encoded pixels, but it is defined as a 4:3 picture — and 720:576 = 5:4, not 4:3, so an HDMI 576p pixel is itself slightly non-square. The 720×480 mode is the same story. So when the framework fits a retro picture into an HDMI frame, it cannot trust pixel counts at all; it has to reason about physical shapes — the picture's intended aspect and the HDMI mode's advertised aspect — and let the pixel arithmetic fall out. That is precisely what the HDMI output-fitting system does (Part 5.1), and it is why that system asks you for a ratio like "4:3" or "10:9" rather than a pixel rectangle.

Three phrases, kept straight, make the rest of the article's aspect talk clear: display aspect (the shape of the whole picture), pixel aspect (the shape of one pixel), and non-square pixels (the normal state of affairs for retro machines and for HDMI's SD modes alike).

1.8 What our three example cores actually emit

To make all of this concrete, here is the native video each reference core produces before the framework touches it. These three are the specimens dissected throughout the article.

  • Commodore 64 (C64MEGA65). The VIC-II video chip emits native RGB with a ~15.64 kHz horizontal frequency and a ~50.124 Hz PAL frame rate. The core's main clock is about 31.53 MHz and the pixel rate is that divided by four (~7.88 MHz). It is progressive. Its active picture is roughly 320×200 of "real" content inside a border; after the core trims and the framework doubles, the analog canvas is 720×540.

  • Amiga 500 (AExp). The OCS chipset emits ~15.625 kHz RGB at ~50 Hz PAL. Low-res modes are progressive; hi-res modes are interlaced, and the core reports the current field so the pipeline can weave them. Its native pixel rate is ~7.09 MHz (low-res) or ~14.19 MHz (any frame containing a hi-res line).

  • Game Boy Color (gbc4mega65). This one has no analog video signal at all. The Game Boy's picture processor drives a 160×144 liquid-crystal panel with per-pixel strobes and a once-per-frame vertical sync — there are no scanlines, no porches, no HSYNC, nothing a monitor could lock to. The core must fabricate a complete raster from scratch, at 59.7275 Hz, and it pads the little 160×144 image up to a 256×224 canvas (with a black border) so there is room for the on-screen menu. This "third kind of core" stresses parts of the pipeline the other two never touch.

Three machines; three horizontal frequencies; one interlaced; one with no sync at all. If the pipeline can carry all three to a modern VGA monitor and an HDMI television, it can carry almost anything. The rest of this article is how it does.


Part 2 — What a MiSTer core hands you, and how to shape it

M2M exists to host a MiSTer core — a piece of FPGA logic originally written for the MiSTer platform — on MEGA65 hardware. So the practical starting point is not "a raw video signal" in the abstract, but "the specific signals a MiSTer core brings to the table," and what you must do to them in CORE/vhdl/main.vhd before the framework will accept them. If you have never seen a MiSTer core, the MiSTer Quick Reference Guide covers the MiSTer side in full; this part covers only the video handoff.

2.1 The signals a MiSTer core produces

A MiSTer core emits its video on a small, conventional set of wires. The names vary a little between cores, but the shape is always this:

  • CLK_VIDEO — the clock the video logic runs on. Usually this is the same as the core's system clock.
  • CE_PIXEL — a clock enable: a signal that is high for one cycle of CLK_VIDEO at each pixel instant. Rather than running a separate slow pixel clock, MiSTer keeps everything on one fast clock and marks pixel boundaries with this enable. (If clock enables are unfamiliar, Clocking and Clock Domain Crossing §1.2 explains the idiom in full; it is the single most useful FPGA-clocking idea, and it is everywhere in M2M.)
  • R, G, B — the color busses (commonly 8 bits each).
  • HSync, VSync — the two sync pulses.
  • HBlank, VBlank — blanking signals. Many cores provide these; some do not.
  • VGA_DE — the data-enable (high during active video). Some cores provide it, some derive it downstream.
  • VGA_F1 — for interlaced cores, the field flag (0 = first field, 1 = second).

The MiSTer framework normally consumes these directly. M2M's job is to translate them into its own conventions and feed them into the two-headed pipeline.

2.2 What the M2M boundary expects, and the name change

The contract between your core and the framework is a set of VHDL ports named video_*, all outputs of your core, all in your core's video clock domain. Reduced to the essentials:

   video_clk_o     : out std_logic;               -- your pixel-domain clock
   video_rst_o     : out std_logic;
   video_ce_o      : out std_logic;               -- native pixel clock-enable
   video_ce_ovl_o  : out std_logic;               -- post-scandoubler CE
   video_red_o     : out std_logic_vector(7 downto 0);  -- 8-bit R
   video_green_o   : out std_logic_vector(7 downto 0);
   video_blue_o    : out std_logic_vector(7 downto 0);
   video_hs_o      : out std_logic;               -- HSYNC, active high
   video_vs_o      : out std_logic;               -- VSYNC, active high
   video_hblank_o  : out std_logic;               -- HBlank, active high
   video_vblank_o  : out std_logic;               -- VBlank, active high

The mapping from MiSTer to M2M is mostly a rename — Rvideo_red_o, HSyncvideo_hs_o, HBlankvideo_hblank_o, and so on — done in main.vhd where you instantiate the core. In the template you can watch the demo core's vga_* names become the contract's video_* names at exactly this boundary. Three things, though, are not just renames, and they are where porters spend their effort:

  1. The core must produce its own video clock. video_clk_o is an output — your core owns its pixel-domain clock and hands it back to the framework, which does not generate it for you. (Split clock ownership is a load-bearing M2M invariant; see Clocking and Clock Domain Crossing §1.3. In practice most cores make video_clk equal to their main clock and pace pixels with video_ce_o.)

  2. You must supply two clock enables, not one. video_ce_o and video_ce_ovl_o are both required, and the difference between them is subtle enough to have its own section below (§2.5) and its own payoff in Part 4.

  3. Blanking must exist and be active-high. If your MiSTer core already outputs HBlank/VBlank, wire them straight through. If it only gives you sync pulses, you must reconstruct blanking — the subject of §2.3.

2.3 Shaping helper #1 — reconstructing blanking with video_sync.vhd

Some cores hand you clean HBlank/VBlank. Others give you only HSync/VSync and expect the surrounding framework to figure out where the active picture is. When you are in the second situation, you do not have to write a blanking generator from scratch — the framework ships one you can drop into main.vhd: M2M/vhdl/controllers/MiSTer/video_sync.vhd (Dar's well-travelled composite_sync/blank generator, used across many MiSTer cores).

You feed it the raw sync pulses and a pixel-domain clock; it counts dots per line and lines per frame between the sync edges, and from those counts it regenerates clean, standard-width hsync_out/vsync_out and the hblank/vblank the pipeline wants. It has a ntsc input to pick the PAL or NTSC line geometry and a wide input for widescreen framing:

   entity video_sync is
   port(
      clk32     : in  std_logic;   -- a pixel-domain clock
      pause     : in  std_logic;   -- freeze (tie to '0' if unused)
      hsync     : in  std_logic;   -- raw sync in
      vsync     : in  std_logic;
      ntsc      : in  std_logic;   -- 0 = PAL geometry, 1 = NTSC
      wide      : in  std_logic;
      hsync_out : out std_logic;   -- clean, standard-width sync out
      vsync_out : out std_logic;
      hblank    : out std_logic;   -- reconstructed blanking
      vblank    : out std_logic
   );

The Commodore 64 core is the reference user of this idea, with a twist worth knowing: it does not use the framework's copy — it ships its own customized video_sync.vhd. The VIC-II's native horizontal sync pulse is a lazy 12.69 µs wide; the C64's version reshapes it down to a tidy 4.82 µs, and at the same time it uses the regenerated blanking to crop the core's output to a clean 382×270 active window before the framework ever sees it. So video_sync.vhd is doing three jobs at once here — regenerating blanking, standardizing the sync width, and trimming the picture — all inside the core, upstream of the framework. That is the general pattern: whatever conditioning your core's raw signal needs, do it in main.vhd, so the framework receives a clean, active-high, properly-blanked RGB stream.

2.4 Shaping helper #2 — fabricating a raster when there is no signal

The Game Boy is the extreme case: its picture processor produces per-pixel strobes and a once-per-frame vsync, and nothing else — no scanlines, no porches, no horizontal sync. You cannot reshape a raster that does not exist; you have to build one.

The Game Boy core does this with another imported MiSTer module, lcd.v, wrapped for VHDL. lcd.v runs on the core's video clock (a nominal 67.108864 MHz, exactly twice the machine clock) and counts out a complete synthetic raster: it defines a 425-pixel-wide line and a 264-line frame in parameters, and generates positive-going HSYNC/VSYNC and both blanking pairs by comparing free-running counters against those parameters. Each pixel is paced at ten video clocks, so 425 pixels is about 4250 clocks; because the frame rate is not a clean division, the last pixel of each line is deliberately stretched by six to make the line exactly 4256 video clocks. That gives 4256 × 264 = 1,123,584 clocks per frame and thus 67108864 / 1123584 = 59.7275 Hz, the authentic Game Boy rate, fabricated entirely by counting the FPGA clock.

Two more tricks make it work as an M2M source. First, because the incoming pixels arrive on the machine clock but the raster is scanned out on the video clock, lcd.v writes pixels into a small frame buffer on one clock and reads them out under the raster on the other — double-buffering decouples the asynchronous capture from the tidy scan-out. Second, the core pads the tiny 160×144 image up to a 256×224 canvas by wrapping it in a black border (the old Super Game Boy screen geometry). That padding is not decoration: a 160×144 picture is far too small to hang an on-screen menu on, whereas 256×224 (which doubles to a 512×448 analog canvas, or 32×28 character cells) has room for one. The border exists so the OSM has somewhere to live.

The lesson reaches past the Game Boy: turning a fixed panel into a video stream means capturing the panel into RAM asynchronously, then scanning it back out under a self-consistent raster you generate yourself. A CRT-native core already is a raster generator and skips all of this; a panel-native core has to become one.

2.5 The two clock enables you owe the framework

Now the subtle one. You must produce two pixel clock enables, and confusing them is a classic first-port bug.

  • video_ce_o is your core's native pixel rate — one enable pulse per pixel the core actually generates, before any doubling. For the C64 that is main-clock-over-four (~7.88 MHz); for the democore it is a simple divide-by-two of the 54 MHz core clock, giving 27 MHz.

  • video_ce_ovl_o is the post-scandoubler pixel rate — one enable per pixel of the analog output, i.e. VGA_DX enables across each visible line — and it must correspond to the analog output resolution VGA_DX×VGA_DY (which you set in globals.vhd; Part 3). It is faster than video_ce_o: about twice as fast for a core whose native width is half VGA_DX, and up to the full video clock for a very low-resolution core. The framework uses it to clock the on-screen-menu overlay and to sample your video onto the analog output at the doubled resolution.

Why two? Because the analog picture that reaches the VGA connector is the scandoubled one — twice as many lines, running at roughly twice the pixel rate — and the menu overlaid on it must be drawn at that same doubled rate to line up. The "overlay" enable (_ovl) is that doubled rate.

The template gets to cheat. Its demo core already renders natively at the full 720×576 overlay resolution, so there is nothing to double, and main.vhd simply ties the two together:

   -- The democore already outputs at full VGA_DX x VGA_DY resolution, so the
   -- native rate IS the overlay rate. A real low-res core must NOT do this.
   video_ce_ovl_o <= video_ce_o;

That line is flagged in the template as an important porting to-do, because it is only correct when your native resolution already equals VGA_DX×VGA_DY. A genuine low-resolution core must run video_ce_ovl_o faster — enough enables to lay down VGA_DX pixels across each doubled line — so the overlay and the analog sampling happen at display resolution rather than at the tiny native one. (The C64, for instance, holds video_ce_ovl_o high on every video clock in Standard mode — four times its native pixel enable; only its retro 15 kHz mode drops to twice native.) Get this wrong and the on-screen menu comes out the wrong size or misaligned. We return to just how subtle this can get — the Game Boy has to phase-lock the overlay enable to the scandoubler itself — in Part 4.4, once the scandoubler has been properly introduced.

2.6 The field flag, for interlaced cores

Finally, if your core is interlaced (§1.5), MiSTer gives you a VGA_F1 field flag. In M2M you route it out as one more video-domain output, conventionally video_fl_o: it toggles every field while the machine is in a laced mode and sits constant '0' otherwise. The Amiga core takes the Minimig chipset's field1 signal and drives video_fl_o with it, one-for-one with MiSTer's assign VGA_F1 = field1. That single bit is what lets the HDMI scaler stitch the two fields back into a full-height picture; we follow it all the way through in Part 5.3. A progressive core simply never drives it, and everything downstream treats a constant '0' as "not interlaced" — so the feature costs nothing when you don't need it.

With the vocabulary built and the core's signals in hand, we can finally open up the framework itself.


Part 3 — The M2M pipeline: architecture and where you wire

Your core produces exactly one video stream. The MEGA65 has two video connectors. The framework's central act is to fan that one stream out to both, processing each one completely differently, and it does so inside a single VHDL entity: av_pipeline.vhd. This part is the map of that entity — what splits where, on which clock, and, just as important, which files you edit to steer it.

3.1 One stream in, two outputs out

Here is the whole pipeline on one page. Read it top to bottom: your core hands its RGB stream to av_pipeline, which immediately forks it into an analog path and a digital path that never rejoin. Everything below is elaborated in Parts 4 and 5; this is the map to keep open while you read them.

        CORE  (your MiSTer core, in CORE/vhdl/main.vhd)
        RGB + hs/vs + hblank/vblank + video_ce + video_ce_ovl
        all on YOUR video_clk
                │
   ┌────────────┴───────────────────┐
   ▼                                ▼
   ANALOG PATH                      DIGITAL PATH
   analog_pipeline.vhd              digital_pipeline.vhd
   on YOUR video_clk                on framework hdmi_clk (+5x tmds)
   (keeps native timing)            (rebuilt to a fixed HDMI mode)
   │                                │
   scandoubler (15k / 2x->31k)      crop / zoom-in
   │                                │
   force RGB=0 outside DE           ascal: 1-frame buffer in HyperRAM
   │                                │
   OSM overlay (the menu)           OSM overlay (the menu)
   │                                │
   csync + sync reshaper            vga_to_hdmi -> TMDS symbols
   │                                │
   phase-shift output regs          4x serialiser (10:1 DDR)
   ▼                                ▼
   VGA_RGB + HS/VS out              3 data + 1 clock TMDS pairs
   │                                │
   ▼                                ▼
   ADV7125 DAC -> VGA               HDMI connector
   (modern VGA monitor,
    or 15 kHz retro CRT)

The single most important structural fact is the fork near the top: the same core RGB enters both paths, and from that point on they share nothing. The analog path keeps your core's native timing — doubling it for a VGA monitor, or leaving it raw at 15 kHz for a retro CRT; the digital path throws the timing away and rebuilds the picture at a standard resolution. They even overlay the on-screen menu separately, and — as we will see — they run on entirely different clocks.

3.2 Two paths, two clock domains

The clocks are where the two paths most fundamentally diverge, and understanding this makes the rest fall into place.

The analog path runs on your core's own video_clk. The scandoubler, the analog overlay, the composite-sync and reshaper stages, and the final output registers are all clocked by the very clock your core handed the framework. The picture that reaches the VGA connector is your native picture — line-doubled for a modern VGA monitor, or passed through untouched at 15 kHz for a retro CRT. Either way, nothing has changed its fundamental timing. At the very end, the framework even phase-shifts the analog output by clocking the final registers on the falling edge of video_clk, so the on-board digital-to-analog converter samples a clean, settled signal. The VGA DAC's own clock (vdac_clk) is literally your video_clk.

The digital path runs on a framework-generated hdmi_clk. HDMI does not tolerate a core's not-quite-round timing; it wants a standard pixel clock for a standard mode. So the framework synthesizes the HDMI pixel clock itself, from the 100 MHz board clock, and — this is the clever bit — makes it reconfigurable: choosing a different HDMI output mode actually retunes the pixel clock to that mode's exact frequency at runtime. Alongside the pixel clock it generates a second clock at five times that rate, tmds_clk, used to serialize the HDMI bits into HDMI's TMDS line code (Part 5.4). For the default 720p50 mode these are 74.25 MHz and 371.25 MHz respectively.

   who clocks what

   YOUR video_clk ─────▶ ANALOG path
                         (scandoubler, analog OSM, VGA out)
                         native timing: raw 15 kHz, or doubled to 31 kHz

   framework hdmi_clk ─▶ DIGITAL path
                         (ascal, HDMI OSM, TMDS out)
                         rebuilt to a fixed HDMI mode; the pixel clock
                         retunes per mode (74.25 / 371.25 MHz at 720p50)

Between them sits one more clock you never own: the audio clock at 12.288 MHz, which carries the sound the HDMI path muxes into its data stream and the analog path sends to the 3.5 mm jack. Audio is out of scope here except where it shares the HDMI wire (Part 5.4).

Because these are genuinely different clock domains, every menu setting the core sends — "use scandoubler," "HDMI mode 720p50," "zoom in," and so on — is safely carried across a clock-domain crossing inside av_pipeline before it reaches the path it controls. You do not have to think about those crossings; the framework builds them. (If you want to understand what it is doing and why, Clocking and Clock Domain Crossing Part 2 is the reference.)

3.3 video_modes_pkg.vhd — video modes, and the analog sync definitions

M2M/vhdl/av_pipeline/video_modes_pkg.vhd is worth opening because it is the concrete definition of "a video mode," and it is the source of the porch and sync numbers Part 1 talked about in the abstract. Despite what its name might suggest it is not only about HDMI: it holds two related things, one for each output path. For the digital path it catalogs the seven fixed HDMI modes; for the analog path it defines the optional VGA sync-reshaper records and presets we meet in Part 4.5. Both belong here because both are, at bottom, descriptions of sync-and-porch timing.

The HDMI side is built on a record, video_modes_t, that is nothing more than the anatomy from §1.2 written as VHDL fields:

   type video_modes_t is record
      CLK_KHZ     : integer;   -- pixel clock, in kHz
      CLK_SEL     : ...;       -- which pixel-clock frequency to synthesize
      CEA_CTA_VIC : integer;   -- the mode's standard ID number
      ASPECT      : ...;       -- 4:3 or 16:9, for HDMI signalling
      PIXEL_REP   : std_logic; -- pixel repetition (for SD modes)
      H_PIXELS    : integer;   -- visible width      ┐
      V_PIXELS    : integer;   -- visible height     │
      H_PULSE, H_BP, H_FP : integer;  -- the porches │  exactly the
      V_PULSE, V_BP, V_FP : integer;  -- and sync    │  §1.2 anatomy
      H_POL, V_POL : std_logic;       -- polarities  ┘
   end record;

Constants of this type are gathered into an array, VIDEO_MODE_VECTOR, of seven slots indexed 0 to 6 — built from six distinct timing records, because the PAL 576p record fills two slots (once behind the "4:3" menu label and once behind the "5:4" one; see below). When the user picks an HDMI mode in the menu, that choice becomes an index into this vector, and the digital pipeline reads the porches and polarities straight out of the chosen record.

Two details from this file save real debugging time:

  • CLK_SEL is what retunes the HDMI clock. Each mode carries a 3-bit code naming the pixel-clock frequency it needs (25.2, 27, 74.25 MHz, …). The framework feeds that code to a reconfigurable clock generator (video_out_clock.vhd), which reprograms itself on the fly to that frequency — glitch-free, by briefly switching to a backup clock while it retunes. This is why the HDMI pixel clock in §3.2 is "reconfigurable": the mode you pick literally determines the clock frequency.

  • The 4:3 and 5:4 labels are deliberately crossed. The menu offers PAL 576p as both "4:3" and "5:4," but both select the same 720×576 timing record — what differs is only how the digital pipeline fits the picture (the "5:4"-labelled mode letterboxes it to a 4:3 shape; the "4:3"-labelled one fills the 5:4-shaped raster). The labels name the target monitor, not the aspect math: as the core's mega65.vhd explains in a comment, the 4/3-adjusted image simply looked best on a 5:4 monitor and vice-versa. It is a rare case where a user-facing name intentionally contradicts the geometry — do not "fix" it. (This whole 4:3-versus-5:4 muddle is really the display-aspect-versus-pixel-count distinction from §1.7; Part 5.1 shows how a core now sets the HDMI fit directly, and independently for the normal and zoomed views.)

3.4 Where you actually wire the video — the four-file map

Almost none of the pipeline is something you build; it is something you configure. Four files carry all of your video decisions, and it helps to know up front which file owns which kind of decision.

CORE/vhdl/globals.vhd — the fixed facts about your video. Here you set the analog output resolution VGA_DX/VGA_DY (the post-scandoubler size, and the size the on-screen menu is laid out against), the exact core clock speed CORE_CLK_SPEED, the optional analog sync reshaper profile VGA_STD_SYNC (Part 4.5), the optional HDMI output-fitting policy HDMI_VIEW (Part 5.1), and — for audio — the biquad filter coefficients. Changing VGA_DX/VGA_DY silently resizes the menu's character grid and its video RAM, so treat it as load-bearing.

CORE/vhdl/mega65.vhd — the live video policy. This is where menu bits become video behavior. A block of "AV-mode" outputs (all in the QNICE clock domain, because they are driven by the menu state) tells the framework how to run each path. Every one of them is an output of your core; the framework consumes them. This table is the control panel of the whole pipeline:

Core output Path it steers What it does
qnice_video_mode_o digital Which of the 7 HDMI modes (an index into VIDEO_MODE_VECTOR)
qnice_scandoubler_o analog 1 = line-double to 31 kHz ("Standard VGA"); 0 = pass 15 kHz
qnice_retro15kHz_o analog 1 = the output is a raw 15 kHz signal (adjusts the overlay)
qnice_csync_o analog 1 = emit composite sync (for SCART) instead of separate H/V
qnice_zoom_crop_o digital 1 = crop the border and zoom the picture to fill the frame
qnice_hdmi_view_size_o digital 2-bit: selects one of four cropped-view sizes (slots in HDMI_VIEW); ignored while uncropped (§5.1)
qnice_ascal_mode_o digital Scaler filter: nearest / bilinear / sharp / bicubic
qnice_ascal_polyphase_o digital 1 = use polyphase filters (overrides the above)
qnice_ascal_triplebuf_o digital Scaler triple-buffering (leave off)
qnice_dvi_o digital 1 = DVI (strip HDMI audio + info packets)
qnice_audio_mute_o / _filter_o audio Mute / apply the biquad filter

A boolean setting is just qnice_<thing>_o <= qnice_osm_control_i(C_MENU_<x>) — read one bit of the 256-bit menu-state register. A multi-way setting (the HDMI mode) is a priority chain over several such bits. The C_MENU_* constants are zero-based line numbers into the menu; because nothing checks that they still match the menu after you edit it, reordering a menu line silently points these at the wrong bits. This coupling is the single most common foot-gun in this file. (The On‐Screen‐Menu (OSM) article covers the menu-state register in full.)

CORE/vhdl/config.vhd — the menu itself, plus scaler policy. Here you write the menu lines the user sees, and a couple of video-relevant switches: ASCAL_USAGE decides who controls the scaler filter — 2 means "the core drives it from menu bits," while 1 means "hands off, the QNICE firmware controls the scaler itself," which is how all three reference cores implement their rich HDMI-filter menus (Part 5.2). The config.vhd Switches and Settings page is the exhaustive reference.

CORE/vhdl/main.vhd — the pixels. Everything in Part 2: instantiate your core, produce the video_* signals, the two clock enables, and any blanking reconstruction. globals and config and mega65 decide what the pipeline does; main.vhd decides what the picture is.

3.5 The on-screen menu overlay, on both paths

One framework feature straddles both paths and deserves a mention here because it appears twice in the pipeline map: the on-screen-menu (OSM) overlay. The QNICE co-processor that runs the Shell draws the menu into a small character-plus-attribute video RAM — one 16-bit word per character cell holding a character code and a color/inverse attribute, rendered through a 16×16 font ROM. The overlay compositor then paints that menu on top of the video wherever the menu is "on," pixel by pixel. It is an opaque replacement, not a blend: where the menu is present, it wins; everywhere else, the video shows through untouched.

The interesting part is that the overlay runs independently in each path — once in the analog pipeline, once in the digital pipeline — each with its own copy of the video RAM (QNICE writes both copies at once). Two facts about it will matter later:

  • The analog overlay is clocked by your video_ce_ovl_o (the doubled enable from §2.5), because it must be drawn at the scandoubled resolution. The digital overlay is clocked once per HDMI pixel, because the scaler has already produced a clean one-pixel-per-clock stream.

  • In the analog "retro 15 kHz" mode, where the picture has half as many lines, the overlay doubles its vertical coordinate so the menu still lands in the right place. This is one reason the retro15kHz bit exists as a separate signal from scandoubler.

The rendering internals are the On‐Screen‐Menu (OSM) article's job; for the video pipeline it is enough to know the menu is composited late in each path, at that path's resolution.

3.6 The picture the framework measures back — on the serial debug console

Finally, the pipeline does not only push pixels forward; it also measures the stream you feed it and reports the numbers back to the Shell. A small module, video_counters.vhd, watches your core's sync and blank signals and reconstructs the full raster anatomy from §1.2 — visible width and height, each porch, each sync-pulse width — purely by timestamping the edges. A one-pulse- per-second strobe lets it count horizontal sync pulses per second, giving the horizontal frequency; from that and the raster totals the firmware derives the vertical refresh rate and even the effective pixel/dot clock.

Where do you see these numbers? The M2M framework has a built-in QNICE debug console — a plain serial terminal — and the Shell logs the measurements to it. You do not have to do anything clever to read them: connect a serial terminal to the MEGA65 (115,200 baud, 8-N-1, no flow control) and leave it attached while the core boots. A couple of seconds after the core starts — the framework deliberately waits for the frequency measurement to settle first — the Shell prints the core's name and its detected video geometry: the resolution, the vertical refresh rate, and the effective pixel clock, exactly the values video_counters measured. It is a running log, so you simply watch it scroll past; no keypress is needed to make it appear. Keep the terminal connected during bring-up and a wealth of diagnostic information streams by for free.

That console is far more than a video read-out. Holding Run/Stop + Cursor Up and then pressing Help on the MEGA65 keyboard drops you into the interactive QNICE Monitor (the QMON> prompt), from which you can read and modify RAM, ROM, and device registers live — including the framework's SysInfo registers behind these very measurements. The full walkthrough — wiring, terminal settings, and the monitor commands — is the "Understanding the QNICE debug console" section of the "Hello World" Tutorial.

This is a debugging gift. When you bring up a new core and the picture is wrong, the console tells you exactly what raster the framework thinks you are producing — is it 720 wide or 360, is the refresh 50 Hz or 25, is the pixel clock what you expected — without a logic analyzer. It reads the signal at the same boundary the pipeline does, so it sees what the pipeline sees.

With the map in hand, we can now walk each path in detail. The analog path first, because it is the simpler of the two and it introduces the scandoubler that everything else refers back to.


Part 4 — The analog pipeline (VGA)

The analog path's mission is modest and faithful: take your core's native RGB, deliver it to a display, paint the menu on it, optionally condition its sync, and drive the on-board DAC. It never rescales and never rebuilds — the picture that reaches the VGA port is your picture, at your timing. Its whole reason to exist is fidelity to a display, and it serves two very different ones: a modern VGA monitor, which needs the 15 kHz signal doubled to 31 kHz, and a genuine period CRT — a Commodore or Philips RGB monitor, a broadcast/PVM tube, a SCART TV — which wants the raw 15 kHz signal exactly as the 1980s hardware made it. Here is the chain in order; each stage gets a section.

   the analog pipeline  (analog_pipeline.vhd, all on YOUR video_clk)

   core RGB + HS/VS + HBlank/VBlank + video_ce
        │
        ▼
   [1] video_mixer / scandoubler ── line-double 15→31 kHz (if enabled)
        │                            produces a clean Data Enable (DE)
        ▼
   [2] force RGB = 0 wherever DE = 0   (the VDAC's rule)
        │
        ▼
   [3] OSM overlay ── paint the menu (clocked by video_ce_ovl)
        │
        ├──▶ [4a] csync ── build composite sync from HS xor VS
        └──▶ [4b] vga_sync_reshaper ── optionally re-shape HS/VS pulses
        │
        ▼
   [5] phase-shift registers (clocked on the FALLING edge of video_clk)
        │
        ▼
   VGA_RED/GREEN/BLUE + VGA_HS/VS  ──▶ ADV7125 DAC ──▶ VGA connector

4.1 The scandoubler — making 15 kHz acceptable to a VGA monitor

Recall the great divide of §1.4: a VGA monitor cannot sync to a 15 kHz retro signal. The scandoubler bridges the gap by the crude, effective method of drawing every input line twice. Store one incoming scanline in a small line buffer, then read it out twice as fast, onto two output lines — the picture is unchanged but each line is on screen for half as long, so the horizontal frequency doubles from ~15 kHz to ~31 kHz, and the monitor locks.

   line doubling

   input (15 kHz):   line 1        line 2        line 3
                     ───────       ───────       ───────

   output (31 kHz):  line 1  line 1  line 2  line 2  line 3  line 3
                     ─────── ─────── ─────── ─────── ─────── ───────
                     (each source line emitted twice, at double the rate)

The scandoubler is an imported MiSTer module (scandoubler.v), wrapped by video_mixer.sv, which M2M drives from the analog pipeline. Three things about how M2M uses it are worth knowing:

  • The two clock enables come home here. The scandoubler takes your native video_ce as its input pixel rate and internally produces a doubled output rate. This is the concrete reason §2.5 asked you for a doubled video_ce_ovl — the doubled output is what the analog overlay and the DAC sample. (There is a genuine subtlety about which doubled enable to use; §4.4.)

  • The fancy features are switched off. MiSTer's scandoubler can do "hq2x" smoothing and gamma correction; M2M disables both — the gamma stage synthesizes to nothing and the scandoubler runs with hq2x turned off. The analog output is plain line-doubling only — M2M has no menu to author a gamma table, and the sharp, unsmoothed look is what most people want on VGA. (Smoothing and CRT-look filtering do exist in M2M, but on the HDMI side, done by the scaler; Part 5.2.)

  • "Standard mode" means scandoubler on. When people say a core is in "Standard VGA" mode, they mean the scandoubler is engaged and the output is ~31 kHz — the setting for an ordinary VGA monitor or a modern display with a VGA input. This is the default, and it is the mode the sync reshaper (§4.5) applies to.

The scandoubler also emits the clean Data Enable signal the rest of the path relies on: it computes DE = (not HBlank) and (not VBlank), i.e. "inside the active picture," which the next stage needs.

4.2 The three analog modes, and composite sync

Not everyone wants 31 kHz. If you are driving a genuine 15 kHz CRT or a SCART television, you want the raw retro signal, un-doubled, exactly as a period display expects. So the analog path offers three modes, controlled by the three independent AV-mode bits from Part 3.4:

Mode scandoubler retro15kHz csync Line rate For
Standard VGA on 0 0 ~31 kHz PC VGA monitor / modern display
Retro 15 kHz, H+V sync off 1 0 ~15 kHz 15 kHz RGB monitor (separate syncs)
Retro 15 kHz, composite sync off 1 1 ~15 kHz SCART TV (via the MiSTer adaptor)

These really are three independent bits, not one three-way switch — the source even warns that "scandoubler off does not automatically mean retro 15 kHz on." The retro15kHz bit does not change the line rate itself (turning the scandoubler off does that); it tells the overlay to adjust for the halved line count (§3.5) and, as we will see, gates the sync reshaper.

The third mode needs composite sync (CSYNC): a single combined sync signal that carries both horizontal and vertical timing on one wire, the way a TV expects over a SCART connector. The framework derives it from the separate syncs with the classic recipe — composite sync is essentially HSync xor VSync, with the horizontal pulses inverted ("serrated") during the vertical interval so a TV's horizontal oscillator keeps flywheeling through the vertical pulse. When CSYNC mode is selected the framework routes this combined signal onto the VGA connector's HSYNC pin (pin 13) and holds the VSYNC pin (pin 14) high, matching the wiring of the MiSTer VGA-to-SCART adaptor. In the other two modes the separate HSYNC and VSYNC go to their normal pins.

The C64 core is the reference here: it offers all three as menu items, deriving the AV-mode bits so that either 15 kHz sub-mode turns the scandoubler off, and only the SCART sub-mode raises csync. The Amiga and Game Boy cores offer exactly the same three-way choice.

Driving a real tube is a genuinely supported use case, not a curiosity — but it comes with hardware and safety strings attached, documented per core (the C64's doc/retrotubes.md is the worked guide). You reach the tube through the VGA connector with the right cable: a VGA-to-SCART cable — which must contain the correct resistors, because a plain pass-through cable feeds the wrong voltages — for a Commodore 1084 or Philips monitor; a VGA-to-DB9-RGB cable for the later Commodore monitors; or a VGA-to-5-BNC cable (RGBS, or RGBHV for separate syncs) for a broadcast/PVM tube. Two rules protect the equipment: only ever feed a CRT the 15 kHz mode — 31 kHz Standard mode can damage the tube — and turn HDMI flicker-free off for analog use, because the clock-dithering that smooths HDMI (Part 5.5) injects glitches a CRT will show. Those are exactly the settings the retro sub-modes exist to provide.

4.3 The VDAC rule: black means zero

A small but mandatory stage sits right after the scandoubler: force the RGB busses to zero everywhere outside Data Enable. The MEGA65's video DAC (an Analog Devices ADV7125) misbehaves if it is fed non-zero color values during blanking — it wants the analog outputs driven to the blanking level, i.e. black, outside the visible window. So the framework explicitly zeroes R, G, and B wherever DE = 0:

   if DE = '1' then
      vga_red <= mix_r;  vga_green <= mix_g;  vga_blue <= mix_b;
   else
      vga_red <= (others => '0');  -- black outside DE (ADV7125 needs it)
      vga_green <= (others => '0');
      vga_blue <= (others => '0');
   end if;

This is why "sync and blank must be active-high and correct" is not pedantry: the blanking signal literally decides when the DAC is allowed to emit color. A core with wrong blanking gets smeared borders or a confused DAC. (Many MiSTer cores already blank their RGB at the source; the democore does it too, as belt-and-suspenders. The framework enforces it regardless.)

4.4 The overlay clock-enable, and the Game Boy's hard lesson

Here the video_ce_ovl thread from §2.5 pays off, and it pays off with a genuine trap.

The analog overlay must be drawn at the scandoubled resolution, so it is clocked by video_ce_ovl. For a core whose native resolution already equals VGA_DX×VGA_DY, that overlay enable is just the native one, and the template's video_ce_ovl_o <= video_ce_o is correct. For a real low-resolution core it must run faster — enough enables to lay down VGA_DX pixels across each doubled line. (The C64 holds the overlay enable high on every video clock in Standard mode; only in its retro 15 kHz mode does it drop to twice the native rate.)

But even that rule hides a subtlety that only bites the analog path, and the Game Boy core is where it bites hardest. The MiSTer scandoubler paces its output pixels on a free-running half-line schedule of its own — it restarts a counter at the falling edge of the input's horizontal blank and emits one output pixel every few clocks from there. If you build video_ce_ovl from your core's native pixel enable instead of from that same reference, the two grids beat against each other: because the scandoubler's half-line length is not a whole multiple of the native pixel period, the sampling phase drifts from one output line to the next. Visibly, alternating lines shift sideways — a comb or ripple pattern — and, far worse, the regenerated horizontal sync gets quantized differently each line, so its period wobbles by tens of nanoseconds and analog monitors lose their lock.

The Game Boy core solves this by deriving video_ce_ovl, in Standard mode, from the scandoubler's own reference — a half-line counter restarted at the same input-hblank falling edge, ticking twice per output pixel, giving exactly 512 enables per visible line to match its 512-pixel-wide analog canvas. In the 15 kHz modes, where there is no scandoubler, it instead builds a 2× grid from phase-locked taps of the native pixel enable.

The lesson for the textbook is precise: counting VGA_DX enables per line is enough for a CRT-native core, but a scandoubled fixed-panel core must phase-lock the overlay enable to the scandoubler, not derive it from its own pixel clock. It is the kind of thing you discover in simulation (the Game Boy authors did) rather than by reading a contract, and it is exactly the sort of hard-won detail this pipeline hides from cores that don't need it.

4.5 The VGA sync reshaper — passing a monitor's mode-classification test

We reach the newest piece of the analog pipeline, and a small, self-contained puzzle that illustrates a whole class of real-world video problems: a signal can be electrically perfect and still be misunderstood by the display.

Recall from §1.6 that a modern VGA monitor guesses which video mode it is receiving partly from the width and polarity of the sync pulses, not from the picture itself. VESA's DMT standard prescribes, for each PC mode, a specific horizontal-sync pulse width and specific sync polarities (640×480 at 60 Hz, for instance, uses a 96-pixel-wide negative HSYNC and a 2-line negative VSYNC). A retro core's scandoubled output has whatever sync widths and polarities the original machine happened to use — often a wide, positive pulse nothing like the VESA numbers. Some monitors take one look at those pulses, decide "this is consumer 480p video, not a PC mode," and switch on overscan and consumer image processing, or refuse the signal outright. The picture is there; the monitor is just filing it under the wrong heading.

The VGA sync reshaper (vga_sync_reshaper.vhd) fixes exactly this, and only this. When enabled, it preserves the leading edge of every sync pulse and the complete raster period, but replaces the pulse widths and output polarities with values a monitor will recognize. It does not move the picture, change the resolution, or alter the frame rate — nothing about the raster geometry or timing changes. It only re-shapes the two sync pulses so the monitor classifies the mode correctly.

Two design decisions make it safe to leave in the framework for everyone:

  • It is downstream of the split and the overlay, and it only touches Standard mode. The reshaper sits after the analog/digital fork and after the analog OSM, so HDMI always keeps the original core syncs, and the reshaper only engages when the scandoubler is on and neither retro mode is active. In the 15 kHz and composite-sync modes it is a straight wire-through — those modes feed period displays that want the native pulse shapes.

  • Its default is an exact bypass. The configuration is a record, and the stock value, C_VGA_SYNC_RESHAPER_OFF, disables the logic entirely and passes the syncs through unchanged. A core that does nothing gets byte-identical behavior to a framework without the feature — which is the backward-compatibility promise the whole framework lives by.

Configuring it

You opt in by setting one constant in CORE/vhdl/globals.vhd, VGA_STD_SYNC. The framework provides ready-made VESA DMT presets and a helper function that converts a preset — expressed as a physical sync duration at a reference pixel clock — into the number of your video-clock cycles that duration works out to. That indirection is what lets one preset work for any core, whatever its clock:

   -- OFF by default: an exact wire-through (backward compatible)
   constant VGA_STD_SYNC : vga_sync_reshaper_cfg_t := C_VGA_SYNC_RESHAPER_OFF;

   -- To opt in: pick a DMT preset and let the framework convert its sync
   -- duration into cycles of your own video clock:
   -- constant VGA_STD_SYNC : vga_sync_reshaper_cfg_t :=
   --  make_vga_sync_reshaper_cfg(C_VGA_SYNC_DMT_640X480_60, CORE_CLK_SPEED);

The second argument is your video-clock frequency. It equals CORE_CLK_SPEED only when your core reuses its main clock for video (as the template does); a core with a separate video clock passes that clock's frequency instead — the Game Boy, below, passes its VIDEO_CLK_SPEED.

The preset C_VGA_SYNC_DMT_640X480_60 carries the standard 640×480@60 numbers — a 96-pixel HSYNC at a 25.175 MHz reference clock, a 2-line VSYNC, both negative. make_vga_sync_reshaper_cfg scales that 96-pixel duration into however many cycles of your video_clk produce the same physical time, and packages it with the polarities. Two more presets ship for 800×600 and 1024×768. Because the preset is described in physical time and converted to your clock, you never do the arithmetic yourself.

The real user: the Game Boy core

This feature is not hypothetical — it exists because a shipping core needed it. The Game Boy's scandoubled output is 512×448, which is geometrically close to consumer 480p, and it carries lcd.v's wide, positive 32-pixel horizontal sync. Several analog VGA monitors saw that and engaged their consumer-video path (overscan, wrong scaling). The Game Boy core turns the reshaper on with exactly the 640×480 preset, converted against its own video clock:

   -- gbc4mega65: present VESA-like sync so Standard mode is read as PC VGA
   constant VGA_STD_SYNC : vga_sync_reshaper_cfg_t :=
      make_vga_sync_reshaper_cfg(C_VGA_SYNC_DMT_640X480_60, VIDEO_CLK_SPEED);

Every source edge and the exact frame period are preserved; only the HSYNC narrows to the VESA width and both syncs flip to negative polarity — enough for the monitors to reclassify the signal as a PC mode and drop the consumer processing. It is the whole feature in one real line of a real globals.vhd.

A contrast worth drawing: the Amiga's different answer

The reshaper is not the only way to make a monitor happy with an analog picture, and the Amiga core solves a related but different problem a different way — a useful comparison because it sharpens what the reshaper is and isn't. AExp does not reshape sync pulses; instead it carries a runtime, SD-card-tunable screen-centering system that can pan the whole analog picture by retiming the sync phase, soft-blank overscan edges, and re-crop the HDMI input — per detected Amiga video mode. The reshaper is a static, compile-time pulse-shape fixup that never moves the raster; the Amiga's screen-centering is a dynamic, per-mode repositioning that deliberately does move it. They address adjacent complaints ("the monitor mis-reads my mode" versus "the picture sits off-center for this particular Amiga mode") and neither replaces the other. Knowing both exist keeps you from reaching for the wrong tool.

4.6 The phase-shifted output, and the DAC

The last analog stage is a set of output registers clocked on the falling edge of video_clk. Sampling the RGB, HSYNC, and VSYNC half a clock late deliberately phase-shifts them so the ADV7125 DAC latches a steady, settled signal rather than one caught mid-transition; the framework registers the syncs the same way as the colors so all of them stay aligned on the wire. These registers are also physically placed (via a floorplanning constraint) close to the FPGA's VGA output pins, to keep the routing delay to the DAC small. The framework then ties the DAC's own control lines to their "just pass the analog through" values — no sync-on-green, no DAC-side blanking (the framework did the blanking itself, in §4.3) — and drives the DAC clock from video_clk.

And that is the whole analog path: native RGB, doubled for a VGA monitor (or left raw for a CRT), menu painted on, sync optionally reshaped, phase-aligned, and handed to the DAC. Nothing here changed your picture's fundamental identity. The digital path, which we turn to now, does the opposite: it takes your picture apart and builds a brand-new one.


Part 5 — The digital pipeline (HDMI)

Where the analog path preserves, the digital path reconstructs. A modern HDMI display will not accept a C64's 50.124 Hz or a Game Boy's 59.7275 Hz — it wants a standard mode at a standard rate. So the digital path captures whole frames from your core, rescales them to one of the seven fixed HDMI modes, paints the menu, encodes the result as HDMI, and serializes it out the connector. It is a much bigger machine than the analog path, and it leans on the MEGA65's HyperRAM to hold frames while it works. Here is the chain:

   the digital pipeline  (digital_pipeline.vhd)

   core RGB + HS/VS + HBlank/VBlank + video_ce   (on YOUR video_clk)
        │
        ▼
   [1] crop / zoom-in ── optionally enlarge blanking to shrink the
        │                active picture (so the scaler zooms it up)
        ▼
   [2] ascal ── the scaler:  autodetect input size, rescale to the
        │  ▲               chosen HDMI mode, filter, and buffer one
        │  │   frame  in ──▶ HyperRAM ◀── read back at the fixed
        │  └──────────────    (via 128-bit Avalon bus)   HDMI rate
        ▼         (crossing here from video_clk to hdmi_clk)
   [3] OSM overlay ── paint the menu at HDMI resolution
        │
        ▼
   [4] vga_to_hdmi ── VGA-style timing → TMDS symbols (+ audio, unless DVI)
        │
        ▼
   [5] 4x serialiser_10to1 ── 3 colour channels + 1 clock channel
        │
        ▼
   TMDS differential pairs ──▶ HDMI connector

5.1 Cropping and output fitting — placing the picture in the HDMI frame

Before the scaler rebuilds your picture, two questions have to be answered: what part of your picture is shown, and what shape and size it becomes when it lands in the HDMI frame. The first is cropping (the input side); the second is output fitting (the output side). They are separate mechanisms that happen to share the one "zoom" menu bit, and keeping them straight is the whole of this section.

Cropping the input (the zoom trick)

The first stage is optional and, at first glance, backwards: to zoom in, it makes the picture smaller. The trick is that the scaler downstream always stretches whatever active area it is given to fill its target rectangle. So if you enlarge the blanking around the picture — telling the scaler "the active area is only this inner rectangle" — the scaler blows that smaller rectangle up, and the result is a zoom.

crop.vhd does this by counting pixels and lines from the incoming blanking and, when the "zoom" menu bit is on, forcing the blanking active for everything outside a fixed inner window. It never moves or resamples anything; it only adds blanking, which is why it can only zoom in, never out.

There is a sharp gotcha here, and it is a recurring theme of this framework: the crop window is hardcoded to a 320×200 picture — the Commodore 64's native active area — with fixed margins. For the C64 core the window fits perfectly. For any other core it is wrong unless you edit the constants: the Game Boy core, for instance, tunes the crop to strip its black Super Game Boy border, exposing the bare 160×144 LCD image. What size that stripped image then becomes on the HDMI screen is a separate, output-side choice — and one the Game Boy hands to the user, which is the subject of "Sizing the cropped view" below.

Fitting the output (aspect ratio in the HDMI frame)

Cropping decided what the scaler sees. Output fitting decides the rectangle inside the HDMI frame that the scaler draws into — and therefore the picture's shape and any bars around it. This is where the aspect-ratio subtleties from §1.7 come home to roost, and it is a first-class, core-configurable feature.

Recall the problem §1.7 set up: you cannot reason about a picture's shape from pixel counts, because neither retro pixels nor HDMI's SD pixels are square. A 720p frame is physically 16:9; a "576p" frame is 720×576 encoded pixels but physically 4:3. If you want a 4:3 retro picture to look 4:3 on a 16:9 HDMI screen, you must draw it into a narrower rectangle with pillarbox bars left and right; on a 4:3 frame you fill it; and if the target shape is wider than the frame you letterbox with bars top and bottom. Getting that rectangle right, for every combination of HDMI mode and desired aspect, is fiddly — and it depends on the mode's physical aspect, not its pixel count.

The framework does this for you through a single policy, HDMI_VIEW, in CORE/vhdl/globals.vhd. It holds two independent fits — one for the uncropped view (used while the zoom bit is off) and one for the cropped view (used while it is on) — so "normal" and "zoomed" can each have their own shape. Each fit chooses one of three modes:

  • LEGACY — reproduce M2M's historical per-mode rectangle exactly (the 16:9 modes get a 4:3 pillarbox, the 5:4 mode a letterbox). This is what keeps existing cores pixel-for-pixel unchanged.
  • FULL_FRAME — fill the entire HDMI active area, edge to edge, aspect be damned.
  • ASPECT — fit a physical aspect ratio you name (4:3, 5:4, 8:7, 10:9, 16:9, 1:1, or any make_hdmi_fit(w, h)), centered, with pillarbox or letterbox bars as needed.

The clever part is ASPECT mode. You give it a physical ratio like 4:3, and it derives the pixel rectangle from the HDMI mode's advertised physical aspect (the 4:3-or-16:9 flag the mode sends in its HDMI info-frame), not from the mode's pixel dimensions. That is exactly what makes it correct for the non-square-pixel SD modes: it knows 720×576 is physically 4:3 and computes the rectangle so the result is the shape you asked for, whatever the pixels' squareness. You think in shapes; the framework does the pixel arithmetic.

Two engineering details are worth knowing. First, every rectangle is computed at elaboration (synthesis) time — the framework builds a small table of one rectangle per HDMI mode, for each view, as VHDL constants, and the only runtime hardware is a mux that picks the rectangle for the current mode and zoom bit; no division happens on the fly. Second, the stock value C_HDMI_VIEW_LEGACY is {UNCROPPED => LEGACY, CROPPED => FULL_FRAME, CROPPED_SIZES => all full} — the old behavior exactly (the normal view keeps the historical placement; zoom fills the frame; and the cropped view offers a single, full size — the next part) — so a core that sets nothing is completely unaffected. It is the same non-breaking-default discipline as the sync reshaper and the interlace flag.

Configuring it is one line. To keep the normal view as-is but present, say, a cropped 10:9 picture:

   -- default: exactly the legacy placement
   constant HDMI_VIEW : hdmi_view_cfg_t := C_HDMI_VIEW_LEGACY;

   -- opt in: legacy uncropped view, a 10:9 fit when the user zooms
   -- constant HDMI_VIEW : hdmi_view_cfg_t :=
   --    make_hdmi_view_cfg(C_HDMI_FIT_LEGACY, C_HDMI_FIT_10_9);

So the two mechanisms sit at opposite ends of the scaler: crop.vhd shrinks what goes in, and the HDMI_VIEW fit shapes what comes out. Together they let a core say, precisely, "show this part of my picture, at this physical shape, in both the normal and the zoomed view."

Sizing the cropped view — the Game Boy's handheld sizes

Fitting the output chose the cropped view's shape. One last knob chooses its size, and it is the only fit decision a core makes at runtime, from a menu, rather than as a compile-time constant.

The motivation is the Game Boy's, and it is concrete. Its screen is a tiny 160×144 LCD; stretched to fill a 720p frame it becomes an 800×720 wall of chunky pixels — a technically razor-sharp 5× enlargement, but nothing like the small, self-contained picture the handheld actually showed. Some players want the faithful 10:9 LCD shape without the LCD filling the television. So the framework lets a core define several sizes of the cropped view and hand the choice to the user.

A size is a rational fraction of the fitted rectangle. You give the framework a fraction — make_hdmi_scale(numerator, denominator), each 1..255, never greater than one — and it shrinks the rectangle that "Fitting the output" computed by that fraction and re-centers it in the frame. Both dimensions scale by the same fraction, so the shape is preserved exactly; only the picture's size, and the border around it, change. C_HDMI_SCALE_FULL is 1/1 — a deliberate short path that reproduces the fitted rectangle untouched. (A fraction that would round a tiny rectangle down to zero pixels is clamped back to one, because the scaler cannot draw nothing.)

There are four size slots, held in the CROPPED_SIZES field of HDMI_VIEW, and the core selects one at runtime with a new 2-bit output, qnice_hdmi_view_size_o, whose binary value indexes slots 0–3. The selector steers only the cropped view; the uncropped view ignores it. You wire it to a menu group exactly as you wire any other AV-mode bit (§3.4).

The Game Boy uses all of it to build a single "Aspect Ratio & Size" menu:

   -- gbc4mega65/globals.vhd:  TV-style 4:3 for the uncropped view, and three
   -- sizes of the borderless 10:9 handheld picture for the cropped view.
   constant HDMI_VIEW : hdmi_view_cfg_t :=
      make_hdmi_view_cfg(
         C_HDMI_FIT_4_3,               -- uncropped ("TV-style"): the full SGB canvas at 4:3
         C_HDMI_FIT_10_9,              -- cropped  ("Handheld"):  the bare LCD at its true 10:9
         (0 => C_HDMI_SCALE_FULL,      -- slot 0: Full   (1/1)
          1 => make_hdmi_scale(9, 14), -- slot 1: Small  (the default)
          2 => make_hdmi_scale(4, 5),  -- slot 2: Medium
          3 => C_HDMI_SCALE_FULL));    -- slot 3: a safe full-size fallback

It raises qnice_zoom_crop_o (selecting the cropped view) for any of the three handheld items and lowers it for TV-style, and drives qnice_hdmi_view_size_o to the chosen slot. The three handheld sizes are concentric — the same 10:9 shape, centered in the frame, differing only in size:

   ┌────────────────────────────────────────────────┐ HDMI frame (schematic, not to scale)
   │            ┌──────────────────────┐            │
   │            │         Full         │            │
   │            │  ┌────────────────┐  │            │
   │            │  │     Medium     │  │            │
   │            │  │  ┌──────────┐  │  │            │
   │            │  │  │  Small   │  │  │            │
   │            │  │  └──────────┘  │  │            │
   │            │  └────────────────┘  │            │
   │            └──────────────────────┘            │
   └────────────────────────────────────────────────┘
   TV-style (4:3) uses the UNCROPPED view instead — the full Super
   Game Boy canvas — so this size selector does not apply to it.

At 720p the three land like this:

At 720p Small (9/14, default) Medium (4/5) Full (1/1)
output rectangle 514×463 640×576 800×720
per Game-Boy pixel ≈3.2× (soft) exact 4×4 exact 5×5

The table hides a nice detail. Full is the 5× integer enlargement (160×5 = 800, 144×5 = 720), and because Medium is 4/5 of Full it lands on an exact block — so both stay pixel-sharp. Small, at 9/14, is deliberately not an integer multiple: it trades the crisp integer scaling for a smaller, handheld-sized picture, which is the whole reason it is the default. Choosing the fractions is choosing that trade-off — sharpness versus size.

Two things keep the feature free for cores that ignore it, in the framework's usual way. The four size rectangles, like every fit rectangle, are computed at elaboration (synthesis) time — one table per slot — so the only runtime hardware is a mux that picks the slot. And when all four slots are C_HDMI_SCALE_FULL (which is exactly what C_HDMI_VIEW_LEGACY sets), the framework detects it and omits the selector's clock-domain crossing and its mux altogether, leaving behavior byte-identical to a framework without the feature — the same non-breaking-default rule as the sync reshaper (§4.5) and the interlace flag (§5.3).

5.2 ascal — the scaler that rebuilds the picture

The heart of the digital path is ascal.vhd, a large, third-party polyphase video scaler (from the temlib project; roughly 2900 lines you should never edit). Conceptually its job is simple to state and hard to do: take the core's native RGB at its native, ragged timing, write each frame into a single-frame buffer in HyperRAM, and read it back out at the exact timing of the chosen HDMI mode — resampling in the process from the core's resolution to the mode's resolution.

A few facts about how M2M uses it are enough for a porter:

  • It autodetects your input size. You do not tell the scaler how big your picture is; it measures the active region for itself and reports the detected width and height back to the firmware (those are the numbers you see beside the raw raster measurements the Shell logs to the serial debug console (§3.6)). This matters because retro cores have variable, ragged active regions; autodetection is the sane default.

  • The frame buffer lives in HyperRAM — one frame of it. The scaler writes over a wide (128-bit) Avalon memory bus (Avalon is Intel's memory-bus protocol), which a width converter narrows to the 16-bit HyperRAM the MEGA65 actually has. It is allocated exactly one framewidth × height × 3 bytes (24 bits of color per pixel), rounded up to a power of two — and used as a single full-frame buffer: the framebuffer display mode and triple-buffering are both off, so one frame is resident at a time and the write pointer (core) and read pointer (HDMI) chase each other around it. That single-frame chase is the only heavy memory user in the base framework, it is why the HDMI path and any HyperRAM your core uses must share the same arbitrated memory (see HyperRAM for Beginners), and — as §5.5 shows — it is the very thing that makes both the frame-rate mismatch and its cure possible.

  • Storing frames in RAM is exactly what makes the mismatch — and flicker-free — possible. Because the scaler decouples the write side (your core's rate) from the read side (HDMI's rate) through a RAM buffer, the two can run at different frame rates at all. That decoupling is a feature, and it is also the source of the tearing problem we solve in §5.5. Hold that thought.

The scaler's filters, and the "CRT emulation" menu

The scaler can resample with several filters, and the choice is what a user perceives as image quality. From cheapest to richest: nearest-neighbor (blocky, sharp pixels), bilinear (soft), sharp bilinear, bicubic, and polyphase (a programmable finite-impulse-response filter with loadable coefficients). The framework builds all of them into the silicon at compile time; a runtime mode word selects which is active, and one bit of that word toggles triple-buffering.

In the template, this is wired straight to the menu: an "HDMI: CRT emulation" item flips the scaler to polyphase mode. But all three reference cores do something richer. They set the scaler-control policy to "hands off, the firmware drives it" (the ASCAL_USAGE = 1 setting from §3.4) and implement a full HDMI filter menu in QNICE assembly. The C64 core's menu is the model: eight options —

   No Filter        (nearest neighbor)
   Sharp Bilinear
   Bicubic
   Smooth           (polyphase, a gentle sharpness curve)
   Lanczos          (polyphase, a sharp windowed filter)
   Scanlines        (polyphase: Lanczos horizontally + a scanline
                     brightness curve vertically)  ← the default
   CRT (S-Video)    (polyphase, an S-Video-like horizontal blur)
   CRT (Composite)  (polyphase, a composite-like horizontal blur)

The three "native" options just set the scaler's mode; the five polyphase options additionally push a pair of coefficient tables (one horizontal, one vertical) into the scaler's filter RAM. The default, "Scanlines," is bit-for-bit the old single "CRT emulation" toggle — a horizontal Lanczos plus a vertical scanline-darkening curve — and switching filters at runtime just rewrites the coefficient RAM, no core reset needed. The Amiga and Game Boy cores carry the same eight-option system. (The coefficient machinery and the QNICE side belong to QNICE and the Shell ROM (m2m-rom.asm); here it is enough to know the "CRT look" is a scaler filter, chosen at runtime, applied only on HDMI.)

5.3 Interlace: weaving fields back together

Recall the Amiga's laced hi-res modes from §1.5, where each frame arrives as two half-height fields. The scaler is where they are stitched back together, and following the one-bit field flag through the pipeline is a small, satisfying story — and the model for how the framework grows a new feature without breaking old cores.

The core drives video_fl_o with its field flag (§2.6). The framework carries that bit — under the name video_fl_i, and crucially defaulting to '0' at every stage — through framework.vhd, av_pipeline.vhd, and into the scaler's interlace input, i_fl. With interlace enabled, the scaler watches that bit: when it sees the flag toggling, it concludes the input is interlaced and weaves — it offsets the write address by one line for alternate fields so the two half-height fields interleave into one double-height frame in the buffer. When the flag stops toggling (a progressive source), it disarms itself a few frames later and behaves exactly as before. A constant '0' is therefore bit-identical to "progressive," which is why a core that never drives the flag is completely unaffected.

That default-'0' discipline is the template for every non-breaking framework enhancement: a new port that defaults to the old behavior lets existing cores re-synthesize unchanged. The Amiga's interlace support is the canonical example, and the reshaper of §4.5 follows the same rule with its C_VGA_SYNC_RESHAPER_OFF default.

There is a nice asymmetry in where deinterlacing happens for the Amiga, which pins down three concepts at once:

  • On HDMI, the scaler weaves electronically, so laced modes like 640×512 appear full-height and deinterlaced.
  • On analog Standard (scandoubled) VGA, the pipeline is field-blind — it "bobs," showing each field doubled, because the scandoubler does not weave.
  • On analog 15 kHz, nothing needs to deinterlace at all: the raw interlaced signal, half-line offset and all, goes straight to a CRT, which weaves it natively the way the Amiga's designers intended — the most authentic picture of the three.

One signal, three destinations, three different answers to "who reassembles the fields."

5.4 vga_to_hdmi and the TMDS serializers — becoming an HDMI signal

After the scaler and the HDMI-side menu overlay, the picture is an ordinary VGA-style stream again — RGB plus active-high sync plus data-enable — but now at a clean, standard mode. The last job is to turn that into an actual HDMI signal on wires. Two imported modules (from the Tyto project) do it.

vga_to_hdmi.vhd is the encoder. It does three things worth naming:

  • It fixes the sync polarity for the mode. The pipeline works in active-high sync throughout, but each HDMI mode declares its own required sync polarity; the encoder reconciles them with a single exclusive-nor per sync, so upstream stages never have to care about polarity. (This is the other end of the polarity story from §1.3.)

  • It distinguishes HDMI from DVI. HDMI is DVI plus "data islands" — packets carrying audio samples, audio-clock-regeneration data, and info-frames describing the video and audio format. The qnice_dvi_o menu bit collapses all of that away: in DVI mode the encoder emits video only, no audio, no info-frames. This is why toggling one menu item can make a DVI-only monitor (or a DVI adapter) go from silent-with-a- picture to working — some sinks choke on the audio islands. Audio rides at a fixed 48 kHz, muxed into these islands, which is the only reason sound appears in a video module at all.

  • It emits TMDS symbols, not serial bits. TMDS — transition-minimized differential signaling — is HDMI's line code; the encoder's output is three parallel 10-bit symbol streams, one per color channel, the result of its 8-bit-to-10-bit encoding, which balances the number of ones and zeros on the wire. Turning those parallel symbols into a serial bitstream is a separate step, and people routinely conflate the two.

That separate step is serialiser_10to1_selectio.vhd, and it has one detail that always raises an eyebrow: it serializes ten bits per pixel using a clock that is only five times the pixel clock, not ten. The trick is double data rate — the Xilinx output serializer clocks a bit on both edges of the fast clock, so 5 × 2 = 10 bits per pixel. (Ten bits also exceeds one serializer primitive's 8-bit limit, so two are cascaded in a master/slave pair.) There are four of these serializers: three carry the color channels, and a fourth carries the HDMI clock — as data. The clock channel is just the constant pattern "0000011111" pushed through an identical serializer, producing a square wave at the pixel rate with exactly the same routing and timing as the three data lanes, so all four differential pairs stay perfectly matched. The HDMI clock is not a separate clock buffer; it is five zeros and five ones through the same machine as the pixels.

   TMDS on the wire (per pixel)

   vga_to_hdmi:  8-bit R  ─▶ 10-bit symbol ─┐
                 8-bit G  ─▶ 10-bit symbol ─┼─▶ 3 parallel symbol streams
                 8-bit B  ─▶ 10-bit symbol ─┘
                                    │
   serialiser (x4):   symbol ─▶ serial bits at 5x pixel clock, DDR (=10 bits)
                      data lane 0,1,2   +   clock lane "0000011111"
                                    │
                              4 differential TMDS pairs ─▶ HDMI

5.5 Flicker-free — the crown jewel

Now the punchline the whole article has been walking toward, and the most important thing a serious core does with the digital pipeline.

Recall two facts. First (§1.6), your core's frame rate is not round: a PAL C64 runs at 50.124 Hz, an Amiga at ~50 Hz, a Game Boy at 59.7275 Hz. Second (§5.2), HDMI insists on an exactly standard rate — 50.000 or 60.000 Hz — and the scaler makes the mismatch survivable by buffering frames in HyperRAM: the core writes frames in at its rate, HDMI reads them out at the standard rate.

But buffering only defers the problem. If the writer and the reader run at even slightly different rates, the write pointer and the read pointer drift relative to each other in the buffer. Eventually the faster one laps the slower one, and at that instant the display shows a frame that is half-new and half-old: a horizontal seam that jumps — a tear, and on a periodic mismatch, a regular flicker.

   the write/read chase in the HyperRAM frame buffer

   [====================================================]  one frame
    ▲read pointer (HDMI, exactly 50.000 Hz)
              ▲write pointer (core, ~50.124 Hz — slightly faster)

   Each frame, the faster pointer creeps further ahead. When it laps
   the other, one displayed frame is part-old + part-new = a tear.

   Fix: keep the write pointer a small, steady distance ahead of the
   read pointer by nudging the CORE clock a hair faster or slower.

The framework provides the measurement for free. A module, hdmi_flicker_free.vhd, watches the scaler's HyperRAM traffic and, once per output frame — at the instant the read pointer wraps back to the start — samples how far ahead the write pointer is. If that lead has shrunk too small, it raises a signal meaning "the core is too slow"; if it has grown too large, one meaning "the core is too fast." Between the two thresholds it says nothing — a deliberate dead-band so the decision does not chatter. Those two signals, hr_high ("too fast") and hr_low ("too slow"), are handed to your core.

The framework cannot act on them, though, because it does not control your clock — you do. So flicker-free is a mechanism the framework offers and the core must opt into, by running its pixel/ main clock from a source it can nudge, and consuming hr_high/hr_low to nudge it. The template does not do this — its clock is fixed — which is exactly why the template tears on HDMI and why "flicker- free is computed but not wired" is a standing note about the demo. A real core wires it, and all three of ours do, in an instructive way.

The universal recipe is: build two slightly different clock speeds that bracket the exact HDMI rate — one just above it, one just below — and switch between them, glitch-free, based on the feedback, so the core's average rate tracks the HDMI rate and the write pointer stays a steady distance ahead. The subtlety — and the reason flicker-free is genuinely core-specific — is which side the native rate falls on:

   the two speeds bracket the fixed HDMI rate; the core dithers
   between them so its average lands on it. Which side is "native"
   depends on the machine:

                     slower ◀─ HDMI rate ─▶ faster
   C64      (vs 50 Hz):  49.999 Hz │ 50.124 Hz   (native is ABOVE)
   Amiga    (vs 50 Hz):    native  │  twin        (native is BELOW)
   Game Boy (vs 60 Hz):    native  │  twin        (native is BELOW)
  • Commodore 64. Its native PAL rate, 50.124 Hz, is above 50. So it builds a near-native "orig" clock giving a 50.124 Hz frame rate and a slightly slower twin giving 49.999 Hz. "Too fast" selects the slower twin; "too slow" selects the original. Two static clock generators feed a glitch-free multiplexer, and the design only releases reset once both are locked. The C64's own manual literally tells users to enable "HDMI: Flicker-free" — the whole two-clock machine exists so that one sentence is true.

  • Amiga (AExp). Its native rate is below 50. So the mapping is inverted: "too slow" selects the faster twin (above 50), "too fast" falls back to the slower native. Same machinery, opposite wiring — a perfect illustration that you cannot copy another core's flicker-free FSM blindly; you must know which side of the HDMI rate your machine sits on.

  • Game Boy (gbc4mega65). Its native rate, 59.7275 Hz, is below 60, so like the Amiga "too slow" picks the faster twin. But it adds a fixed-panel twist: recall it runs a video clock at exactly twice its machine clock (§2.4), and that 2:1 ratio must never break. So it switches both clocks together, from the same multiplexer select, keeping the ratio intact. A fixed-panel core with a hard N:1 video-to-machine clock ratio has to switch every clock in lockstep; a CRT-native core that reuses one clock for everything does not face this.

In every case a menu item gates the whole thing (users who output to a raw analog monitor want it off, because the speed-dithering that smooths HDMI would inject glitches into a CRT that is following the core's sync directly). And in every case the two clock speeds are so close, and the switch so rare — less than once a second — that nothing visible or audible betrays the dithering. The core's clock is gently, invisibly pulled into lockstep with the display, and the tear never happens.

This is the feature that separates a demo from a shippable core, and it is the clearest example in the whole framework of the division of labor this article keeps returning to: the framework measures and offers; the core decides and acts.

With both paths walked end to end, the last part steps back and compares how our three cores made their choices — the concrete payoff of everything above.


Part 6 — Three cores, three sets of choices

Everything in this article converges on one question a porter has to answer: given my machine, how should each stage of the pipeline behave? The three reference cores answer it three different ways, and laid side by side they turn the abstract pipeline into a set of concrete decisions you can copy. If you have read this far, this table should read as a summary, not a surprise.

Commodore 64 (C64MEGA65) Amiga 500 (AExp) Game Boy Color (gbc4mega65)
Native source VIC-II, real RGB + sync OCS chipset, real RGB + sync LCD panel: pixel strobes, no sync
How the raster is made core reshapes + crops it (own video_sync.vhd, to 382×270) emitted by the chipset fabricated by lcd.v (425×264)
Horizontal frequency ~15.64 kHz ~15.625 kHz synthetic
Native frame rate 50.124 Hz (PAL) ~50 Hz (PAL) 59.7275 Hz
Main / video clock ~31.53 MHz ~28.375 MHz 33.5 MHz main, 67.1 MHz video (2×)
Scan type progressive progressive + interlaced hi-res progressive
Analog canvas (VGA_DX×VGA_DY) 720×540 720×576 512×448 (160×144 in a black SGB border)
Analog modes Standard / 15 kHz H+V / 15 kHz CSYNC same three same three
HDMI modes offered PAL 50 Hz (16:9 default, 4:3, 5:4) PAL 50 Hz only (16:9, 4:3, 5:4) 60 Hz family (16:9 default, 720×480, 640×480, 800×600)
HDMI filters 8-option custom (Scanlines default) 8-option custom 8-option custom
Interlace weave yes (video_fl → scaler)
Sync reshaper (VGA_STD_SYNC) — (uses runtime screen-centering) yes (DMT 640×480@60)
Flicker-free yes — native above 50 Hz (twins 50.124 ↔ 49.999 Hz) yes — native below 50 Hz (mapping inverted vs C64) yes — native below 60 Hz; both clocks switch in lockstep
Zoom / crop fits its native 320×200 screen-centering strips the border → Small/Medium/Full 10:9 handheld sizes (Full = 5× integer)

Read down each column and you have that core's entire video design. Read across each row and you have a menu of how one decision can go. Three short syntheses:

The Commodore 64 is the "textbook CRT-native core." Its chip emits a real 15 kHz RGB signal; the core conditions that signal in main.vhd (reshaping the sync and cropping to a clean window), then lets the framework do the rest — scandouble for VGA, scale for HDMI. Its two contributions to your mental model are its rich firmware-driven HDMI filter menu (the eight-option system every core now copies) and its flicker-free implementation, whose native-above-50 clock pair is the canonical example. It uses neither interlace nor the sync reshaper, because it needs neither.

The Amiga is the "everything-at-once" core. It is the reason the framework has interlace weave at all: its hi-res laced modes drive video_fl, the scaler weaves on HDMI, the 15 kHz output lets a CRT weave natively, and Standard VGA bobs — the clearest tour of "where deinterlacing happens." Its flicker-free is the C64's, inverted, because the Amiga sits below 50 Hz. And instead of the static sync reshaper it carries a runtime, per-mode screen-centering system — a reminder that the framework's built-in tools are a starting point, and a core with a specific need can build its own.

The Game Boy is the "third kind of core," and it stresses the pipeline in ways the CRT machines never do. With no native video signal, it fabricates a raster and double-buffers a fixed panel into a stream. It is the real-world user of the sync reshaper (its scandoubled 512×448 output was being misread as consumer 480p), and the first user of runtime cropped-view sizes (§5.1) — three sizes of its faithful 10:9 LCD picture, so the tiny panel need not be blown up to fill the screen. Its video_ce_ovl has to phase-lock to the scandoubler to avoid comb artifacts and sync jitter — the hardest overlay-CE case in any core. And its flicker-free has to switch two clocks in lockstep to protect its fixed 2:1 video-to-machine ratio. If you are porting anything that isn't a CRT machine — a handheld, an LCD console, an arcade board with an exotic scan — the Game Boy is your reference.

The porter's video checklist

Distilling the whole article into the order you actually do things:

  1. Get the pixels into main.vhd. Instantiate your core; map its R/G/B, HSync, VSync, HBlank, VBlank to the video_*_o ports. If your core lacks blanking, reconstruct it with video_sync.vhd (§2.3). If it has no sync at all, fabricate a raster (§2.4). Ensure sync and blank are active-high and RGB is zero during blanking.
  2. Produce two clock enables. video_ce_o at the native pixel rate; video_ce_ovl_o at the post-scandoubler rate matching VGA_DX/VGA_DY. Do not blindly tie them equal unless your native resolution already fills the analog canvas (§2.5, §4.4).
  3. Set the fixed facts in globals.vhd. VGA_DX/VGA_DY (post-scandoubler size), CORE_CLK_SPEED (exact), audio coefficients; only if a monitor misreads your Standard mode, VGA_STD_SYNC (§4.5); and, if you want a specific HDMI shape, a zoomed aspect, or runtime cropped-view sizes, the HDMI_VIEW output fit (§5.1).
  4. Wire the menu policy in mega65.vhd. Drive the AV-mode outputs (§3.4) from menu bits: HDMI mode, the three analog modes, zoom, DVI, audio. Keep the C_MENU_* indices in sync with the menu.
  5. Write the menu in config.vhd. The HDMI-mode radio group and the analog-mode choice; set ASCAL_USAGE (2 for menu-driven filters, 1 if you build a firmware filter menu like the reference cores).
  6. Decide on the hard features. For a tear-free HDMI picture, implement flicker-free with a dual-speed clock that brackets the HDMI rate on the correct side (§5.5). For an interlaced machine, drive video_fl_o (§5.3). For a fixed panel, mind the overlay-CE phase-lock (§4.4).
  7. Watch the serial debug console. Confirm the framework measures and logs the raster, refresh, and pixel clock you expect before chasing anything harder (§3.6).

Steps 1–5 get a picture on both connectors; step 6 is what makes it good; step 7 is how you check your work without a logic analyzer.

Where to go next

  • The exact porting stepsThe Ultimate MiSTer2MEGA65 Porting Guide turns the checklist above into concrete edits, file by file, with the MiSTer-to-M2M pattern catalog.
  • Clocks, clock enables, and building the flicker-free dual clock — [[Clocking and Clock Domain Crossing]] is the companion to this article; flicker-free lives at the intersection of the two.
  • The on-screen menu and its state registerOn‐Screen‐Menu (OSM) covers the overlay's rendering and the C_MENU_* menu-bit contract this article leaned on.
  • The switches you set in config.vhdconfig.vhd Switches and Settings is the exhaustive reference for ASCAL_USAGE, the menu structure, and the video-related general settings.
  • The memory the scaler runs onHyperRAM for Beginners and [[HyperRAM FIFO and Caching Strategies]] explain the RAM the HDMI frame buffer (and flicker-free) depends on.
  • The firmware that drives the HDMI filtersQNICE and the Shell ROM (m2m-rom.asm) covers the QNICE side of the eight-option filter menu.
  • The MiSTer side you are reproducingMiSTer Quick Reference Guide describes what a MiSTer core hands you before M2M gets involved.
  • Background primers — the video-timing and pixel-clock links in §1.2 are a gentle, ten-minute foundation if any of the raster vocabulary still feels new.

Clone this wiki locally