Ch443b: revert texture 4-bank split; 2-cycle drain (F_SETTLE + multicycle)
The four-bank tex_mem split (27dfd0b) closed the -0.370 EMIF drain write-address fanout but the owner GUI fit showed the fitter SCATTERED the banks, pushing the DESIGN-clock sampler read cone (ras_v0_x -> perspective-UV -> texel addr -> tex_mem portbaddr, the design's fundamental ~40ns critical path) to -2.208 ns. Net worse. Codex's call (Option 1 + honest write-side multicycle), implemented: - Restore the MONOLITHIC 65536x32 tex_mem, recovering the clean 25 MHz read-cone placement. Sampler/read-address path stays fully timed (Ch439g); nothing about it is relaxed. - Make the EMIF drain write genuinely two-cycle: new F_SETTLE state between F_DRAIN and F_WRITE. drain_idx_q/drain_word_q are loaded in F_DRAIN, HELD unchanged through F_SETTLE (the load block gates on F_DRAIN), and the RAM write + CRC happen at the later F_WRITE edge. - SDC: fail-closed 2-cycle-setup / 1-cycle-hold multicycle from ONLY u_texcache|drain_idx_q[*] to tex_mem (a 6.45 ns EMIF window for the drain write-address). Scoped -from the drain regs, so the sampler read path (different launch regs) is untouched. HALTs if tex_mem is present but drain_idx_q renamed. The AW buffer (gs_axi_aw_regbuf) and the tile-CDC max-skew 2.5 relax from27dfd0bare KEPT unchanged (both closed their families in the fit). Verified: tb_gs_texture_cache (monolithic + F_SETTLE, distinct-per-byte- lane + full-word, 0 errors), aw/w regbuf, texture_psmt8_clut, scanout_diag, ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760). No Quartus/board/push from here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,20 +85,22 @@ 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.
|
||||
// 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];
|
||||
// Ch443 (Codex): MONOLITHIC 65536x32 texture RAM. A four-bank width split was tried
|
||||
// to relieve the -0.370 ns EMIF drain write-address fanout, but it scattered the banks
|
||||
// and pushed the DESIGN-clock sampler read cone (ras_v0_x -> perspective-UV -> texel
|
||||
// addr -> tex_mem portbaddr) to -2.208 ns. The monolithic RAM restores that clean 25MHz
|
||||
// placement; the drain write-address is instead given a FUNCTIONALLY HONEST 2-cycle
|
||||
// window (F_SETTLE state + a drain-only multicycle SDC exception), never touching the
|
||||
// sampler-facing read port.
|
||||
(* ramstyle = "M20K" *) logic [31:0] tex_mem [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;
|
||||
// Ch443: F_SETTLE inserted between F_DRAIN and F_WRITE. drain_idx_q/drain_word_q are
|
||||
// loaded at the F_DRAIN edge and held unchanged through F_SETTLE; the RAM write-enable
|
||||
// asserts only at the later F_WRITE edge. The drain address is thus functionally
|
||||
// required 2 cycles after launch -> a fail-closed 2-cycle-setup/1-cycle-hold multicycle
|
||||
// (drain_idx_q -> tex_mem only) is honest, giving the EMIF write-address a 6.45 ns window.
|
||||
typedef enum logic [2:0] { F_IDLE, F_AR, F_R, F_DRAIN, F_SETTLE, F_WRITE, F_DONE } fstate_t;
|
||||
fstate_t fst;
|
||||
logic [$clog2(N_BEATS):0] beat; // 0..N_BEATS
|
||||
logic [255:0] fill_data_q;
|
||||
@@ -113,10 +115,7 @@ 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;
|
||||
// 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;
|
||||
logic [WIDX_BITS-1:0] drain_idx_q;
|
||||
// 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.
|
||||
@@ -164,16 +163,19 @@ module gs_texture_cache #(
|
||||
end
|
||||
end
|
||||
F_DRAIN: begin
|
||||
// LOAD half only (see the separate load block). Advance to F_SETTLE so
|
||||
// the just-loaded address/word are held one extra cycle before the write.
|
||||
fst <= F_SETTLE;
|
||||
end
|
||||
F_SETTLE: begin
|
||||
// HOLD: drain_idx_q/drain_word_q are NOT reloaded (load block gates on
|
||||
// F_DRAIN), so they stay stable across this edge. No RAM write here.
|
||||
fst <= F_WRITE;
|
||||
end
|
||||
F_WRITE: begin
|
||||
// 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)
|
||||
// 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
|
||||
if (fill_lane == 3'd7) begin
|
||||
fill_beats <= fill_beats + 32'd1;
|
||||
fill_bytes <= fill_bytes + 32'd32;
|
||||
@@ -215,10 +217,7 @@ 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_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;
|
||||
drain_idx_q <= fill_word_idx; // held through F_SETTLE (block gates on F_DRAIN), written at F_WRITE
|
||||
end
|
||||
end
|
||||
|
||||
@@ -227,18 +226,9 @@ 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) 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
|
||||
if (tex_rd_en) tex_rd_data <= tex_mem[rd_word];
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user