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>
369 lines
22 KiB
Systemverilog
369 lines
22 KiB
Systemverilog
// retroDE_ps2 — gs_lpddr_zc_emit (Ch357 — Z-then-color emit integration: request FIFO + persistent-Z RMW + color writer)
|
|
//
|
|
// Codex Ch357 integration constraints:
|
|
// (1) Cross gs_clk->axi_clk with ONE async request FIFO carrying the COMPLETE fragment packet {XY, clamped Z, color,
|
|
// masks, scene marker}. No raw level-bus CDC.
|
|
// (2) Z decision FIRST; enqueue color ONLY on pass. A rejected fragment produces NO color write.
|
|
// (3) Ordered end-of-scene marker through the SAME FIFO: on it, flush the dirty Z line, wait Z BRESP + all accepted color
|
|
// BRESPs, then assert combined frame_drained.
|
|
// (4) Z cache persists across scheduler epochs (texture rebind must not clear it) — handled by gs_lpddr_z_rmw (scene_flush
|
|
// keeps the line valid+clean).
|
|
// (5) Preclear/invalidate Z once at frame start (clear_start).
|
|
// (6) LPDDR map (gs_lpddr_map_pkg): color 0x000000, Z 0x140000, tex 0x200000 — disjoint.
|
|
//
|
|
// Structure: the request async FIFO is the ONLY CDC. Everything downstream runs in axi_clk: the Z RMW (its own Z AXI
|
|
// read+write master), and the reused gs_lpddr_axi_master COLOR writer (its gs_clk tied to axi_clk, so its internal CDC FIFO
|
|
// is same-clock). A tiny in-order COLOR-ALIGN FIFO carries {coladdr,color} beside the Z pipeline so the pass decision lands
|
|
// with the right pixel. Two AXI masters are exposed (z_* and c_*); the system arbitrates them onto the shared LPDDR port.
|
|
|
|
module gs_lpddr_zc_emit #(
|
|
parameter [31:0] COLBASE = 32'h0000_0000,
|
|
parameter [31:0] ZBASE = 32'h0014_0000,
|
|
parameter int FB_PXW = 256,
|
|
parameter int FB_H = 210,
|
|
parameter [15:0] Z_CLEAR = 16'h0000,
|
|
parameter int REQ_DEPTH= 32,
|
|
parameter int COL_DEPTH= 64
|
|
) (
|
|
// ---- gs_clk fragment/marker producer ----
|
|
input logic gs_clk,
|
|
input logic gs_rst_n,
|
|
input logic enable,
|
|
input logic g_valid,
|
|
output logic g_ready,
|
|
input logic [11:0] g_x,
|
|
input logic [11:0] g_y,
|
|
input logic [15:0] g_zq, // clamped Z (clamp16 done upstream)
|
|
input logic g_zmsk,
|
|
input logic g_ztest, // 1 = depth-test this fragment; 0 = always pass (non-Z draw)
|
|
input logic [1:0] g_ztst, // GS TEST.ZTST: NEVER/ALWAYS/GEQUAL/GREATER
|
|
input logic [31:0] g_color,
|
|
input logic [16:0] g_alpha, // {ABE,A,B,C,D,FIX}, snapped at raster emit
|
|
input logic [3:0] g_be, // FRAME.FBMSK-compressed byte enables
|
|
input logic g_scene, // 1 = end-of-scene marker (x/y/z/color ignored)
|
|
|
|
// ---- axi_clk ----
|
|
input logic axi_clk,
|
|
input logic axi_rst_n,
|
|
input logic clear_start, // preclear Z once at frame start
|
|
output logic clear_done,
|
|
output logic frame_drained, // combined: color drained AND Z drained for the scene
|
|
|
|
// ---- Z AXI master (read+write) ----
|
|
output logic [31:0] z_araddr, output logic [7:0] z_arlen, output logic [2:0] z_arsize, output logic [1:0] z_arburst,
|
|
output logic z_arvalid, input logic z_arready,
|
|
input logic [255:0] z_rdata, input logic [1:0] z_rresp, input logic z_rlast, input logic z_rvalid, output logic z_rready,
|
|
output logic [31:0] z_awaddr, output logic [7:0] z_awlen, output logic [2:0] z_awsize, output logic [1:0] z_awburst,
|
|
output logic z_awvalid, input logic z_awready,
|
|
output logic [255:0] z_wdata, output logic [31:0] z_wstrb, output logic z_wlast, output logic z_wvalid, input logic z_wready,
|
|
input logic z_bvalid, output logic z_bready, input logic [1:0] z_bresp,
|
|
|
|
// ---- Color AXI master (write only) ----
|
|
output logic [31:0] c_awaddr, output logic [7:0] c_awlen, output logic [2:0] c_awsize, output logic [1:0] c_awburst,
|
|
output logic c_awvalid, input logic c_awready,
|
|
output logic [255:0] c_wdata, output logic [31:0] c_wstrb, output logic c_wlast, output logic c_wvalid, input logic c_wready,
|
|
input logic c_bvalid, output logic c_bready, input logic [1:0] c_bresp,
|
|
|
|
// ---- status ----
|
|
output logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err,
|
|
// Destination-color read AXI master. Inert for full-byte opaque writes;
|
|
// ABE and partial FRAME.FBMSK writes both use it for destination RMW.
|
|
output logic [31:0] d_araddr, output logic [7:0] d_arlen, output logic [2:0] d_arsize, output logic [1:0] d_arburst,
|
|
output logic d_arvalid, input logic d_arready,
|
|
input logic [255:0] d_rdata, input logic [1:0] d_rresp, input logic d_rlast, input logic d_rvalid, output logic d_rready,
|
|
output logic idle
|
|
);
|
|
// ---------------- request async FIFO (the ONLY CDC) ----------------
|
|
// X/Y have already been clipped to this framebuffer before reaching the
|
|
// external ROP. Do not spend twelve FIFO bits on each coordinate: the
|
|
// production 640x480 path needs only 10+9. Besides saving storage, this
|
|
// keeps the deep request FIFO below the RAM-banking threshold that put a
|
|
// bank mux on its 310 MHz registered-read path after ZTST was added.
|
|
localparam int XW = (FB_PXW <= 1) ? 1 : $clog2(FB_PXW);
|
|
localparam int YW = (FB_H <= 1) ? 1 : $clog2(FB_H);
|
|
localparam int PW = 74 + XW + YW; // {scene,be,alpha,color,ztest,ztst,zmsk,zq,y[YW],x[XW]}
|
|
localparam int Y_LSB = XW;
|
|
localparam int ZQ_LSB = XW + YW;
|
|
localparam int ZMSK_BIT = ZQ_LSB + 16;
|
|
localparam int ZTST_LSB = ZMSK_BIT + 1;
|
|
localparam int ZTEST_BIT = ZTST_LSB + 2;
|
|
localparam int COLOR_LSB = ZTEST_BIT + 1;
|
|
localparam int ALPHA_LSB = COLOR_LSB + 32;
|
|
localparam int BE_LSB = ALPHA_LSB + 17;
|
|
logic [PW-1:0] req_wdata, req_rdata; logic req_wfull, req_rempty, req_rd;
|
|
assign req_wdata = {g_scene, g_be, g_alpha, g_color, g_ztest, g_ztst, g_zmsk,
|
|
g_zq, g_y[YW-1:0], g_x[XW-1:0]};
|
|
assign g_ready = enable && !req_wfull;
|
|
// Ch439e — the measured f52 occupancy peak is 6,115, so 8K cannot be cut
|
|
// without dropping fragments from the intentionally unthrottled producer.
|
|
// Split that fixed capacity by both depth and width instead: each physical
|
|
// address copy drives one quarter of the original deep/wide RAM tree.
|
|
gs_async_fifo #(.WIDTH(PW), .DEPTH(REQ_DEPTH), .REGISTERED_READ(1'b1),
|
|
.QUADRANT_READ(1'b1)) u_req (
|
|
.wclk(gs_clk), .wrst_n(gs_rst_n), .wr(g_valid && g_ready), .wdata(req_wdata), .wfull(req_wfull),
|
|
.rclk(axi_clk), .rrst_n(axi_rst_n), .rd(req_rd), .rdata(req_rdata), .rempty(req_rempty)
|
|
);
|
|
// REGISTERED request-HEAD stage. Ch406 makes the deep request FIFO read
|
|
// synchronous: an accepted req_rd is followed by req_read_pending, which
|
|
// captures the complete registered RAM output here one cycle later.
|
|
// Ch421 keeps stage occupancy independent of packet contents: decoding the
|
|
// RAM's scene bit directly into separate fragment/marker valid registers
|
|
// was the complete post-Ch420 setup family. A single validity register is
|
|
// driven only by req_read_pending; scene/type decode happens after the full
|
|
// packet register boundary.
|
|
logic req_stage_valid;
|
|
logic req_read_pending;
|
|
logic [PW-1:0] req_stage_packet;
|
|
// Ch423 — packet DATA is intentionally unreset and lives outside the
|
|
// async-reset control process. Keeping the lone unreset data register in
|
|
// that process made Quartus fold axi_rst_n (the 4k-fanout EMIF-cal signal)
|
|
// into its clock enable. Only req_stage_valid makes this payload
|
|
// observable, so the local registered pending pulse is the complete and
|
|
// sufficient capture enable.
|
|
always_ff @(posedge axi_clk)
|
|
if (req_read_pending) req_stage_packet <= req_rdata;
|
|
wire [PW-2:0] req_stage_data = req_stage_packet[PW-2:0];
|
|
wire req_stage_frag_valid = req_stage_valid && !req_stage_packet[PW-1];
|
|
wire req_stage_marker_valid = req_stage_valid && req_stage_packet[PW-1];
|
|
wire rq_scene = req_stage_marker_valid;
|
|
wire [16:0] rq_alpha = req_stage_data[ALPHA_LSB +: 17];
|
|
wire [3:0] rq_be = req_stage_data[BE_LSB +: 4];
|
|
wire [31:0] rq_color = req_stage_data[COLOR_LSB +: 32];
|
|
wire rq_ztest = req_stage_data[ZTEST_BIT];
|
|
wire [1:0] rq_ztst = req_stage_data[ZTST_LSB +: 2];
|
|
wire rq_zmsk = req_stage_data[ZMSK_BIT];
|
|
wire [15:0] rq_zq = req_stage_data[ZQ_LSB +: 16];
|
|
wire [11:0] rq_y = {{(12-YW){1'b0}}, req_stage_data[Y_LSB +: YW]};
|
|
wire [11:0] rq_x = {{(12-XW){1'b0}}, req_stage_data[0 +: XW]};
|
|
|
|
`ifndef SYNTHESIS
|
|
initial begin
|
|
if (XW > 12 || YW > 12)
|
|
$fatal(1, "gs_lpddr_zc_emit framebuffer dimensions exceed 12-bit fragment coordinates");
|
|
end
|
|
`endif
|
|
|
|
// ---------------- Z RMW (axi_clk) ----------------
|
|
logic z_fvalid, z_fready, z_pvalid, z_pready, z_ppass, z_sflush, z_drained;
|
|
logic [11:0] z_px, z_py; logic [15:0] z_pzq;
|
|
gs_lpddr_z_rmw #(.ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .Z_CLEAR(Z_CLEAR)) u_z (
|
|
.clk(axi_clk), .rst_n(axi_rst_n), .enable(enable), .clear_start(clear_start), .clear_done(clear_done),
|
|
.scene_flush(z_sflush), .z_drained(z_drained),
|
|
.f_valid(z_fvalid), .f_ready(z_fready), .f_x(rq_x), .f_y(rq_y), .f_z({16'd0, rq_zq}),
|
|
.f_zmsk(rq_zmsk || !rq_ztest), .f_ztst(rq_ztest ? rq_ztst : 2'd1),
|
|
.p_valid(z_pvalid), .p_ready(z_pready), .p_pass(z_ppass), .p_x(z_px), .p_y(z_py), .p_zq(z_pzq),
|
|
.araddr(z_araddr), .arlen(z_arlen), .arsize(z_arsize), .arburst(z_arburst), .arvalid(z_arvalid), .arready(z_arready),
|
|
.rdata(z_rdata), .rresp(z_rresp), .rlast(z_rlast), .rvalid(z_rvalid), .rready(z_rready),
|
|
.awaddr(z_awaddr), .awlen(z_awlen), .awsize(z_awsize), .awburst(z_awburst), .awvalid(z_awvalid), .awready(z_awready),
|
|
.wdata(z_wdata), .wstrb(z_wstrb), .wlast(z_wlast), .wvalid(z_wvalid), .wready(z_wready),
|
|
.bvalid(z_bvalid), .bready(z_bready), .bresp(z_bresp),
|
|
.beats_read(z_beats_read), .beats_written(z_beats_written), .bresp_err(bresp_err), .idle(z_idle)
|
|
);
|
|
// A fragment carries ztest: when ztest=0 it must ALWAYS pass. Feed the RMW a zmsk so it never writes Z for a
|
|
// non-Z fragment, and force its zq to max so GEQUAL always passes. (All scheduler draws are ztest=1.)
|
|
// (Handled at feed below via the always-pass override on the pass decision.)
|
|
|
|
// ---------------- color-align FIFO (in-order {coladdr,color,ztest}) ----------------
|
|
localparam int CAW = 86; // {ztest, be[3:0], alpha[16:0], coladdr[31:0], color[31:0]}
|
|
localparam int CAPTR_W = $clog2(COL_DEPTH);
|
|
logic [CAW-1:0] ca_mem [0:COL_DEPTH-1];
|
|
logic [CAPTR_W:0] ca_head, ca_tail;
|
|
logic ca_wr_q;
|
|
logic [CAPTR_W-1:0] ca_waddr_q;
|
|
logic [CAW-1:0] ca_wdata_q;
|
|
wire ca_empty = (ca_head==ca_tail);
|
|
// Power-of-two ring full test: same low address with opposite wrap bit.
|
|
// This is equivalent to (tail-head)==COL_DEPTH while occupancy is bounded,
|
|
// but removes the pointer subtract/carry chain from feed_ok -> ca_mem write
|
|
// enable (the post-scanout fit's final -0.007 ns, two-endpoint family).
|
|
wire ca_full = (ca_tail[CAPTR_W] != ca_head[CAPTR_W])
|
|
&& (ca_tail[CAPTR_W-1:0] == ca_head[CAPTR_W-1:0]);
|
|
wire [31:0] frag_coladdr = COLBASE + (((rq_y*FB_PXW) + rq_x) << 2);
|
|
// Register the color-align RAM write port. Ch423 captures address/data
|
|
// every cycle instead of enabling these wide DATA registers with feed_ok.
|
|
// Their contents are observable only when the separately-reset ca_wr_q is
|
|
// high, so this removes ca_tail->ca_full->feed_ok from 82 data-register
|
|
// enables without changing the committed address/data/write-strobe tuple.
|
|
// Nonblocking ordering makes the RAM consume the prior cycle's captured
|
|
// tuple exactly when the prior feed_ok raised ca_wr_q.
|
|
always_ff @(posedge axi_clk) begin
|
|
ca_waddr_q <= ca_tail[CAPTR_W-1:0];
|
|
ca_wdata_q <= {rq_ztest, rq_be, rq_alpha, frag_coladdr, rq_color};
|
|
end
|
|
always_ff @(posedge axi_clk)
|
|
if (ca_wr_q) ca_mem[ca_waddr_q] <= ca_wdata_q;
|
|
|
|
// ---------------- color writer (reused; gs_clk tied to axi_clk => internal FIFO is same-clock) ----------------
|
|
logic col_px_emit; logic [31:0] col_px_addr; logic [31:0] col_px_pix32; logic col_flush, col_drained;
|
|
logic col_px_ready; // Ch357 (Codex) — color-writer producer-backpressure ready (elastic stage can accept)
|
|
// Ch357 (Codex) — ONE-ENTRY color output register. Registering the payload here breaks the ca_mem->barrel-insert
|
|
// cone (the -0.98ns 310MHz path) AND holds the emit valid until u_c accepts it, so no pixel is dropped.
|
|
logic col_out_valid; logic [31:0] col_out_addr; logic [31:0] col_out_color; logic [16:0] col_out_alpha; logic [3:0] col_out_be;
|
|
logic blend_in_ready, blend_out_valid, blend_idle, col_writer_idle;
|
|
logic blend_write_pending, blend_flush_issued, blend_drain_seen_low;
|
|
wire blend_input_gate = !blend_write_pending;
|
|
logic [31:0] blend_out_addr, blend_out_color;
|
|
gs_lpddr_color_blend u_blend (
|
|
.clk(axi_clk), .rst_n(axi_rst_n),
|
|
.in_valid(col_out_valid && blend_input_gate), .in_ready(blend_in_ready), .in_addr(col_out_addr), .in_color(col_out_color), .in_alpha(col_out_alpha), .in_be(col_out_be),
|
|
.out_valid(blend_out_valid), .out_ready(col_px_ready), .out_addr(blend_out_addr), .out_color(blend_out_color), .idle(blend_idle),
|
|
.araddr(d_araddr), .arlen(d_arlen), .arsize(d_arsize), .arburst(d_arburst), .arvalid(d_arvalid), .arready(d_arready),
|
|
.rdata(d_rdata), .rresp(d_rresp), .rlast(d_rlast), .rvalid(d_rvalid), .rready(d_rready)
|
|
);
|
|
logic col_commit; logic [1:0] commit_sr;
|
|
// one-shot ctrl_commit toggle after reset to latch fb_base=COLBASE
|
|
always_ff @(posedge axi_clk or negedge axi_rst_n)
|
|
if (!axi_rst_n) begin commit_sr<=2'b00; col_commit<=1'b0; end
|
|
else begin commit_sr<={commit_sr[0],1'b1}; if (commit_sr==2'b01) col_commit<=~col_commit; end
|
|
gs_lpddr_axi_master #(.FIFO_DEPTH(COL_DEPTH), .PIX_BYTES(4), .ELASTIC_BACKPRESSURE(1'b1)) u_c (
|
|
.gs_clk(axi_clk), .gs_rst_n(axi_rst_n), .enable(enable),
|
|
.arm(enable), .canary(1'b0), .fb_base(COLBASE), .ctrl_commit(col_commit),
|
|
.px_emit(col_px_emit), .px_addr(col_px_addr), .px_pix32(col_px_pix32), .px_ready(col_px_ready), .flush(col_flush),
|
|
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
|
|
.awaddr(c_awaddr), .awlen(c_awlen), .awsize(c_awsize), .awburst(c_awburst), .awvalid(c_awvalid), .awready(c_awready),
|
|
.wdata(c_wdata), .wstrb(c_wstrb), .wlast(c_wlast), .wvalid(c_wvalid), .wready(c_wready),
|
|
.bvalid(c_bvalid), .bready(c_bready), .bresp(c_bresp),
|
|
.beats_written(c_beats_written), .bursts_issued(), .bresp_err_count(),
|
|
.fifo_overflow_count(col_ovf), .idle(col_writer_idle), .frame_drained(col_drained)
|
|
);
|
|
|
|
// Destination RMW reads must observe the preceding blended/masked write. Merely
|
|
// waiting for u_c.idle is insufficient: the elastic packer can hold a
|
|
// partial beat while the AXI-side FIFO still reports empty. Flush an
|
|
// ordered marker after every blended output and wait for col_drained to go
|
|
// low then high. That high transition is after the data beat's BRESP, so
|
|
// the next destination read sees the committed result. This deliberately
|
|
// serializes the color stream only while an ABE or partial-byte write is
|
|
// being committed.
|
|
wire blend_rmw_in_accept = col_out_valid && blend_in_ready && blend_input_gate
|
|
&& (col_out_alpha[16] || (col_out_be != 4'hF));
|
|
wire blend_write_accept = blend_out_valid && col_px_ready && blend_write_pending;
|
|
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
|
if (!axi_rst_n) begin
|
|
blend_write_pending <= 1'b0;
|
|
blend_flush_issued <= 1'b0;
|
|
blend_drain_seen_low <= 1'b0;
|
|
end else begin
|
|
if (blend_rmw_in_accept) begin
|
|
blend_write_pending <= 1'b1;
|
|
blend_flush_issued <= 1'b0;
|
|
blend_drain_seen_low <= 1'b0;
|
|
end else if (blend_write_accept) begin
|
|
blend_flush_issued <= 1'b1;
|
|
end else if (blend_write_pending && blend_flush_issued) begin
|
|
if (!col_drained)
|
|
blend_drain_seen_low <= 1'b1;
|
|
else if (blend_drain_seen_low) begin
|
|
blend_write_pending <= 1'b0;
|
|
blend_flush_issued <= 1'b0;
|
|
blend_drain_seen_low <= 1'b0;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
// ---------------- control FSM (axi_clk): feed Z, emit color on pass, drain on scene marker ----------------
|
|
typedef enum logic [1:0] { C_RUN, C_MARK_WAIT, C_DRAIN } cst_t; cst_t cst;
|
|
|
|
// POP only when the head stage and the synchronous-read pipeline are empty;
|
|
// CONSUME the staged fragment via feed_ok. req_rd remains a registered
|
|
// pulse, isolated from feed_ok and the color-align control cones.
|
|
wire req_read_accept = req_rd && !req_rempty;
|
|
// feed the STAGED fragment into the Z RMW (and push its color) when it is a fragment and both can accept
|
|
wire feed_ok = (cst==C_RUN) && req_stage_frag_valid && z_fready && !ca_full;
|
|
// consume the STAGED end-of-scene marker once all fed fragments have drained (see C_MARK_WAIT)
|
|
wire marker_consume = (cst==C_MARK_WAIT) && req_stage_marker_valid && ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending;
|
|
assign z_fvalid = feed_ok;
|
|
// Ch357 (Codex) — PRODUCER BACKPRESSURE via a ONE-ENTRY output register (col_out_*). Consume a Z result only when the
|
|
// output register can accept the color it would emit: z_pready = !col_out_valid || col_px_ready (empty, OR draining
|
|
// this cycle -> simultaneous drain/refill). ca_head pops on z_pvalid && z_pready (pass OR reject; a reject consumes its
|
|
// CA entry WITHOUT loading the output register). col_px_* are driven from the REGISTER (not combinationally from
|
|
// ca_mem), which breaks the ca_mem->u_c barrel-insert cone (the -0.98ns 310MHz path) AND holds the payload valid until
|
|
// u_c accepts it (px_ready), so no pixel is dropped. No comb loop: col_px_ready depends only on u_c's registered stage.
|
|
assign z_pready = !col_out_valid || (blend_in_ready && blend_input_gate);
|
|
wire [CAW-1:0] ca_headword = ca_mem[ca_head[CAPTR_W-1:0]];
|
|
wire ca_head_zt = ca_headword[85];
|
|
wire [3:0] ca_head_be = ca_headword[84:81];
|
|
wire [16:0] ca_head_alpha= ca_headword[80:64];
|
|
wire [31:0] ca_head_addr = ca_headword[63:32];
|
|
wire [31:0] ca_head_col = ca_headword[31:0];
|
|
wire z_consume = z_pvalid && z_pready; // pop the color-align head this cycle
|
|
wire z_emit_pass = z_consume && (z_ppass || !ca_head_zt); // load the output register (Z pass or non-Z frag)
|
|
assign col_px_emit = blend_out_valid;
|
|
assign col_px_addr = blend_out_addr;
|
|
assign col_px_pix32 = blend_out_color;
|
|
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
|
|
if (!axi_rst_n) begin
|
|
cst<=C_RUN; ca_head<=0; ca_tail<=0;
|
|
ca_wr_q<=1'b0;
|
|
col_flush<=0; z_sflush<=0; frame_drained<=0;
|
|
col_out_valid<=0; col_out_addr<=0; col_out_color<=0; col_out_alpha<=0; col_out_be<=4'hF;
|
|
req_stage_valid<=1'b0;
|
|
req_rd<=1'b0; req_read_pending<=1'b0;
|
|
end else begin
|
|
col_flush<=blend_write_accept;
|
|
ca_wr_q<=1'b0;
|
|
|
|
// u_req has a registered read port. Allow one accepted pop, wait
|
|
// one cycle for its RAM output, then fill the request-head stage.
|
|
// The pending gate prevents a second pop while that word is in
|
|
// flight but req_stage_valid has not asserted yet.
|
|
req_rd <= !req_stage_valid && !req_read_pending && !req_rempty && !req_rd;
|
|
req_read_pending <= req_read_accept;
|
|
|
|
// Capture one cycle after the synchronous FIFO pop. Capture needs
|
|
// an empty stage; consume needs a full one, so they remain mutually
|
|
// exclusive.
|
|
if (req_read_pending) begin
|
|
req_stage_valid <= 1'b1;
|
|
end
|
|
if (feed_ok || marker_consume) req_stage_valid <= 1'b0;
|
|
|
|
// push color-align entry as we feed a fragment
|
|
if (feed_ok) begin
|
|
ca_wr_q <= 1'b1;
|
|
ca_tail <= ca_tail + 1'b1;
|
|
end
|
|
|
|
// ONE-ENTRY color output register. DRAIN when u_c accepts (px_ready); may be re-loaded the same cycle (refill
|
|
// below) for simultaneous drain/refill. A consumed Z result (z_consume = z_pvalid && z_pready) pops the
|
|
// color-align head (pass OR reject); a PASS additionally loads the register (a reject writes no color).
|
|
if (col_out_valid && blend_in_ready && blend_input_gate) col_out_valid <= 1'b0;
|
|
if (z_consume) begin
|
|
ca_head <= ca_head + 1'b1;
|
|
if (z_emit_pass) begin
|
|
col_out_valid <= 1'b1;
|
|
col_out_addr <= ca_head_addr;
|
|
col_out_color <= ca_head_col;
|
|
col_out_alpha <= ca_head_alpha;
|
|
col_out_be <= ca_head_be;
|
|
end
|
|
end
|
|
|
|
case (cst)
|
|
C_RUN: if (req_stage_marker_valid) cst<=C_MARK_WAIT; // staged marker: stop feeding, wait in-flight to drain
|
|
C_MARK_WAIT: begin
|
|
// all fed fragments have resulted (CA empty), Z idle, AND the output register drained to u_c
|
|
if (ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending) begin
|
|
col_flush<=1'b1; // push color partial beat + EOF marker
|
|
z_sflush <=1'b1; // flush the dirty Z line (cache persists)
|
|
// req_rd pops the marker this cycle (see assign)
|
|
cst<=C_DRAIN;
|
|
end
|
|
end
|
|
C_DRAIN: begin
|
|
z_sflush<=1'b1; // hold until z_drained
|
|
if (col_drained && z_drained) begin
|
|
frame_drained<=1'b1; z_sflush<=1'b0; cst<=C_RUN;
|
|
end
|
|
end
|
|
default: cst<=C_RUN;
|
|
endcase
|
|
// frame_drained is a per-scene ack; drop it once new fragments flow again (feed_ok => a fresh fragment consumed)
|
|
if (feed_ok) frame_drained<=1'b0;
|
|
end
|
|
end
|
|
|
|
assign idle = (cst==C_RUN) && req_rempty && !req_stage_valid && ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending;
|
|
endmodule : gs_lpddr_zc_emit
|