Skip to content

Commit 9f1c2d6

Browse files
committed
fix: generate 27.000 MHz from a dedicated video PLL
The fitter rejected 27.000000 MHz on the shared PLL: all outputs of one PLL divide a common VCO, and lcm(100 MHz clk_sys, 27 MHz) = 2700 MHz is beyond the Cyclone V's 600-1600 MHz VCO range. 27.027027 MHz (1000/37) is exactly the closest sharable frequency, which is why stock MiSTer uses it. - Revert rtl/pll/pll_0002.v to stock; its 27.027 MHz output is now unconnected. - Add rtl/pll_video.v (+ pll_video_0002.v, qip): dedicated PLL whose sole output is exact 27.000000 MHz (VCO 1350 MHz = 50 x 27, C = 50); CLK_VIDEO comes from it, and the native video path holds in reset until it locks. - Add menu.sdc declaring the video clock asynchronous to all other clocks. It was absent from sys_top.sdc's exclusive clock groups, so TimeQuest analyzed the two-flop synchronizer and line-FIFO crossings as related paths (worst slack -10.4 ns, TNS -1529). All crossings are designed CDC structures. With the constraint, the full Quartus 17.0.2 compile meets timing on every domain (worst setup slack +0.53 ns, TNS 0), which the previous 27.027 MHz baseline did not (-4.8 ns, TNS -426). - Document the shared-VCO constraint in docs/native-video-plan.md.
1 parent 23bcb88 commit 9f1c2d6

8 files changed

Lines changed: 163 additions & 10 deletions

File tree

docs/native-video-plan.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,17 @@ geometry (section 4) plus safe-area UI rules (section 6).
145145

146146
## 4. Target timings
147147

148-
Everything derives from **one PLL change**: output 1 of `rtl/pll/pll_0002.v`
149-
goes from 27.027027 MHz to **27.000000 MHz** — the universal SD video clock
150-
(it is exactly 1716 × NTSC line rate and 1728 × PAL line rate).
148+
Everything derives from one clock change: CLK_VIDEO goes from 27.027027 MHz
149+
to **27.000000 MHz** — the universal SD video clock (it is exactly 1716 ×
150+
NTSC line rate and 1728 × PAL line rate).
151+
152+
> **Implementation note (found at fit time):** 27.000 MHz cannot come from
153+
> the existing PLL. All outputs of one PLL divide a shared VCO, and
154+
> lcm(100 MHz clk_sys, 27 MHz) = 2700 MHz exceeds the Cyclone V's
155+
> 600–1600 MHz VCO range — 27.027027 (1000 MHz / 37) is precisely the
156+
> closest sharable frequency, which is why stock MiSTer uses it. The fix is
157+
> a dedicated video PLL (`rtl/pll_video.v`, VCO 1350 MHz = 50 × 27, C = 50)
158+
> whose sole output drives CLK_VIDEO; `pll_0002.v` stays stock.
151159
152160
| Mode | ce_pix | H total | H active / FP / sync / BP (px) | V total | V active / FP / sync / BP (lines) | Line rate | Refresh |
153161
|---|---|---|---|---|---|---|---|
@@ -348,9 +356,12 @@ These are as much a part of the fix as the RTL — geometry alone doesn't solve
348356

349357
FPGA (this repo):
350358

351-
1. **`rtl/pll/pll_0002.v`**: `output_clock_frequency1` 27.027027 MHz →
352-
`27.000000 MHz`. (Same single-line style as the earlier 20→27.027 change;
353-
no other PLL params move.)
359+
1. ~~`rtl/pll/pll_0002.v`: `output_clock_frequency1` 27.027027 MHz →
360+
`27.000000 MHz`.~~ Superseded: the shared PLL cannot fit 27.000 MHz (see
361+
the implementation note in §4). Instead `pll_0002.v` stays stock and a
362+
new dedicated `rtl/pll_video.v` (+ `rtl/pll_video/pll_video_0002.v`,
363+
`rtl/pll_video.qip`) generates CLK_VIDEO = 27.000000 MHz; menu.sv holds
364+
the native video path in reset until it locks.
354365
2. **`rtl/native_video_timing.sv`**: mode-0 constants — H 352/12/32/33,
355366
V 240/3/3/16. Structure the constants as per-mode parameter sets selected
356367
by a `mode` input (tied to 0 until Phases B/C) so later modes are additive.

files.qip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
set_global_assignment -name QIP_FILE rtl/pll_video.qip
2+
set_global_assignment -name SDC_FILE menu.sdc
13
set_global_assignment -name SYSTEMVERILOG_FILE rtl/sdram.sv
24
set_global_assignment -name VERILOG_FILE rtl/lfsr.v
35
set_global_assignment -name SYSTEMVERILOG_FILE rtl/cos.sv

menu.sdc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Core-level timing constraints (processed after sys/sys_top.sdc).
2+
3+
# CLK_VIDEO comes from a dedicated PLL (rtl/pll_video.v) and is asynchronous
4+
# to every other clock in the design: all crossings into and out of the video
5+
# domain go through two-flop synchronizers or the line FIFO's dual-clock
6+
# logic (rtl/native_video_reader.sv, rtl/native_video_top.sv). Without this
7+
# group, derive_pll_clocks leaves the 27 MHz output related to the other
8+
# clocks (shared 50 MHz reference, and absent from sys_top.sdc's exclusive
9+
# groups), and the fitter tries to close those CDC paths against a ~1 ns
10+
# edge relationship.
11+
set_clock_groups -asynchronous \
12+
-group [get_clocks { *|pll_video|pll_video_inst|altera_pll_i|*[*].*|divclk}]

menu.sv

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,21 @@ pll pll
237237
.refclk(CLK_50M),
238238
.rst(0),
239239
.outclk_0(clk_sys),
240-
.outclk_1(CLK_VIDEO),
240+
.outclk_1(), // stock 27.027 MHz output, unused (see pll_video)
241241
.locked(locked)
242242
);
243243

244+
// Exact 27.000000 MHz video clock from its own PLL: 27 MHz can't share a
245+
// VCO with the 100 MHz clk_sys (lcm = 2700 MHz, above the VCO ceiling).
246+
wire vid_locked;
247+
pll_video pll_video
248+
(
249+
.refclk(CLK_50M),
250+
.rst(0),
251+
.outclk_0(CLK_VIDEO),
252+
.locked(vid_locked)
253+
);
254+
244255

245256
///////////////////// SDRAM ///////////////////
246257
//
@@ -472,7 +483,7 @@ wire [1:0] native_mode;
472483
reg [1:0] ce_div;
473484
reg ce_pix;
474485
always @(posedge CLK_VIDEO) begin
475-
if (RESET) ce_div <= 2'd0;
486+
if (RESET | ~vid_locked) ce_div <= 2'd0;
476487
else ce_div <= ce_div + 2'd1;
477488
ce_pix <= (native_mode == 2'd1) ? ce_div[0] : (ce_div == 2'd0);
478489
end
@@ -498,7 +509,7 @@ native_video_top native_video
498509
.clk_sys (clk_sys),
499510
.clk_vid (CLK_VIDEO),
500511
.ce_pix (ce_pix),
501-
.reset (RESET),
512+
.reset (RESET | ~vid_locked),
502513

503514
.ddr_busy (DDRAM_BUSY),
504515
.ddr_burstcnt (DDRAM_BURSTCNT),

rtl/pll/pll_0002.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ module pll_0002(
2525
.output_clock_frequency0("100.000000 MHz"),
2626
.phase_shift0("0 ps"),
2727
.duty_cycle0(50),
28-
.output_clock_frequency1("27.000000 MHz"),
28+
.output_clock_frequency1("27027027 Hz"),
2929
.phase_shift1("0 ps"),
3030
.duty_cycle1(50),
3131
.output_clock_frequency2("0 MHz"),

rtl/pll_video.qip

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) "pll_video.v"]
2+
set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) "pll_video/pll_video_0002.v"]
3+
4+
set_instance_assignment -name PLL_COMPENSATION_MODE DIRECT -to "*pll_video_0002*|altera_pll:altera_pll_i*|*"
5+
set_instance_assignment -name PLL_AUTO_RESET ON -to "*pll_video_0002*|altera_pll:altera_pll_i*|*"
6+
set_instance_assignment -name PLL_BANDWIDTH_PRESET AUTO -to "*pll_video_0002*|altera_pll:altera_pll_i*|*"

rtl/pll_video.v

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Dedicated video PLL: exact 27.000000 MHz for SD CRT timing.
2+
//
3+
// This cannot come from the main PLL: every output of a PLL divides the
4+
// same VCO, and the smallest common multiple of 100 MHz (clk_sys) and
5+
// 27 MHz is 2700 MHz — outside the Cyclone V's 600-1600 MHz VCO range.
6+
// (That constraint is why stock MiSTer uses 27.027027 MHz: 1000 MHz / 37
7+
// is the closest to 27 MHz a VCO shared with 100 MHz can reach.)
8+
// Standalone, 27 MHz is exact: VCO = 50 MHz x 27 = 1350 MHz, C = 50.
9+
10+
`timescale 1 ps / 1 ps
11+
module pll_video (
12+
input wire refclk, // refclk.clk
13+
input wire rst, // reset.reset
14+
output wire outclk_0, // outclk0.clk
15+
output wire locked // locked.export
16+
);
17+
18+
pll_video_0002 pll_video_inst (
19+
.refclk (refclk), // refclk.clk
20+
.rst (rst), // reset.reset
21+
.outclk_0 (outclk_0), // outclk0.clk
22+
.locked (locked) // locked.export
23+
);
24+
25+
endmodule

rtl/pll_video/pll_video_0002.v

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
`timescale 1ns/10ps
2+
module pll_video_0002(
3+
4+
// interface 'refclk'
5+
input wire refclk,
6+
7+
// interface 'reset'
8+
input wire rst,
9+
10+
// interface 'outclk0'
11+
output wire outclk_0,
12+
13+
// interface 'locked'
14+
output wire locked
15+
);
16+
17+
altera_pll #(
18+
.fractional_vco_multiplier("false"),
19+
.reference_clock_frequency("50.0 MHz"),
20+
.operation_mode("direct"),
21+
.number_of_clocks(1),
22+
.output_clock_frequency0("27.000000 MHz"),
23+
.phase_shift0("0 ps"),
24+
.duty_cycle0(50),
25+
.output_clock_frequency1("0 MHz"),
26+
.phase_shift1("0 ps"),
27+
.duty_cycle1(50),
28+
.output_clock_frequency2("0 MHz"),
29+
.phase_shift2("0 ps"),
30+
.duty_cycle2(50),
31+
.output_clock_frequency3("0 MHz"),
32+
.phase_shift3("0 ps"),
33+
.duty_cycle3(50),
34+
.output_clock_frequency4("0 MHz"),
35+
.phase_shift4("0 ps"),
36+
.duty_cycle4(50),
37+
.output_clock_frequency5("0 MHz"),
38+
.phase_shift5("0 ps"),
39+
.duty_cycle5(50),
40+
.output_clock_frequency6("0 MHz"),
41+
.phase_shift6("0 ps"),
42+
.duty_cycle6(50),
43+
.output_clock_frequency7("0 MHz"),
44+
.phase_shift7("0 ps"),
45+
.duty_cycle7(50),
46+
.output_clock_frequency8("0 MHz"),
47+
.phase_shift8("0 ps"),
48+
.duty_cycle8(50),
49+
.output_clock_frequency9("0 MHz"),
50+
.phase_shift9("0 ps"),
51+
.duty_cycle9(50),
52+
.output_clock_frequency10("0 MHz"),
53+
.phase_shift10("0 ps"),
54+
.duty_cycle10(50),
55+
.output_clock_frequency11("0 MHz"),
56+
.phase_shift11("0 ps"),
57+
.duty_cycle11(50),
58+
.output_clock_frequency12("0 MHz"),
59+
.phase_shift12("0 ps"),
60+
.duty_cycle12(50),
61+
.output_clock_frequency13("0 MHz"),
62+
.phase_shift13("0 ps"),
63+
.duty_cycle13(50),
64+
.output_clock_frequency14("0 MHz"),
65+
.phase_shift14("0 ps"),
66+
.duty_cycle14(50),
67+
.output_clock_frequency15("0 MHz"),
68+
.phase_shift15("0 ps"),
69+
.duty_cycle15(50),
70+
.output_clock_frequency16("0 MHz"),
71+
.phase_shift16("0 ps"),
72+
.duty_cycle16(50),
73+
.output_clock_frequency17("0 MHz"),
74+
.phase_shift17("0 ps"),
75+
.duty_cycle17(50),
76+
.pll_type("General"),
77+
.pll_subtype("General")
78+
) altera_pll_i (
79+
.rst (rst),
80+
.outclk (outclk_0),
81+
.locked (locked),
82+
.fboutclk ( ),
83+
.fbclk (1'b0),
84+
.refclk (refclk)
85+
);
86+
endmodule

0 commit comments

Comments
 (0)