Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions