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
29 changes: 18 additions & 11 deletions rtl/native_video_reader.sv
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//
// DDR contract v2 (one 64-bit beat at 0x3A000000, read each vblank):
// word0 [31:0]: (frame_counter << 2) | active_buffer; 0 = writer stopped
// word1 [63:32]: [31:16] magic 0x5A50 ("ZP")
// [15:8] h_offset, signed pixels (+ = right)
// [7:4] v_offset, signed lines (+ = down)
// [3:0] mode: 0 = 352x240p60, 1 = 720x480i60, 2 = 352x288p50
// word1 [63:32]: [31:16] magic, [15:8] signed h_offset (+ = right)
// 0x5A50 legacy: [7:4] signed v_offset, [3:0] mode
// 0x5A51 extended: [7:2] signed v_offset, [1:0] mode
// mode: 0 = 352x240p60, 1 = 720x480i60, 2 = 352x288p50 (+v = down)
// 0x3A001000: buffer 0 0x3A180000: buffer 1 (tight stride, width*4 B)
//
// The magic is mandatory, and a block is only painted once the writer has
Expand Down Expand Up @@ -49,7 +49,7 @@ module native_video_reader
// domain; the timing module latches them at the field wrap.
output reg [1:0] mode_out,
output reg signed [7:0] h_offset_out,
output reg signed [3:0] v_offset_out,
output reg signed [5:0] v_offset_out,

output reg [7:0] r_out,
output reg [7:0] g_out,
Expand Down Expand Up @@ -149,8 +149,14 @@ reg [8:0] scan_lines;
reg scan_interlaced;
reg two_bursts;

wire magic_ok = (ctrl_word1[31:16] == MAGIC_V2);
wire [1:0] ctrl_mode = (ctrl_word1[3:0] > 4'd2) ? 2'd0 : ctrl_word1[1:0];
wire extended = (ctrl_word1[31:16] == 16'h5A51);
wire magic_ok = extended || (ctrl_word1[31:16] == MAGIC_V2);
wire [3:0] raw_mode = extended ? {2'b00, ctrl_word1[1:0]} : ctrl_word1[3:0];
wire [1:0] ctrl_mode = (raw_mode > 4'd2) ? 2'd0 : raw_mode[1:0];
wire signed [7:0] ctrl_h = $signed(ctrl_word1[15:8]);
wire signed [3:0] legacy_v = $signed(ctrl_word1[7:4]);
// Preserve legacy clamp semantics while extended writers use wider porches.
wire signed [7:0] legacy_h = (ctrl_h > 8'sd8) ? 8'sd8 : (ctrl_h < -8'sd8) ? -8'sd8 : ctrl_h;

// 480i: source line = displayed line * 2 + field, from one progressive frame.
wire [8:0] src_line = scan_interlaced ? ({cur_line[7:0], 1'b0} + {8'd0, field_ddr}) : cur_line;
Expand Down Expand Up @@ -184,7 +190,7 @@ always @(posedge ddr_clk) begin
fifo_aclr_cnt <= 4'd0;
mode_out <= 2'd0;
h_offset_out <= 8'sd0;
v_offset_out <= 4'sd0;
v_offset_out <= 6'sd0;
line_words <= 9'd160;
scan_lines <= 9'd240;
scan_interlaced <= 1'b0;
Expand Down Expand Up @@ -247,13 +253,14 @@ always @(posedge ddr_clk) begin
prev_frame_counter <= 30'd0;
mode_out <= 2'd0;
h_offset_out <= 8'sd0;
v_offset_out <= 4'sd0;
v_offset_out <= 6'sd0;
state <= ST_IDLE;
end
else begin
mode_out <= ctrl_mode;
h_offset_out <= $signed(ctrl_word1[15:8]);
v_offset_out <= $signed(ctrl_word1[7:4]);
h_offset_out <= extended ? ctrl_h : legacy_h;
v_offset_out <= extended ? $signed(ctrl_word1[7:2]) :
(legacy_v > 4'sd2 ? 6'sd2 : {{2{legacy_v[3]}}, legacy_v});
line_words <= (ctrl_mode == 2'd1) ? 9'd360 : 9'd176;
scan_lines <= (ctrl_mode == 2'd2) ? 9'd288 : 9'd240;
scan_interlaced <= (ctrl_mode == 2'd1);
Expand Down
33 changes: 17 additions & 16 deletions rtl/native_video_timing.sv
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// the caller) and are latched here at the field wrap so a mid-frame update
// can't corrupt sync. Offsets shift the image by repartitioning front/back
// porch; totals are invariant, so line/frame rates never move. Out-of-range
// offsets are clamped to the supported -8..+8 px / -8..+2 line window, which
// extended offsets are clamped to the common -31..+9 px / -14..+2 line window, which
// keeps every mode's effective porches at or above 2 px / 1 line.

module native_video_timing
Expand All @@ -18,8 +18,8 @@ module native_video_timing
input wire reset,

input wire [1:0] mode_in,
input wire signed [7:0] h_offset_in, // + = right, honored -8..+8 px
input wire signed [3:0] v_offset_in, // + = down, honored -8..+2 lines
input wire signed [7:0] h_offset_in, // + = right, honored -31..+9 px
input wire signed [5:0] v_offset_in, // + = down, honored -14..+2 lines

output reg [1:0] mode, // latched active mode; selects the ce_pix divider
output reg field, // 480i field number, 0 in progressive modes
Expand Down Expand Up @@ -67,25 +67,26 @@ end

wire [1:0] next_mode = (mode_in == 2'd3) ? MODE_NTSC : mode_in;

function automatic signed [4:0] clamp_h(input signed [7:0] v);
if (v > 8'sd8) clamp_h = 5'sd8;
else if (v < -8'sd8) clamp_h = -5'sd8;
else clamp_h = v[4:0];
function automatic signed [7:0] clamp_h(input signed [7:0] v);
if (v > 8'sd9) clamp_h = 8'sd9;
else if (v < -8'sd31) clamp_h = -8'sd31;
else clamp_h = v;
endfunction

function automatic signed [3:0] clamp_v(input signed [3:0] v);
if (v > 4'sd2) clamp_v = 4'sd2;
else clamp_v = v;
function automatic signed [5:0] clamp_v(input signed [5:0] v);
if (v > 6'sd2) clamp_v = 6'sd2;
else if (v < -6'sd14) clamp_v = -6'sd14;
else clamp_v = v;
endfunction

reg signed [4:0] h_offset;
reg signed [3:0] v_offset;
reg signed [7:0] h_offset;
reg signed [5:0] v_offset;

// Sync starts shift with the offset; two's-complement subtraction in
// unsigned arithmetic yields the correct result at both ends of the range.
wire [9:0] H_SYNC_START = H_ACTIVE + (H_FP - {{5{h_offset[4]}}, h_offset});
wire [9:0] H_SYNC_START = H_ACTIVE + (H_FP - {{2{h_offset[7]}}, h_offset});
wire [9:0] H_SYNC_END = H_SYNC_START + H_SYNC;
wire [8:0] V_SYNC_START = V_ACTIVE + (V_FP - {{5{v_offset[3]}}, v_offset});
wire [8:0] V_SYNC_START = V_ACTIVE + (V_FP - {{3{v_offset[5]}}, v_offset});
wire [8:0] V_SYNC_END = V_SYNC_START + V_SYNC;

// In 480i the odd field's vsync transitions half a scanline (H_TOTAL/2
Expand All @@ -105,8 +106,8 @@ always @(posedge clk) begin
if(reset) begin
mode <= MODE_NTSC;
field <= 1'b0;
h_offset <= 5'sd0;
v_offset <= 4'sd0;
h_offset <= 8'sd0;
v_offset <= 6'sd0;
hcount <= 10'd0;
vcount <= 9'd0;
hsync <= 1'b0;
Expand Down
4 changes: 2 additions & 2 deletions rtl/native_video_top.sv
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ wire tim_field;

wire [1:0] rd_mode;
wire signed [7:0] rd_h_offset;
wire signed [3:0] rd_v_offset;
wire signed [5:0] rd_v_offset;

reg [1:0] mode_sync [1:0];
reg [7:0] h_offset_sync [1:0];
reg [3:0] v_offset_sync [1:0];
reg [5:0] v_offset_sync [1:0];
always @(posedge clk_vid) begin
mode_sync[0] <= rd_mode; mode_sync[1] <= mode_sync[0];
h_offset_sync[0] <= rd_h_offset; h_offset_sync[1] <= h_offset_sync[0];
Expand Down
27 changes: 27 additions & 0 deletions tb/native_video_reader_tb.sv
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,33 @@ initial begin
wait (active === 1'b1);
$display("pass timeout: recovered after writer republish");

// Phase 8: exact Slint extended PAL word, then wider signed trims.
$display("--- phase 8: extended protocol ---");
ctrl_q = {32'h5A5100F6, 30'd16, 1'b0, 1'b1};
wait (vmode === 2'd2); wait_frames(3);
check("extended PAL mode bits", vmode, 2);
check("extended PAL signed v", dut.timing.v_offset, -3);
check_frame_fetch("extended-pal", BUF1_V2, 176, 288, 176, 1, 0, fld_a);
ctrl_q = {16'h5A51, -8'sd31, -6'sd14, 2'd0, 30'd17, 1'b0, 1'b0};
wait (vmode === 2'd0); wait_frames(3);
check("extended wide h", dut.timing.h_offset, -31);
check("extended wide v", dut.timing.v_offset, -14);
check("extended wide active", {31'd0, active}, 1);
ctrl_q = {16'h5A51, 8'sd9, 6'sd2, 2'd1, 30'd18, 1'b0, 1'b1};
wait (vmode === 2'd1); wait_frames(3);
check("extended 480i h", dut.timing.h_offset, 9);
check("extended 480i v", dut.timing.v_offset, 2);
check_frame_fetch("extended-480i", BUF1_V2, 360, 240, 180, 2, 1, fld_a);

// Legacy mode nibble and clamp behavior remain intact.
publish(1, 4'd0, 8'sd100, 4'sd7, 19, 0);
wait (vmode === 2'd0); wait_frames(3);
check("legacy h clamp preserved", dut.timing.h_offset, 8);
check("legacy v clamp preserved", dut.timing.v_offset, 2);
ctrl_q = {32'h5A520000, 30'd20, 1'b0, 1'b0};
wait_frames(3);
check("unknown magic rejected", {31'd0, active}, 0);

if (errors == 0) $display("ALL CHECKS PASSED");
else begin
$display("%0d CHECK(S) FAILED", errors);
Expand Down
33 changes: 25 additions & 8 deletions tb/native_video_timing_tb.sv
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ reg reset = 1;

reg [1:0] mode_in = 2'd0;
reg signed [7:0] h_offset_in = 8'sd0;
reg signed [3:0] v_offset_in = 4'sd0;
reg signed [5:0] v_offset_in = 6'sd0;

wire [1:0] mode;
wire field, hsync, vsync, hblank, vblank, de, new_frame, new_line;
Expand Down Expand Up @@ -192,19 +192,36 @@ initial begin
h_offset_in = -8'sd8; settle(4);
check("h=-8 sync->active", hs_to_de, 57);
h_offset_in = 8'sd100; settle(4);
check("h=+100 clamps to +8", hs_to_de, 73);
check("h=+100 clamps to +9", hs_to_de, 74);
h_offset_in = -8'sd100; settle(4);
check("h=-100 clamps to -8", hs_to_de, 57);
check("h=-100 clamps to -31", hs_to_de, 34);
h_offset_in = 8'sd0;

v_offset_in = 4'sd2; settle(4);
v_offset_in = 6'sd2; settle(4);
check("v=+2 vsync->active", vs_to_de, 429*21);
check("v=+2 field period", vs_period, 429*262);
v_offset_in = -4'sd8; settle(4);
v_offset_in = -6'sd8; settle(4);
check("v=-8 vsync->active", vs_to_de, 429*11);
v_offset_in = 4'sd7; settle(4);
check("v=+7 clamps to +2", vs_to_de, 429*21);
v_offset_in = 4'sd0;
v_offset_in = 6'sd31; settle(4);
check("v=+31 clamps to +2", vs_to_de, 429*21);
v_offset_in = -6'sd32; settle(4);
check("v=-32 clamps to -14", vs_to_de, 429*5);
check("v=-32 preserves period", vs_period, 429*262);

// Extended negative trims must also preserve PAL and both 480i fields.
h_offset_in = -8'sd31;
mode_in = 2'd2; settle(4);
check("pal extended h", hs_to_de, 38);
check("pal extended v", vs_to_de, 432*7);
check("pal extended field", vs_period, 432*312);
mode_in = 2'd1; settle(5);
check("480i extended h", hs_to_de, 88);
check("480i extended field A", vs_period, 225225);
check("480i extended field B", vs_period_d, 225225);
check("480i extended active lines", de_lines_last, 240);
mode_in = 2'd0;
h_offset_in = 8'sd0;
v_offset_in = 6'sd0;

// ---- mode change sanity: back to NTSC after everything --------------
settle(4);
Expand Down
Loading