Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
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>
This commit is contained in:
@@ -25,7 +25,17 @@
|
||||
// overflow, done-ish (idle && fifo empty). enable=0 → fully inert.
|
||||
|
||||
module gs_lpddr_axi_master #(
|
||||
parameter int FIFO_DEPTH = 16
|
||||
parameter int FIFO_DEPTH = 16,
|
||||
// Ch353 — pixel width in BYTES: 2 = PSMCT16 (default, byte-identical to Ch318), 4 = PSMCT32.
|
||||
// A 32-byte (256-bit) beat holds 32/PIX_BYTES lanes (16 for PSMCT16, 8 for PSMCT32). The
|
||||
// &ns beat-complete test is unchanged (a full beat is always 32 strobe bits either way).
|
||||
parameter int PIX_BYTES = 2,
|
||||
// Ch357 (Codex) — ELASTIC_BACKPRESSURE: when 1, insert a one-entry elastic (skid) stage between the packer and the
|
||||
// async FIFO and expose real producer backpressure via px_ready (= the stage can accept a beat this cycle). The packer
|
||||
// then advances ONLY on px_emit && px_ready, and the partial/EOF flushes retry until the stage accepts — so a beat is
|
||||
// NEVER dropped even if the FIFO momentarily fills, WITHOUT relying on the "packer never outruns the drain" invariant.
|
||||
// Default 0 = the legacy direct-write packer, byte-identical (px_ready tied 1). Only u_zc_emit|u_c sets this to 1.
|
||||
parameter bit ELASTIC_BACKPRESSURE = 1'b0
|
||||
) (
|
||||
// GS clock domain — flush pixel stream
|
||||
input logic gs_clk,
|
||||
@@ -50,7 +60,15 @@ module gs_lpddr_axi_master #(
|
||||
input logic ctrl_commit,
|
||||
input logic px_emit,
|
||||
input logic [31:0] px_addr, // FB-relative byte address (raster_pixel_fb_addr_q)
|
||||
input logic [15:0] px_pix16,
|
||||
input logic [31:0] px_pix32, // pixel data; PSMCT16 callers drive {16'd0, pix16} (low PIX_BITS used)
|
||||
// Ch357 (Codex) — producer backpressure. ELASTIC_BACKPRESSURE=1: high when the packer can accept px_emit this cycle
|
||||
// (the elastic stage can take a beat); the producer must HOLD px_emit/px_addr/px_pix32 until px_ready. Tied 1 for the
|
||||
// legacy packer (no backpressure), so legacy producers that leave it unconnected are unaffected.
|
||||
output logic px_ready,
|
||||
// Ch353 — end-of-scene partial-beat flush (pulse). Sparse triangle coverage may leave the final
|
||||
// beat incomplete; pulse `flush` after the last px_emit to push the dangling partial beat. The
|
||||
// PSMCT16 tile path always completes beats on tile-row boundaries, so it ties this to 0.
|
||||
input logic flush,
|
||||
|
||||
// f2sdram (LPDDR AXI) clock domain
|
||||
input logic axi_clk,
|
||||
@@ -79,15 +97,29 @@ module gs_lpddr_axi_master #(
|
||||
output logic [31:0] bursts_issued,
|
||||
output logic [31:0] bresp_err_count,
|
||||
output logic [31:0] fifo_overflow_count,
|
||||
output logic idle
|
||||
output logic idle,
|
||||
// Ch353 (Codex) — ORDERED drain ack: high after the EOF marker pops (i.e. after the last data beat's BRESP).
|
||||
// Coherent in the axi_clk domain — the scanout gate uses THIS, not a GS-domain sample of fbw_idle.
|
||||
output logic frame_drained
|
||||
);
|
||||
localparam int PW = 320; // {addr[31:0], data[255:0], strb[31:0]}
|
||||
localparam int PW = 321; // {marker, addr[31:0], data[255:0], strb[31:0]}
|
||||
localparam int MARK = PW-1; // Ch353 (Codex) — ordered EOF marker bit. A flush pushes any partial beat then an
|
||||
// EOF marker; the AXI FSM sets frame_drained when it POPS the marker — which, by the
|
||||
// in-order FIFO + per-beat B handshake, happens only AFTER the last data beat's BRESP.
|
||||
// This is a coherent same-(axi)-domain drain ack for the scanout gate (no GS-domain
|
||||
// sampling of fbw_idle, no pointer-in-flight race).
|
||||
// Ch353 — pixel-width derived params. At PIX_BYTES=2 these reproduce the exact Ch318 PSMCT16 logic.
|
||||
localparam int PIX_BITS = PIX_BYTES * 8; // 16 or 32
|
||||
localparam int LANE_LO = (PIX_BYTES == 4) ? 2 : 1; // low addr bits inside one pixel ($clog2 PIX_BYTES)
|
||||
localparam logic [PIX_BYTES-1:0] STRB1 = '1; // per-pixel byte-strobe mask (2'b11 or 4'b1111)
|
||||
localparam logic [255:0] PIX_MASK = {{(256-PIX_BITS){1'b0}}, {PIX_BITS{1'b1}}};
|
||||
|
||||
// ============================ GS-clock PACKER ============================
|
||||
logic [31:0] cur_addr;
|
||||
logic [255:0] cur_data;
|
||||
logic [31:0] cur_strb;
|
||||
logic has_data;
|
||||
logic flush_pending; // Ch353 — latched end-of-scene flush request (pushes partial then EOF marker)
|
||||
logic fifo_wr;
|
||||
logic [PW-1:0] fifo_wdata;
|
||||
logic fifo_wfull;
|
||||
@@ -112,10 +144,6 @@ module gs_lpddr_axi_master #(
|
||||
end
|
||||
end
|
||||
|
||||
// High for the one cycle the snapshot updates. Admission is blocked then so the FSM never consumes a beat
|
||||
// straddling a config change (old base/arm on the pop cycle, new on the next).
|
||||
wire commit_edge = (commit_sync[2] != commit_sync[1]);
|
||||
|
||||
// arm crosses from axi_clk into gs_clk — 2-FF synchronizer (from the COHERENT latched arm).
|
||||
logic arm_s1, arm_gs;
|
||||
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
|
||||
@@ -123,35 +151,50 @@ module gs_lpddr_axi_master #(
|
||||
else begin arm_s1 <= arm_axi; arm_gs <= arm_s1; end
|
||||
end
|
||||
|
||||
// Ch357 (Codex) — effective FIFO write bus (muxed by the generate below) + one-entry elastic stage (elastic mode only).
|
||||
logic fifo_wr_ram; // drive to u_fifo.wr (already ANDed with !fifo_wfull in both modes)
|
||||
logic [PW-1:0] fifo_wdata_ram; // drive to u_fifo.wdata
|
||||
logic stage_valid; // elastic: a beat waits in the skid stage
|
||||
logic [PW-1:0] stage_data; // elastic: the staged beat
|
||||
wire stage_ready = !stage_valid || !fifo_wfull; // elastic: stage can accept a beat THIS cycle
|
||||
wire stage_drain = stage_valid && !fifo_wfull; // elastic: staged beat enters FIFO THIS cycle
|
||||
|
||||
generate
|
||||
if (!ELASTIC_BACKPRESSURE) begin : g_legacy
|
||||
// ------- LEGACY packer (byte-identical to the pre-Ch357 direct-write path) -------
|
||||
assign px_ready = 1'b1; // no backpressure exposed
|
||||
assign fifo_wr_ram = fifo_wr && !fifo_wfull;
|
||||
assign fifo_wdata_ram = fifo_wdata;
|
||||
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
|
||||
if (!gs_rst_n) begin
|
||||
cur_addr <= '0; cur_data <= '0; cur_strb <= '0; has_data <= 1'b0;
|
||||
fifo_wr <= 1'b0; fifo_wdata <= '0; fifo_overflow_count <= '0;
|
||||
fifo_wr <= 1'b0; fifo_wdata <= '0; fifo_overflow_count <= '0; flush_pending <= 1'b0;
|
||||
end else begin
|
||||
fifo_wr <= 1'b0;
|
||||
if (flush) flush_pending <= 1'b1; // Ch353 — latch the end-of-scene flush request
|
||||
if (enable && arm_gs && px_emit) begin // gate: no accumulation until armed
|
||||
logic [31:0] abeat;
|
||||
logic [3:0] lane; // 0..15 (which 16-bit lane)
|
||||
logic [4:0] lane; // 0..LANES-1 (which PIX_BYTES-wide lane in the 32-byte beat)
|
||||
logic [255:0] nd;
|
||||
logic [31:0] ns;
|
||||
abeat = {px_addr[31:5], 5'd0};
|
||||
lane = px_addr[4:1];
|
||||
lane = px_addr[4:0] >> LANE_LO;
|
||||
if (has_data && (abeat != cur_addr)) begin
|
||||
// line changed before the previous beat filled — flush it, restart
|
||||
fifo_wdata <= {cur_addr, cur_data, cur_strb};
|
||||
// line changed before the previous beat filled — flush it (marker=0), restart
|
||||
fifo_wdata <= {1'b0, cur_addr, cur_data, cur_strb};
|
||||
fifo_wr <= 1'b1;
|
||||
cur_addr <= abeat;
|
||||
cur_data <= (256'(px_pix16) << ({28'd0, lane} * 16));
|
||||
cur_strb <= (32'd3 << ({28'd0, lane} * 2));
|
||||
cur_data <= (256'(px_pix32[PIX_BITS-1:0]) << ({27'd0, lane} * PIX_BITS));
|
||||
cur_strb <= (32'(STRB1) << ({27'd0, lane} * PIX_BYTES));
|
||||
has_data <= 1'b1;
|
||||
end else begin
|
||||
nd = has_data ? cur_data : 256'd0;
|
||||
ns = has_data ? cur_strb : 32'd0;
|
||||
nd[ ({28'd0, lane} * 16) +: 16 ] = px_pix16;
|
||||
ns[ ({28'd0, lane} * 2) +: 2 ] = 2'b11;
|
||||
nd[ ({27'd0, lane} * PIX_BITS) +: PIX_BITS ] = px_pix32[PIX_BITS-1:0];
|
||||
ns[ ({27'd0, lane} * PIX_BYTES) +: PIX_BYTES ] = STRB1;
|
||||
if (&ns) begin
|
||||
// beat complete (all 16 lanes) — flush, beat consumed
|
||||
fifo_wdata <= {abeat, nd, ns};
|
||||
// beat complete (all lanes strobed) — flush (marker=0), beat consumed
|
||||
fifo_wdata <= {1'b0, abeat, nd, ns};
|
||||
fifo_wr <= 1'b1;
|
||||
has_data <= 1'b0;
|
||||
end else begin
|
||||
@@ -161,12 +204,148 @@ module gs_lpddr_axi_master #(
|
||||
has_data <= 1'b1;
|
||||
end
|
||||
end
|
||||
end else if (enable && arm_gs && flush_pending && has_data && !fifo_wr && !fifo_wfull) begin
|
||||
// Ch353 — end-of-scene partial-beat flush: push the dangling beat (marker=0) so no pixels are
|
||||
// stranded. Codex — gate on `!fifo_wr && !fifo_wfull`: a PRIOR registered push may still be entering
|
||||
// the FIFO this cycle (fifo_wfull is REGISTERED, lags a cycle), so wait until no push is in flight
|
||||
// AND not full, then the !wfull check is accurate. Covers a scene ending on a full beat, a partial
|
||||
// right after a line-change, and (since the marker also waits for fifo_wr to clear) partial->marker.
|
||||
// If gated, state is RETAINED and retried — the partial is never dropped.
|
||||
fifo_wdata <= {1'b0, cur_addr, cur_data, cur_strb};
|
||||
fifo_wr <= 1'b1;
|
||||
has_data <= 1'b0;
|
||||
end else if (enable && arm_gs && flush_pending && !has_data && !fifo_wr && !fifo_wfull) begin
|
||||
// Ch353 (Codex) — partial (if any) is pushed; now push the ordered EOF MARKER (payload irrelevant),
|
||||
// same `!fifo_wr && !fifo_wfull` gate. flush_pending retained until accepted. The AXI FSM sets
|
||||
// frame_drained when it pops this, i.e. after the last data beat's BRESP.
|
||||
fifo_wdata <= {1'b1, cur_addr, cur_data, cur_strb};
|
||||
fifo_wr <= 1'b1;
|
||||
flush_pending <= 1'b0;
|
||||
end
|
||||
// overflow witness: a push attempt while the FIFO is full (must stay 0)
|
||||
if (fifo_wr && fifo_wfull)
|
||||
fifo_overflow_count <= fifo_overflow_count + 32'd1;
|
||||
end
|
||||
end
|
||||
end else begin : g_elastic
|
||||
// ------- ELASTIC packer (Codex): full producer backpressure via a one-entry skid stage -------
|
||||
// px_ready: the input stage can accept a pixel THIS cycle. The packer advances (accumulates, produces a
|
||||
// beat, retries a flush) ONLY when stage_ready, so every produced beat lands in the stage the SAME cycle
|
||||
// it is produced -> never dropped. The stage drains into the FIFO whenever !fifo_wfull; simultaneous
|
||||
// drain+refill keeps the stage full with the new beat (the drain writes the OLD stage_data to the FIFO
|
||||
// first, via fifo_wr_ram below).
|
||||
//
|
||||
// Ch358 (Codex) — REGISTERED INPUT STAGE: capture {derived beat address, lane, pixel} into in_* registers
|
||||
// BEFORE the packer. The high-address equality (beat-change) compare and the lane shifts then run off
|
||||
// LOCAL in_* registers instead of gating stage_data[255:0] straight from the producer's cross-module
|
||||
// px_addr register — the Ch358 fit's failing setup cone (col_out_addr -> stage_data, WNS -0.176). +1 cycle
|
||||
// pixel latency; ordering preserved: flush_pending is serviced only when in_valid is EMPTY, so the
|
||||
// partial/EOF marker can never overtake a captured pixel.
|
||||
logic in_valid;
|
||||
logic [31:0] in_beat; // {px_addr[31:5], 5'd0} — derived beat address (low 5 bits constant 0)
|
||||
logic [4:0] in_lane; // px_addr[4:0] >> LANE_LO
|
||||
logic [PIX_BITS-1:0] in_pix;
|
||||
// Ch358 (Codex) — PRE-REGISTERED beat-change decision (the 26.1 fit's residual -0.012 family was
|
||||
// in_beat -> the compare -> stage_data[255:0]): in_same_q = (this pixel's beat == the PREVIOUSLY captured
|
||||
// pixel's beat, last_beat_q), registered at capture. Exact by invariant: the packer reads the compare ONLY
|
||||
// when has_data=1 at consumption, and has_data=1 implies the preceding captured pixel MERGED into cur_addr
|
||||
// (beat-complete and flush both clear has_data and never read it) -> cur_addr == last_beat_q at capture.
|
||||
// A pending flush cannot intervene while in_valid holds a pixel. No latency change.
|
||||
logic in_same_q;
|
||||
logic [31:0] last_beat_q;
|
||||
// Ch367 — map the lane-selected pixel into full beat masks in a distinct registered stage. The beat
|
||||
// accumulator below then sees only registered masks, not in_lane driving cur_data[255:0] directly.
|
||||
logic map_valid, map_same_q;
|
||||
logic [31:0] map_beat;
|
||||
logic [255:0] map_data;
|
||||
logic [255:0] map_mask;
|
||||
logic [31:0] map_strb;
|
||||
wire map_ready = !map_valid || stage_ready;
|
||||
wire in_advance = in_valid && map_ready; // input stage advances into the map stage
|
||||
assign px_ready = !in_valid || map_ready;
|
||||
assign fifo_wr_ram = stage_drain; // drains the CURRENT stage_data (already implies !fifo_wfull)
|
||||
assign fifo_wdata_ram = stage_data;
|
||||
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
|
||||
if (!gs_rst_n) begin
|
||||
cur_addr <= '0; cur_data <= '0; cur_strb <= '0; has_data <= 1'b0;
|
||||
stage_valid <= 1'b0; stage_data <= '0; fifo_overflow_count <= '0; flush_pending <= 1'b0;
|
||||
in_valid <= 1'b0; in_beat <= '0; in_lane <= '0; in_pix <= '0;
|
||||
in_same_q <= 1'b0; last_beat_q <= '0;
|
||||
map_valid <= 1'b0; map_same_q <= 1'b0; map_beat <= '0; map_data <= '0; map_mask <= '0; map_strb <= '0;
|
||||
end else begin
|
||||
logic stage_load; // a beat is being loaded into the stage this cycle
|
||||
stage_load = 1'b0;
|
||||
// (0) INPUT STAGE: consume first; a same-cycle capture below overrides (set wins) — classic
|
||||
// pipeline advance. Capture is gated on px_ready so an occupied, non-advancing stage is never
|
||||
// overwritten (the producer must HOLD px_emit/px_addr/px_pix32 until px_ready, as before).
|
||||
if (in_advance) in_valid <= 1'b0;
|
||||
if (enable && arm_gs && px_emit && px_ready) begin
|
||||
in_valid <= 1'b1;
|
||||
in_beat <= {px_addr[31:5], 5'd0};
|
||||
in_lane <= px_addr[4:0] >> LANE_LO;
|
||||
in_pix <= px_pix32[PIX_BITS-1:0];
|
||||
in_same_q <= ({px_addr[31:5], 5'd0} == last_beat_q); // vs the PREVIOUS captured pixel's beat
|
||||
last_beat_q <= {px_addr[31:5], 5'd0};
|
||||
end
|
||||
// (1) MAP: a drain and refill can coincide; the refill wins and preserves throughput.
|
||||
if (in_advance) begin
|
||||
map_valid <= 1'b1;
|
||||
map_same_q <= in_same_q;
|
||||
map_beat <= in_beat;
|
||||
map_data <= (256'(in_pix) << ({27'd0, in_lane} * PIX_BITS));
|
||||
map_mask <= (PIX_MASK << ({27'd0, in_lane} * PIX_BITS));
|
||||
map_strb <= (32'(STRB1) << ({27'd0, in_lane} * PIX_BYTES));
|
||||
end else if (stage_ready && map_valid) begin
|
||||
map_valid <= 1'b0;
|
||||
end
|
||||
// (2) DRAIN: the staged beat enters the FIFO if there is room (may be re-loaded below same cycle).
|
||||
if (stage_drain) stage_valid <= 1'b0;
|
||||
// (3) latch the flush request UNCONDITIONALLY so it is never lost while backpressured.
|
||||
if (flush) flush_pending <= 1'b1;
|
||||
// (4) PACKER: only when the stage can accept a beat this cycle; consumes the mapped input.
|
||||
if (stage_ready) begin
|
||||
if (map_valid) begin
|
||||
logic [255:0] nd; logic [31:0] ns;
|
||||
if (has_data && !map_same_q) begin
|
||||
stage_data <= {1'b0, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
|
||||
cur_addr <= map_beat;
|
||||
cur_data <= map_data;
|
||||
cur_strb <= map_strb;
|
||||
has_data <= 1'b1;
|
||||
end else begin
|
||||
nd = has_data ? cur_data : 256'd0;
|
||||
ns = has_data ? cur_strb : 32'd0;
|
||||
// Preserve the legacy packer's last-writer-wins behavior when two accepted
|
||||
// fragments target one pixel in the same beat. map_data is sparse, so OR
|
||||
// would corrupt a later color whose bit pattern overlaps the earlier one.
|
||||
nd = (nd & ~map_mask) | map_data;
|
||||
ns = ns | map_strb;
|
||||
if (&ns) begin
|
||||
stage_data <= {1'b0, map_beat, nd, ns}; stage_valid <= 1'b1; stage_load = 1'b1;
|
||||
has_data <= 1'b0;
|
||||
end else begin
|
||||
cur_addr <= map_beat; cur_data <= nd; cur_strb <= ns; has_data <= 1'b1;
|
||||
end
|
||||
end
|
||||
end else if (!in_valid && enable && arm_gs && flush_pending && has_data) begin
|
||||
// partial-beat flush — retries here every cycle until stage_ready (guaranteed inside this if)
|
||||
stage_data <= {1'b0, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
|
||||
has_data <= 1'b0;
|
||||
end else if (!in_valid && enable && arm_gs && flush_pending && !has_data) begin
|
||||
// ordered EOF marker — retries until accepted (in_valid empty => no pixel can be overtaken)
|
||||
stage_data <= {1'b1, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
|
||||
flush_pending <= 1'b0;
|
||||
end
|
||||
end
|
||||
// overflow witness (accepted-write accounting): a stage load while the stage is occupied AND not
|
||||
// draining would DROP the previous beat. Gating on stage_ready makes this impossible; the witness
|
||||
// fires only if that invariant is ever violated. Must stay 0 (asserted by the saturation TB).
|
||||
if (stage_load && stage_valid && !stage_drain)
|
||||
fifo_overflow_count <= fifo_overflow_count + 32'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ============================ async FIFO (CDC) ============================
|
||||
logic [PW-1:0] fifo_rdata;
|
||||
@@ -183,7 +362,7 @@ module gs_lpddr_axi_master #(
|
||||
end
|
||||
wire fifo_wrst_n = wrst_sync[1];
|
||||
gs_async_fifo #(.WIDTH(PW), .DEPTH(FIFO_DEPTH)) u_fifo (
|
||||
.wclk(gs_clk), .wrst_n(fifo_wrst_n), .wr(fifo_wr && !fifo_wfull), .wdata(fifo_wdata), .wfull(fifo_wfull),
|
||||
.wclk(gs_clk), .wrst_n(fifo_wrst_n), .wr(fifo_wr_ram), .wdata(fifo_wdata_ram), .wfull(fifo_wfull),
|
||||
.rclk(axi_clk), .rrst_n(axi_rst_n), .rd(fifo_rd), .rdata(fifo_rdata), .rempty(fifo_rempty)
|
||||
);
|
||||
|
||||
@@ -209,26 +388,37 @@ module gs_lpddr_axi_master #(
|
||||
assign awvalid = (state == S_AW);
|
||||
assign wvalid = (state == S_W);
|
||||
assign bready = (state == S_B);
|
||||
assign fifo_rd = (state == S_IDLE) && !fifo_rempty && arm_axi && !commit_edge;
|
||||
// A commit and a FIFO admission may coincide safely: S_IDLE latches the full address and canary decision
|
||||
// from the OLD snapshot at that edge, then AW/W/B runs solely from those registered values. The next
|
||||
// admission sees the NEW snapshot. Do not gate this with the raw commit synchronizer: that turns the
|
||||
// control edge into a read-pointer/rempty timing cone inside the async FIFO.
|
||||
assign fifo_rd = (state == S_IDLE) && !fifo_rempty && arm_axi;
|
||||
assign idle = (state == S_IDLE) && fifo_rempty;
|
||||
|
||||
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
||||
if (!axi_rst_n) begin
|
||||
state <= S_IDLE; beat_addr <= '0; beat_data <= '0; beat_strb <= '0; awaddr_q <= '0;
|
||||
beats_written <= '0; bursts_issued <= '0; bresp_err_count <= '0;
|
||||
beats_written <= '0; bursts_issued <= '0; bresp_err_count <= '0; frame_drained <= 1'b0;
|
||||
end else begin
|
||||
unique case (state)
|
||||
S_IDLE: if (!fifo_rempty && arm_axi && !commit_edge) begin
|
||||
beat_addr <= fifo_rdata[319:288]; // {addr, data, strb}
|
||||
beat_data <= fifo_rdata[287:32];
|
||||
beat_strb <= fifo_rdata[31:0];
|
||||
awaddr_q <= fb_base_axi + fifo_rdata[319:288]; // latch FULL AW addr from the STABLE base
|
||||
// canary: write ONLY the offset-0 (top-of-frame) 32-byte line;
|
||||
// discard every other beat (fifo_rd still pops it this cycle).
|
||||
if (canary_axi && (fifo_rdata[319:288] != 32'd0))
|
||||
state <= S_IDLE;
|
||||
else
|
||||
state <= S_AW;
|
||||
S_IDLE: if (!fifo_rempty && arm_axi) begin
|
||||
if (fifo_rdata[MARK]) begin
|
||||
// Ch353 — ordered EOF marker popped: every prior data beat's BRESP has completed (in-order
|
||||
// FIFO + per-beat B). Assert the drain ack; consume the marker (fifo_rd pops it, no AXI).
|
||||
frame_drained <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end else begin
|
||||
frame_drained <= 1'b0; // new frame data in flight — drop the ack
|
||||
beat_addr <= fifo_rdata[319:288]; // {marker, addr, data, strb}
|
||||
beat_data <= fifo_rdata[287:32];
|
||||
beat_strb <= fifo_rdata[31:0];
|
||||
awaddr_q <= fb_base_axi + fifo_rdata[319:288]; // latch FULL AW addr from the STABLE base
|
||||
// canary: write ONLY the offset-0 (top-of-frame) 32-byte line; discard every other beat.
|
||||
if (canary_axi && (fifo_rdata[319:288] != 32'd0))
|
||||
state <= S_IDLE;
|
||||
else
|
||||
state <= S_AW;
|
||||
end
|
||||
end
|
||||
S_AW: if (awready) begin
|
||||
bursts_issued <= bursts_issued + 32'd1;
|
||||
|
||||
Reference in New Issue
Block a user