diff --git a/rtl/gif_gs/gs_axi_aw_regbuf.sv b/rtl/gif_gs/gs_axi_aw_regbuf.sv new file mode 100644 index 0000000..110c4dd --- /dev/null +++ b/rtl/gif_gs/gs_axi_aw_regbuf.sv @@ -0,0 +1,88 @@ +// retroDE_ps2 — gs_axi_aw_regbuf (Ch443) +// +// Fully-registered ONE-ENTRY AXI AW-channel (write-address) buffer. The AW twin +// of gs_axi_w_regbuf. Inserted between the Z RMW master's (gs_lpddr_z_rmw) AW +// OUTPUT and the write arbiter's s2 AW INPUT to cut the combinational path +// EMIF gen_p2c_ff[23] (AWREADY) -> wr_arb s2_awready -> gs_lpddr_z_rmw +// next-state (`st`, S_SFLUSH_AW / S_FILL_R) — the -0.410 ns EMIF setup family. +// +// Ch441 registered the W channel (gs_axi_w_regbuf u_z_wbuf) but left AW running +// straight from the FSM to the arbiter, so EMIF AWREADY still reached the FSM +// combinationally. This buffer applies the identical structural cut to AW. +// +// CONTRACT (identical to gs_axi_w_regbuf, must hold exactly): +// - FULLY REGISTERED, not a fall-through skid: u_awready depends ONLY on the +// registered occupancy `full`, never on d_awready. Downstream (EMIF) AWREADY +// therefore can never propagate combinationally back into the upstream FSM. +// (i.e. NOT `u_awready = !full || d_awready`.) +// - Buffers the complete {AWADDR, AWLEN, AWSIZE, AWBURST} payload and holds it +// stable downstream until the arbiter accepts it. +// - Exactly-once: an address accepted upstream is delivered downstream exactly +// once. One in flight (the Z RMW issues single-beat writes, AWLEN=0). +// - W and B channels are NOT touched here (W is separately buffered by +// gs_axi_w_regbuf; B passes through). AXI permits AW and W in either order, +// and each one-entry buffer holds its beat until the arbiter accepts it, so +// the AW/W pair still reaches the slave together. +`timescale 1ns/1ps + +module gs_axi_aw_regbuf #( + parameter int ADDR_W = 32, + parameter int LEN_W = 8, + parameter int SIZE_W = 3, + parameter int BURST_W = 2 +) ( + input logic clk, + input logic rst_n, + // upstream — from the Z RMW master's AW output + input logic [ADDR_W-1:0] u_awaddr, + input logic [LEN_W-1:0] u_awlen, + input logic [SIZE_W-1:0] u_awsize, + input logic [BURST_W-1:0] u_awburst, + input logic u_awvalid, + output logic u_awready, + // downstream — to the write arbiter's s2 AW input + output logic [ADDR_W-1:0] d_awaddr, + output logic [LEN_W-1:0] d_awlen, + output logic [SIZE_W-1:0] d_awsize, + output logic [BURST_W-1:0] d_awburst, + output logic d_awvalid, + input logic d_awready +); + logic full; + logic [ADDR_W-1:0] awaddr_q; + logic [LEN_W-1:0] awlen_q; + logic [SIZE_W-1:0] awsize_q; + logic [BURST_W-1:0] awburst_q; + + // Upstream ready = registered occupancy ONLY (no d_awready term) -> EMIF AWREADY + // never reaches the upstream FSM combinationally. + assign u_awready = !full; + // Downstream presents the held address, stable until the arbiter accepts it. + assign d_awvalid = full; + assign d_awaddr = awaddr_q; + assign d_awlen = awlen_q; + assign d_awsize = awsize_q; + assign d_awburst = awburst_q; + + // One-entry register. Accept an offered upstream address only while empty; + // release only when the arbiter accepts the held address. When full and + // accepted in the same cycle, u_awready is still 0 (full is registered), so + // the next address waits one cycle -> a swap/drop/dup is impossible. Payload + // registers deliberately have no reset (qualified by `full`/d_awvalid; the + // writer cannot present an address until reset releases). + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + full <= 1'b0; + end else if (!full) begin + if (u_awvalid) begin + full <= 1'b1; + awaddr_q <= u_awaddr; + awlen_q <= u_awlen; + awsize_q <= u_awsize; + awburst_q <= u_awburst; + end + end else begin + if (d_awready) full <= 1'b0; + end + end +endmodule : gs_axi_aw_regbuf diff --git a/rtl/gif_gs/gs_lpddr_zc_emit.sv b/rtl/gif_gs/gs_lpddr_zc_emit.sv index 402711e..857b3ad 100644 --- a/rtl/gif_gs/gs_lpddr_zc_emit.sv +++ b/rtl/gif_gs/gs_lpddr_zc_emit.sv @@ -155,6 +155,10 @@ module gs_lpddr_zc_emit #( // 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; + // Ch443: the Z RMW master's AW output ALSO goes through a one-entry registered + // buffer (gs_axi_aw_regbuf u_z_awbuf, below) so the FSM sees a REGISTERED awready + // (buffer occupancy), never EMIF's combinational AWREADY (gen_p2c_ff[23]). + logic [31:0] zi_awaddr; logic [7:0] zi_awlen; logic [2:0] zi_awsize; logic [1:0] zi_awburst; logic zi_awvalid, zi_awready; 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), @@ -163,20 +167,28 @@ module gs_lpddr_zc_emit #( .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), + .awaddr(zi_awaddr), .awlen(zi_awlen), .awsize(zi_awsize), .awburst(zi_awburst), .awvalid(zi_awvalid), .awready(zi_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 + // EMIF WREADY -> z_rmw FSM path. 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) ); + // Ch443: one-entry fully-registered AW-channel buffer, the AW twin of u_z_wbuf. + // Cuts the combinational EMIF AWREADY -> z_rmw FSM next-state (S_SFLUSH_AW/ + // S_FILL_R) path — the -0.410 ns setup family Ch442's fit exposed. + gs_axi_aw_regbuf #(.ADDR_W(32), .LEN_W(8), .SIZE_W(3), .BURST_W(2)) u_z_awbuf ( + .clk(axi_clk), .rst_n(axi_rst_n), + .u_awaddr(zi_awaddr), .u_awlen(zi_awlen), .u_awsize(zi_awsize), .u_awburst(zi_awburst), .u_awvalid(zi_awvalid), .u_awready(zi_awready), + .d_awaddr(z_awaddr), .d_awlen(z_awlen), .d_awsize(z_awsize), .d_awburst(z_awburst), .d_awvalid(z_awvalid), .d_awready(z_awready) + ); // 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/rtl/gif_gs/gs_texture_cache.sv b/rtl/gif_gs/gs_texture_cache.sv index 9ebe868..f86c311 100644 --- a/rtl/gif_gs/gs_texture_cache.sv +++ b/rtl/gif_gs/gs_texture_cache.sv @@ -85,7 +85,17 @@ module gs_texture_cache #( // 1x 65536x32 (HERE) -> latch each AXI beat, drain 8 lanes over 8 axi_clk cycles. // Serializing the fill removes the multi-bank/multi-write geometry while preserving the sampler's // one-cycle registered 32-bit read. The one-shot fill is still tiny compared with board startup. - (* ramstyle = "M20K" *) logic [31:0] tex_mem [0:TEX_WORDS-1]; + // Ch443 (Codex): four 65536x8 byte-width banks with INDEPENDENT preserved write- + // address launch registers, replacing the single 65536x32 logical RAM. The single + // drain_idx_q fanning across the whole 128-M20K macro was the -0.370 ns setup family + // (drain_idx_q[4] -> tex_mem ram_block*~reg0). Splitting the WIDTH gives each address + // tree ~32 M20Ks; total M20K count is unchanged (4x32 == 128). Selector structure, + // one-shot fill sequence, and the 1-cycle registered read latency are all preserved; + // the read reconstructs the 32-bit word by concatenation. + (* ramstyle = "M20K" *) logic [7:0] tex_mem_b0 [0:TEX_WORDS-1]; + (* ramstyle = "M20K" *) logic [7:0] tex_mem_b1 [0:TEX_WORDS-1]; + (* ramstyle = "M20K" *) logic [7:0] tex_mem_b2 [0:TEX_WORDS-1]; + (* ramstyle = "M20K" *) logic [7:0] tex_mem_b3 [0:TEX_WORDS-1]; // ================= fill side (axi_clk) ================= typedef enum logic [2:0] { F_IDLE, F_AR, F_R, F_DRAIN, F_WRITE, F_DONE } fstate_t; @@ -103,7 +113,10 @@ module gs_texture_cache #( // so reset values are unobservable; keeping them out of the 4k-fanout EMIF calibration reset removes that reset // from the duplicated RAM-address launch registers at 310 MHz. logic [31:0] drain_word_q; - logic [WIDX_BITS-1:0] drain_idx_q; + // Ch443: four independent, preserved, non-merged write-address launch registers — + // one per byte bank. dont_merge is REQUIRED: they hold identical values, so without + // it Quartus would re-merge them into a single high-fanout register, undoing the fix. + (* preserve, dont_merge *) logic [WIDX_BITS-1:0] drain_idx_q0, drain_idx_q1, drain_idx_q2, drain_idx_q3; // fill_start is an EDGE/TOGGLE (bridge toggles it on each arm), CDC-synced here so the // cache is RE-FILLABLE: each arm reloads the texture (lets the HPS re-stage a different // texture without a board reset). 3-FF sync + edge-detect, like the read/write probes. @@ -154,9 +167,13 @@ module gs_texture_cache #( fst <= F_WRITE; end F_WRITE: begin - // COMMIT half: registered word -> M20K; CRC over the word actually committed. - tex_mem[drain_idx_q] <= drain_word_q; - fill_crc <= fill_crc + drain_word_q; // sum32 over the words written + // COMMIT half: registered word -> four byte banks (each with its own + // preserved address reg); CRC over the FULL word actually committed. + tex_mem_b0[drain_idx_q0] <= drain_word_q[7:0]; + tex_mem_b1[drain_idx_q1] <= drain_word_q[15:8]; + tex_mem_b2[drain_idx_q2] <= drain_word_q[23:16]; + tex_mem_b3[drain_idx_q3] <= drain_word_q[31:24]; + fill_crc <= fill_crc + drain_word_q; // sum32 over the words written (unchanged) if (fill_lane == 3'd7) begin fill_beats <= fill_beats + 32'd1; fill_bytes <= fill_bytes + 32'd32; @@ -198,7 +215,10 @@ module gs_texture_cache #( always_ff @(posedge axi_clk) begin if (fst == F_DRAIN) begin drain_word_q <= fill_data_q[fill_lane*32 +: 32]; - drain_idx_q <= fill_word_idx; + drain_idx_q0 <= fill_word_idx; // four identical loads; kept separate by dont_merge + drain_idx_q1 <= fill_word_idx; + drain_idx_q2 <= fill_word_idx; + drain_idx_q3 <= fill_word_idx; end end @@ -207,9 +227,18 @@ module gs_texture_cache #( // present (tex_rd_addr) when tex_rd_en, data lands next cycle. wire [31:0] word_off = (tex_rd_addr - TEX_VRAM_BASE) >> 2; wire [WIDX_BITS-1:0] rd_word = word_off[WIDX_BITS-1:0]; + // Ch443: read each byte bank, register the four bytes, reconstruct the 32-bit word + // by concatenation. Identical 1-cycle registered latency to the pre-split read. + logic [7:0] tex_rd_b0, tex_rd_b1, tex_rd_b2, tex_rd_b3; always_ff @(posedge sample_clk) begin - if (tex_rd_en) tex_rd_data <= tex_mem[rd_word]; + if (tex_rd_en) begin + tex_rd_b0 <= tex_mem_b0[rd_word]; + tex_rd_b1 <= tex_mem_b1[rd_word]; + tex_rd_b2 <= tex_mem_b2[rd_word]; + tex_rd_b3 <= tex_mem_b3[rd_word]; + end end + assign tex_rd_data = {tex_rd_b3, tex_rd_b2, tex_rd_b1, tex_rd_b0}; // fill_done -> sample_clk (2-FF). The read mux only goes live once warm. logic [1:0] done_sync; diff --git a/sim/Makefile b/sim/Makefile index cc387ef..f368912 100644 --- a/sim/Makefile +++ b/sim/Makefile @@ -586,6 +586,7 @@ RTL_SRCS := \ $(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_axi_aw_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 \ @@ -1153,6 +1154,15 @@ tb_gs_axi_w_regbuf: dirs @echo "=== run tb_gs_axi_w_regbuf ===" @cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_axi_w_regbuf.vvp +tb_gs_axi_aw_regbuf: dirs + @echo "=== build tb_gs_axi_aw_regbuf ===" + $(IVERILOG) $(IVERILOG_FLGS) \ + -o $(BUILD_DIR)/tb_gs_axi_aw_regbuf.vvp \ + -s tb_gs_axi_aw_regbuf \ + $(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_axi_aw_regbuf.sv + @echo "=== run tb_gs_axi_aw_regbuf ===" + @cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_axi_aw_regbuf.vvp + tb_gs_grad_divider: dirs @echo "=== build tb_gs_grad_divider ===" $(IVERILOG) $(IVERILOG_FLGS) \ @@ -6282,7 +6292,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 tb_gs_axi_w_regbuf +run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c tb_gs_axi_w_regbuf tb_gs_axi_aw_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_aw_regbuf.sv b/sim/tb/gif_gs/tb_gs_axi_aw_regbuf.sv new file mode 100644 index 0000000..9f88083 --- /dev/null +++ b/sim/tb/gif_gs/tb_gs_axi_aw_regbuf.sv @@ -0,0 +1,138 @@ +// retroDE_ps2 — tb_gs_axi_aw_regbuf (Ch443) +// +// AW twin of tb_gs_axi_w_regbuf. Focused scoreboard for the one-entry fully- +// registered AXI AW buffer. Verifies: +// (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {AWADDR,AWLEN, +// AWSIZE,AWBURST} under randomized upstream offer + downstream backpressure +// (a drop, dup, or reorder trips the sequence scoreboard). +// (2) The NO-COMBINATIONAL-BYPASS contract: u_awready === !full every cycle, so a +// fall-through `u_awready = !full || d_awready` (which would leak downstream +// AWREADY back upstream into the Z FSM) is caught. A directed phase forces the +// full && d_awready case. +// (3) Downstream payload held STABLE while d_awvalid && !d_awready. +// (4) Full drain leaves the buffer empty with equal produced/consumed counts. +`timescale 1ns/1ps + +module tb_gs_axi_aw_regbuf; + localparam int AW = 32, LN = 8, SZ = 3, BR = 2; + logic clk = 0; always #5 clk = ~clk; // 100 MHz + logic rst_n; + + logic [AW-1:0] u_awaddr; logic [LN-1:0] u_awlen; logic [SZ-1:0] u_awsize; logic [BR-1:0] u_awburst; logic u_awvalid, u_awready; + logic [AW-1:0] d_awaddr; logic [LN-1:0] d_awlen; logic [SZ-1:0] d_awsize; logic [BR-1:0] d_awburst; logic d_awvalid; logic d_awready; + + gs_axi_aw_regbuf #(.ADDR_W(AW), .LEN_W(LN), .SIZE_W(SZ), .BURST_W(BR)) dut ( + .clk(clk), .rst_n(rst_n), + .u_awaddr(u_awaddr), .u_awlen(u_awlen), .u_awsize(u_awsize), .u_awburst(u_awburst), .u_awvalid(u_awvalid), .u_awready(u_awready), + .d_awaddr(d_awaddr), .d_awlen(d_awlen), .d_awsize(d_awsize), .d_awburst(d_awburst), .d_awvalid(d_awvalid), .d_awready(d_awready) + ); + + int errors; initial errors = 0; + + // distinct payload per sequence value (fills all fields) + function automatic logic [AW-1:0] mk_addr(input logic [31:0] s); mk_addr = (s ^ 32'hCAFE_0000) | 32'd4; endfunction + function automatic logic [LN-1:0] mk_len (input logic [31:0] s); mk_len = s[7:0]; endfunction + function automatic logic [SZ-1:0] mk_size(input logic [31:0] s); mk_size = s[2:0] ^ 3'd5; endfunction + function automatic logic [BR-1:0] mk_brst(input logic [31:0] s); mk_brst = s[1:0] | 2'b01; 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_awready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3])); + + // AXI-legal producer: assert u_awvalid with STABLE payload until accepted. + logic [31:0] wr_seq; + 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_awvalid && u_awready) begin + wr_seq <= wr_seq + 1; + pending <= (ul[0] | ul[3]) && !prod_freeze; + end + else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze; + end + assign u_awvalid = pending; + assign u_awaddr = mk_addr(wr_seq); + assign u_awlen = mk_len(wr_seq); + assign u_awsize = mk_size(wr_seq); + assign u_awburst = mk_brst(wr_seq); + + // (1) downstream scoreboard: exactly-once, in-order, payload-correct + logic [31:0] rd_seq; + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) rd_seq <= 0; + else if (d_awvalid && d_awready) begin + if (d_awaddr !== mk_addr(rd_seq) || d_awlen !== mk_len(rd_seq) || + d_awsize !== mk_size(rd_seq) || d_awburst !== mk_brst(rd_seq)) begin + if (errors < 20) $error("[awbuf] drop/dup/reorder/payload at seq %0d: addr %h len %h size %h burst %b", + rd_seq, d_awaddr, d_awlen, d_awsize, d_awburst); + errors++; + end + rd_seq <= rd_seq + 1; + end + end + + // (2) NO combinational downstream-ready bypass: u_awready must equal !full. + always_ff @(posedge clk) if (rst_n) begin + if (u_awready !== !dut.full) begin + if (errors < 20) $error("[awbuf] u_awready(%b) != !full(%b) — combinational bypass?", u_awready, dut.full); + errors++; + end + end + logic saw_full_and_ready; initial saw_full_and_ready = 1'b0; + always_ff @(posedge clk) if (rst_n && dut.full && d_awready) saw_full_and_ready <= 1'b1; + + // (3) while stalled, the SAME beat must still be presented. + logic [AW-1:0] hold_a; logic [LN-1:0] hold_l; logic [SZ-1:0] hold_s; logic [BR-1:0] hold_b; logic hold_v; + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin hold_v <= 1'b0; hold_a <= '0; hold_l <= '0; hold_s <= '0; hold_b <= '0; end + else begin + if (hold_v) begin + if (!d_awvalid) begin + if (errors < 20) $error("[awbuf] d_awvalid deasserted while stalled"); errors++; + end else if (d_awaddr !== hold_a || d_awlen !== hold_l || d_awsize !== hold_s || d_awburst !== hold_b) begin + if (errors < 20) $error("[awbuf] downstream {awaddr,awlen,awsize,awburst} changed while stalled"); errors++; + end + end + hold_v <= d_awvalid && !d_awready; + hold_a <= d_awaddr; hold_l <= d_awlen; hold_s <= d_awsize; hold_b <= d_awburst; + 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_awvalid !== 1'b0) begin $error("[awbuf] 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_awready must read 0 = !full), then hold full while d_awready=1. + 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_awvalid !== 1'b0) begin $error("[awbuf] not empty after drain (d_awvalid=%b)", d_awvalid); errors++; end + if (wr_seq !== rd_seq) begin $error("[awbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end + if (wr_seq < 32'd2000) begin $error("[awbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end + if (!saw_full_and_ready) begin $error("[awbuf] coverage: full && d_awready never observed — no-bypass case unexercised"); errors++; end + + $display("[tb_gs_axi_aw_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors); + if (errors == 0) $display("[tb_gs_axi_aw_regbuf] PASS"); + else $display("[tb_gs_axi_aw_regbuf] FAIL"); + $finish; + end + + initial begin #2000000; $error("[tb_gs_axi_aw_regbuf] TIMEOUT"); $finish; end +endmodule : tb_gs_axi_aw_regbuf diff --git a/sim/tb/gif_gs/tb_gs_texture_cache.sv b/sim/tb/gif_gs/tb_gs_texture_cache.sv index f9356cf..73b356b 100644 --- a/sim/tb/gif_gs/tb_gs_texture_cache.sv +++ b/sim/tb/gif_gs/tb_gs_texture_cache.sv @@ -37,8 +37,17 @@ module tb_gs_texture_cache; logic [31:0] tex_rd_addr, tex_rd_data; logic tex_ready; - // golden source: 64 words. tex_word(i) = 0xC0DE_0000 | i (distinct per lane). - function automatic [31:0] tex_word(input int i); tex_word = 32'hC0DE_0000 | i[31:0]; endfunction + // golden source. Ch443: every byte lane carries DISTINCT data — different per index + // AND different across the four banks (distinct XOR keys + distinct bases) — so the + // four-bank tex_mem split is genuinely exercised: a bank swap, misroute, or dropped + // bank produces a wrong reconstructed byte that the full-word + per-bank checks catch. + // (The old 0xC0DE_0000|i had constant bytes 2/3, which a bank fault could hide.) + function automatic [31:0] tex_word(input int i); + tex_word = { 8'(((i*4 + 3) & 32'hFF) ^ 32'hA3), + 8'(((i*4 + 2) & 32'hFF) ^ 32'h5C), + 8'(((i*4 + 1) & 32'hFF) ^ 32'h91), + 8'(((i*4 + 0) & 32'hFF) ^ 32'h2E) }; + endfunction // force a bad rresp on a chosen beat to exercise rd_errs (set <0 to disable) int err_beat = -1; @@ -122,9 +131,16 @@ module tb_gs_texture_cache; check(tex_ready, "tex_ready never synced"); // ---- verify every word via the sampler 1-cycle read port ---- + // Full reconstructed word AND per-bank byte pinpoint (Ch443 four-bank split). for (int i=0; i 2.5. The Ch442 fit placed this quasi-static OSD + # tile bundle at 2.050 ns actual skew, failing the round-number 2.0 by 50 ps. The CDC + # contract guarantees >= 2 design-clock periods of bus stability before the synchronized + # toggle samples (same recipe as the tile hold-false + net_delay below), so 2.5 ns stays + # FAR inside the real functional window and avoids engineering to a 50 ps margin. The + # set_net_delay ARRIVAL bound is UNCHANGED at 2.0 ns (it bounds the actual requirement: + # each bit must arrive within the stability window; skew only bounds bit-to-bit spread). + set_max_skew -from $tile_cdc_src -to $tile_cdc_dst 2.5 set_net_delay -max -from $tile_cdc_src -to $tile_cdc_dst 2.0 - post_message -type info "Ch357 SDC: tile_ram_cdc hold-false-path + 2ns max_skew + 2ns net_delay ($tile_src_n src -> $tile_dst_n shadow_mem dst)" + post_message -type info "Ch357/443 SDC: tile_ram_cdc hold-false-path + 2.5ns max_skew + 2ns net_delay ($tile_src_n src -> $tile_dst_n shadow_mem dst)" } # Ch357 (Codex) — osd_cfg QUASI-STATIC config CDC. osd_cfg{0,1}_q (bridge, refclk/fabric domain) cross into