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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -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.)
|
||||
|
||||
+11
-1
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user