Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ the core itself: 352x240p60 (NTSC) by default, with 720x480i60 and 352x288p50
(PAL) selectable by the ARM-side launcher through a DDR control block (see
[DDR contract](rtl/native_video_reader.sv)). There are no video options in the OSD — the
mode and the H/V centering trims are owned by the launcher. Idle video stays
black during frontend handoff; upstream-style snow appears behind the stock
OSD. Native frontend frames take priority over both backgrounds.
black during frontend handoff. Upstream-style snow appears behind the stock
OSD, and whenever Main sets `status[10]` to report that no frontend owns or
is about to take the screen (frontend off, quit, or given up), so a setup
with no frontend and no OSD, such as kiosk mode, still shows snow. Native
frontend frames take priority over both backgrounds.

Native writers must use the v2 magic and publish a live frame counter. The
legacy 320-pixel, no-magic DDR contract is no longer accepted.
Expand Down
1 change: 1 addition & 0 deletions files.qip
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ set_global_assignment -name SYSTEMVERILOG_FILE sys/mister_magik_bootstrap_black.
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_bootstrap_video.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_snow_phase.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_pixel_enable.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_snow_enable.sv
31 changes: 17 additions & 14 deletions menu.sv
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ end

wire FB = status[5];
wire [2:0] led = status[8:6];
// Set by Main while no frontend owns or is about to take the screen.
wire snow_allowed = status[10];

// Pixel clock: CLK_VIDEO = 27.000 MHz (the universal SD video clock).
// ce_pix /4 = 6.75 MHz gives exactly 15734.27 Hz (NTSC, 429-px line) and
Expand Down Expand Up @@ -402,26 +404,27 @@ zaparoo_snow_phase snow_motion (
.new_frame(native_new_frame), .vcount(native_vcount), .phase(snow_phase)
);

// OSD status comes from the HDMI domain. Main disables OSD before handing
// video to the frontend, so startup stays black without a new bus command.
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED_IF_ASYNCHRONOUS" *)
reg [1:0] snow_osd_sync = 0;
always @(posedge CLK_VIDEO) begin
if (RESET | ~vid_locked) begin
snow_sample <= 0;
snow_osd_sync <= 0;
end else begin
if (ce_pix) snow_sample <= snow_random[2:0];
snow_osd_sync <= {snow_osd_sync[0], OSD_STATUS};
end
if (RESET | ~vid_locked) snow_sample <= 0;
else if (ce_pix) snow_sample <= snow_random[2:0];
end

// Black handoff, snow behind stock OSD, or native frontend RGB. All three
// share the same native sync/DE; no video-mode switch is needed.
// Snow shows behind the stock OSD, and whenever Main reports that no frontend
// will draw. Main disables the OSD and leaves status[10] clear before handing
// video to the frontend, so that startup stays black.
wire show_snow;
zaparoo_snow_enable snow_enable (
.clk(CLK_VIDEO), .reset(RESET | ~vid_locked),
.osd_status(OSD_STATUS), .snow_allowed(snow_allowed),
.show_snow(show_snow)
);

// Black handoff, snow, or native frontend RGB. All three share the same
// native sync/DE; no video-mode switch is needed.
zaparoo_bootstrap_video bootstrap_video (
.native_active(native_active),
.native_rgb({native_r, native_g, native_b}),
.show_snow(snow_osd_sync[1]),
.show_snow(show_snow),
.snow_rgb({snow_pixel, snow_pixel, snow_pixel}),
.de_in(native_de), .hs_in(native_hs), .vs_in(native_vs),
.rgb_out({VGA_R, VGA_G, VGA_B}),
Expand Down
3 changes: 2 additions & 1 deletion rtl/zaparoo_bootstrap_video.sv
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Native CRT frames take priority; idle stays black except behind stock OSD.
// Native CRT frames take priority; idle is black unless show_snow is set
// (see zaparoo_snow_enable).
`timescale 1ns/1ps
module zaparoo_bootstrap_video (
input wire native_active,
Expand Down
31 changes: 31 additions & 0 deletions rtl/zaparoo_snow_enable.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Decides when idle video shows snow instead of handoff black. Both inputs
// come from other clock domains and are synchronized here.
// osd_status: the stock OSD is up, which always sits on snow.
// snow_allowed: Main reports that no frontend owns or is about to take the
// screen. Clear at reset, so startup stays black until Main
// says otherwise, and under a Main that never sets it.
`timescale 1ns/1ps
module zaparoo_snow_enable (
input wire clk,
input wire reset,
input wire osd_status,
input wire snow_allowed,
output wire show_snow
);
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED_IF_ASYNCHRONOUS" *)
reg [1:0] osd_sync = 0;
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED_IF_ASYNCHRONOUS" *)
reg [1:0] allowed_sync = 0;
always @(posedge clk) begin
if (reset) begin
osd_sync <= 0;
allowed_sync <= 0;
end
else begin
osd_sync <= {osd_sync[0], osd_status};
allowed_sync <= {allowed_sync[0], snow_allowed};
end
end
assign show_snow = osd_sync[1] | allowed_sync[1];
endmodule
6 changes: 3 additions & 3 deletions tb/bootstrap_video_tb.sv
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ module bootstrap_video_tb;
native_active = 0; #1;
assert(rgb_out == 0) else $fatal(1, "writer stop did not restore black");
show_snow = 1; de_in = 1; #1;
assert(rgb_out == snow_rgb) else $fatal(1, "OSD did not reveal snow");
assert(rgb_out == snow_rgb) else $fatal(1, "show_snow did not reveal snow");
de_in = 0; #1;
assert(rgb_out == 0) else $fatal(1, "snow painted outside DE");
native_active = 1; #1;
assert(rgb_out == native_rgb) else $fatal(1, "snow covered native frontend");
native_active = 0; show_snow = 0; de_in = 1; #1;
assert(rgb_out == 0) else $fatal(1, "OSD close did not restore black");
$display("PASS: black handoff, OSD snow, native CRT priority and timing");
assert(rgb_out == 0) else $fatal(1, "clearing show_snow did not restore black");
$display("PASS: black handoff, snow, native CRT priority and timing");
$finish;
end
endmodule
2 changes: 2 additions & 0 deletions tb/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ run_test bootstrap_video_tb \

run_test snow_phase_tb ../rtl/zaparoo_snow_phase.sv snow_phase_tb.sv

run_test snow_enable_tb ../rtl/zaparoo_snow_enable.sv snow_enable_tb.sv

run_test pixel_enable_tb ../rtl/zaparoo_pixel_enable.sv pixel_enable_tb.sv

run_test scanout_tb \
Expand Down
68 changes: 68 additions & 0 deletions tb/snow_enable_tb.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-or-later
`timescale 1ns/1ps
module snow_enable_tb;
reg clk = 0;
always #5 clk = ~clk;
reg reset = 1;
reg osd_status = 0;
reg snow_allowed = 0;
wire show_snow;
zaparoo_snow_enable dut (.*);

// Two synchronizer stages: an input change is visible after two edges.
task settle;
begin
repeat (2) @(posedge clk);
#1;
end
endtask

initial begin
// Reset wins over both sources, so a video restart begins black.
osd_status = 1; snow_allowed = 1;
repeat (4) begin
@(posedge clk); #1;
assert(show_snow === 1'b0) else $fatal(1, "snow during reset");
end
@(negedge clk); reset = 0; osd_status = 0; snow_allowed = 0;
settle;
assert(show_snow === 1'b0) else $fatal(1, "idle did not start black");

// Every combination: either source reveals snow, neither keeps black.
for (integer i = 0; i < 4; i = i + 1) begin
@(negedge clk); {osd_status, snow_allowed} = i[1:0];
@(posedge clk); #1;
settle;
assert(show_snow === (osd_status | snow_allowed))
else $fatal(1, "snow source mismatch: osd=%0d allowed=%0d", osd_status, snow_allowed);
end

// No frontend and no OSD (kiosk): snow stays up across an OSD cycle.
@(negedge clk); osd_status = 0; snow_allowed = 1;
settle;
assert(show_snow === 1'b1) else $fatal(1, "no snow without frontend or OSD");
@(negedge clk); osd_status = 1;
settle;
@(negedge clk); osd_status = 0;
repeat (4) begin
@(posedge clk); #1;
assert(show_snow === 1'b1) else $fatal(1, "OSD close dropped allowed snow");
end

// A frontend taking the screen returns to black within the sync depth.
@(negedge clk); snow_allowed = 0;
settle;
assert(show_snow === 1'b0) else $fatal(1, "frontend takeover did not restore black");

// One edge after a change the output still holds: no path skips a stage.
@(negedge clk); snow_allowed = 1;
@(posedge clk); #1;
assert(show_snow === 1'b0) else $fatal(1, "snow bypassed the synchronizer");

$display("PASS: snow enable reset, OSD and no-frontend sources, takeover black");
$finish;
end
initial begin
#20000; $fatal(1, "snow enable test timeout");
end
endmodule
Loading