|
|
|
@@ -0,0 +1,217 @@
|
|
|
|
|
// retroDE_ps2 — tb_gs_scanout_diag (Ch442 A+B scanout diagnostic)
|
|
|
|
|
//
|
|
|
|
|
// Integration test for the READ-ONLY A+B diagnostic that splits the single
|
|
|
|
|
// LPDDR_STATUS[5] scan-error bit into independently observable causes and
|
|
|
|
|
// captures the FIRST raw-underflow event of each video-source-enabled session.
|
|
|
|
|
//
|
|
|
|
|
// Wires the REAL gs_lpddr_scanout_lb diag outputs into the REAL ps2_hps_bridge
|
|
|
|
|
// diag inputs and reads back 0x120 SCAN_DIAG_STATUS / 0x124 SCAN_DIAG_FIRST over
|
|
|
|
|
// the bridge AXI4-lite slave. A behavioral EMIF read responder provokes each
|
|
|
|
|
// cause SEPARATELY:
|
|
|
|
|
// P0 baseline : healthy reads, no active pixels -> all diag bits 0 (no false positive)
|
|
|
|
|
// P1 starvation: arready held low + active row 0 -> underflow, first-failure snapshot captured
|
|
|
|
|
// P2 clear : drop video source (enable=0) -> valid + underflow clear
|
|
|
|
|
// P3 rrerror : SLVERR reads, NO active pixels -> read-error flag set WITHOUT underflow
|
|
|
|
|
//
|
|
|
|
|
// Three asynchronous clocks (emif / video / bridge) exercise every CDC on the
|
|
|
|
|
// path: the two split live flags, and the bundled-data snapshot handshake.
|
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
|
|
|
|
|
|
module tb_gs_scanout_diag;
|
|
|
|
|
// ---- three independent clock domains ----
|
|
|
|
|
logic clk = 1'b0; always #10 clk = ~clk; // bridge (50 MHz, 20 ns)
|
|
|
|
|
logic emif_clk = 1'b0; always #2.5 emif_clk = ~emif_clk; // EMIF (200 MHz)
|
|
|
|
|
logic video_clk = 1'b0; always #4 video_clk = ~video_clk; // video (125 MHz)
|
|
|
|
|
logic reset_n = 1'b0, emif_rst_n = 1'b0;
|
|
|
|
|
|
|
|
|
|
localparam int N_ROWS = 8;
|
|
|
|
|
localparam int ROW_BEATS = 2;
|
|
|
|
|
localparam int STRIDE = 64; // PSMCT32: 16 px/row, 2 beats
|
|
|
|
|
|
|
|
|
|
// ---- scanout_lb <-> EMIF responder ----
|
|
|
|
|
logic so_enable, frame_start, in_window;
|
|
|
|
|
logic [11:0] pixel_x, pixel_y;
|
|
|
|
|
logic [7:0] so_r, so_g, so_b;
|
|
|
|
|
logic so_line_valid, so_underflow;
|
|
|
|
|
logic [31:0] so_rd_errs;
|
|
|
|
|
logic so_diag_rderr_nz, so_diag_valid;
|
|
|
|
|
logic [29:0] so_diag_first;
|
|
|
|
|
logic [6:0] so_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)) dut_scan (
|
|
|
|
|
.axi_clk(emif_clk), .axi_rst_n(emif_rst_n), .enable(so_enable),
|
|
|
|
|
.video_clk(video_clk), .frame_start(frame_start),
|
|
|
|
|
.pixel_x(pixel_x), .pixel_y(pixel_y), .in_window(in_window),
|
|
|
|
|
.r(so_r), .g(so_g), .b(so_b),
|
|
|
|
|
.line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
|
|
|
|
.diag_rderr_nz(so_diag_rderr_nz), .diag_valid(so_diag_valid),
|
|
|
|
|
.diag_first(so_diag_first), .diag_stat(so_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 read responder (emif_clk) ----
|
|
|
|
|
localparam logic [1:0] M_NORMAL = 2'd0, M_STARVE = 2'd1, M_ERR = 2'd2;
|
|
|
|
|
logic [1:0] resp_mode = M_NORMAL;
|
|
|
|
|
logic pend; logic [1:0] pend_resp; logic [29:0] pend_addr;
|
|
|
|
|
// grant AR when not starving and no response in flight
|
|
|
|
|
assign arready = (resp_mode != M_STARVE) && arvalid && !pend && !rvalid;
|
|
|
|
|
always_ff @(posedge emif_clk or negedge emif_rst_n) begin
|
|
|
|
|
if (!emif_rst_n) begin
|
|
|
|
|
pend <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0; rresp <= 2'b00; rdata <= 256'd0;
|
|
|
|
|
end else begin
|
|
|
|
|
if (arready) begin
|
|
|
|
|
pend <= 1'b1;
|
|
|
|
|
pend_addr <= araddr;
|
|
|
|
|
pend_resp <= (resp_mode == M_ERR) ? 2'b10 : 2'b00; // SLVERR vs OKAY
|
|
|
|
|
end
|
|
|
|
|
if (pend && !rvalid) begin
|
|
|
|
|
rvalid <= 1'b1; rlast <= 1'b1; rresp <= pend_resp;
|
|
|
|
|
rdata <= {8{2'b01, pend_addr}}; // deterministic nonzero payload
|
|
|
|
|
pend <= 1'b0;
|
|
|
|
|
end else if (rvalid && rready) begin
|
|
|
|
|
rvalid <= 1'b0; rlast <= 1'b0;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
// ---- bridge diag inputs, driven from scanout_lb (top wiring mirror) ----
|
|
|
|
|
wire scan_diag_uf_i = so_underflow; // live sticky underflow alone
|
|
|
|
|
wire scan_diag_rderr_nz_i = so_diag_rderr_nz; // live (rd_errs != 0)
|
|
|
|
|
wire scan_diag_valid_i = so_diag_valid; // first-failure captured (held to !enable)
|
|
|
|
|
wire [29:0] scan_diag_first_i = so_diag_first; // snapshot {nf_s0,nf_v,scan_y}
|
|
|
|
|
wire [6:0] scan_diag_stat_i = so_diag_stat; // snapshot {vphase,line_valid,lookahead,base}
|
|
|
|
|
|
|
|
|
|
// ---- bridge AXI4-lite slave master signals ----
|
|
|
|
|
logic [3:0] s_axi_awid=0; logic [37:0] s_axi_awaddr=0; logic [7:0] s_axi_awlen=0;
|
|
|
|
|
logic [2:0] s_axi_awsize=0; logic [1:0] s_axi_awburst=0; logic s_axi_awlock=0;
|
|
|
|
|
logic [3:0] s_axi_awcache=0; logic [2:0] s_axi_awprot=0; logic s_axi_awvalid=0, s_axi_awready;
|
|
|
|
|
logic [127:0] s_axi_wdata=0; logic [15:0] s_axi_wstrb=0; logic s_axi_wlast=0, s_axi_wvalid=0, s_axi_wready;
|
|
|
|
|
logic [3:0] s_axi_bid; logic [1:0] s_axi_bresp; logic s_axi_bvalid; logic s_axi_bready=1;
|
|
|
|
|
logic [3:0] s_axi_arid=0; logic [37:0] s_axi_araddr=0; logic [7:0] s_axi_arlen=0;
|
|
|
|
|
logic [2:0] s_axi_arsize=0; logic [1:0] s_axi_arburst=0; logic s_axi_arlock=0;
|
|
|
|
|
logic [3:0] s_axi_arcache=0; logic [2:0] s_axi_arprot=0; logic s_axi_arvalid=0, s_axi_arready;
|
|
|
|
|
logic [3:0] s_axi_rid; logic [127:0] s_axi_rdata; logic [1:0] s_axi_rresp; logic s_axi_rlast, s_axi_rvalid; logic s_axi_rready=0;
|
|
|
|
|
|
|
|
|
|
// Partial named-port bridge instance: only clk/reset/AXI-slave/scan_diag_* are
|
|
|
|
|
// relevant to the 0x120/0x124 read path. All other ports feed unrelated registers.
|
|
|
|
|
ps2_hps_bridge u_bridge (
|
|
|
|
|
.clk(clk), .reset_n(reset_n),
|
|
|
|
|
.s_axi_awid(s_axi_awid), .s_axi_awaddr(s_axi_awaddr), .s_axi_awlen(s_axi_awlen),
|
|
|
|
|
.s_axi_awsize(s_axi_awsize), .s_axi_awburst(s_axi_awburst), .s_axi_awlock(s_axi_awlock),
|
|
|
|
|
.s_axi_awcache(s_axi_awcache), .s_axi_awprot(s_axi_awprot), .s_axi_awvalid(s_axi_awvalid),
|
|
|
|
|
.s_axi_awready(s_axi_awready),
|
|
|
|
|
.s_axi_wdata(s_axi_wdata), .s_axi_wstrb(s_axi_wstrb), .s_axi_wlast(s_axi_wlast),
|
|
|
|
|
.s_axi_wvalid(s_axi_wvalid), .s_axi_wready(s_axi_wready),
|
|
|
|
|
.s_axi_bid(s_axi_bid), .s_axi_bresp(s_axi_bresp), .s_axi_bvalid(s_axi_bvalid), .s_axi_bready(s_axi_bready),
|
|
|
|
|
.s_axi_arid(s_axi_arid), .s_axi_araddr(s_axi_araddr), .s_axi_arlen(s_axi_arlen),
|
|
|
|
|
.s_axi_arsize(s_axi_arsize), .s_axi_arburst(s_axi_arburst), .s_axi_arlock(s_axi_arlock),
|
|
|
|
|
.s_axi_arcache(s_axi_arcache), .s_axi_arprot(s_axi_arprot), .s_axi_arvalid(s_axi_arvalid),
|
|
|
|
|
.s_axi_arready(s_axi_arready),
|
|
|
|
|
.s_axi_rid(s_axi_rid), .s_axi_rdata(s_axi_rdata), .s_axi_rresp(s_axi_rresp),
|
|
|
|
|
.s_axi_rlast(s_axi_rlast), .s_axi_rvalid(s_axi_rvalid), .s_axi_rready(s_axi_rready),
|
|
|
|
|
.scan_diag_uf_i(scan_diag_uf_i), .scan_diag_rderr_nz_i(scan_diag_rderr_nz_i),
|
|
|
|
|
.scan_diag_valid_i(scan_diag_valid_i), .scan_diag_first_i(scan_diag_first_i),
|
|
|
|
|
.scan_diag_stat_i(scan_diag_stat_i)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// ---- AXI read task (single-beat, lane by addr[3:2]) ----
|
|
|
|
|
task automatic axi_read32(input logic [37:0] addr, output logic [31:0] data);
|
|
|
|
|
@(posedge clk);
|
|
|
|
|
s_axi_arid<=0; s_axi_araddr<=addr; s_axi_arlen<=0; s_axi_arsize<=3'd2;
|
|
|
|
|
s_axi_arburst<=2'b01; s_axi_arvalid<=1'b1; s_axi_rready<=1'b1;
|
|
|
|
|
wait (s_axi_arready); @(posedge clk); s_axi_arvalid<=1'b0;
|
|
|
|
|
wait (s_axi_rvalid);
|
|
|
|
|
case (addr[3:2])
|
|
|
|
|
2'b00: data = s_axi_rdata[31:0];
|
|
|
|
|
2'b01: data = s_axi_rdata[63:32];
|
|
|
|
|
2'b10: data = s_axi_rdata[95:64];
|
|
|
|
|
default: data = s_axi_rdata[127:96];
|
|
|
|
|
endcase
|
|
|
|
|
@(posedge clk); s_axi_rready<=1'b0;
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
// sweep pixel_y across all rows (in the video domain) to advance disp_row so the
|
|
|
|
|
// prefetcher loads rows; in_window stays as caller set it.
|
|
|
|
|
task automatic sweep_rows(input int hold_cycles);
|
|
|
|
|
for (int y = 0; y < N_ROWS; y++) begin
|
|
|
|
|
@(posedge video_clk); pixel_y <= y[11:0]; pixel_x <= 12'd0;
|
|
|
|
|
repeat (hold_cycles) @(posedge video_clk);
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
task automatic pulse_frame_start();
|
|
|
|
|
@(posedge video_clk); frame_start <= 1'b1;
|
|
|
|
|
repeat (3) @(posedge video_clk); frame_start <= 1'b0;
|
|
|
|
|
repeat (3) @(posedge video_clk);
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
int errors = 0;
|
|
|
|
|
task automatic chk(input string label, input logic cond);
|
|
|
|
|
if (!cond) begin $error("[scanout_diag] FAIL: %s", label); errors++; end
|
|
|
|
|
else $display("[scanout_diag] ok : %s", label);
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
logic [31:0] st, fst;
|
|
|
|
|
initial begin
|
|
|
|
|
so_enable=0; frame_start=0; in_window=0; pixel_x=0; pixel_y=0; resp_mode=M_NORMAL;
|
|
|
|
|
repeat (6) @(posedge clk); reset_n=1; emif_rst_n=1;
|
|
|
|
|
repeat (6) @(posedge clk);
|
|
|
|
|
|
|
|
|
|
// -------- P1 FIRST: cold-start row-zero starvation -> first-failure snapshot --------
|
|
|
|
|
// Run before any row ever loads so line_valid is genuinely 0 (line_valid is sticky,
|
|
|
|
|
// reset only by axi_rst_n) — the authentic row-zero-miss the field defect resembles.
|
|
|
|
|
resp_mode=M_STARVE; so_enable=1;
|
|
|
|
|
pulse_frame_start();
|
|
|
|
|
@(posedge video_clk) begin in_window<=1'b1; pixel_x<=12'd0; pixel_y<=12'd0; end
|
|
|
|
|
repeat (60) @(posedge video_clk); // hold active row 0 while nothing loads
|
|
|
|
|
repeat (60) @(posedge clk);
|
|
|
|
|
axi_read32(38'h120, st);
|
|
|
|
|
axi_read32(38'h124, fst);
|
|
|
|
|
chk("P1 valid=1", st[0]===1'b1);
|
|
|
|
|
chk("P1 underflow=1", st[1]===1'b1);
|
|
|
|
|
chk("P1 rderr_nz=0", st[2]===1'b0);
|
|
|
|
|
chk("P1 cause_base=1", st[3]===1'b1);
|
|
|
|
|
chk("P1 cause_lookah=0", st[4]===1'b0);
|
|
|
|
|
chk("P1 line_valid=0", st[5]===1'b0); // cold start: no row loaded yet
|
|
|
|
|
chk("P1 snap scan_y=0", fst[9:0]===10'd0); // row-zero miss
|
|
|
|
|
chk("P1 snap nf_v=0", fst[19:10]===10'd0);
|
|
|
|
|
chk("P1 snap nf_s0=0", fst[29:20]===10'd0);
|
|
|
|
|
|
|
|
|
|
// -------- P2: clear via video-source disable --------------------------------------
|
|
|
|
|
so_enable=0; in_window=0;
|
|
|
|
|
repeat (40) @(posedge clk);
|
|
|
|
|
axi_read32(38'h120, st);
|
|
|
|
|
chk("P2 valid cleared", st[0]===1'b0);
|
|
|
|
|
chk("P2 underflow cleared", st[1]===1'b0);
|
|
|
|
|
|
|
|
|
|
// -------- P3: healthy baseline (rows load, no active pixels) -> NO false positive --
|
|
|
|
|
resp_mode=M_NORMAL; so_enable=1; in_window=0;
|
|
|
|
|
pulse_frame_start(); sweep_rows(24);
|
|
|
|
|
repeat (40) @(posedge clk);
|
|
|
|
|
axi_read32(38'h120, st);
|
|
|
|
|
chk("P3 valid=0", st[0]===1'b0);
|
|
|
|
|
chk("P3 underflow=0", st[1]===1'b0);
|
|
|
|
|
chk("P3 rderr_nz=0", st[2]===1'b0); // OKAY reads: still zero
|
|
|
|
|
|
|
|
|
|
// -------- P4: SLVERR reads, no active pixels -> read-error WITHOUT underflow -------
|
|
|
|
|
resp_mode=M_ERR; in_window=0;
|
|
|
|
|
pulse_frame_start(); sweep_rows(24); // reload every row; each read returns SLVERR
|
|
|
|
|
repeat (60) @(posedge clk);
|
|
|
|
|
axi_read32(38'h120, st);
|
|
|
|
|
chk("P4 rderr_nz=1", st[2]===1'b1);
|
|
|
|
|
chk("P4 valid=0", st[0]===1'b0); // in_window=0 -> underflow impossible
|
|
|
|
|
chk("P4 underflow=0", st[1]===1'b0);
|
|
|
|
|
|
|
|
|
|
if (errors==0) $display("[tb_gs_scanout_diag] PASS");
|
|
|
|
|
else $display("[tb_gs_scanout_diag] FAIL (%0d errors)", errors);
|
|
|
|
|
$finish;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
initial begin #500000; $error("[tb_gs_scanout_diag] TIMEOUT"); $finish; end
|
|
|
|
|
endmodule : tb_gs_scanout_diag
|