// retroDE_ps2 — tb_gs_scanout_restart (Ch443c) // // Focused test for the frame-restart latency repair in gs_lpddr_scanout_lb's AXI // prefetch FSM. On a vsync (frame_start) that lands MID-ROW, the FSM must, after the // current single-beat response is accepted, abandon the remainder of the now-obsolete // row (no next old-row AR, no publish/increment) and restart the prefetch at // V_SOURCE_START. Proves: // (1) the in-flight response is accepted normally (no deadlock); // (2) NO further old-row AR issues after the restart trigger; // (3) the restart's first AR targets row V_SOURCE_START (=32); // (4) rows 32 and 33 become resident before the first active (in_window) consumption; // (5) no post-restart underflow on the displayed frame. `timescale 1ns/1ps module tb_gs_scanout_restart; logic emif_clk = 0; always #2.5 emif_clk = ~emif_clk; // 200 MHz logic video_clk = 0; always #4 video_clk = ~video_clk; // 125 MHz logic rst_n; localparam int N_ROWS = 48, VSS = 32, ROW_BEATS = 2, STRIDE = 64; localparam int ROW32_ADDR = VSS * STRIDE; // 2048 localparam int ROW33_ADDR = (VSS+1) * STRIDE; // 2112 logic enable, frame_start, in_window; logic [11:0] pixel_x, pixel_y; logic [7:0] r,g,b; logic line_valid, underflow; logic [31:0] rd_errs; logic diag_rderr_nz, diag_valid; logic [29:0] diag_first; logic [6:0] diag_stat; logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize; logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready; gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS), .N_ROWS(N_ROWS), .PSMCT32(1'b1), .V_SOURCE_START(VSS)) dut ( .axi_clk(emif_clk), .axi_rst_n(rst_n), .enable(enable), .video_clk(video_clk), .frame_start(frame_start), .pixel_x(pixel_x), .pixel_y(pixel_y), .in_window(in_window), .r(r), .g(g), .b(b), .line_valid(line_valid), .underflow(underflow), .rd_errs(rd_errs), .diag_rderr_nz(diag_rderr_nz), .diag_valid(diag_valid), .diag_first(diag_first), .diag_stat(diag_stat), .araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready), .rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready) ); // behavioral EMIF responder: grant AR, return OKAY R one cycle later (fast). logic pend; logic [29:0] pend_addr; assign arready = arvalid && !pend && !rvalid; always_ff @(posedge emif_clk or negedge rst_n) begin if (!rst_n) begin pend<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; end else begin if (arready) begin pend<=1; pend_addr<=araddr; end if (pend && !rvalid) begin rvalid<=1; rlast<=1; rresp<=0; rdata<={8{2'b01,pend_addr}}; pend<=0; end else if (rvalid && rready) begin rvalid<=0; rlast<=0; end end end int errors = 0; task automatic chk(input string s, input logic c); if (!c) begin $error("[restart] FAIL: %s", s); errors++; end else $display("[restart] ok : %s", s); endtask // ---- monitors (emif domain) ---- // capture, at the restart trigger, whether we were mid-fetch and which row. logic restart_seen, restart_midfetch; logic [$clog2(N_ROWS):0] row_at_restart; // track the FIRST araddr issued (arvalid rising) AFTER the restart trigger. logic arv_prev, armed_first_ar, got_first_ar; logic [29:0] first_ar_after_restart; // flag ANY old-row AR after restart (araddr not equal to a fresh row-32-onwards fetch // before we've seen the row-32 restart AR). always_ff @(posedge emif_clk or negedge rst_n) begin if (!rst_n) begin restart_seen<=0; restart_midfetch<=0; row_at_restart<=0; arv_prev<=0; armed_first_ar<=0; got_first_ar<=0; first_ar_after_restart<=0; end else begin arv_prev <= arvalid; // restart trigger = fs_edge_e inside the DUT // lstate_t = { L_IDLE=0, L_AR=1, L_R=2, L_C=3 } if (dut.fs_edge_e && !restart_seen) begin restart_seen <= 1'b1; restart_midfetch <= (dut.lst == 2'd1) || (dut.lst == 2'd2); // L_AR or L_R row_at_restart <= dut.cur_row; armed_first_ar <= 1'b1; end // first arvalid rising edge after the restart trigger if (armed_first_ar && arvalid && !arv_prev && !got_first_ar) begin got_first_ar <= 1'b1; first_ar_after_restart<= araddr; end end end task automatic pulse_fs(); @(posedge video_clk) frame_start <= 1'b1; repeat (3) @(posedge video_clk); frame_start <= 1'b0; repeat (2) @(posedge video_clk); endtask initial begin rst_n=0; enable=0; frame_start=0; in_window=0; pixel_x=0; pixel_y=VSS[11:0]; repeat (8) @(posedge emif_clk); rst_n=1; repeat (6) @(posedge emif_clk); // ---- warm the prefetch: enable, first frame, advance disp_row so rows load ---- enable=1; pulse_fs(); // climb pixel_y from VSS upward (in_window=0 -> no underflow) so the prefetch // actively fetches rows 32,33,34,... Inject the mid-row restart partway through. for (int y=VSS; y= 34. for (int y=VSS; y=34)", dut.next_fetch >= ($clog2(N_ROWS)+1)'(VSS+2)); chk("line_valid set (rows resident)", line_valid === 1'b1); // ---- (5): now consume actively at row 32 -> NO underflow ---- @(posedge video_clk) begin in_window <= 1'b1; pixel_y <= VSS[11:0]; pixel_x <= 12'd0; end repeat (60) @(posedge video_clk); chk("no post-restart underflow at row 32", underflow === 1'b0); if (errors==0) $display("[tb_gs_scanout_restart] PASS"); else $display("[tb_gs_scanout_restart] FAIL (%0d errors)", errors); $finish; end initial begin #400000; $error("[tb_gs_scanout_restart] TIMEOUT"); $finish; end endmodule : tb_gs_scanout_restart