ba74bbd5aa
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog TBs, fidelity attribution tooling. Functional baseline before removing the dead bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
780 lines
45 KiB
Systemverilog
780 lines
45 KiB
Systemverilog
// ============================================================================
|
|
// gs_lpddr_scanout_lb.sv (Ch321 Brick 2)
|
|
//
|
|
// LINE-BUFFER LPDDR4B scanout — the architectural successor to the whole-frame
|
|
// cache (gs_lpddr_scanout). Instead of mirroring the entire framebuffer in
|
|
// on-chip RAM (which defeats the point of putting the FB in LPDDR), this holds
|
|
// just two scanlines (three with the optional Ch438 low-pass): it displays row
|
|
// L while prefetching row L+1. On-chip cost is O(width), not O(width*height).
|
|
//
|
|
// NARROW SCOPE (Ch321): the 128x128 PSMCT16 demo. The frame is LINEAR (the GS
|
|
// writer mirrors the rasterizer's linear flush addresses), display window at
|
|
// origin, 1:1 (MAG off) — so the reader serves pixel (col=pixel_x, line=pixel_y)
|
|
// directly when inside the window. No general MAG/window handling beyond that.
|
|
//
|
|
// Two clock domains:
|
|
// axi_clk (emif_clk) — AXI4 burst-read one row (ROW_BEATS beats) into a buffer
|
|
// video_clk (design) — pixel_x/pixel_y index the active line buffer -> r/g/b
|
|
//
|
|
// Prefetch handshake: on each new display line (and at frame start) the video
|
|
// side requests the next FB row via a toggle; the axi side fills the OTHER
|
|
// buffer. `underflow` flags any pixel read before its row finished loading.
|
|
// ============================================================================
|
|
`timescale 1ns/1ps
|
|
|
|
module gs_lpddr_scanout_lb #(
|
|
parameter [29:0] FB_BASE = 30'd0,
|
|
parameter int STRIDE_BYTES = 256, // PSMCT16 128px*2B=256; PSMCT32 128px*4B=512
|
|
parameter int ROW_BEATS = 8, // STRIDE_BYTES / 32 (PSMCT16 128px=8; PSMCT32 128px=16)
|
|
parameter int N_ROWS = 128,
|
|
// Ch327a — PSMCT32 (ABGR8888, 8 px/256-bit beat) vs the original PSMCT16 (RGBA5551,
|
|
// 16 px/beat). The Ch326 LPDDR-only spill framebuffer is PSMCT32 @ COLOR_SPILL_BASE, so the
|
|
// line-buffer must decode it — NOT a config flip of the Ch321 PSMCT16/FB-at-0 path.
|
|
parameter bit PSMCT32 = 1'b0,
|
|
// Ch418 — the captured SH3 DISPLAY2 is 512 source pixels wide
|
|
// (FBW=8) and MAGH=4, i.e. five VCKs per source sample. The board
|
|
// emits a 640-pixel active line, so reducing that authentic 2560-VCK
|
|
// display domain to VGA requires the exact nearest-neighbour map
|
|
// source_x = floor(display_x * 4 / 5).
|
|
// Keep this opt-in: legacy demos and directed scanout tests remain 1:1.
|
|
// The implementation below is a five-state phase accumulator, not an
|
|
// inferred divider, and therefore adds no wide arithmetic timing cone.
|
|
parameter bit H_STRETCH_5_TO_4 = 1'b0,
|
|
// Captured SH3 interlace presentation: DISPLAY2.DH=895 represents
|
|
// 448 source lines, beginning at DISPFB2.DBY=32. Mapping those lines
|
|
// onto the board's 480-line active raster is source_y =
|
|
// V_SOURCE_START + floor(display_y*14/15). As with horizontal scale,
|
|
// this remains opt-in and uses a tiny phase accumulator.
|
|
parameter int V_SOURCE_START = 0,
|
|
parameter bit V_STRETCH_15_TO_14 = 1'b0,
|
|
// Ch436 — optional linear reconstruction between the two resident source
|
|
// rows. The 15:14 mapper already keeps row L and L+1 in the alternating
|
|
// line buffers, so this adds no framebuffer traffic or line storage.
|
|
parameter bit V_LINEAR_FILTER = 1'b0,
|
|
// Ch437 — horizontal linear reconstruction for the 5:4 presentation map.
|
|
// A two-beat register cache provides x and x+1 from each resident row while
|
|
// retaining ONE read port per physical line buffer. H_SOURCE_PIXELS is the
|
|
// active source width (512 for SH3 DISPLAY2); zero means the physical stride.
|
|
parameter bit H_LINEAR_FILTER = 1'b0,
|
|
parameter int H_SOURCE_PIXELS = 0,
|
|
// Ch438 — separable [1 2 1]/4 low-pass reconstruction in source space.
|
|
// Three rotating line buffers retain rows y-1/y/y+1; the horizontal taps
|
|
// reuse the Ch437 two-beat cache, so every physical RAM still has exactly
|
|
// one registered read port. This is mutually exclusive with the Ch436/437
|
|
// linear filters in the SH3 profile and uses only adds plus shifts.
|
|
parameter bit BINOMIAL_3X3_FILTER = 1'b0
|
|
)(
|
|
// ---- AXI read clock domain (emif_clk) ----
|
|
input logic axi_clk,
|
|
input logic axi_rst_n,
|
|
input logic enable, // 1 = active (prefetch + serve)
|
|
|
|
// ---- video clock domain (design_clk) ----
|
|
input logic video_clk,
|
|
input logic frame_start, // vsync pulse/level (synced internally)
|
|
input logic [11:0] pixel_x, // raster column (display)
|
|
input logic [11:0] pixel_y, // raster line (display)
|
|
input logic in_window, // PCRTC displayed-frame window gate
|
|
output logic [7:0] r,
|
|
output logic [7:0] g,
|
|
output logic [7:0] b,
|
|
|
|
// ---- status (axi_clk domain; bridge syncs) ----
|
|
output logic line_valid, // at least one row has been loaded
|
|
output logic underflow, // a pixel was read before its row was ready (sticky)
|
|
output logic [31:0] rd_errs, // non-OKAY read responses (cumulative)
|
|
|
|
// ---- AXI4 read channel to the EMIF user port (axi_clk, 256-bit) ----
|
|
output logic [29:0] araddr,
|
|
output logic [1:0] arburst,
|
|
output logic [6:0] arid,
|
|
output logic [7:0] arlen,
|
|
output logic [2:0] arsize,
|
|
output logic arvalid,
|
|
input logic arready,
|
|
input logic [255:0] rdata,
|
|
input logic [1:0] rresp,
|
|
input logic rlast,
|
|
input logic rvalid,
|
|
output logic rready
|
|
);
|
|
localparam int RB_BITS = $clog2(ROW_BEATS); // 3 for 8
|
|
|
|
assign arburst = 2'b01; // INCR
|
|
assign arid = 7'd3; // distinct: writer=0, probe=1, frame-cache=2, line-buf=3
|
|
assign arlen = 8'd0; // SINGLE-BEAT per read — the only AXI read pattern proven on this
|
|
// EMIF (writer/probe/frame-cache all use arlen=0). A multi-beat
|
|
// burst (arlen=ROW_BEATS-1) was untested and garbled on hardware.
|
|
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.
|
|
logic [255:0] lb0 [0:ROW_BEATS-1];
|
|
logic [255:0] lb1 [0:ROW_BEATS-1];
|
|
logic [255:0] lb2 [0:ROW_BEATS-1];
|
|
|
|
// ================= video side (video_clk) =================
|
|
// No miss-prone request toggle. The video side just exposes the current
|
|
// in-window display row; the axi side free-runs, fetching rows sequentially
|
|
// and staying one row ahead (see below). disp_row_v resets on vsync.
|
|
logic [$clog2(N_ROWS):0] disp_row_v;
|
|
logic [2:0] fs_sync_v;
|
|
wire fs_edge_v = fs_sync_v[1] && !fs_sync_v[2]; // RISING edge only: one reset per frame_start pulse
|
|
// The buffer holding display line L is L&1 (row L is fetched into L&1). Select
|
|
// it DIRECTLY from pixel_y[0] (tracks the current pixel) — a separately-registered
|
|
// "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;
|
|
logic [1:0] stretch_buf_q;
|
|
logic in_window_v_q;
|
|
always_ff @(posedge video_clk) begin
|
|
if (!enable) begin
|
|
stretch_src_y_q <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
|
|
stretch_vphase_q <= 4'd0;
|
|
stretch_buf_q <= 2'(V_SOURCE_BUF);
|
|
in_window_v_q <= 1'b0;
|
|
end else begin
|
|
in_window_v_q <= in_window;
|
|
if (fs_edge_v) begin
|
|
stretch_src_y_q <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
|
|
stretch_vphase_q <= 4'd0;
|
|
stretch_buf_q <= 2'(V_SOURCE_BUF);
|
|
end else if (V_STRETCH_15_TO_14 && in_window_v_q && !in_window) begin
|
|
// End of one output line. phase 0 repeats the current
|
|
// source line once; phases 14..1 advance while counting
|
|
// down, giving 15 output lines per 14 source lines.
|
|
if (stretch_vphase_q == 4'd0)
|
|
stretch_vphase_q <= 4'd14;
|
|
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;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
wire [$clog2(N_ROWS):0] scan_y = V_STRETCH_15_TO_14
|
|
? stretch_src_y_q
|
|
: ($clog2(N_ROWS)+1)'(pixel_y);
|
|
wire disp_buf = scan_y[0];
|
|
wire [1:0] scan_buf3 = stretch_buf_q;
|
|
|
|
always_ff @(posedge video_clk) begin
|
|
if (!enable) begin
|
|
disp_row_v <= ($clog2(N_ROWS)+1)'(V_SOURCE_START); fs_sync_v <= 3'd0;
|
|
end else begin
|
|
fs_sync_v <= {fs_sync_v[1:0], frame_start};
|
|
if (fs_edge_v) disp_row_v <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
|
|
// Publish a stretch-row advance during horizontal blanking, not
|
|
// only after the first active pixel. The AXI side then has the
|
|
// full blank interval to replace the retired parity buffer with
|
|
// row L+1 before linear scanout needs it.
|
|
else if (scan_y < ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
disp_row_v <= scan_y;
|
|
end
|
|
end
|
|
|
|
// Ch418 horizontal presentation mapper. At output x=0 the state is
|
|
// {src=0,phase=0}; successive active clocks produce source columns
|
|
// 0,0,1,2,3,4,4,5,6,7,...,511
|
|
// for output columns 0..639. Blanking resets the state before each line.
|
|
// phase==0 is the sole repeat; all other phases advance source_x. This is
|
|
// algebraically identical to floor(x*4/5), using only a 3-bit decrement
|
|
// and a 12-bit increment.
|
|
logic [11:0] stretch_src_x_q;
|
|
logic [2:0] stretch_phase_q;
|
|
always_ff @(posedge video_clk) begin
|
|
if (!enable || !in_window) begin
|
|
stretch_src_x_q <= 12'd0;
|
|
stretch_phase_q <= 3'd0;
|
|
end else if (H_STRETCH_5_TO_4) begin
|
|
if (stretch_phase_q == 3'd0) begin
|
|
stretch_phase_q <= 3'd4;
|
|
end else begin
|
|
stretch_src_x_q <= stretch_src_x_q + 12'd1;
|
|
stretch_phase_q <= stretch_phase_q - 3'd1;
|
|
end
|
|
end
|
|
end
|
|
wire [11:0] scan_x = H_STRETCH_5_TO_4 ? stretch_src_x_q : pixel_x;
|
|
|
|
// Registered (sync-read) pixel: pick buffer + beat + within-beat lane from scan_x.
|
|
// PSMCT32: 8 px/256-bit beat -> beat = scan_x>>3, lane = scan_x[2:0] (32-bit).
|
|
// PSMCT16: 16 px/beat -> beat = scan_x>>4, lane = scan_x[3:0] (16-bit).
|
|
localparam int PXSH = PSMCT32 ? 3 : 4; // px-per-beat shift
|
|
localparam int PX_PER_ROW = PSMCT32 ? (STRIDE_BYTES/4) : (STRIDE_BYTES/2);
|
|
localparam int H_SOURCE_PX = (H_SOURCE_PIXELS != 0) ? H_SOURCE_PIXELS : PX_PER_ROW;
|
|
localparam int H_LAST_BEAT = (H_SOURCE_PX-1) >> PXSH;
|
|
wire [RB_BITS-1:0] col_beat = scan_x[RB_BITS+PXSH-1 -: RB_BITS];
|
|
wire [3:0] col_lane = PSMCT32 ? {1'b0, scan_x[2:0]} : scan_x[3:0];
|
|
logic [255:0] prv_word_q, cur_word_q, nxt_word_q;
|
|
// Only lane zero of the lookahead beat can be selected: x+1 crosses a
|
|
// beat exactly when x is its final lane. Keep 32 bits, not another pair
|
|
// of 256-bit payload registers.
|
|
logic [31:0] prv_left_px_q, cur_left_px_q, nxt_left_px_q;
|
|
logic [31:0] prv_look_px_q, cur_look_px_q, nxt_look_px_q;
|
|
logic [3:0] lane_q, vphase_q;
|
|
logic [2:0] hphase_q;
|
|
logic first_source_x_q, last_source_x_q;
|
|
logic in_q;
|
|
generate
|
|
if (H_LINEAR_FILTER || BINOMIAL_3X3_FILTER) begin : g_hlinear_cache
|
|
// Slot parity equals beat parity. Horizontal blanking continually
|
|
// primes beats 0 and 1 after the vertical row selector settles.
|
|
// On entry to each subsequent beat, the retired slot is refilled
|
|
// with beat+1. No second line-buffer read port is required.
|
|
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 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;
|
|
|
|
// Keep the inferred line-buffer read ports canonical: exactly one
|
|
// unconditional registered address and one registered data output
|
|
// per physical array. The prior conditional multi-address reads
|
|
// made Quartus expand both arrays into 30,720 flip-flops.
|
|
always_ff @(posedge video_clk) begin
|
|
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];
|
|
video_rd_tag_q <= video_rd_addr_q;
|
|
video_rd_valid_q <= video_rd_req_q;
|
|
end
|
|
|
|
always_ff @(posedge video_clk) begin
|
|
// The registered RAM response arrives with its beat tag two
|
|
// control edges after the request. Store physical lb0/lb1
|
|
// independently; row parity is selected only at pixel output.
|
|
if (video_rd_valid_q) begin
|
|
if (video_rd_tag_q[0]) begin
|
|
lb0_cache1_q <= lb0_video_rd_q;
|
|
lb1_cache1_q <= lb1_video_rd_q;
|
|
lb2_cache1_q <= lb2_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;
|
|
end
|
|
end
|
|
|
|
if (!enable) begin
|
|
blank_prime_q <= 1'b0;
|
|
active_beat_q <= '0;
|
|
video_rd_addr_q <= '0;
|
|
video_rd_req_q <= 1'b0;
|
|
in_q <= 1'b0;
|
|
end else if (!in_window) begin
|
|
// Alternate requests for beats 0 and 1 throughout blank.
|
|
// VGA supplies far more than the four clocks needed for
|
|
// both registered responses to settle into the cache.
|
|
video_rd_addr_q <= blank_prime_q ? RB_BITS'(1) : '0;
|
|
video_rd_req_q <= 1'b1;
|
|
blank_prime_q <= ~blank_prime_q;
|
|
active_beat_q <= '0;
|
|
in_q <= 1'b0;
|
|
end else begin
|
|
video_rd_req_q <= 1'b0;
|
|
// Source x and x+1 come from the two cached beats. The
|
|
// current beat is selected by parity; the opposite slot
|
|
// is its already-fetched successor.
|
|
if (BINOMIAL_3X3_FILTER) begin
|
|
// Select the three rotating physical rows. The first
|
|
// displayed source row clamps y-1 to y; the final row
|
|
// similarly clamps y+1. For a lane-zero sample the
|
|
// opposite cache slot still contains the preceding
|
|
// beat; by the time lane seven needs x+1 it contains
|
|
// the newly fetched successor beat.
|
|
case (scan_buf3)
|
|
2'd0: begin
|
|
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);
|
|
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]);
|
|
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]);
|
|
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
|
|
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);
|
|
cur_word_q <= col_beat[0] ? lb1_cache1_q : lb1_cache0_q;
|
|
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
|
|
: (col_beat[0] ? lb2_cache1_q : lb2_cache0_q);
|
|
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
|
? (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224])
|
|
: (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224]);
|
|
cur_left_px_q <= col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224];
|
|
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224])
|
|
: (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224]);
|
|
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
|
|
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
|
|
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
|
|
cur_look_px_q <= col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0];
|
|
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? (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
|
|
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);
|
|
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]);
|
|
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] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
|
|
end
|
|
endcase
|
|
end else if (disp_buf) begin
|
|
prv_word_q <= '0;
|
|
cur_word_q <= col_beat[0] ? lb1_cache1_q : lb1_cache0_q;
|
|
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
|
|
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
|
|
cur_look_px_q <= col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0];
|
|
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
|
|
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
|
|
end else begin
|
|
prv_word_q <= '0;
|
|
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);
|
|
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
|
|
if (!BINOMIAL_3X3_FILTER) begin
|
|
prv_left_px_q <= '0;
|
|
cur_left_px_q <= '0;
|
|
nxt_left_px_q <= '0;
|
|
prv_look_px_q <= '0;
|
|
end
|
|
lane_q <= col_lane;
|
|
vphase_q <= stretch_vphase_q;
|
|
hphase_q <= stretch_phase_q;
|
|
first_source_x_q <= (scan_x == 12'd0);
|
|
last_source_x_q <= (scan_x >= 12'(H_SOURCE_PX-1));
|
|
in_q <= (pixel_x < PX_PER_ROW) && (pixel_y < N_ROWS);
|
|
|
|
if ((col_beat != active_beat_q) &&
|
|
(col_beat < RB_BITS'(H_LAST_BEAT))) begin
|
|
active_beat_q <= col_beat;
|
|
video_rd_addr_q <= col_beat + 1'b1;
|
|
video_rd_req_q <= 1'b1;
|
|
end
|
|
end
|
|
end
|
|
end else begin : g_direct_read
|
|
always_ff @(posedge video_clk) begin
|
|
// One video read from each physical buffer supplies the current row
|
|
// and its already-prefetched successor in parallel. Select them in
|
|
// this same registered stage so parity cannot lag at a line boundary.
|
|
if (disp_buf) begin
|
|
prv_word_q <= '0;
|
|
cur_word_q <= lb1[col_beat];
|
|
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? lb1[col_beat] : lb0[col_beat];
|
|
end else begin
|
|
prv_word_q <= '0;
|
|
cur_word_q <= lb0[col_beat];
|
|
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
|
|
? lb0[col_beat] : lb1[col_beat];
|
|
end
|
|
cur_look_px_q <= '0;
|
|
nxt_look_px_q <= '0;
|
|
prv_left_px_q <= '0;
|
|
cur_left_px_q <= '0;
|
|
nxt_left_px_q <= '0;
|
|
prv_look_px_q <= '0;
|
|
lane_q <= col_lane;
|
|
vphase_q <= stretch_vphase_q;
|
|
hphase_q <= stretch_phase_q;
|
|
first_source_x_q <= 1'b1;
|
|
last_source_x_q <= 1'b1;
|
|
in_q <= in_window && (pixel_x < PX_PER_ROW) && (pixel_y < N_ROWS);
|
|
end
|
|
end
|
|
endgenerate
|
|
|
|
// PSMCT32 ABGR8888 (r=[7:0],g=[15:8],b=[23:16]) — matches gs_lpddr_scanout (frame-cache).
|
|
wire [31:0] px32_prv = prv_word_q[lane_q[2:0]*32 +: 32];
|
|
wire [31:0] px32_cur = cur_word_q[lane_q[2:0]*32 +: 32];
|
|
wire [31:0] px32_nxt = nxt_word_q[lane_q[2:0]*32 +: 32];
|
|
wire [31:0] px32_prv_left = first_source_x_q ? px32_prv :
|
|
((lane_q[2:0] == 3'd0)
|
|
? prv_left_px_q
|
|
: prv_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
|
|
wire [31:0] px32_cur_left = first_source_x_q ? px32_cur :
|
|
((lane_q[2:0] == 3'd0)
|
|
? cur_left_px_q
|
|
: cur_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
|
|
wire [31:0] px32_nxt_left = first_source_x_q ? px32_nxt :
|
|
((lane_q[2:0] == 3'd0)
|
|
? nxt_left_px_q
|
|
: nxt_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
|
|
wire [31:0] px32_prv_right = last_source_x_q ? px32_prv :
|
|
((lane_q[2:0] == 3'd7)
|
|
? prv_look_px_q
|
|
: prv_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
|
|
wire [31:0] px32_cur_right = last_source_x_q ? px32_cur :
|
|
((lane_q[2:0] == 3'd7)
|
|
? cur_look_px_q
|
|
: cur_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
|
|
wire [31:0] px32_nxt_right = last_source_x_q ? px32_nxt :
|
|
((lane_q[2:0] == 3'd7)
|
|
? nxt_look_px_q
|
|
: nxt_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
|
|
// PSMCT16 RGBA5551 5-bit lanes expanded to 8-bit.
|
|
wire [15:0] px16_prv = prv_word_q[lane_q*16 +: 16];
|
|
wire [15:0] px16_cur = cur_word_q[lane_q*16 +: 16];
|
|
wire [15:0] px16_nxt = nxt_word_q[lane_q*16 +: 16];
|
|
wire [15:0] px16_prv_left = first_source_x_q ? px16_prv :
|
|
((lane_q == 4'd0)
|
|
? prv_left_px_q[15:0]
|
|
: prv_word_q[(lane_q-1'b1)*16 +: 16]);
|
|
wire [15:0] px16_cur_left = first_source_x_q ? px16_cur :
|
|
((lane_q == 4'd0)
|
|
? cur_left_px_q[15:0]
|
|
: cur_word_q[(lane_q-1'b1)*16 +: 16]);
|
|
wire [15:0] px16_nxt_left = first_source_x_q ? px16_nxt :
|
|
((lane_q == 4'd0)
|
|
? nxt_left_px_q[15:0]
|
|
: nxt_word_q[(lane_q-1'b1)*16 +: 16]);
|
|
wire [15:0] px16_prv_right = last_source_x_q ? px16_prv :
|
|
((lane_q == 4'd15)
|
|
? prv_look_px_q[15:0]
|
|
: prv_word_q[(lane_q+1'b1)*16 +: 16]);
|
|
wire [15:0] px16_cur_right = last_source_x_q ? px16_cur :
|
|
((lane_q == 4'd15)
|
|
? cur_look_px_q[15:0]
|
|
: cur_word_q[(lane_q+1'b1)*16 +: 16]);
|
|
wire [15:0] px16_nxt_right = last_source_x_q ? px16_nxt :
|
|
((lane_q == 4'd15)
|
|
? nxt_look_px_q[15:0]
|
|
: nxt_word_q[(lane_q+1'b1)*16 +: 16]);
|
|
wire [7:0] r16_cur = {px16_cur[4:0], px16_cur[4:2]};
|
|
wire [7:0] g16_cur = {px16_cur[9:5], px16_cur[9:7]};
|
|
wire [7:0] b16_cur = {px16_cur[14:10], px16_cur[14:12]};
|
|
wire [7:0] r16_nxt = {px16_nxt[4:0], px16_nxt[4:2]};
|
|
wire [7:0] g16_nxt = {px16_nxt[9:5], px16_nxt[9:7]};
|
|
wire [7:0] b16_nxt = {px16_nxt[14:10], px16_nxt[14:12]};
|
|
wire [7:0] r16_prv = {px16_prv[4:0], px16_prv[4:2]};
|
|
wire [7:0] g16_prv = {px16_prv[9:5], px16_prv[9:7]};
|
|
wire [7:0] b16_prv = {px16_prv[14:10], px16_prv[14:12]};
|
|
wire [7:0] r16_prv_left = {px16_prv_left[4:0], px16_prv_left[4:2]};
|
|
wire [7:0] g16_prv_left = {px16_prv_left[9:5], px16_prv_left[9:7]};
|
|
wire [7:0] b16_prv_left = {px16_prv_left[14:10], px16_prv_left[14:12]};
|
|
wire [7:0] r16_cur_left = {px16_cur_left[4:0], px16_cur_left[4:2]};
|
|
wire [7:0] g16_cur_left = {px16_cur_left[9:5], px16_cur_left[9:7]};
|
|
wire [7:0] b16_cur_left = {px16_cur_left[14:10], px16_cur_left[14:12]};
|
|
wire [7:0] r16_nxt_left = {px16_nxt_left[4:0], px16_nxt_left[4:2]};
|
|
wire [7:0] g16_nxt_left = {px16_nxt_left[9:5], px16_nxt_left[9:7]};
|
|
wire [7:0] b16_nxt_left = {px16_nxt_left[14:10], px16_nxt_left[14:12]};
|
|
wire [7:0] r16_prv_right = {px16_prv_right[4:0], px16_prv_right[4:2]};
|
|
wire [7:0] g16_prv_right = {px16_prv_right[9:5], px16_prv_right[9:7]};
|
|
wire [7:0] b16_prv_right = {px16_prv_right[14:10], px16_prv_right[14:12]};
|
|
wire [7:0] r16_cur_right = {px16_cur_right[4:0], px16_cur_right[4:2]};
|
|
wire [7:0] g16_cur_right = {px16_cur_right[9:5], px16_cur_right[9:7]};
|
|
wire [7:0] b16_cur_right = {px16_cur_right[14:10], px16_cur_right[14:12]};
|
|
wire [7:0] r16_nxt_right = {px16_nxt_right[4:0], px16_nxt_right[4:2]};
|
|
wire [7:0] g16_nxt_right = {px16_nxt_right[9:5], px16_nxt_right[9:7]};
|
|
wire [7:0] b16_nxt_right = {px16_nxt_right[14:10], px16_nxt_right[14:12]};
|
|
|
|
function automatic logic [7:0] blend15(
|
|
input logic [7:0] cur,
|
|
input logic [7:0] nxt,
|
|
input logic [3:0] frac
|
|
);
|
|
logic [12:0] weighted;
|
|
begin
|
|
if (frac == 4'd0)
|
|
blend15 = cur;
|
|
else begin
|
|
weighted = ((4'd15-frac) * cur) + (frac * nxt) + 13'd7;
|
|
blend15 = weighted / 13'd15;
|
|
end
|
|
end
|
|
endfunction
|
|
|
|
function automatic logic [7:0] blend5(
|
|
input logic [7:0] left,
|
|
input logic [7:0] right,
|
|
input logic [2:0] frac
|
|
);
|
|
logic [10:0] weighted;
|
|
begin
|
|
if (frac == 3'd0)
|
|
blend5 = left;
|
|
else begin
|
|
weighted = ((3'd5-frac) * left) + (frac * right) + 11'd2;
|
|
blend5 = weighted / 11'd5;
|
|
end
|
|
end
|
|
endfunction
|
|
|
|
function automatic logic [7:0] binom3(
|
|
input logic [7:0] left,
|
|
input logic [7:0] center,
|
|
input logic [7:0] right
|
|
);
|
|
logic [9:0] weighted;
|
|
begin
|
|
weighted = {2'b0,left} + {1'b0,center,1'b0} + {2'b0,right} + 10'd2;
|
|
binom3 = weighted[9:2];
|
|
end
|
|
endfunction
|
|
|
|
wire [7:0] prv_r = PSMCT32 ? px32_prv[7:0] : r16_prv;
|
|
wire [7:0] prv_g = PSMCT32 ? px32_prv[15:8] : g16_prv;
|
|
wire [7:0] prv_b = PSMCT32 ? px32_prv[23:16] : b16_prv;
|
|
wire [7:0] cur_r = PSMCT32 ? px32_cur[7:0] : r16_cur;
|
|
wire [7:0] cur_g = PSMCT32 ? px32_cur[15:8] : g16_cur;
|
|
wire [7:0] cur_b = PSMCT32 ? px32_cur[23:16] : b16_cur;
|
|
wire [7:0] nxt_r = PSMCT32 ? px32_nxt[7:0] : r16_nxt;
|
|
wire [7:0] nxt_g = PSMCT32 ? px32_nxt[15:8] : g16_nxt;
|
|
wire [7:0] nxt_b = PSMCT32 ? px32_nxt[23:16] : b16_nxt;
|
|
wire [7:0] prv_left_r = PSMCT32 ? px32_prv_left[7:0] : r16_prv_left;
|
|
wire [7:0] prv_left_g = PSMCT32 ? px32_prv_left[15:8] : g16_prv_left;
|
|
wire [7:0] prv_left_b = PSMCT32 ? px32_prv_left[23:16] : b16_prv_left;
|
|
wire [7:0] cur_left_r = PSMCT32 ? px32_cur_left[7:0] : r16_cur_left;
|
|
wire [7:0] cur_left_g = PSMCT32 ? px32_cur_left[15:8] : g16_cur_left;
|
|
wire [7:0] cur_left_b = PSMCT32 ? px32_cur_left[23:16] : b16_cur_left;
|
|
wire [7:0] nxt_left_r = PSMCT32 ? px32_nxt_left[7:0] : r16_nxt_left;
|
|
wire [7:0] nxt_left_g = PSMCT32 ? px32_nxt_left[15:8] : g16_nxt_left;
|
|
wire [7:0] nxt_left_b = PSMCT32 ? px32_nxt_left[23:16] : b16_nxt_left;
|
|
wire [7:0] prv_right_r = PSMCT32 ? px32_prv_right[7:0] : r16_prv_right;
|
|
wire [7:0] prv_right_g = PSMCT32 ? px32_prv_right[15:8] : g16_prv_right;
|
|
wire [7:0] prv_right_b = PSMCT32 ? px32_prv_right[23:16] : b16_prv_right;
|
|
wire [7:0] cur_right_r = PSMCT32 ? px32_cur_right[7:0] : r16_cur_right;
|
|
wire [7:0] cur_right_g = PSMCT32 ? px32_cur_right[15:8] : g16_cur_right;
|
|
wire [7:0] cur_right_b = PSMCT32 ? px32_cur_right[23:16] : b16_cur_right;
|
|
wire [7:0] nxt_right_r = PSMCT32 ? px32_nxt_right[7:0] : r16_nxt_right;
|
|
wire [7:0] nxt_right_g = PSMCT32 ? px32_nxt_right[15:8] : g16_nxt_right;
|
|
wire [7:0] nxt_right_b = PSMCT32 ? px32_nxt_right[23:16] : b16_nxt_right;
|
|
wire [3:0] filter_frac = V_LINEAR_FILTER ? vphase_q : 4'd0;
|
|
wire [2:0] hfilter_frac = H_LINEAR_FILTER ? hphase_q : 3'd0;
|
|
wire [7:0] left_r = blend15(cur_r, nxt_r, filter_frac);
|
|
wire [7:0] left_g = blend15(cur_g, nxt_g, filter_frac);
|
|
wire [7:0] left_b = blend15(cur_b, nxt_b, filter_frac);
|
|
wire [7:0] right_r = blend15(cur_right_r, nxt_right_r, filter_frac);
|
|
wire [7:0] right_g = blend15(cur_right_g, nxt_right_g, filter_frac);
|
|
wire [7:0] right_b = blend15(cur_right_b, nxt_right_b, filter_frac);
|
|
wire [7:0] linear_out_r = blend5(left_r, right_r, hfilter_frac);
|
|
wire [7:0] linear_out_g = blend5(left_g, right_g, hfilter_frac);
|
|
wire [7:0] linear_out_b = blend5(left_b, right_b, hfilter_frac);
|
|
wire [7:0] bin_prv_r = binom3(prv_left_r, prv_r, prv_right_r);
|
|
wire [7:0] bin_prv_g = binom3(prv_left_g, prv_g, prv_right_g);
|
|
wire [7:0] bin_prv_b = binom3(prv_left_b, prv_b, prv_right_b);
|
|
wire [7:0] bin_cur_r = binom3(cur_left_r, cur_r, cur_right_r);
|
|
wire [7:0] bin_cur_g = binom3(cur_left_g, cur_g, cur_right_g);
|
|
wire [7:0] bin_cur_b = binom3(cur_left_b, cur_b, cur_right_b);
|
|
wire [7:0] bin_nxt_r = binom3(nxt_left_r, nxt_r, nxt_right_r);
|
|
wire [7:0] bin_nxt_g = binom3(nxt_left_g, nxt_g, nxt_right_g);
|
|
wire [7:0] bin_nxt_b = binom3(nxt_left_b, nxt_b, nxt_right_b);
|
|
wire [7:0] out_r = BINOMIAL_3X3_FILTER ? binom3(bin_prv_r, bin_cur_r, bin_nxt_r)
|
|
: linear_out_r;
|
|
wire [7:0] out_g = BINOMIAL_3X3_FILTER ? binom3(bin_prv_g, bin_cur_g, bin_nxt_g)
|
|
: linear_out_g;
|
|
wire [7:0] out_b = BINOMIAL_3X3_FILTER ? binom3(bin_prv_b, bin_cur_b, bin_nxt_b)
|
|
: linear_out_b;
|
|
|
|
assign r = !in_q ? 8'd0 : out_r;
|
|
assign g = !in_q ? 8'd0 : out_g;
|
|
assign b = !in_q ? 8'd0 : out_b;
|
|
|
|
// ================= axi side (axi_clk) — row fill FSM =================
|
|
// free-running prefetcher: fetch rows sequentially, staying <= disp_row+1 ahead.
|
|
// disp_row crosses video->axi (slowly-changing; the +1 throttle tolerates a 1-off
|
|
// transient). frame_start is edge-detected here to reset next_fetch every frame.
|
|
logic [2:0] fs_sync_e;
|
|
wire fs_edge_e = fs_sync_e[1] && !fs_sync_e[2]; // RISING edge only: one prefetch restart per frame_start pulse
|
|
logic [$clog2(N_ROWS):0] disp_row_s0, disp_row_limit_e;
|
|
logic [$clog2(N_ROWS):0] next_fetch; // next row to load (0..N_ROWS)
|
|
logic [1:0] next_fetch_buf;
|
|
typedef enum logic [1:0] { L_IDLE, L_AR, L_R, L_C } lstate_t;
|
|
lstate_t lst;
|
|
logic [$clog2(N_ROWS):0] cur_row;
|
|
logic [1:0] cur_buf;
|
|
logic [RB_BITS:0] beat;
|
|
logic fs_pending; // a vsync restart is pending; applied in L_IDLE (never mid-read)
|
|
// Ch358 (Codex) — one-entry RESPONSE STAGE: the 26.1 STA leader (WNS -0.884 at 640) was the EMIF read-FIFO
|
|
// RAM output driving lb0/lb1's write port in the SAME rvalid cycle (RAM->bus->RAM in one 310MHz period).
|
|
// L_R now only CAPTURES {rdata, beat, buf, last} into per-buffer physical write registers. L_C commits the
|
|
// selected LB RAM; next_fetch/line_valid advance only after that commit. One outstanding read.
|
|
// Ch439b — physical write-port stage. The Ch438 fit exposed a route-only
|
|
// r_data_q -> lb1 RAM path at 310 MHz after adding the third line buffer.
|
|
// Give each inferred RAM its own data/address/enable launch registers so
|
|
// the fitter can place them beside that RAM instead of routing one shared
|
|
// 256-bit register bank across all three memories. Capture directly from
|
|
// AXI in L_R and commit in L_C: the first Ch439 L_R->L_C->L_W form added a
|
|
// 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;
|
|
|
|
always_ff @(posedge axi_clk) begin
|
|
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);
|
|
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;
|
|
end else begin
|
|
fs_sync_e <= {fs_sync_e[1:0], frame_start};
|
|
disp_row_s0 <= disp_row_v; // 2-FF sync of the display row
|
|
// Register the already-incremented throttle limit. This remains the
|
|
// 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;
|
|
// 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
|
|
// rdata->local-register->RAM timing cut without paying an L_C
|
|
// bubble after every beat. The final beat uses L_C only as a
|
|
// one-cycle row-end flush before next_fetch becomes visible.
|
|
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;
|
|
lb0_we_q <= 1'b0;
|
|
lb1_we_q <= 1'b0;
|
|
lb2_we_q <= 1'b0;
|
|
// vsync: mark a prefetch restart. DEFER it to L_IDLE so an in-flight AXI
|
|
// read is never aborted mid-handshake (which would deadlock the slave).
|
|
if (fs_edge_e) fs_pending <= 1'b1;
|
|
case (lst)
|
|
L_IDLE: begin
|
|
if (fs_pending) begin
|
|
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)};
|
|
// restart at the captured display source row
|
|
fs_pending <= 1'b0;
|
|
end else if (enable && (next_fetch < N_ROWS) && (next_fetch <= disp_row_limit_e)) begin
|
|
cur_row <= next_fetch;
|
|
cur_buf <= next_fetch_buf;
|
|
araddr <= FB_BASE + (next_fetch * STRIDE_BYTES);
|
|
beat <= '0;
|
|
arvalid <= 1'b1;
|
|
lst <= L_AR;
|
|
end
|
|
end
|
|
L_AR: begin
|
|
if (arready) begin
|
|
arvalid <= 1'b0;
|
|
rready <= 1'b1;
|
|
lst <= L_R;
|
|
end
|
|
end
|
|
L_R: begin
|
|
if (rvalid) begin
|
|
// Capture directly into the selected RAM-local port
|
|
// stage. L_C commits it on the following cycle.
|
|
lb0_we_q <= 1'b0;
|
|
lb1_we_q <= 1'b0;
|
|
lb2_we_q <= 1'b0;
|
|
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
|
|
default: begin lb0_wdata_q <= rdata; lb0_waddr_q <= beat[RB_BITS-1:0]; lb0_we_q <= 1'b1; end
|
|
endcase
|
|
if (rresp != 2'b00) rd_errs <= rd_errs + 32'd1;
|
|
rready <= 1'b0;
|
|
if (beat == ROW_BEATS-1) begin
|
|
// The local register captures this last response
|
|
// now; L_C flushes it into RAM on the next edge.
|
|
lst <= L_C;
|
|
end else begin
|
|
// Previous behavior inserted L_C here and lost one
|
|
// EMIF clock per beat. The RAM-local stage commits
|
|
// independently above, so immediately issue the
|
|
// next read just as the pre-Ch439 FSM did.
|
|
beat <= beat + 1'b1;
|
|
araddr <= araddr + 30'd32;
|
|
arvalid <= 1'b1;
|
|
lst <= L_AR;
|
|
end
|
|
end
|
|
end
|
|
L_C: begin
|
|
// The global pipeline commit above writes the final beat
|
|
// on this edge. Publish the completed row only now.
|
|
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 + 1'b1;
|
|
else
|
|
next_fetch_buf <= {1'b0, ~next_fetch_buf[0]};
|
|
lst <= L_IDLE;
|
|
end
|
|
default: lst <= L_IDLE;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// underflow (sticky, video domain): an in-window pixel for line pixel_y is read
|
|
// before that row was prefetched. The axi side loads rows 0..next_fetch-1, so row
|
|
// pixel_y is ready iff pixel_y < next_fetch. next_fetch crosses axi->video synced
|
|
// (slowly-changing; a 1-off transient is harmless). Resets on vsync.
|
|
logic [$clog2(N_ROWS):0] nf_s0, nf_v;
|
|
logic underflow_v;
|
|
always_ff @(posedge video_clk) begin
|
|
nf_s0 <= next_fetch; nf_v <= nf_s0;
|
|
if (!enable || fs_edge_v) underflow_v <= 1'b0;
|
|
else if (in_window && (scan_y < ($clog2(N_ROWS)+1)'(N_ROWS)) &&
|
|
((scan_y >= nf_v) ||
|
|
((BINOMIAL_3X3_FILTER ||
|
|
(V_LINEAR_FILTER && (stretch_vphase_q != 4'd0))) &&
|
|
(scan_y + 1'b1 < ($clog2(N_ROWS)+1)'(N_ROWS)) &&
|
|
(scan_y + 1'b1 >= nf_v))))
|
|
underflow_v <= 1'b1;
|
|
end
|
|
assign underflow = underflow_v;
|
|
endmodule
|