// 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), // Ch440: 2 depth x 4 width banks (was QUADRANT_READ = 2 depth x // 2 width) -> halves each read-address register's M20K load to // shorten the 310 MHz raddr->mem address routing, keeping the // proven 2:1 depth output selector, 8192 capacity, and latency. .QUAD_WIDTH4_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; // Ch441: the Z RMW master's W output goes into a one-entry fully-registered W // buffer (gs_axi_w_regbuf u_z_wbuf, below); the BUFFER drives the zc_emit z_w* // ports (write arbiter s2). This cuts the combinational EMIF WREADY -> z_rmw // FSM path. AW and B pass straight through (z_awaddr.../z_bvalid... unchanged). logic [255:0] zi_wdata; logic [31:0] zi_wstrb; logic zi_wlast, zi_wvalid, zi_wready; 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(zi_wdata), .wstrb(zi_wstrb), .wlast(zi_wlast), .wvalid(zi_wvalid), .wready(zi_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) ); // Ch441: one-entry fully-registered W-channel buffer between the Z RMW master // (u_z) and the write arbiter s2 (via the z_w* ports). Breaks the combinational // EMIF WREADY -> z_rmw FSM path. AW/B untouched; the arbiter's bready_q still // arms on the real EMIF m_wvalid && m_wready && m_wlast (buffer is upstream). gs_axi_w_regbuf #(.WDATA_W(256), .WSTRB_W(32)) u_z_wbuf ( .clk(axi_clk), .rst_n(axi_rst_n), .u_wdata(zi_wdata), .u_wstrb(zi_wstrb), .u_wlast(zi_wlast), .u_wvalid(zi_wvalid), .u_wready(zi_wready), .d_wdata(z_wdata), .d_wstrb(z_wstrb), .d_wlast(z_wlast), .d_wvalid(z_wvalid), .d_wready(z_wready) ); // 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