Skip to content

480p (CEA‑861 VIC 2/3) Sync Polarity Incorrect — HDMI Core Outputs #55

@BrentRector

Description

@BrentRector

❗ 480p (CEA‑861 VIC 2/3) Sync Polarity Incorrect — HDMI Core Outputs Non‑Compliant Timing

Summary

While debugging HDMI compatibility issues in the a2fpga/a2fpga_core project, I traced the root cause to the upstream HDMI core from this repository:

➡️ https://github.com/hdl-util/hdmi

The 480p (720×480p @ 59.94/60 Hz) mode in src/hdmi.sv uses incorrect sync polarity.
CEA‑861 requires positive‑polarity HSYNC and VSYNC for 480p (VIC 2/3), but the HDMI core outputs negative‑polarity syncs.

This causes many modern HDMI displays to reject the signal entirely, even though:

  • Their EDIDs explicitly list 480p as supported
  • They accept 480p from PC GPUs
  • They accept 480‑line DVI modes (which ignore polarity)

The fix is a one‑line correction:
➡️ Use invert = 0 for 480p.


🔍 Investigation Summary

This issue was discovered in the A2FPGA project, but the faulty logic is directly inherited from the upstream hdl-util/hdmi core.

1. Displays do advertise 480p support (EDID)

Example from Samsung LS57CG952NNXZA:

720 x 480p at 60Hz - EDTV (16:9, 32:27)
720 x 480p at 60Hz - EDTV (16:9, 32:27)

2. Windows GPU 480p works

Selecting 480p (720×480) in NVIDIA Control Panel produces a stable image.

3. DVI modes work (A2DVI @ 720×480 and 640×480)

Both tested monitors accept:

  • 720×480 DVI
  • 640×480 DVI

This confirms the displays accept:

  • 480‑line timings
  • Non‑CEA PC timings
  • DVI‑style syncs (which ignore polarity)

4. Only HDMI 480p from the hdl-util core fails

Both displays show no signal when driven by the HDMI core’s 480p mode.

5. Timing comparison shows only one mismatch: sync polarity

The upstream 480p block in src/hdmi.sv (lines 84–96):

        2, 3:
        begin
            assign frame_width = 858;
            assign frame_height = 525;
            assign screen_width = 720;
            assign screen_height = 480;
            assign hsync_pulse_start = 16;
            assign hsync_pulse_size = 62;
            assign vsync_pulse_start = 9;
            assign vsync_pulse_size = 6;
            assign invert = 1;
        end

Everything matches CEA‑861 except:

invert = 1 → active‑LOW sync pulses

✔ CEA‑861 requires active‑HIGH sync pulses for 480p

This is the root cause of the failure.


🧠 Technical Clarification: Why 480p Requires invert = 0

There is often confusion around “positive sync” vs “negative sync” because:

  • VGA/PC timing defines polarity relative to blanking
  • CEA‑861/HDMI defines polarity relative to the pulse itself

CEA‑861 polarity rules for 480p (VIC 2/3):

  • HSYNC polarity: +
  • VSYNC polarity: +

Meaning:

  • The sync pulse must be HIGH
  • The blanking interval is LOW

How the HDMI core implements polarity

hsync <= invert ^ (cx >= start && cx < start+size);

Thus:

  • invert = 0active‑HIGH pulse
  • invert = 1active‑LOW pulse

Therefore:

  • CEA‑861 positive polarity = invert = 0
  • Current implementation (invert = 1) = non‑compliant

🧪 Empirical Confirmation

Windows → HDMI 480p → Samsung

Works (active‑HIGH syncs)

A2DVI → 720×480 → Samsung

Works (DVI ignores polarity)

A2FPGA → HDMI 480p → Samsung

Fails (active‑LOW syncs from hdl-util core)

The only difference is sync polarity.

Modern displays validate CEA‑861 strictly for SD/EDTV modes like 480p.


🎨 480p (CEA‑861 VIC 2/3) Color Timing Diagram

Legend:

  • 🟩 Active Video
  • 🟦 Front Porch
  • 🟥 Sync Pulse
  • 🟨 Back Porch

1. Horizontal Timing (858 pixels total)

Region Pixels
Active Video 720
Front Porch 16
Sync Pulse 62
Back Porch 60

Horizontal Diagram (scaled)

🟩 (720 px ACTIVE VIDEO)
🟦 (16 px FRONT PORCH)
🟥 (62 px SYNC)
🟨 (60 px BACK PORCH)

2. Vertical Timing (525 lines total)

Region Lines
Active Video 480
Front Porch 9
Sync Pulse 6
Back Porch 30

Vertical Diagram (scaled)

🟩 (480 lines ACTIVE VIDEO)
🟦 (9 lines FRONT PORCH)
🟥 (6 lines VSYNC PULSE)
🟨 (30 lines BACK PORCH)

3. HSYNC / VSYNC Polarity

Case A — invert = 0 (CEA‑861‑correct, active‑HIGH pulses)

HSYNC:  0 0 0 1 1 1 0 0 0
              ↑ HIGH pulse

VSYNC:  0 0 0 1 1 1 0 0 0
              ↑ HIGH pulse

Case B — invert = 1 (current behavior, active‑LOW pulses)

HSYNC:  1 1 1 0 0 0 1 1 1
              ↑ LOW pulse

VSYNC:  1 1 1 0 0 0 1 1 1
              ↑ LOW pulse

🛠 The Fix

Change:

assign invert = 1;

to:

// CEA‑861 480p (VIC 2/3) requires active‑HIGH sync pulses.
// The previous implementation inverted the syncs, producing
// negative‑polarity hsync/vsync. Many modern HDMI sinks reject
// 480p when sync polarity is incorrect.
assign invert = 0;

📎 Unified Diff Patch (Exact Upstream Line Numbers)

This patch applies cleanly to the current upstream master branch.

diff --git a/src/hdmi.sv b/src/hdmi.sv
index 3e3f3c1..0000000 100644
--- a/src/hdmi.sv
+++ b/src/hdmi.sv
@@ -84,7 +84,13 @@ generate
             assign hsync_pulse_start = 16;
             assign hsync_pulse_size = 62;
             assign vsync_pulse_start = 9;
             assign vsync_pulse_size = 6;
-            assign invert = 1;
+            // CEA‑861 480p (VIC 2/3) requires POSITIVE sync polarity.
+            // The previous implementation inverted the syncs, producing
+            // negative‑polarity hsync/vsync. Many modern HDMI sinks reject
+            // 480p when sync polarity is incorrect, even though they accept
+            // the same resolution from GPUs or via DVI-style signaling.
+            // Use non-inverted syncs to be fully CEA-compliant.
+            assign invert = 0;
         end

🧪 Testing Plan

✔ Regression on known‑good displays

Ensure 480p still works on displays that previously accepted it.

✔ Samsung LS57CG952NNXZA (Odyssey Neo G9 57")

  • Before patch: No signal
  • After patch: Stable 480p

✔ Ingnok 15.6" portable monitor

  • Before patch: No signal
  • After patch: Stable 480p

✔ Cross‑check with PC GPU

A2FPGA 480p should match NVIDIA 480p polarity.

✔ Verify other modes

640×480, 720p, 1080p unaffected.


🚀 Optional Future Enhancements

Not required for this fix, but beneficial:

  • Parameterize timing so downstream projects derive timing from a single source
  • Add assertions to validate CEA‑861 compliance per VIC
  • Add a “strict CEA mode”
  • Add automated timing validation tests

✅ Final Notes

This issue was discovered in a2fpga/a2fpga_core, but the incorrect sync polarity originates in the upstream HDMI core from this repository.

The fix is:

  • Minimal
  • Safe
  • Fully CEA‑861 compliant
  • Required for compatibility with modern HDMI displays

Please consider merging this correction so downstream projects (including A2FPGA and others using hdl-util/hdmi) can output standards‑compliant 480p.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions