❗ 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:
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 = 0 → active‑HIGH pulse
invert = 1 → active‑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:
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.
❗ 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.svuses 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:
The fix is a one‑line correction:
➡️ Use
invert = 0for 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:
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:
This confirms the displays accept:
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):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 = 0There is often confusion around “positive sync” vs “negative sync” because:
CEA‑861 polarity rules for 480p (VIC 2/3):
Meaning:
How the HDMI core implements polarity
Thus:
invert = 0→ active‑HIGH pulseinvert = 1→ active‑LOW pulseTherefore:
invert = 0invert = 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:
1. Horizontal Timing (858 pixels total)
Horizontal Diagram (scaled)
2. Vertical Timing (525 lines total)
Vertical Diagram (scaled)
3. HSYNC / VSYNC Polarity
Case A —
invert = 0(CEA‑861‑correct, active‑HIGH pulses)Case B —
invert = 1(current behavior, active‑LOW pulses)🛠 The Fix
Change:
to:
📎 Unified Diff Patch (Exact Upstream Line Numbers)
This patch applies cleanly to the current upstream
masterbranch.🧪 Testing Plan
✔ Regression on known‑good displays
Ensure 480p still works on displays that previously accepted it.
✔ Samsung LS57CG952NNXZA (Odyssey Neo G9 57")
✔ Ingnok 15.6" portable monitor
✔ 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:
✅ 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:
Please consider merging this correction so downstream projects (including A2FPGA and others using hdl-util/hdmi) can output standards‑compliant 480p.