From 5c2cb49aea35db5a360b83c0b8a03ffa4f0156f1 Mon Sep 17 00:00:00 2001 From: Callan Barrett Date: Wed, 16 Sep 2026 16:50:37 +0800 Subject: [PATCH] fix: accept extended native CRT control words --- rtl/native_video_reader.sv | 29 ++++++++++++++++++----------- rtl/native_video_timing.sv | 33 +++++++++++++++++---------------- rtl/native_video_top.sv | 4 ++-- tb/native_video_reader_tb.sv | 27 +++++++++++++++++++++++++++ tb/native_video_timing_tb.sv | 33 +++++++++++++++++++++++++-------- 5 files changed, 89 insertions(+), 37 deletions(-) diff --git a/rtl/native_video_reader.sv b/rtl/native_video_reader.sv index 7160a6f..8c88401 100644 --- a/rtl/native_video_reader.sv +++ b/rtl/native_video_reader.sv @@ -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 @@ -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, @@ -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; @@ -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; @@ -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); diff --git a/rtl/native_video_timing.sv b/rtl/native_video_timing.sv index 0d442cd..34d9f22 100644 --- a/rtl/native_video_timing.sv +++ b/rtl/native_video_timing.sv @@ -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 @@ -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 @@ -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 @@ -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; diff --git a/rtl/native_video_top.sv b/rtl/native_video_top.sv index 0b55f62..4bf8393 100644 --- a/rtl/native_video_top.sv +++ b/rtl/native_video_top.sv @@ -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]; diff --git a/tb/native_video_reader_tb.sv b/tb/native_video_reader_tb.sv index fa82dab..8516fb5 100644 --- a/tb/native_video_reader_tb.sv +++ b/tb/native_video_reader_tb.sv @@ -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); diff --git a/tb/native_video_timing_tb.sv b/tb/native_video_timing_tb.sv index e1ec8ec..d1ce2d4 100644 --- a/tb/native_video_timing_tb.sv +++ b/tb/native_video_timing_tb.sv @@ -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; @@ -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);