From dd7ca4fb8ef8729d016ee200460290e7b0ef3861 Mon Sep 17 00:00:00 2001 From: thejayman77 Date: Tue, 21 Jul 2026 14:11:40 -0400 Subject: [PATCH] Ch441: one-entry fully-registered W buffer, Z-RMW master -> wr_arb s2 [READY FOR REVIEW] Cuts the lone remaining EMIF setup fail (-0.016 ns, -0.259 skew): the combinational EMIF gen_p2c_ff -> wr_arb s2_wready -> gs_lpddr_z_rmw next-state (st, endpoint labelled S_FILL_R via the shared encoded state register). New gs_axi_w_regbuf: fully-registered one-entry W buffer (Option B per Codex). - u_wready = !full ONLY (registered occupancy) -> EMIF WREADY never reaches the Z FSM combinationally. NOT a fall-through skid (no !full-OR-d_wready term). - Buffers WDATA/WSTRB/WLAST; downstream held stable until accepted; exactly-once. - AW/B untouched; arbiter bready_q unchanged (still arms on real EMIF W handshake). - z_rmw may enter B-wait once the beat is buffered -- safe: EMIF cannot return B until the buffered beat reaches it. Single-beat writes -> the 1-beat/2-cycle buffer rate is far above the Z write rate (no new FIFO pressure). Wired in zc_emit between u_z W output (zi_*) and the z_w* ports. New file in sim Makefile RTL_SRCS + synth QSF (both). Focused tb_gs_axi_w_regbuf: exactly-once/order/ payload scoreboard + no-combinational-bypass check (u_wready===!full incl. full && d_wready) + downstream-stable check; standalone target + in make run. Texture-cache +0.016 paths NOT touched. No simulations or Quartus run. Co-Authored-By: Claude Opus 4.8 (1M context) --- rtl/gif_gs/gs_axi_w_regbuf.sv | 78 +++++++++++ rtl/gif_gs/gs_lpddr_zc_emit.sv | 16 ++- sim/Makefile | 12 +- sim/tb/gif_gs/tb_gs_axi_w_regbuf.sv | 131 ++++++++++++++++++ .../de25_nano_psmct32_raster_demo_top.qsf | 1 + 5 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 rtl/gif_gs/gs_axi_w_regbuf.sv create mode 100644 sim/tb/gif_gs/tb_gs_axi_w_regbuf.sv diff --git a/rtl/gif_gs/gs_axi_w_regbuf.sv b/rtl/gif_gs/gs_axi_w_regbuf.sv new file mode 100644 index 0000000..87e49e0 --- /dev/null +++ b/rtl/gif_gs/gs_axi_w_regbuf.sv @@ -0,0 +1,78 @@ +// retroDE_ps2 — gs_axi_w_regbuf (Ch441) +// +// Fully-registered ONE-ENTRY AXI W-channel buffer. Inserted between the Z RMW +// master's (gs_lpddr_z_rmw) W OUTPUT and the write arbiter's s2 W INPUT to cut +// the combinational path EMIF gen_p2c_ff -> fbr wready -> wr_arb s2_wready -> +// gs_lpddr_z_rmw next-state (`st`) — the -0.016 ns / -0.259 ns-skew setup family. +// +// CONTRACT (per Codex review, must hold exactly): +// - FULLY REGISTERED, not a fall-through skid: u_wready depends ONLY on the +// registered occupancy `full`, never on d_wready. Downstream (EMIF) WREADY +// therefore can never propagate combinationally back into the upstream FSM. +// (i.e. NOT `u_wready = !full || d_wready`.) +// - Buffers the complete {WDATA, WSTRB, WLAST} payload and holds it stable on +// the downstream side until the arbiter accepts it. +// - Exactly-once: a beat accepted upstream is delivered downstream exactly once. +// One beat in flight (the Z RMW issues single-beat writes, AWLEN=0/WLAST=1). +// - AW and B channels are NOT touched (they pass straight through, outside this +// module). The write arbiter's bready_q is unaffected: it still arms only on +// the REAL downstream m_wvalid && m_wready && m_wlast, because this buffer +// sits UPSTREAM of the arbiter and presents a clean registered W to it. +// - The upstream FSM may enter its B-wait state once the beat is accepted here; +// that is safe because EMIF cannot return B until the buffered beat reaches it. +`timescale 1ns/1ps + +module gs_axi_w_regbuf #( + parameter int WDATA_W = 256, + parameter int WSTRB_W = 32 +) ( + input logic clk, + input logic rst_n, + // upstream — from the Z RMW master's W output + input logic [WDATA_W-1:0] u_wdata, + input logic [WSTRB_W-1:0] u_wstrb, + input logic u_wlast, + input logic u_wvalid, + output logic u_wready, + // downstream — to the write arbiter's s2 W input + output logic [WDATA_W-1:0] d_wdata, + output logic [WSTRB_W-1:0] d_wstrb, + output logic d_wlast, + output logic d_wvalid, + input logic d_wready +); + logic full; + logic [WDATA_W-1:0] wdata_q; + logic [WSTRB_W-1:0] wstrb_q; + logic wlast_q; + + // Upstream ready = registered occupancy ONLY (no d_wready term) -> EMIF WREADY + // never reaches the upstream FSM combinationally. + assign u_wready = !full; + // Downstream presents the held beat, stable until the arbiter accepts it. + assign d_wvalid = full; + assign d_wdata = wdata_q; + assign d_wstrb = wstrb_q; + assign d_wlast = wlast_q; + + // One-entry register. Accept an offered upstream beat only while empty; + // release only when the arbiter accepts the held beat. When full and accepted + // in the same cycle, u_wready is still 0 (full is registered), so the next + // beat waits one cycle -> a swap/drop/dup is impossible. Payload registers + // deliberately have no reset (qualified by `full`/d_wvalid; the writer cannot + // present a beat until reset releases), avoiding a wide reset fanout. + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + full <= 1'b0; + end else if (!full) begin + if (u_wvalid) begin + full <= 1'b1; + wdata_q <= u_wdata; + wstrb_q <= u_wstrb; + wlast_q <= u_wlast; + end + end else begin + if (d_wready) full <= 1'b0; + end + end +endmodule : gs_axi_w_regbuf diff --git a/rtl/gif_gs/gs_lpddr_zc_emit.sv b/rtl/gif_gs/gs_lpddr_zc_emit.sv index ce39767..402711e 100644 --- a/rtl/gif_gs/gs_lpddr_zc_emit.sv +++ b/rtl/gif_gs/gs_lpddr_zc_emit.sv @@ -150,6 +150,11 @@ module gs_lpddr_zc_emit #( // ---------------- 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), @@ -159,10 +164,19 @@ module gs_lpddr_zc_emit #( .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), + .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.) diff --git a/sim/Makefile b/sim/Makefile index a97758d..ec78ff4 100644 --- a/sim/Makefile +++ b/sim/Makefile @@ -585,6 +585,7 @@ RTL_SRCS := \ $(RTL_ROOT)/gif_gs/gs_lpddr_map_pkg.sv \ $(RTL_ROOT)/gif_gs/gs_lpddr_axi_master.sv \ $(RTL_ROOT)/gif_gs/gs_lpddr_z_rmw.sv \ + $(RTL_ROOT)/gif_gs/gs_axi_w_regbuf.sv \ $(RTL_ROOT)/gif_gs/gs_lpddr_zc_emit.sv \ $(RTL_ROOT)/gif_gs/gs_lpddr_rd_probe.sv \ $(RTL_ROOT)/gif_gs/gs_lpddr_scanout.sv \ @@ -1143,6 +1144,15 @@ tb_gs_lpddr_zc_emit: dirs @echo "=== run tb_gs_lpddr_zc_emit ===" @cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_lpddr_zc_emit.vvp +tb_gs_axi_w_regbuf: dirs + @echo "=== build tb_gs_axi_w_regbuf ===" + $(IVERILOG) $(IVERILOG_FLGS) \ + -o $(BUILD_DIR)/tb_gs_axi_w_regbuf.vvp \ + -s tb_gs_axi_w_regbuf \ + $(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_axi_w_regbuf.sv + @echo "=== run tb_gs_axi_w_regbuf ===" + @cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_axi_w_regbuf.vvp + tb_gs_grad_divider: dirs @echo "=== build tb_gs_grad_divider ===" $(IVERILOG) $(IVERILOG_FLGS) \ @@ -6263,7 +6273,7 @@ run: tb_top_psmct32_sh3_zs640c12_cap tb_top_psmct32_sh3_zint640c12 run: tb_top_psmct32_sh3_zs640b24_cap tb_top_psmct32_sh3_zint640b24 .PHONY: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c sh3_zs640c24c_fixture sh3_zs640motionabc_bootlet -run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c +run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c tb_gs_axi_w_regbuf run: tb_ee_fetch tb_gs tb_intc tb_platform_video tb_bgcolor_via_dma tb_sif_mailbox \ tb_sif_command_echo tb_sif_command_echo_rearm tb_sif_negative_path \ diff --git a/sim/tb/gif_gs/tb_gs_axi_w_regbuf.sv b/sim/tb/gif_gs/tb_gs_axi_w_regbuf.sv new file mode 100644 index 0000000..693c987 --- /dev/null +++ b/sim/tb/gif_gs/tb_gs_axi_w_regbuf.sv @@ -0,0 +1,131 @@ +// retroDE_ps2 — tb_gs_axi_w_regbuf (Ch441) +// +// Focused scoreboard for the one-entry fully-registered AXI W buffer. Verifies: +// (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {WDATA,WSTRB,WLAST} +// under randomized upstream offer + downstream backpressure (a drop, dup, or +// reorder trips the sequence scoreboard). +// (2) The NO-COMBINATIONAL-BYPASS contract: u_wready === !full every cycle, so a +// fall-through `u_wready = !full || d_wready` (which would leak downstream +// WREADY back upstream into the Z FSM) is caught. A directed phase forces the +// full && d_wready case. +// (3) Downstream payload held STABLE while d_wvalid && !d_wready. +// (4) Full drain leaves the buffer empty with equal produced/consumed counts. +`timescale 1ns/1ps + +module tb_gs_axi_w_regbuf; + localparam int WD = 256, WS = 32; + logic clk = 0; always #5 clk = ~clk; // 100 MHz + logic rst_n; + + logic [WD-1:0] u_wdata; logic [WS-1:0] u_wstrb; logic u_wlast, u_wvalid, u_wready; + logic [WD-1:0] d_wdata; logic [WS-1:0] d_wstrb; logic d_wlast, d_wvalid; logic d_wready; + + gs_axi_w_regbuf #(.WDATA_W(WD), .WSTRB_W(WS)) dut ( + .clk(clk), .rst_n(rst_n), + .u_wdata(u_wdata), .u_wstrb(u_wstrb), .u_wlast(u_wlast), .u_wvalid(u_wvalid), .u_wready(u_wready), + .d_wdata(d_wdata), .d_wstrb(d_wstrb), .d_wlast(d_wlast), .d_wvalid(d_wvalid), .d_wready(d_wready) + ); + + int errors; initial errors = 0; + + // distinct nonzero payload per sequence value (fills all 256 + 32 bits) + function automatic logic [WD-1:0] mk(input logic [31:0] s); + mk = {s^32'hDEADBEEF, s+32'd5, ~s, s^32'hA5A5A5A5, s+32'd3, s^32'h0F0F0F0F, s+32'd1, s}; + endfunction + function automatic logic [WS-1:0] mk_strb(input logic [31:0] s); + mk_strb = (s ^ 32'hFFFF0000) | 32'd1; // nonzero, varies with s + endfunction + + // LFSR backpressure on both sides + logic [15:0] ul = 16'hACE1, dl = 16'h1357; + always_ff @(posedge clk) begin + ul <= {ul[14:0], ul[15]^ul[13]^ul[12]^ul[10]}; + dl <= {dl[14:0], dl[15]^dl[13]^dl[12]^dl[10]}; + end + logic force_ready, force_stall, prod_freeze; + assign d_wready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3])); + + // AXI-legal producer: assert u_wvalid with STABLE payload until accepted. + logic [31:0] wr_seq; // beats accepted UPSTREAM (into the buffer) + logic pending; + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin wr_seq <= 0; pending <= 1'b0; end + else if (u_wvalid && u_wready) begin + wr_seq <= wr_seq + 1; + pending <= (ul[0] | ul[3]) && !prod_freeze; // maybe offer the next beat + end + else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze; + end + assign u_wvalid = pending; + assign u_wdata = mk(wr_seq); // stable while pending (wr_seq only advances on accept) + assign u_wstrb = mk_strb(wr_seq); + assign u_wlast = 1'b1; // single-beat writes + + // (1) downstream scoreboard: exactly-once, in-order, payload-correct + logic [31:0] rd_seq; // beats delivered DOWNSTREAM + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) rd_seq <= 0; + else if (d_wvalid && d_wready) begin + if (d_wdata !== mk(rd_seq) || d_wstrb !== mk_strb(rd_seq) || d_wlast !== 1'b1) begin + if (errors < 20) $error("[wbuf] drop/dup/reorder/payload at seq %0d: wdata %h strb %h last %b", + rd_seq, d_wdata, d_wstrb, d_wlast); + errors++; + end + rd_seq <= rd_seq + 1; + end + end + + // (2) NO combinational downstream-ready bypass: u_wready must equal !full. + always_ff @(posedge clk) if (rst_n) begin + if (u_wready !== !dut.full) begin + if (errors < 20) $error("[wbuf] u_wready(%b) != !full(%b) — combinational bypass?", u_wready, dut.full); + errors++; + end + end + + // (3) stable downstream payload while stalled (d_wvalid && !d_wready) + logic [WD-1:0] hold_d; logic hold_v; + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin hold_v <= 1'b0; hold_d <= '0; end + else begin + if (hold_v && d_wvalid && (d_wdata !== hold_d)) begin + if (errors < 20) $error("[wbuf] downstream payload changed while stalled"); errors++; + end + hold_v <= d_wvalid && !d_wready; + hold_d <= d_wdata; + end + end + + initial begin + rst_n = 0; force_ready = 0; force_stall = 0; prod_freeze = 0; + repeat (6) @(posedge clk); rst_n = 1; + @(posedge clk); + if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after reset"); errors++; end + + // Phase 1: randomized offer + backpressure + repeat (20000) @(posedge clk); + + // Phase 2 (directed): stall downstream so the buffer fills and STAYS full + // (u_wready must read 0 = !full), then hold full while d_wready=1 — this is + // the full && d_wready case a fall-through skid would mishandle. + force_stall = 1; repeat (200) @(posedge clk); + force_stall = 0; force_ready = 1; repeat (200) @(posedge clk); + force_ready = 0; + + // Phase 3: freeze producer, drain fully + prod_freeze = 1; force_ready = 1; + begin int g; g = 0; while ((wr_seq !== rd_seq) && g < 4000) begin @(posedge clk); g++; end end + repeat (10) @(posedge clk); + + if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after drain (d_wvalid=%b)", d_wvalid); errors++; end + if (wr_seq !== rd_seq) begin $error("[wbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end + if (wr_seq < 32'd2000) begin $error("[wbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end + + $display("[tb_gs_axi_w_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors); + if (errors == 0) $display("[tb_gs_axi_w_regbuf] PASS"); + else $display("[tb_gs_axi_w_regbuf] FAIL"); + $finish; + end + + initial begin #2000000; $error("[tb_gs_axi_w_regbuf] TIMEOUT"); $finish; end +endmodule : tb_gs_axi_w_regbuf diff --git a/synth/de25_nano/top_psmct32_raster_demo/de25_nano_psmct32_raster_demo_top.qsf b/synth/de25_nano/top_psmct32_raster_demo/de25_nano_psmct32_raster_demo_top.qsf index 04f51f9..af7dcec 100644 --- a/synth/de25_nano/top_psmct32_raster_demo/de25_nano_psmct32_raster_demo_top.qsf +++ b/synth/de25_nano/top_psmct32_raster_demo/de25_nano_psmct32_raster_demo_top.qsf @@ -1060,6 +1060,7 @@ set_global_assignment -name SYSTEMVERILOG_FILE rtl/gif_gs/gs_tile_reload.sv # Ch357 — persistent-Z ROP (instantiated under GS_SH3_LPDDR_FB_Z; idle otherwise). map_pkg first (package). set_global_assignment -name SYSTEMVERILOG_FILE rtl/gif_gs/gs_lpddr_map_pkg.sv set_global_assignment -name SYSTEMVERILOG_FILE rtl/gif_gs/gs_lpddr_z_rmw.sv +set_global_assignment -name SYSTEMVERILOG_FILE rtl/gif_gs/gs_axi_w_regbuf.sv set_global_assignment -name SYSTEMVERILOG_FILE rtl/gif_gs/gs_lpddr_zc_emit.sv # ---------------------------------------------------------------------------- # Design RTL added during the EE/qbert chapters (Ch287+) that reached the demo