Skip to content

Commit 5db804a

Browse files
committed
feat: broadcast-geometry native video with PAL and 480i modes
Implements the FPGA side of docs/native-video-plan.md (all phases; the zaparoo-launcher side comes separately). - PLL output 1 retargeted 27.027027 -> 27.000000 MHz, giving exact NTSC (15734.27 Hz) and PAL (15625.00 Hz) line rates. - native_video_timing rebuilt around per-mode parameter sets: 352x240p60 (Switchres ntsc porches), 720x480i60 (CEA-861, 262+263-line fields with half-line vsync offset on the odd field), 352x288p50 (Switchres pal). Mode and trims latch at the field wrap; offsets clamp to -8..+8 px / -8..+2 lines. Field flips at the start of vblank so the reader's line preload always fetches the parity about to be displayed. - native_video_reader parses DDR control word1: magic 0x5A50 selects the v2 layout (buffers +0x1000/+0x180000, tight stride) and carries mode and h/v offsets; without magic the legacy 320x240 layout is scanned centered with 16-px side bars. word0 == 0 and DDR timeouts now clear frame_ready so the core reverts to the noise pattern instead of scanning a dead buffer. 480i fetches source line 2*line+field as two 180-beat bursts; line FIFO deepened to 1024 words for the interlaced 2-line preload. - menu.sv: OSD video options removed (CONF_STR back to stock), ce_pix divider switches /4 / /2 by mode, VGA_F1 driven by the field bit. - Self-checking iverilog testbenches in tb/ (run via tb/run.sh) verify all mode timings in exact pixel ticks, the half-line interlace (both vsync intervals exactly 262.5 lines), offset clamping, the v2/legacy fetch sequences, double buffering, writer-stop reversion, and timeout recovery. - Readme documents the native video output and the forced_scandoubler / vga_scaler=1 note.
1 parent 3a19e6d commit 5db804a

11 files changed

Lines changed: 1449 additions & 130 deletions

Readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Startup core for MiSTer
22

3+
## Native CRT video (this fork)
4+
5+
This fork drives the analog output with a native 15 kHz signal generated by
6+
the core itself: 352x240p60 (NTSC) by default, with 720x480i60 and 352x288p50
7+
(PAL) selectable by the ARM-side launcher through a DDR control block (see
8+
`docs/native-video-plan.md`). There are no video options in the OSD — the
9+
mode and the H/V centering trims are owned by the launcher; the core shows
10+
its noise pattern until the launcher publishes frames.
11+
12+
Note: `forced_scandoubler` (the "Forced scandoubler" MiSTer.ini setting) is
13+
ignored by this core — the analog output is always 15 kHz. If your VGA output
14+
feeds a 31 kHz-only monitor, set `vga_scaler=1` in MiSTer.ini instead.
15+
316
* **ESC** - Back/Options
417
* **Enter** - OK
518
* **F1** - Cycle Background/Wallpaper

docs/native-video-plan.md

Lines changed: 482 additions & 0 deletions
Large diffs are not rendered by default.

menu.sv

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ assign DDRAM_CLK = clk_sys;
185185
assign CE_PIXEL = ce_pix;
186186

187187
assign VGA_SL = 0;
188-
assign VGA_F1 = 0;
188+
// Field number for 480i: ascal (HDMI) keys deinterlacing off this, and the
189+
// analog csync path passes it through. 0 in the progressive modes.
190+
assign VGA_F1 = native_field;
189191
assign VIDEO_ARX = 0;
190192
assign VIDEO_ARY = 0;
191193
assign VGA_SCALER= 0;
@@ -207,15 +209,12 @@ wire [26:0] act_cnt2 = {~act_cnt[26],act_cnt[25:0]};
207209
assign LED_POWER[0]= FB ? led[2] : act_cnt2[26] ? act_cnt2[25:18] > act_cnt2[7:0] : act_cnt2[25:18] <= act_cnt2[7:0];
208210

209211

210-
`include "build_id.v"
211-
// Image centering options use signed two's-complement ordering with 0 first,
212-
// so the power-on default (status bits = 0) selects the calibrated base timing.
212+
`include "build_id.v"
213+
// No video options here: native video mode and centering trims arrive via
214+
// the DDR control block written by the launcher (see rtl/native_video_reader.sv).
213215
localparam CONF_STR = {
214216
"MENU;UART31250,MIDI;",
215217
"-;",
216-
"O[13:10],H Offset,0,+2,+4,+6,+8,+10,+12,+14,-16,-14,-12,-10,-8,-6,-4,-2;",
217-
"O[17:14],V Offset,0,+1,+2,+3,+4,+5,+6,+7,-8,-7,-6,-5,-4,-3,-2,-1;",
218-
"-;",
219218
"V,v",`BUILD_DATE
220219
};
221220

@@ -348,8 +347,9 @@ always @(posedge clk_sys) begin
348347
end
349348

350349
// DDR clear loop removed: native_video_reader owns DDRAM_* signals.
351-
// When status[9]=0 the reader is held in idle (rd=0, we=0) and DDR is unused;
352-
// when status[9]=1 the reader takes over to fetch the linux-rendered framebuffer.
350+
// The reader polls the launcher's control block once per vblank; until the
351+
// launcher publishes frames it issues a single 64-bit read per frame and the
352+
// core shows the noise pattern.
353353

354354
//////////////////////////// MT32pi //////////////////////////////////
355355

@@ -460,27 +460,28 @@ end
460460

461461
localparam lfsr_n = 63;
462462

463-
wire PAL = status[4];
464463
wire FB = status[5];
465464
wire [2:0] led = status[8:6];
466465

467-
// Pixel clock: CLK_VIDEO = 27.027 MHz; ce_pix /4 = ~6.756 MHz, which gives
468-
// an NTSC-spec 15.734 kHz line rate when fed into native_video_timing
469-
// (H_TOTAL=429). Both the cosine fallback and the FB reader use this ce_pix.
466+
// Pixel clock: CLK_VIDEO = 27.000 MHz (the universal SD video clock).
467+
// ce_pix /4 = 6.75 MHz gives exactly 15734.27 Hz (NTSC, 429-px line) and
468+
// 15625.00 Hz (PAL, 432-px line); the 480i mode runs /2 = 13.5 MHz with an
469+
// 858-px line for the same 15734.27 Hz. Both the cosine fallback and the FB
470+
// reader use this ce_pix.
471+
wire [1:0] native_mode;
470472
reg [1:0] ce_div;
471473
reg ce_pix;
472474
always @(posedge CLK_VIDEO) begin
473475
if (RESET) ce_div <= 2'd0;
474476
else ce_div <= ce_div + 2'd1;
475-
ce_pix <= (ce_div == 2'd0);
477+
ce_pix <= (native_mode == 2'd1) ? ce_div[0] : (ce_div == 2'd0);
476478
end
477479

478480
// Native video timing + DDR reader. Timing outputs (sync, DE, vcount, frame
479481
// edge) are the SINGLE source of truth for VGA scanout in both modes — that's
480-
// what guarantees the CRT sees a clean 15.734 kHz line rate whether we're
481-
// painting cosine noise or reading a Linux-rendered framebuffer.
482-
wire mode_zaparoo = status[9];
483-
482+
// what guarantees the CRT sees a clean 15 kHz line rate whether we're
483+
// painting cosine noise or reading a Linux-rendered framebuffer. Mode and
484+
// centering trims come from the launcher's DDR control block, not the OSD.
484485
wire [7:0] native_r;
485486
wire [7:0] native_g;
486487
wire [7:0] native_b;
@@ -489,6 +490,7 @@ wire native_vs;
489490
wire native_de;
490491
wire [8:0] native_vcount;
491492
wire native_new_frame;
493+
wire native_field;
492494
wire native_active;
493495

494496
native_video_top native_video
@@ -518,17 +520,13 @@ native_video_top native_video
518520
.vga_vblank (),
519521
.vga_vcount (native_vcount),
520522
.vga_new_frame (native_new_frame),
521-
.enable (mode_zaparoo),
522-
.active (native_active),
523-
524-
// H offset is a 4-bit signed OSD value doubled into 2-pixel steps.
525-
// V offset is a normal 4-bit signed OSD value in 1-line steps.
526-
.h_offset ($signed({status[13:10], 1'b0})),
527-
.v_offset ($signed(status[17:14]))
523+
.vga_mode (native_mode),
524+
.vga_field (native_field),
525+
.active (native_active)
528526
);
529527

530-
// Cosine + LFSR fallback noise pattern, painted into the 320x240 active area
531-
// of the shared native timing. vvc steps once per frame; the LFSR walks every
528+
// Cosine + LFSR fallback noise pattern, painted into the active area of the
529+
// shared native timing (352x240 when no launcher is publishing frames). vvc steps once per frame; the LFSR walks every
532530
// pixel; cos LUT is indexed by vvc + vcount so the pattern shifts vertically
533531
// over time. Outside the active area we drive black to keep sync clean.
534532
reg [9:0] vvc;
@@ -550,11 +548,12 @@ cos cos(vvc + {native_vcount, 2'b00}, cos_out);
550548

551549
wire [7:0] comp_v = (cos_g >= rnd_c) ? {cos_g - rnd_c, 2'b00} : 8'd0;
552550

553-
// Mode A (default): cosine pattern paints into the native active area.
554-
// Mode B (status[9]=1, frame ready): DDR-read RGB replaces the cosine pattern.
551+
// Default: cosine pattern paints into the native active area. Once the
552+
// launcher publishes frames (valid control block, advancing counter), the
553+
// DDR-read RGB replaces the cosine pattern; it reverts when the writer stops.
555554
// Sync/DE come from the same native timing in both cases — the CRT sees one
556-
// continuous, NTSC-spec signal regardless of which RGB source is selected.
557-
wire use_native = mode_zaparoo & native_active;
555+
// continuous, broadcast-spec signal regardless of which RGB source is selected.
556+
wire use_native = native_active;
558557

559558
assign VGA_DE = native_de;
560559
assign VGA_HS = native_hs;

0 commit comments

Comments
 (0)