Skip to content

Commit ef38ec1

Browse files
committed
fix: drive both modes from the native NTSC timing for clean CRT sync
The previous commit kept the original cosine timing block (H_TOTAL=638, forced_scandoubler-conditional ce_pix) but switched the PLL to 27.027 MHz. That produced a line rate of ~42 kHz scandoubled or ~21 kHz interlaced — nothing standard, so a CRT could not lock on the analog VGA output. This commit makes native_video_timing the single source of truth for sync and DE in both modes: - ce_pix is now a fixed /4 divider of CLK_VIDEO -> 6.756 MHz pixel rate -> 15.749 kHz line rate (within 0.1% of NTSC 15.734 kHz). - VGA_HS/VS/DE always come from native_video_top regardless of status[9]. - The cosine + LFSR fallback paints into the active area only; outside DE we drive black so sync stays clean. - vvc steps on native_new_frame instead of the old vc wrap; cos LUT is indexed by vcount from the shared timing. - native_video_top exposes vcount and new_frame so the cosine path can reuse the same vertical position the FB reader sees. The cosine pattern still renders (it was always intended as fallback noise), but now at NTSC-spec 320x240 timing instead of broken 27 MHz / 638-cycle timing. Sync locks on real CRTs. PAL parametrisation is deferred — native_video_timing is currently NTSC-only. forced_scandoubler is still wired from hps_io but unused; preserve it as a known placeholder for the eventual PAL/scandoubler follow-up.
1 parent 50bf5cc commit ef38ec1

2 files changed

Lines changed: 59 additions & 85 deletions

File tree

menu.sv

Lines changed: 49 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -459,77 +459,21 @@ wire PAL = status[4];
459459
wire FB = status[5];
460460
wire [2:0] led = status[8:6];
461461

462-
reg [9:0] hc;
463-
reg [9:0] vc;
464-
reg [9:0] vvc;
465-
466-
reg [lfsr_n:0] rnd_reg;
467-
wire [lfsr_n:0] rnd;
468-
469-
wire [5:0] rnd_c = {rnd_reg[0],rnd_reg[1],rnd_reg[2],rnd_reg[2],rnd_reg[2],rnd_reg[2]};
470-
471-
lfsr #(lfsr_n) random(rnd);
472-
473-
always @(posedge CLK_VIDEO) begin
474-
if(forced_scandoubler) ce_pix <= 1;
475-
else ce_pix <= ~ce_pix;
476-
477-
if(ce_pix) begin
478-
if(hc == 637) begin
479-
hc <= 0;
480-
if(vc == (PAL ? (forced_scandoubler ? 623 : 311) : (forced_scandoubler ? 523 : 261))) begin
481-
vc <= 0;
482-
vvc <= vvc + 9'd6;
483-
end else begin
484-
vc <= vc + 1'd1;
485-
end
486-
end else begin
487-
hc <= hc + 1'd1;
488-
end
489-
490-
rnd_reg <= rnd;
491-
end
492-
end
493-
494-
reg HBlank;
495-
reg HSync;
496-
reg VBlank;
497-
reg VSync;
498-
499-
reg ce_pix;
462+
// Pixel clock: CLK_VIDEO = 27.027 MHz; ce_pix /4 = ~6.756 MHz, which gives
463+
// an NTSC-spec 15.734 kHz line rate when fed into native_video_timing
464+
// (H_TOTAL=429). Both the cosine fallback and the FB reader use this ce_pix.
465+
reg [1:0] ce_div;
466+
reg ce_pix;
500467
always @(posedge CLK_VIDEO) begin
501-
if (hc == 529) HBlank <= 1;
502-
else if (hc == 0) HBlank <= 0;
503-
504-
if (hc == 544) begin
505-
HSync <= 1;
506-
507-
if(PAL) begin
508-
if(vc == (forced_scandoubler ? 609 : 304)) VSync <= 1;
509-
else if (vc == (forced_scandoubler ? 617 : 308)) VSync <= 0;
510-
511-
if(vc == (forced_scandoubler ? 601 : 300)) VBlank <= 1;
512-
else if (vc == 0) VBlank <= 0;
513-
end
514-
else begin
515-
if(vc == (forced_scandoubler ? 490 : 245)) VSync <= 1;
516-
else if (vc == (forced_scandoubler ? 496 : 248)) VSync <= 0;
517-
518-
if(vc == (forced_scandoubler ? 480 : 240)) VBlank <= 1;
519-
else if (vc == 0) VBlank <= 0;
520-
end
521-
end
522-
523-
if (hc == 590) HSync <= 0;
468+
if (RESET) ce_div <= 2'd0;
469+
else ce_div <= ce_div + 2'd1;
470+
ce_pix <= (ce_div == 2'd0);
524471
end
525472

526-
reg [7:0] cos_out;
527-
wire [5:0] cos_g = cos_out[7:3]+6'd32;
528-
cos cos(vvc + {vc>>forced_scandoubler, 2'b00}, cos_out);
529-
530-
wire [7:0] comp_v = (cos_g >= rnd_c) ? {cos_g - rnd_c, 2'b00} : 8'd0;
531-
532-
// Runtime FB-mode gate driven by the HPS-side launcher via status[9].
473+
// Native video timing + DDR reader. Timing outputs (sync, DE, vcount, frame
474+
// edge) are the SINGLE source of truth for VGA scanout in both modes — that's
475+
// what guarantees the CRT sees a clean 15.734 kHz line rate whether we're
476+
// painting cosine noise or reading a Linux-rendered framebuffer.
533477
wire mode_zaparoo = status[9];
534478

535479
wire [7:0] native_r;
@@ -538,6 +482,8 @@ wire [7:0] native_b;
538482
wire native_hs;
539483
wire native_vs;
540484
wire native_de;
485+
wire [8:0] native_vcount;
486+
wire native_new_frame;
541487
wire native_active;
542488

543489
native_video_top native_video
@@ -565,22 +511,46 @@ native_video_top native_video
565511
.vga_de (native_de),
566512
.vga_hblank (),
567513
.vga_vblank (),
514+
.vga_vcount (native_vcount),
515+
.vga_new_frame (native_new_frame),
568516
.enable (mode_zaparoo),
569517
.active (native_active)
570518
);
571519

572-
// Mode A (default): cosine+LFSR pattern drives RGB and the original PAL/NTSC
573-
// scandoubler timing drives sync/DE. HDMI wallpaper compositor runs unchanged.
574-
// Mode B (status[9]=1, frame ready): native_video_top drives RGB+sync from the
575-
// linux-rendered 320x240 RGBX8888 buffer in DDR. Falls back to cosine until the
576-
// first frame is loaded so the screen is never undriven.
520+
// Cosine + LFSR fallback noise pattern, painted into the 320x240 active area
521+
// of the shared native timing. vvc steps once per frame; the LFSR walks every
522+
// pixel; cos LUT is indexed by vvc + vcount so the pattern shifts vertically
523+
// over time. Outside the active area we drive black to keep sync clean.
524+
reg [9:0] vvc;
525+
reg [lfsr_n:0] rnd_reg;
526+
wire [lfsr_n:0] rnd;
527+
wire [5:0] rnd_c = {rnd_reg[0],rnd_reg[1],rnd_reg[2],rnd_reg[2],rnd_reg[2],rnd_reg[2]};
528+
529+
lfsr #(lfsr_n) random(rnd);
530+
531+
always @(posedge CLK_VIDEO) begin
532+
if (RESET) vvc <= 10'd0;
533+
else if (native_new_frame) vvc <= vvc + 10'd6;
534+
if (ce_pix) rnd_reg <= rnd;
535+
end
536+
537+
reg [7:0] cos_out;
538+
wire [5:0] cos_g = cos_out[7:3] + 6'd32;
539+
cos cos(vvc + {native_vcount, 2'b00}, cos_out);
540+
541+
wire [7:0] comp_v = (cos_g >= rnd_c) ? {cos_g - rnd_c, 2'b00} : 8'd0;
542+
543+
// Mode A (default): cosine pattern paints into the native active area.
544+
// Mode B (status[9]=1, frame ready): DDR-read RGB replaces the cosine pattern.
545+
// Sync/DE come from the same native timing in both cases — the CRT sees one
546+
// continuous, NTSC-spec signal regardless of which RGB source is selected.
577547
wire use_native = mode_zaparoo & native_active;
578548

579-
assign VGA_DE = use_native ? native_de : ~(HBlank | VBlank);
580-
assign VGA_HS = use_native ? native_hs : HSync;
581-
assign VGA_VS = use_native ? native_vs : VSync;
582-
assign VGA_R = use_native ? native_r : comp_v;
583-
assign VGA_G = use_native ? native_g : comp_v;
584-
assign VGA_B = use_native ? native_b : comp_v;
549+
assign VGA_DE = native_de;
550+
assign VGA_HS = native_hs;
551+
assign VGA_VS = native_vs;
552+
assign VGA_R = use_native ? native_r : (native_de ? comp_v : 8'd0);
553+
assign VGA_G = use_native ? native_g : (native_de ? comp_v : 8'd0);
554+
assign VGA_B = use_native ? native_b : (native_de ? comp_v : 8'd0);
585555

586556
endmodule

rtl/native_video_top.sv

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ module native_video_top
2525
output wire vga_de,
2626
output wire vga_hblank,
2727
output wire vga_vblank,
28+
output wire [8:0] vga_vcount,
29+
output wire vga_new_frame,
2830

2931
input wire enable,
3032
output wire active
@@ -86,11 +88,13 @@ native_video_reader reader
8688
.frame_ready (frame_ready)
8789
);
8890

89-
assign vga_hs = tim_hs;
90-
assign vga_vs = tim_vs;
91-
assign vga_de = tim_de;
92-
assign vga_hblank = tim_hblank;
93-
assign vga_vblank = tim_vblank;
94-
assign active = enable & frame_ready;
91+
assign vga_hs = tim_hs;
92+
assign vga_vs = tim_vs;
93+
assign vga_de = tim_de;
94+
assign vga_hblank = tim_hblank;
95+
assign vga_vblank = tim_vblank;
96+
assign vga_vcount = tim_vcount;
97+
assign vga_new_frame = tim_new_frame;
98+
assign active = enable & frame_ready;
9599

96100
endmodule

0 commit comments

Comments
 (0)