// ============================================================================ // tb_gs_texture_cache.sv (Ch322 Brick 1) // // Proves the prefilled texture cache: fill an 8x8 PSMCT32 texture (256 B = 8 // single-beat 256-bit reads) from a tiny EMIF-ish read model into the on-chip // RAM, then verify: // (1) fill_done asserts, fill_beats=8, fill_bytes=256, rd_errs=0 // (2) every cached word == the source word (sampler-side 1-cycle reads) // (3) the read is 1-cycle REGISTERED (data lands the cycle AFTER tex_rd_en) // (4) a forced non-OKAY rresp increments rd_errs // ============================================================================ `timescale 1ns/1ps module tb_gs_texture_cache; localparam int TEX_BYTES = 256; localparam int N_BEATS = 8; localparam int TEX_WORDS = TEX_BYTES/4; // 64 localparam [29:0] LPDDR_TEX_BASE = 30'h0010_0000; localparam [31:0] TEX_VRAM_BASE = 32'd2048; logic axi_clk = 0, sample_clk = 0, axi_rst_n = 0; always #5 axi_clk = ~axi_clk; // 100 MHz-ish always #7 sample_clk = ~sample_clk; // asynchronous, slower // fill control logic fill_start; logic fill_done; logic [31:0] fill_beats, fill_bytes, rd_errs; // AXI read channel logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid; logic [7:0] arlen; logic [2:0] arsize; logic arvalid, arready; logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready; // sampler read port logic tex_rd_en; 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 // force a bad rresp on a chosen beat to exercise rd_errs (set <0 to disable) int err_beat = -1; gs_texture_cache #( .LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS) ) dut ( .axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .fill_start(fill_start), .fill_done(fill_done), .fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(rd_errs), .araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready), .rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready), .sample_clk(sample_clk), .tex_rd_en(tex_rd_en), .tex_rd_addr(tex_rd_addr), .tex_rd_data(tex_rd_data), .tex_ready(tex_ready) ); // ---- single-beat EMIF read model (arlen=0 only), ~ a few cycles latency ---- // returns 8 words per 256-bit beat from the golden source, indexed by araddr. typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst; logic [29:0] beat_addr; logic [3:0] dly; int beat_idx; always_ff @(posedge axi_clk) begin if (!axi_rst_n) begin sst <= S_IDLE; arready <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0; rresp <= 2'b00; rdata <= '0; dly <= '0; end else begin arready <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0; case (sst) S_IDLE: if (arvalid) begin beat_addr <= araddr; arready <= 1'b1; beat_idx <= (araddr - LPDDR_TEX_BASE) >> 5; // 32 B/beat dly <= 4'd3; sst <= S_WAIT; end S_WAIT: if (dly==0) sst <= S_DATA; else dly <= dly - 1'b1; S_DATA: if (rready) begin for (int w=0; w<8; w++) rdata[w*32 +: 32] <= tex_word(beat_idx*8 + w); rresp <= (beat_idx == err_beat) ? 2'b10 : 2'b00; // SLVERR on chosen beat rvalid <= 1'b1; rlast <= 1'b1; sst <= S_IDLE; end endcase end end int errors = 0; task automatic check(input bit cond, input string msg); if (!cond) begin errors++; $display("[texcache] FAIL: %s", msg); end endtask // sampler 1-cycle read helper (sample_clk domain) task automatic read_word(input int widx, output logic [31:0] got); @(posedge sample_clk); tex_rd_addr <= TEX_VRAM_BASE + widx*4; tex_rd_en <= 1'b1; @(posedge sample_clk); tex_rd_en <= 1'b0; // data for THIS address lands now (1-cycle registered) @(posedge sample_clk); // sample the registered output got = tex_rd_data; endtask logic [31:0] got; initial begin fill_start = 0; tex_rd_en = 0; tex_rd_addr = 0; repeat (4) @(posedge axi_clk); axi_rst_n = 1; repeat (2) @(posedge axi_clk); // ---- pass 1: clean fill ---- // fill_start is an EDGE/TOGGLE: flip once (0->1) and HOLD — a 0->1->0 pulse would // trigger a second fill on the falling edge. Pass 2's reset re-creates the edge. @(posedge axi_clk); fill_start <= 1'b1; // wait for fill_done begin int g=0; while (!fill_done && g<2000) begin @(posedge axi_clk); g++; end end check(fill_done, "fill_done never asserted"); check(fill_beats == N_BEATS, $sformatf("fill_beats=%0d exp %0d", fill_beats, N_BEATS)); check(fill_bytes == TEX_BYTES, $sformatf("fill_bytes=%0d exp %0d", fill_bytes, TEX_BYTES)); check(rd_errs == 32'd0, $sformatf("rd_errs=%0d exp 0", rd_errs)); // wait for tex_ready to cross into sample_clk begin int g=0; while (!tex_ready && g<200) begin @(posedge sample_clk); g++; end end check(tex_ready, "tex_ready never synced"); // ---- verify every word via the sampler 1-cycle read port ---- for (int i=0; i