Ch443e: binomial 4th line buffer + prefetch lead-2 (fix lookahead underflow)
The Ch443d board per-frame diagnostic identified the real displayed-frame
failure as a BINOMIAL vertical-lookahead starvation: displaying source
row r while interpolating r-1/r/r+1, the prefetch led by only one row
(next_fetch <= disp_row+1), so the lookahead row r+1 was still in flight
when the 3x3 filter read it (board: scan_y=33, nf_v=34, cause_lookahead=1,
read-error=0, deterministic every frame).
Fix (BINOMIAL_3X3_FILTER only; legacy 2/3-buffer, lead-1 paths unchanged):
- Add a 4th rotating line buffer (lb3) with its own RAM-local write/read/
cache registers. The 3x3 filter needs r-1/r/r+1 resident (3 buffers), so
leading by 2 (fetch r+2 while displaying r) without overwriting r-1
requires a 4th buffer.
- Prefetch lead-2 for binomial: disp_row_limit_e = disp_row+2. The in-flight
r+2 lands in the 4th buffer (b+2 mod 4), always distinct from prev/cur/next
(b-1/b/b+1 mod 4), so it never clobbers a row being read.
- Modulo-4 rotation everywhere: V_SOURCE_BUF%4, stretch_buf_q, next_fetch_buf,
reset alignment at V_SOURCE_START, and the read-cache prev/cur/next case
extended to 4 branches with (b-1)/b/(b+1) mod 4 selection + first/last-row
clamps preserved.
New tb_gs_scanout_binomial_lookahead reproduces the board condition under
realistic EMIF latency (LAT=7) + backpressure and proves: NO binomial
lookahead underflow, correct 3x3 output across modulo-4 wrap + clamps (full
oracle, 1280 px), and coverage that mid-frame rows past V_SOURCE_START+1
with vphase!=0 were exercised under prefetch pressure.
All green: binomial (4-buffer, identical output), lookahead (new),
scanout_lb {,_hstretch,_psm32_256,_fb}, scanout_restart, scanout_diag,
ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL (Z 0/307200,
COLOR 0/245760). Also commits the Ch443d board evidence that identified
this defect. No Quartus/board/push from here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
sample 1: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 2: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 3: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 4: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 5: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 6: 0x02C=0x000000F1 0x120=0x00000D30 0x124=0x02208821
|
||||
sample 7: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
sample 8: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
|
||||
@@ -0,0 +1,44 @@
|
||||
# Ch443d board verdict — per-frame scanout diagnostic (zsrt139f52, 2026-07-23)
|
||||
|
||||
Image: `retroDE_ps2.ch443d.core.rbf` SHA `87c382f1...` (26.1 GUI compile of `0c9b2bf` =
|
||||
`6319d7c` RTL + SEED 3). Non-canonical candidate; Ch441 restored after capture.
|
||||
|
||||
## Fit + render (accepted)
|
||||
Seed-3 GUI compile: EMIF setup +0.132, all timing classes >=0, 0 violated, no
|
||||
AWREADY/texcache-fill/drain/scanout-diag families, RAM 317/358. Board f52 replay
|
||||
byte-identical to golden (FB `d0047677`, texture CRC `0x13cfe390`, 0 drops).
|
||||
|
||||
## Per-frame diagnostic (8 samples over many displayed frames, stable)
|
||||
| reg | raw | decode |
|
||||
|-----|-----|--------|
|
||||
| 0x02C | `0xF1` | scan underflow (bit5)=1, bresp=0 |
|
||||
| 0x120 | `0x0D33` | valid=1, **underflow=1**, **read-error=0**, cause_base=**0**, **cause_lookahead=1**, line_valid=1, **vphase=13** |
|
||||
| 0x124 | `0x02208821` | **scan_y=33, nf_v=34, nf_s0=34** |
|
||||
|
||||
## VERDICT — the REAL displayed-frame failure (NOT the warm-up, NOT a read error)
|
||||
The Ch443c per-frame clear + restart-latency fix worked as intended: the pre-display
|
||||
warm-up capture (previously scan_y=32/nf=32/base) is GONE. The diagnostic now reports the
|
||||
true steady-state failure, and it is a **vertical LINEAR-FILTER LOOKAHEAD starvation**:
|
||||
|
||||
- scan_y=33 is resident (33 < nf_v=34 -> base cause correctly 0).
|
||||
- At vphase=13 the V_LINEAR_FILTER interpolates between source rows 33 and 34, so it needs
|
||||
the LOOKAHEAD row scan_y+1 = 34. But nf_v=34 means next_fetch is AT row 34 -> row 34 is
|
||||
being fetched, NOT yet resident -> `scan_y+1 >= nf_v` -> lookahead cause fires -> underflow.
|
||||
- read-error=0: not an AXI error. Deterministic every frame: structural, not a CDC glitch.
|
||||
|
||||
Root cause: the prefetch throttle leads by exactly ONE row (`next_fetch <= disp_row+1`), but
|
||||
the vertical linear filter needs the lookahead row (disp_row+1) FULLY resident. The prefetch
|
||||
is one row short of the filter's requirement, so displaying row 33 while row 34 is still
|
||||
loading races the filter's read of row 34.
|
||||
|
||||
## Open question + fix direction (for Codex)
|
||||
- Leading by 2 (`disp_row+2`) would make the lookahead row resident in time, but V_LINEAR uses
|
||||
only 2 line buffers (parity) -- disp_row and disp_row+2 share a buffer, so leading by 2
|
||||
needs a 3rd line buffer (as BINOMIAL already has) or a different prefetch/latency structure.
|
||||
- The FB (memory) is byte-perfect; whether this lookahead underflow is HDMI-VISIBLE (a subtle
|
||||
artifact at inter-row filter boundaries) needs an actual HDMI capture -- the flag is
|
||||
conservative (fires when the lookahead row's fetch is in-flight, which may still complete
|
||||
before the specific pixels are read).
|
||||
|
||||
Evidence: `ch443d_perframe_diag_8samples.txt`, `sh3_zsrt139f52_ch443d_board_fb.mem`,
|
||||
`ch443d_rbf.sha256`.
|
||||
@@ -0,0 +1 @@
|
||||
87c382f1c1664fd049f1546f6ec250cc6f7d23a3d3475aa2ebad9194086aa73e docs/hardware/ch443d_board_validation/retroDE_ps2.ch443d.core.rbf
|
||||
File diff suppressed because it is too large
Load Diff
@@ -116,10 +116,16 @@ module gs_lpddr_scanout_lb #(
|
||||
assign arsize = 3'b101; // 32 bytes
|
||||
|
||||
// Two line buffers for legacy/linear scanout; Ch438 enables a third so the
|
||||
// previous, current, and next source rows are resident simultaneously.
|
||||
// previous, current, and next source rows are resident simultaneously. Ch443e
|
||||
// adds a FOURTH buffer for BINOMIAL only: the 3x3 filter needs r-1/r/r+1 resident
|
||||
// (3 buffers), so to lead the prefetch by 2 (fetch r+2 while displaying r) without
|
||||
// overwriting r-1 (still needed) a 4th rotating buffer is required. Non-binomial
|
||||
// configs leave lb3 unused (pruned) and keep the legacy 2/3-buffer, lead-1 behavior.
|
||||
localparam int NBUF = BINOMIAL_3X3_FILTER ? 4 : 3; // buffers actually rotated
|
||||
logic [255:0] lb0 [0:ROW_BEATS-1];
|
||||
logic [255:0] lb1 [0:ROW_BEATS-1];
|
||||
logic [255:0] lb2 [0:ROW_BEATS-1];
|
||||
logic [255:0] lb3 [0:ROW_BEATS-1];
|
||||
|
||||
// ================= video side (video_clk) =================
|
||||
// No miss-prone request toggle. The video side just exposes the current
|
||||
@@ -133,7 +139,7 @@ module gs_lpddr_scanout_lb #(
|
||||
// "disp_buf" lags by one cycle and corrupts col 0 of each line.
|
||||
logic [$clog2(N_ROWS):0] stretch_src_y_q;
|
||||
logic [3:0] stretch_vphase_q;
|
||||
localparam int V_SOURCE_BUF = V_SOURCE_START % 3;
|
||||
localparam int V_SOURCE_BUF = V_SOURCE_START % NBUF;
|
||||
logic [1:0] stretch_buf_q;
|
||||
logic in_window_v_q;
|
||||
always_ff @(posedge video_clk) begin
|
||||
@@ -157,8 +163,8 @@ module gs_lpddr_scanout_lb #(
|
||||
else begin
|
||||
stretch_src_y_q <= stretch_src_y_q + 1'b1;
|
||||
stretch_vphase_q <= stretch_vphase_q - 1'b1;
|
||||
stretch_buf_q <= (stretch_buf_q == 2'd2) ? 2'd0
|
||||
: stretch_buf_q + 1'b1;
|
||||
stretch_buf_q <= (stretch_buf_q == 2'(NBUF-1)) ? 2'd0
|
||||
: stretch_buf_q + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -236,11 +242,12 @@ module gs_lpddr_scanout_lb #(
|
||||
logic [255:0] lb0_cache0_q, lb0_cache1_q;
|
||||
logic [255:0] lb1_cache0_q, lb1_cache1_q;
|
||||
logic [255:0] lb2_cache0_q, lb2_cache1_q;
|
||||
logic [255:0] lb3_cache0_q, lb3_cache1_q; // Ch443e — binomial 4th buffer
|
||||
logic blank_prime_q;
|
||||
logic [RB_BITS-1:0] active_beat_q;
|
||||
logic [RB_BITS-1:0] video_rd_addr_q, video_rd_tag_q;
|
||||
logic video_rd_req_q, video_rd_valid_q;
|
||||
logic [255:0] lb0_video_rd_q, lb1_video_rd_q, lb2_video_rd_q;
|
||||
logic [255:0] lb0_video_rd_q, lb1_video_rd_q, lb2_video_rd_q, lb3_video_rd_q;
|
||||
|
||||
// Keep the inferred line-buffer read ports canonical: exactly one
|
||||
// unconditional registered address and one registered data output
|
||||
@@ -250,6 +257,7 @@ module gs_lpddr_scanout_lb #(
|
||||
lb0_video_rd_q <= lb0[video_rd_addr_q];
|
||||
lb1_video_rd_q <= lb1[video_rd_addr_q];
|
||||
lb2_video_rd_q <= lb2[video_rd_addr_q];
|
||||
lb3_video_rd_q <= lb3[video_rd_addr_q]; // Ch443e — binomial 4th buffer read port
|
||||
video_rd_tag_q <= video_rd_addr_q;
|
||||
video_rd_valid_q <= video_rd_req_q;
|
||||
end
|
||||
@@ -263,10 +271,12 @@ module gs_lpddr_scanout_lb #(
|
||||
lb0_cache1_q <= lb0_video_rd_q;
|
||||
lb1_cache1_q <= lb1_video_rd_q;
|
||||
lb2_cache1_q <= lb2_video_rd_q;
|
||||
lb3_cache1_q <= lb3_video_rd_q;
|
||||
end else begin
|
||||
lb0_cache0_q <= lb0_video_rd_q;
|
||||
lb1_cache0_q <= lb1_video_rd_q;
|
||||
lb2_cache0_q <= lb2_video_rd_q;
|
||||
lb3_cache0_q <= lb3_video_rd_q;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -297,31 +307,35 @@ module gs_lpddr_scanout_lb #(
|
||||
// opposite cache slot still contains the preceding
|
||||
// beat; by the time lane seven needs x+1 it contains
|
||||
// the newly fetched successor beat.
|
||||
// Ch443e — modulo-4 rotation across FOUR buffers. Current row is
|
||||
// buffer scan_buf3; prev=(b-1)%4, next=(b+1)%4; the 4th buffer
|
||||
// (b+2)%4 is being prefetched (row r+2, lead-2) and is not read here.
|
||||
// First row clamps prev->cur; last row clamps next->cur.
|
||||
case (scan_buf3)
|
||||
2'd0: begin
|
||||
2'd0: begin // cur=lb0 prev=lb3 next=lb1
|
||||
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb0_cache1_q : lb0_cache0_q)
|
||||
: (col_beat[0] ? lb2_cache1_q : lb2_cache0_q);
|
||||
: (col_beat[0] ? lb3_cache1_q : lb3_cache0_q);
|
||||
cur_word_q <= col_beat[0] ? lb0_cache1_q : lb0_cache0_q;
|
||||
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb0_cache1_q : lb0_cache0_q)
|
||||
: (col_beat[0] ? lb1_cache1_q : lb1_cache0_q);
|
||||
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224]);
|
||||
: (col_beat[0] ? lb3_cache0_q[255:224] : lb3_cache1_q[255:224]);
|
||||
cur_left_px_q <= col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224];
|
||||
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224]);
|
||||
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0]);
|
||||
: (col_beat[0] ? lb3_cache0_q[31:0] : lb3_cache1_q[31:0]);
|
||||
cur_look_px_q <= col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0];
|
||||
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0]);
|
||||
end
|
||||
2'd1: begin
|
||||
2'd1: begin // cur=lb1 prev=lb0 next=lb2
|
||||
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
|
||||
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
|
||||
@@ -344,27 +358,50 @@ module gs_lpddr_scanout_lb #(
|
||||
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0]);
|
||||
end
|
||||
default: begin
|
||||
2'd2: begin // cur=lb2 prev=lb1 next=lb3
|
||||
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb2_cache1_q : lb2_cache0_q)
|
||||
: (col_beat[0] ? lb1_cache1_q : lb1_cache0_q);
|
||||
cur_word_q <= col_beat[0] ? lb2_cache1_q : lb2_cache0_q;
|
||||
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb2_cache1_q : lb2_cache0_q)
|
||||
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
|
||||
: (col_beat[0] ? lb3_cache1_q : lb3_cache0_q);
|
||||
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224]);
|
||||
cur_left_px_q <= col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224];
|
||||
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224]);
|
||||
: (col_beat[0] ? lb3_cache0_q[255:224] : lb3_cache1_q[255:224]);
|
||||
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0]);
|
||||
cur_look_px_q <= col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0];
|
||||
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb3_cache0_q[31:0] : lb3_cache1_q[31:0]);
|
||||
end
|
||||
default: begin // cur=lb3 prev=lb2 next=lb0
|
||||
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb3_cache1_q : lb3_cache0_q)
|
||||
: (col_beat[0] ? lb2_cache1_q : lb2_cache0_q);
|
||||
cur_word_q <= col_beat[0] ? lb3_cache1_q : lb3_cache0_q;
|
||||
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb3_cache1_q : lb3_cache0_q)
|
||||
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
|
||||
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb3_cache0_q[255:224] : lb3_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224]);
|
||||
cur_left_px_q <= col_beat[0] ? lb3_cache0_q[255:224] : lb3_cache1_q[255:224];
|
||||
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb3_cache0_q[255:224] : lb3_cache1_q[255:224])
|
||||
: (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224]);
|
||||
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
||||
? (col_beat[0] ? lb3_cache0_q[31:0] : lb3_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0]);
|
||||
cur_look_px_q <= col_beat[0] ? lb3_cache0_q[31:0] : lb3_cache1_q[31:0];
|
||||
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
||||
? (col_beat[0] ? lb3_cache0_q[31:0] : lb3_cache1_q[31:0])
|
||||
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
|
||||
end
|
||||
endcase
|
||||
@@ -656,9 +693,9 @@ module gs_lpddr_scanout_lb #(
|
||||
// cycle per beat and produced sustained line-buffer underflow on hardware.
|
||||
// Data/address registers intentionally have no reset; the reset write-
|
||||
// enables qualify them.
|
||||
logic [255:0] lb0_wdata_q, lb1_wdata_q, lb2_wdata_q;
|
||||
logic [RB_BITS-1:0] lb0_waddr_q, lb1_waddr_q, lb2_waddr_q;
|
||||
logic lb0_we_q, lb1_we_q, lb2_we_q;
|
||||
logic [255:0] lb0_wdata_q, lb1_wdata_q, lb2_wdata_q, lb3_wdata_q;
|
||||
logic [RB_BITS-1:0] lb0_waddr_q, lb1_waddr_q, lb2_waddr_q, lb3_waddr_q;
|
||||
logic lb0_we_q, lb1_we_q, lb2_we_q, lb3_we_q; // Ch443e — binomial 4th buffer
|
||||
// Ch442 diag: registered single-bit reduction of the read-error counter (emif domain).
|
||||
// Diagnostic-only; nothing downstream reads it, so it cannot perturb the fetch FSM.
|
||||
logic rd_err_nz_q;
|
||||
@@ -668,14 +705,14 @@ module gs_lpddr_scanout_lb #(
|
||||
if (!axi_rst_n) begin
|
||||
fs_sync_e <= 3'd0;
|
||||
disp_row_s0 <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
|
||||
disp_row_limit_e <= ($clog2(N_ROWS)+1)'(V_SOURCE_START + 1);
|
||||
disp_row_limit_e <= ($clog2(N_ROWS)+1)'(V_SOURCE_START + (BINOMIAL_3X3_FILTER ? 2 : 1));
|
||||
next_fetch <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
|
||||
next_fetch_buf <= BINOMIAL_3X3_FILTER ? 2'(V_SOURCE_BUF)
|
||||
: {1'b0, 1'(V_SOURCE_START)};
|
||||
lst <= L_IDLE; araddr <= '0; arvalid <= 1'b0; rready <= 1'b0;
|
||||
cur_row <= '0; cur_buf <= 2'd0; beat <= '0;
|
||||
line_valid <= 1'b0; rd_errs <= 32'd0; fs_pending <= 1'b0;
|
||||
lb0_we_q <= 1'b0; lb1_we_q <= 1'b0; lb2_we_q <= 1'b0;
|
||||
lb0_we_q <= 1'b0; lb1_we_q <= 1'b0; lb2_we_q <= 1'b0; lb3_we_q <= 1'b0;
|
||||
rd_err_nz_q <= 1'b0;
|
||||
end else begin
|
||||
fs_sync_e <= {fs_sync_e[1:0], frame_start};
|
||||
@@ -684,7 +721,10 @@ module gs_lpddr_scanout_lb #(
|
||||
// second CDC stage, but removes disp_row -> (+1) -> compare -> araddr
|
||||
// enable from one 310 MHz cycle (the post-alpha fit's -0.125 ns family).
|
||||
// The extra bit represents N_ROWS exactly on the final display row.
|
||||
disp_row_limit_e <= disp_row_s0 + 1'b1;
|
||||
// Ch443e — BINOMIAL leads by 2 (fetch disp_row+2 while displaying disp_row) so
|
||||
// the 3x3 filter's lookahead row (disp_row+1) is FULLY resident before it is read;
|
||||
// the 4th rotating buffer holds the in-flight disp_row+2. Legacy stays lead-1.
|
||||
disp_row_limit_e <= disp_row_s0 + (BINOMIAL_3X3_FILTER ? 2'd2 : 2'd1);
|
||||
// Ch439c — RAM-local response pipeline. Commit the response
|
||||
// captured on the preceding cycle while the AXI FSM advances to
|
||||
// (or waits for) the next single-beat read. This keeps the
|
||||
@@ -694,9 +734,11 @@ module gs_lpddr_scanout_lb #(
|
||||
if (lb0_we_q) lb0[lb0_waddr_q] <= lb0_wdata_q;
|
||||
if (lb1_we_q) lb1[lb1_waddr_q] <= lb1_wdata_q;
|
||||
if (lb2_we_q) lb2[lb2_waddr_q] <= lb2_wdata_q;
|
||||
if (lb3_we_q) lb3[lb3_waddr_q] <= lb3_wdata_q;
|
||||
lb0_we_q <= 1'b0;
|
||||
lb1_we_q <= 1'b0;
|
||||
lb2_we_q <= 1'b0;
|
||||
lb3_we_q <= 1'b0;
|
||||
// Ch442 diag: register the read-error-nonzero flag (emif domain). One cycle of lag
|
||||
// vs rd_errs is irrelevant for a stuck-nonzero indicator; keeps a clean launch flop.
|
||||
rd_err_nz_q <= (rd_errs != 32'd0);
|
||||
@@ -734,6 +776,7 @@ module gs_lpddr_scanout_lb #(
|
||||
lb0_we_q <= 1'b0;
|
||||
lb1_we_q <= 1'b0;
|
||||
lb2_we_q <= 1'b0;
|
||||
lb3_we_q <= 1'b0;
|
||||
if (fs_pending || fs_edge_e) begin
|
||||
// Ch443c (Codex): a vsync restart is pending. This single-beat AXI
|
||||
// transaction is COMPLETE (rvalid accepted), so it is protocol-safe to
|
||||
@@ -748,6 +791,7 @@ module gs_lpddr_scanout_lb #(
|
||||
case (cur_buf)
|
||||
2'd1: begin lb1_wdata_q <= rdata; lb1_waddr_q <= beat[RB_BITS-1:0]; lb1_we_q <= 1'b1; end
|
||||
2'd2: begin lb2_wdata_q <= rdata; lb2_waddr_q <= beat[RB_BITS-1:0]; lb2_we_q <= 1'b1; end
|
||||
2'd3: begin lb3_wdata_q <= rdata; lb3_waddr_q <= beat[RB_BITS-1:0]; lb3_we_q <= 1'b1; end
|
||||
default: begin lb0_wdata_q <= rdata; lb0_waddr_q <= beat[RB_BITS-1:0]; lb0_we_q <= 1'b1; end
|
||||
endcase
|
||||
if (beat == ROW_BEATS-1) begin
|
||||
@@ -771,7 +815,7 @@ module gs_lpddr_scanout_lb #(
|
||||
line_valid <= 1'b1;
|
||||
next_fetch <= next_fetch + 1'b1; // rows 0..next_fetch are now loaded
|
||||
if (BINOMIAL_3X3_FILTER)
|
||||
next_fetch_buf <= (next_fetch_buf == 2'd2) ? 2'd0
|
||||
next_fetch_buf <= (next_fetch_buf == 2'd3) ? 2'd0 // Ch443e — mod-4 rotation
|
||||
: next_fetch_buf + 1'b1;
|
||||
else
|
||||
next_fetch_buf <= {1'b0, ~next_fetch_buf[0]};
|
||||
|
||||
@@ -1281,6 +1281,15 @@ tb_gs_lpddr_scanout_lb_binomial: dirs
|
||||
@echo "=== run tb_gs_lpddr_scanout_lb_binomial ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_lpddr_scanout_lb_binomial.vvp
|
||||
|
||||
tb_gs_scanout_binomial_lookahead: dirs
|
||||
@echo "=== build tb_gs_scanout_binomial_lookahead ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
-o $(BUILD_DIR)/tb_gs_scanout_binomial_lookahead.vvp \
|
||||
-s tb_gs_scanout_binomial_lookahead \
|
||||
$(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_scanout_binomial_lookahead.sv
|
||||
@echo "=== run tb_gs_scanout_binomial_lookahead ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_scanout_binomial_lookahead.vvp
|
||||
|
||||
tb_gs_scanout_diag: dirs
|
||||
@echo "=== build tb_gs_scanout_diag ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// ============================================================================
|
||||
// tb_gs_scanout_binomial_lookahead — Ch443e
|
||||
//
|
||||
// Reproduces the board's binomial vertical-lookahead underflow (per-frame diag:
|
||||
// scan_y=33, next_fetch=34, cause_lookahead=1) under REALISTIC EMIF read latency
|
||||
// and backpressure, and proves the Ch443e fix (4th rotating buffer + prefetch
|
||||
// lead-2, modulo-4) eliminates it:
|
||||
// - NO binomial lookahead underflow across the whole displayed frame;
|
||||
// - correct previous/current/next 3x3 output across modulo-4 wrap (full oracle);
|
||||
// - first/last-row clamping + 15:14 stretch-phase transitions correct;
|
||||
// - coverage: mid-frame source rows past V_SOURCE_START+1 with vphase!=0 are
|
||||
// actually displayed while the prefetch is under latency pressure.
|
||||
// Production-like: V_SOURCE_START=32, V_STRETCH_15_TO_14, BINOMIAL, PSMCT32.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_gs_scanout_binomial_lookahead;
|
||||
localparam int SRC_W=64, OUT_W=80, OUT_H=16;
|
||||
localparam int STRIDE=320, ROW_BEATS=10, N_ROWS=48, VSTART=32;
|
||||
|
||||
logic axi_clk=0, video_clk=0, rst_n=0, enable=0;
|
||||
always #2 axi_clk=~axi_clk; // fast EMIF
|
||||
always #10 video_clk=~video_clk; // video
|
||||
|
||||
logic frame_start=0, in_window=0;
|
||||
logic [11:0] pixel_x=0, pixel_y=0;
|
||||
wire [7:0] r,g,b;
|
||||
wire line_valid, underflow;
|
||||
wire [31:0] rd_errs;
|
||||
wire [29:0] araddr; wire [1:0] arburst; wire [6:0] arid;
|
||||
wire [7:0] arlen; wire [2:0] arsize; wire arvalid, rready;
|
||||
logic arready=0, rvalid=0, rlast=0;
|
||||
logic [255:0] rdata=0; logic [1:0] rresp=0;
|
||||
|
||||
logic [255:0] mem [0:N_ROWS*ROW_BEATS-1];
|
||||
initial begin
|
||||
for (int beat=0; beat<N_ROWS*ROW_BEATS; beat++) mem[beat]='0;
|
||||
for (int y=0; y<N_ROWS; y++)
|
||||
for (int x=0; x<SRC_W; x++)
|
||||
mem[y*ROW_BEATS + (x>>3)][(x&7)*32 +: 32] = {8'hff, 8'(8'h80+x+y), 8'(y), 8'(x)};
|
||||
end
|
||||
|
||||
gs_lpddr_scanout_lb #(
|
||||
.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(N_ROWS), .PSMCT32(1'b1), .H_STRETCH_5_TO_4(1'b1),
|
||||
.V_SOURCE_START(VSTART), .V_STRETCH_15_TO_14(1'b1),
|
||||
.V_LINEAR_FILTER(1'b0), .H_LINEAR_FILTER(1'b0),
|
||||
.H_SOURCE_PIXELS(SRC_W), .BINOMIAL_3X3_FILTER(1'b1)
|
||||
) dut (
|
||||
.axi_clk(axi_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), .araddr(araddr), .arburst(arburst), .arid(arid),
|
||||
.arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready)
|
||||
);
|
||||
|
||||
// ---- REALISTIC EMIF responder: AR accept, then R after LAT cycles, with
|
||||
// periodic extra backpressure. Stresses the prefetch so a lead-1 design
|
||||
// would starve the binomial lookahead (as the board did). ----
|
||||
localparam int LAT=7;
|
||||
typedef enum logic [1:0] {S_AR, S_WAIT, S_R} state_t;
|
||||
state_t state=S_AR;
|
||||
localparam int MEM_BEAT_BITS=$clog2(N_ROWS*ROW_BEATS);
|
||||
logic [MEM_BEAT_BITS-1:0] beat_q;
|
||||
int wait_c=0; logic [3:0] bp=0;
|
||||
always_ff @(posedge axi_clk) begin
|
||||
arready <= 1'b0;
|
||||
bp <= bp + 1'b1;
|
||||
if (!rst_n) begin state<=S_AR; rvalid<=1'b0; rlast<=1'b0; wait_c<=0; end
|
||||
else case (state)
|
||||
S_AR: if (arvalid && !arready) begin
|
||||
beat_q<=araddr[MEM_BEAT_BITS+4:5]; arready<=1'b1; wait_c<=LAT + (bp[1:0]); state<=S_WAIT;
|
||||
end
|
||||
S_WAIT: begin
|
||||
if (wait_c>0) wait_c<=wait_c-1;
|
||||
else begin rdata<=mem[beat_q]; rresp<=2'b00; rlast<=1'b1; rvalid<=1'b1; state<=S_R; end
|
||||
end
|
||||
S_R: if (rready && rvalid) begin rvalid<=1'b0; rlast<=1'b0; state<=S_AR; end
|
||||
endcase
|
||||
end
|
||||
|
||||
// ---- golden binomial oracle (division only in TB) ----
|
||||
function automatic int source_x(input int ox); source_x=(ox*4)/5; endfunction
|
||||
function automatic int source_y(input int oy); source_y=VSTART+(oy*14)/15; endfunction
|
||||
function automatic int clamp_x(input int x); clamp_x=(x<0)?0:((x>=SRC_W)?SRC_W-1:x); endfunction
|
||||
function automatic int clamp_y(input int y); clamp_y=(y<VSTART)?VSTART:((y>=N_ROWS)?N_ROWS-1:y); endfunction
|
||||
function automatic int sample(input int ch, input int x, input int y);
|
||||
int xx,yy; begin xx=clamp_x(x); yy=clamp_y(y);
|
||||
case (ch) 0:sample=xx; 1:sample=yy; default:sample=8'h80+xx+yy; endcase end
|
||||
endfunction
|
||||
function automatic int hbin(input int ch, input int x, input int y);
|
||||
hbin=(sample(ch,x-1,y)+2*sample(ch,x,y)+sample(ch,x+1,y)+2)/4; endfunction
|
||||
function automatic int binomial(input int ch, input int x, input int y);
|
||||
binomial=(hbin(ch,x,y-1)+2*hbin(ch,x,y)+hbin(ch,x,y+1)+2)/4; endfunction
|
||||
|
||||
int errors=0, checked=0;
|
||||
logic check_en=0, uflow_sticky=0, cov_midframe_vphase=0;
|
||||
logic [11:0] x_d, y_d; logic in_d;
|
||||
always_ff @(posedge video_clk) begin
|
||||
x_d<=pixel_x; y_d<=pixel_y; in_d<=in_window;
|
||||
if (rst_n && enable && underflow) uflow_sticky<=1'b1; // ANY underflow at ANY time fails
|
||||
// coverage: a mid-frame source row (> VSTART+1) is on display with vphase!=0
|
||||
if (rst_n && enable && in_window && dut.scan_y > ($clog2(N_ROWS)+1)'(VSTART+1)
|
||||
&& dut.stretch_vphase_q != 4'd0)
|
||||
cov_midframe_vphase<=1'b1;
|
||||
if (check_en && in_d) begin
|
||||
int sx,sy,er,eg,eb;
|
||||
sx=source_x(x_d); sy=source_y(y_d);
|
||||
er=binomial(0,sx,sy); eg=binomial(1,sx,sy); eb=binomial(2,sx,sy);
|
||||
checked++;
|
||||
if (r!==8'(er)||g!==8'(eg)||b!==8'(eb)) begin
|
||||
if (errors<32) $display("[binlook] out=(%0d,%0d) src=(%0d,%0d) got=%02x/%02x/%02x exp=%02x/%02x/%02x",
|
||||
x_d,y_d,sx,sy,r,g,b,8'(er),8'(eg),8'(eb));
|
||||
errors++;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
repeat(8) @(posedge axi_clk); rst_n=1; enable=1;
|
||||
frame_start=1; repeat(3) @(posedge video_clk); frame_start=0;
|
||||
repeat(400) @(posedge axi_clk); // let the lead-2 prefetch prime rows 32,33,34,35
|
||||
repeat(3) @(posedge video_clk);
|
||||
check_en=1;
|
||||
for (int y=0; y<OUT_H; y++) begin
|
||||
for (int x=0; x<OUT_W; x++) begin
|
||||
@(negedge video_clk); pixel_x=12'(x); pixel_y=12'(y); in_window=1;
|
||||
@(posedge video_clk);
|
||||
end
|
||||
@(negedge video_clk); in_window=0;
|
||||
repeat(25) @(posedge video_clk); // horizontal blank between output lines
|
||||
end
|
||||
check_en=0;
|
||||
$display("[binlook] checked=%0d errors=%0d underflow(sticky)=%0b rd_errs=%0d cov_midframe_vphase=%0b",
|
||||
checked,errors,uflow_sticky,rd_errs,cov_midframe_vphase);
|
||||
if (checked==OUT_W*OUT_H && errors==0 && !uflow_sticky && rd_errs==0 && cov_midframe_vphase)
|
||||
$display("[tb_gs_scanout_binomial_lookahead] PASS");
|
||||
else
|
||||
$display("[tb_gs_scanout_binomial_lookahead] FAIL");
|
||||
$finish;
|
||||
end
|
||||
initial begin #4_000_000; $display("[tb_gs_scanout_binomial_lookahead] TIMEOUT"); $finish; end
|
||||
endmodule
|
||||
Reference in New Issue
Block a user