diff --git a/rtl/gif_gs/gs_texture_cache.sv b/rtl/gif_gs/gs_texture_cache.sv index f86c311..c4fa0fa 100644 --- a/rtl/gif_gs/gs_texture_cache.sv +++ b/rtl/gif_gs/gs_texture_cache.sv @@ -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; diff --git a/sim/tb/gif_gs/tb_gs_texture_cache.sv b/sim/tb/gif_gs/tb_gs_texture_cache.sv index 73b356b..cc48daa 100644 --- a/sim/tb/gif_gs/tb_gs_texture_cache.sv +++ b/sim/tb/gif_gs/tb_gs_texture_cache.sv @@ -131,16 +131,17 @@ 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). + // Full reconstructed word AND per-byte-lane pinpoint. The distinct-per-byte golden + // exercises all four byte lanes of the monolithic 32-bit word (Ch443). for (int i=0; i tex_mem packed-DSP arc cut ($ch439_bili_taps_n src -> $tdn dst); all non-tap texture-address launches remain timed" +# Ch443 (Codex) — texture-cache EMIF DRAIN write-address multicycle. The fill FSM now holds +# drain_idx_q/drain_word_q stable across F_DRAIN -> F_SETTLE -> F_WRITE and asserts the RAM +# write-enable ONLY at the later F_WRITE edge, so the drain address is functionally required +# 2 EMIF cycles after launch. Give ONLY drain_idx_q -> tex_mem a 2-cycle setup / 1-cycle hold +# multicycle (a 6.45 ns window for the -0.370 ns drain write-address fanout that closing the +# four-bank split had opened). SCOPED -from the drain registers, so it does NOT touch the +# sampler/read-address path (ras_v0_x / walker / UV / perspective -> tex_mem portbaddr) — that +# launches from DIFFERENT registers and stays fully timed (Ch439g above). Reuses the tex_mem +# destination collection $tex_mem_dst / $tdn defined for the Ch357/Ch439g exceptions. +# Fail-closed: if tex_mem exists but drain_idx_q[*] is gone (renamed), HALT — never silently +# omit the exception (which would let the drain path refail setup unconstrained). +set tex_drain_src [get_keepers -nowarn {*u_texcache|drain_idx_q[*]}] +set tex_drain_src_n [get_collection_size $tex_drain_src] +if { $tdn == 0 } { + post_message -type info "Ch443 SDC: texture drain multicycle inactive (no tex_mem in this profile)" +} else { + if { $tex_drain_src_n == 0 } { error "Ch443 SDC FATAL: tex_mem present but u_texcache|drain_idx_q[*] matched 0 keepers (renamed? drain multicycle orphaned -> write-address would silently refail setup)" } + set_multicycle_path -setup -end 2 -from $tex_drain_src -to $tex_mem_dst + set_multicycle_path -hold -end 1 -from $tex_drain_src -to $tex_mem_dst + post_message -type info "Ch443 SDC: texture drain_idx_q -> tex_mem 2-cycle setup / 1-cycle hold multicycle ($tex_drain_src_n src -> $tdn dst)" +} + # Ch358 (Codex) — the SAME quasi-static EMIF calibration-ready signal also reaches the scanout LINE-BUFFER RAMs # (26.1 fit at 640x480: lock_sync_inst|dreg[1] -> u_lpddr_scan_lb|lb0/lb1, 10 endpoints at -0.010ns). These are # startup/reset-derived line-buffer control paths, NOT runtime scanout data (the line buffers are inactive until