featured image

How a 25 MHz Clock Becomes 307,200 Pixels on a Screen

A deep dive into the `simple_480p` VGA timing generator, revealing how two counters and a handful of comparators produce the exact sequence of signals required to drive a 640×480 display at 60 Hz — all from first principles in Verilog.

Published

Fri Aug 08 2025

Technologies Used

FPGA Verilog
Beginner 16 minutes

Why Your Monitor Expects Exactly 800 Pixels Per Line (Even Though It Only Shows 640)

Every VGA monitor in the world obeys an invisible contract. The display doesn’t simply receive pixel data — it requires a precisely timed sequence of active pixels, blank intervals, and synchronization pulses, repeated line by line, frame by frame. If a single pixel clock cycle is out of place, the screen tears, rolls, or goes black entirely.

This tutorial walks through the simple_480p module in this Wordle project — a compact Verilog module that generates the exact timing signals a 640×480 VGA display demands. By the end, you’ll understand how two ten-bit counters and a handful of comparators produce a stable sixty-frame-per-second video signal. No external IP cores, no vendor primitives — just counter arithmetic.

VGA timing is governed by the VESA DMT (Display Monitor Timing) standard. The 640×480 @ 60 Hz mode has been supported by every monitor manufactured since 1987. Understanding it is foundational to any display-adjacent hardware design.

What You Need to Know Before Starting

You need to understand binary counters (a register that increments on each clock edge), the difference between combinational and sequential logic, and what a clock signal is and what “posedge” means in Verilog.

Tools: Verilog HDL (IEEE 1364-2001) and any simulator (Verilator, Icarus Verilog, or Vivado). File under discussion: modules/display/simple_480p.v (72 lines).

A Typewriter Drawing an Invisible Grid

Think of a VGA display as a typewriter. A carriage sweeps left to right across the page, laying down characters. At the end of each line, the carriage returns and the paper advances one line. When the bottom of the page is reached, the paper feeds back to the top.

A VGA signal works the same way. A horizontal counter (sx) sweeps from pixel 0 to pixel 799 on each line. When it wraps, a vertical counter (sy) advances from line 0 to line 524. At every position, the display checks whether it’s inside the visible area, inside a blanking interval, or inside a sync pulse.

The entire timing generator is just these two counters and a few comparators.

Step 1: Define the Timing Boundaries

The VGA 640×480 standard specifies four regions per horizontal line. These constants encode them as pixel-clock cycle counts, zero-indexed:

// Horizontal timing — 800 pixel clocks per line
parameter HA_END = 639;           // last visible pixel
parameter HS_STA = HA_END + 16;   // = 655: sync pulse begins after 16-clock front porch
parameter HS_END = HS_STA + 96;   // = 751: sync pulse lasts 96 clocks
parameter LINE   = 799;           // last clock in the line (48-clock back porch follows sync)

The same structure repeats vertically, counted in lines rather than pixels:

// Vertical timing — 525 lines per frame
parameter VA_END = 479;           // last visible line
parameter VS_STA = VA_END + 10;   // = 489: sync pulse begins after 10-line front porch
parameter VS_END = VS_STA + 2;    // = 491: sync pulse lasts 2 lines
parameter SCREEN = 524;           // last line in the frame (33-line back porch follows)

Why these exact numbers? At a 25 MHz pixel clock, 800 pixels per line produces a horizontal frequency of 31.25 kHz. Dividing by 525 lines gives 59.52 Hz — the standard VGA refresh rate. Change any one of these parameters and the monitor will reject the signal.

Step 2: Generate Sync Signals Combinationally

Sync signals tell the monitor when to start a new line (hsync) or a new frame (vsync). VGA 640×480 uses negative-polarity syncs — the signal is LOW during the pulse:

always @* begin
    hsync = ~(sx >= HS_STA && sx < HS_END);  // LOW during pixels 655–750
    vsync = ~(sy >= VS_STA && sy < VS_END);  // LOW during lines 489–490
    de    = (sx <= HA_END && sy <= VA_END);   // HIGH only in the 640×480 visible rectangle
end

The de (data enable) signal is what downstream modules use to decide whether to output a pixel color. When de is low, the display ignores whatever color data it receives — that interval is for sync pulses and blanking porches.

Step 3: Advance the Counters

The horizontal counter increments every pixel clock. When it reaches the end of a line (799), it wraps to zero and advances the vertical counter. When the vertical counter reaches the end of a frame (524), it also wraps to zero:

always @(posedge clk_pix) begin
    if (sx == LINE) begin
        sx <= 0;
        sy <= (sy == SCREEN) ? 0 : sy + 1;
    end else begin
        sx <= sx + 1;
    end

    if (rst_pix) begin
        sx <= 0;
        sy <= 0;
    end
end

The reset check is placed after the counter logic inside the same always block. Because it executes last, it overrides the counter — ensuring a clean reset regardless of the current counter state. Placing it before the counter logic would produce identical behavior in synthesis, but placing it inside an if-else with the counter logic would create a priority that might confuse some tools.

The Math Behind the Refresh Rate

The module has no concept of “60 Hz” — the refresh rate is an emergent property of the pixel clock and the timing constants:

QuantityCalculationResult
Pixels per line640 active + 16 front porch + 96 sync + 48 back porch800
Lines per frame480 active + 10 front porch + 2 sync + 33 back porch525
Clocks per frame800 × 525420,000
Frame rate25,000,000 Hz ÷ 420,00059.52 Hz

The pixel clock is the single input that determines all timing. In the FPGA design, 100 MHz is divided by four using a two-bit counter to produce exactly 25 MHz. In simulation, the same division happens identically in Verilog.

Every downstream module — the board background renderer, the letter sprite engine, the color logic — simply reads sx, sy, and de from this generator. They never need to know about blanking intervals or sync pulses. This separation is what makes the display pipeline composable.

When the Counters Lie: Blank Screens and Clock Violations

Wrong pixel clock. If you supply 50 MHz instead of 25 MHz, every timing interval halves. The monitor detects an unsupported horizontal frequency (62.5 kHz instead of 31.25 kHz) and displays nothing — or a rolling, distorted image. There’s no error message. The screen just goes dark.

Counter overflow. The sx register is ten bits wide (range 0–1023), but it only counts to 799. If a reset glitch allowed sx to reach 800+ without wrapping, de would stay low permanently, producing a frame with no visible pixels. The synchronous reset guard prevents this.

Metastability. The pixel clock and the system clock are in different domains. In this design, the pixel clock is derived from the system clock via a simple divider, guaranteeing a fixed phase relationship. If the clocks were independent — from separate oscillators, for example — a clock-domain crossing synchronizer would be required on every signal crossing the boundary. The divider approach eliminates this entire class of bugs.

The skill you’ve acquired isn’t specific to 640×480. Every VESA display mode — 800×600, 1024×768, 1920×1080 — follows the same structure: active region, front porch, sync pulse, back porch, repeated horizontally and vertically. Change the six parameters and the pixel clock, and this same module generates any standard VGA signal. Video timing is arithmetic, not protocol.

We respect your privacy.

← View All Tutorials

Related Projects

    Ask me anything!