// retroDE_ps2 — tb_gs_raster_backpressure_stress // ============================================================================= // Ch172 backpressure acceptance TB. Demonstrates that: // // 1. A GIF stream with more sprites than the raster command FIFO depth // now completes WITHOUT dropping any primitive (in Ch171 this would // have been a `push_drop` silent loss). // 2. While the FIFO is full, `gif_in_ready` (= `dmac_gif_ready`) goes // LOW for at least one cycle — the upstream stall actually happens. // 3. The final VRAM contents match the per-sprite expected colors at // every probed coordinate. // 4. `raster_overflow` stays LOW the entire run. // 5. The DMA still asserts done after backpressure clears. // // Strategy: build an 8-SPRITE payload procedurally inside the TB (rather // than `$readmemh` from baked .mem files) and shove it through the same // `top_psmct32_raster_demo_bram` instance the board uses, with backpressure // hooked up. 8 sprites × FIFO_DEPTH=2 means 6 of the 8 sprites would have // dropped under Ch171's no-backpressure regime — under Ch172 backpressure // all 8 complete. // // We use a smaller per-sprite raster footprint (40×30 pixels each, 8 sprites // tiled across the 320×240 paint area) so the sim watchdog is comfortable. // ============================================================================= `timescale 1ns/1ps module tb_gs_raster_backpressure_stress; // Match the board-wrapper Ch169 + Ch171 + Ch172 overrides. localparam int H_ACTIVE = 640; localparam int V_ACTIVE = 480; localparam int H_FRONT = 16; localparam int H_SYNC_W = 96; localparam int H_BACK = 48; localparam int V_FRONT = 10; localparam int V_SYNC_W = 2; localparam int V_BACK = 33; localparam int VRAM_BYTES = 512 * 1024; localparam int N_SPRITES = 8; localparam int SPRITE_W = 40; localparam int SPRITE_H = 30; // Per-sprite tile layout: 4 across × 2 down, each tile 80×60 of the // 320×240 paint region. Within each tile, fill the upper-left 40×30 // (the sprite) so adjacent tiles don't overlap. function automatic int sprite_x0(input int s); return (s % 4) * 80; endfunction function automatic int sprite_y0(input int s); return (s / 4) * 60; endfunction function automatic int sprite_x1(input int s); return sprite_x0(s) + SPRITE_W - 1; endfunction function automatic int sprite_y1(input int s); return sprite_y0(s) + SPRITE_H - 1; endfunction // 8 distinct colors so every sprite is identifiable on a VRAM probe. function automatic logic [7:0] sprite_r(input int s); case (s) 0: return 8'hFF; 1: return 8'h00; 2: return 8'h00; 3: return 8'hFF; 4: return 8'hFF; 5: return 8'h00; 6: return 8'hFF; 7: return 8'h80; default: return 8'h00; endcase endfunction function automatic logic [7:0] sprite_g(input int s); case (s) 0: return 8'h00; 1: return 8'hFF; 2: return 8'h00; 3: return 8'hFF; 4: return 8'h00; 5: return 8'hFF; 6: return 8'h80; 7: return 8'h80; default: return 8'h00; endcase endfunction function automatic logic [7:0] sprite_b(input int s); case (s) 0: return 8'h00; 1: return 8'h00; 2: return 8'hFF; 3: return 8'h00; 4: return 8'hFF; 5: return 8'hFF; 6: return 8'h00; 7: return 8'h80; default: return 8'h00; endcase endfunction // ========================================================= // DUT // ========================================================= logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; logic core_go; logic [7:0] r, g, b; logic hsync, vsync, de; logic core_halt; logic dma_done_seen; logic frame_seen; top_psmct32_raster_demo_bram #( .H_ACTIVE (H_ACTIVE), .V_ACTIVE (V_ACTIVE), .H_FRONT (H_FRONT), .H_SYNC (H_SYNC_W), .H_BACK (H_BACK), .V_FRONT (V_FRONT), .V_SYNC (V_SYNC_W), .V_BACK (V_BACK), .VRAM_BYTES (VRAM_BYTES), // Ch252 — mirror the hardware build profile (PSMCT32 only, // no PSMT4 RMW) to clear the replication-tripwire $fatal. .VRAM_ENABLE_READ2 (1'b0) ) dut ( // Ch255 — heartbeat override inputs are dormant in this // stress TB; coverage of the override path itself lives in // tb_top_psmct32_raster_demo_bram_ch171. .joy_a_pressed_i (1'b0), .joy_b_pressed_i (1'b0), .clk(clk), .rst_n(rst_n), .core_go(core_go), .r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync), .de(de), .core_halt(core_halt), .dma_done_seen(dma_done_seen), .frame_seen(frame_seen) ); // ========================================================= // Observers // ========================================================= bit saw_in_ready_low; // backpressure asserted at least once int in_ready_low_cycles; // total cycles in_ready was LOW bit saw_raster_overflow; // should NEVER fire under Ch172 always_ff @(posedge clk) begin if (rst_n) begin if (!dut.u_gif.in_ready) begin saw_in_ready_low <= 1'b1; in_ready_low_cycles <= in_ready_low_cycles + 1; end if (dut.u_gs.raster_overflow) saw_raster_overflow <= 1'b1; end end // ========================================================= // PSMCT32 page/block address reference (FBW=5 for 320-wide // framebuffer, matches Ch171 bake.py + board wrapper). // ========================================================= function automatic int ref_block_idx_ct32(input int by, input int bx); case ({by[1:0], bx[2:0]}) 5'd0: return 0; 5'd1: return 1; 5'd2: return 4; 5'd3: return 5; 5'd4: return 16; 5'd5: return 17; 5'd6: return 20; 5'd7: return 21; 5'd8: return 2; 5'd9: return 3; 5'd10: return 6; 5'd11: return 7; 5'd12: return 18; 5'd13: return 19; 5'd14: return 22; 5'd15: return 23; 5'd16: return 8; 5'd17: return 9; 5'd18: return 12; 5'd19: return 13; 5'd20: return 24; 5'd21: return 25; 5'd22: return 28; 5'd23: return 29; 5'd24: return 10; 5'd25: return 11; 5'd26: return 14; 5'd27: return 15; 5'd28: return 26; 5'd29: return 27; 5'd30: return 30; default: return 31; endcase endfunction function automatic int ref_addr_psmct32(input int x_v, input int y_v); int page_x, page_y, page_idx, page_base; int by, bx, blk_idx, xb, yb; page_x = x_v / 64; page_y = y_v / 32; page_idx = page_y * 5 + page_x; // fbw=5 page_base = page_idx * 8192; by = (y_v % 32) / 8; bx = (x_v % 64) / 8; blk_idx = ref_block_idx_ct32(by, bx); xb = x_v % 8; yb = y_v % 8; return page_base + blk_idx * 256 + yb * 32 + xb * 4; endfunction // ========================================================= // Procedural bootlet + payload preload. The board path uses // $readmemh; here we bypass that by writing directly into the // ee_ram and bios_rom backings via hierarchical refs at time 0 // (before reset deasserts). This keeps the TB self-contained: // 8-sprite GIF payload built in SystemVerilog without a baked // .mem. // // Module-scope ints for the build loop (iverilog 12 quirk — // declarations-with-initializers inside loop bodies silently // zero-out; Ch171 caught this in tb_top_..._bram_ch171). // ========================================================= localparam int QWC_TOTAL = N_SPRITES * 6; // 6 qwords per sprite (1 tag + 5 A+D) int build_i; int build_qw_base; int build_s; int build_eop; int probe_x_v, probe_y_v, probe_addr; logic [31:0] probe_got, probe_expected; int errors; // Helper: assemble a 128-bit qword via SystemVerilog concatenation. function automatic logic [127:0] mk_giftag(input int nloop, input int eop); // {regs[63:0]=0xE_EEEE, NREG=5 in [63:60], FLG=0 in [59:58], // 42'd0, EOP at bit 15, NLOOP at [14:0]} -> lower 64 bits // Upper 64 bits = REGS = 5 nibbles of 0xE. logic [63:0] lower; lower = '0; lower[63:60] = 4'd5; // NREG = 5 lower[59:58] = 2'd0; // FLG = PACKED lower[15] = eop[0]; // EOP lower[14:0] = nloop[14:0]; // NLOOP return {64'h0000_0000_000E_EEEE, lower}; endfunction function automatic logic [127:0] mk_aplusd(input int reg_num, input logic [63:0] data64); return { 56'd0, reg_num[7:0], data64 }; endfunction function automatic logic [63:0] mk_xyz2(input int x, input int y); logic [63:0] v; v = '0; v[15:4] = x[11:0]; v[31:20] = y[11:0]; return v; endfunction function automatic logic [63:0] mk_rgbaq(input logic [7:0] rr, input logic [7:0] gg, input logic [7:0] bb); return {32'd0, 8'hFF, bb, gg, rr}; endfunction // GIF register-number constants (match bake.py). localparam int RN_PRIM = 8'h00; localparam int RN_RGBAQ = 8'h01; localparam int RN_XYZ2 = 8'h05; localparam int RN_FRAME_1 = 8'h4C; localparam logic [63:0] FRAME_1_PSMCT32_FBW5 = 64'h0000_0000_0005_0000; // FBW=5 in bits[21:16] localparam logic [63:0] PRIM_SPRITE = 64'd6; // EE bootlet: same shape as bake.py's Ch171 bootlet (with // DISPLAY1_hi for 320×240, DISPFB1 FBW=5, kick DMAC ch2 with // QWC = N_SPRITES * 6). Encoded as 32-bit MIPS words. function automatic logic [31:0] enc_lui(input int rt, input int imm); return (32'h0F << 26) | (rt[4:0] << 16) | imm[15:0]; endfunction function automatic logic [31:0] enc_ori(input int rt, input int rs, input int imm); return (32'h0D << 26) | (rs[4:0] << 21) | (rt[4:0] << 16) | imm[15:0]; endfunction function automatic logic [31:0] enc_sw(input int rt, input int rs, input int imm); return (32'h2B << 26) | (rs[4:0] << 21) | (rt[4:0] << 16) | imm[15:0]; endfunction initial begin // ---- Build the 8-sprite payload directly into ee_ram_stub ---- // PAYLOAD_MADR = 0x100 = qword index 16 (16-byte qwords). build_qw_base = 16; for (build_s = 0; build_s < N_SPRITES; build_s++) begin build_eop = (build_s == N_SPRITES - 1) ? 1 : 0; dut.u_ram.mem[build_qw_base + build_s*6 + 0] = mk_giftag(1, build_eop); dut.u_ram.mem[build_qw_base + build_s*6 + 1] = mk_aplusd(RN_PRIM, PRIM_SPRITE); dut.u_ram.mem[build_qw_base + build_s*6 + 2] = mk_aplusd(RN_FRAME_1, FRAME_1_PSMCT32_FBW5); dut.u_ram.mem[build_qw_base + build_s*6 + 3] = mk_aplusd(RN_RGBAQ, mk_rgbaq(sprite_r(build_s), sprite_g(build_s), sprite_b(build_s))); dut.u_ram.mem[build_qw_base + build_s*6 + 4] = mk_aplusd(RN_XYZ2, mk_xyz2(sprite_x0(build_s), sprite_y0(build_s))); dut.u_ram.mem[build_qw_base + build_s*6 + 5] = mk_aplusd(RN_XYZ2, mk_xyz2(sprite_x1(build_s), sprite_y1(build_s))); end // ---- Build the EE bootlet directly into bios_rom_stub ---- // (Mirrors bake.py's bootlet_for_display1_hi() with FBW=5 + // DW=319/DH=239 and QWC=N_SPRITES*6.) dut.u_bios.mem[0] = enc_lui( 1, 16'h1200); // r1 = 0x1200_0000 dut.u_bios.mem[1] = enc_lui( 2, 16'h0000); // r2 = 0 dut.u_bios.mem[2] = enc_ori( 2, 2, 16'h0A00); // DISPFB1 = FBW=5, PSM=PSMCT32 dut.u_bios.mem[3] = enc_sw ( 2, 1, 16'h0070); // *DISPFB1 = r2 dut.u_bios.mem[4] = enc_sw ( 0, 1, 16'h0080); // *DISPLAY1_lo = 0 dut.u_bios.mem[5] = enc_lui( 2, 16'h000E); // DISPLAY1_hi upper dut.u_bios.mem[6] = enc_ori( 2, 2, 16'hF13F); // DISPLAY1_hi lower (DW=319/DH=239) dut.u_bios.mem[7] = enc_sw ( 2, 1, 16'h0084); // *DISPLAY1_hi = r2 dut.u_bios.mem[8] = enc_ori( 2, 0, 16'h0001); // r2 = PMODE.EN1 dut.u_bios.mem[9] = enc_sw ( 2, 1, 16'h0000); // *PMODE = r2 dut.u_bios.mem[10] = enc_lui(10, 16'h1000); // r10 = 0x1000_0000 dut.u_bios.mem[11] = enc_ori(10, 10, 16'hA000); // r10 = DMAC ch2 base dut.u_bios.mem[12] = enc_ori(11, 0, 16'h0100); // r11 = PAYLOAD_MADR dut.u_bios.mem[13] = enc_sw (11, 10, 16'h0010); // *MADR = r11 dut.u_bios.mem[14] = enc_ori(11, 0, QWC_TOTAL[15:0]); // r11 = QWC = N_SPRITES*6 dut.u_bios.mem[15] = enc_sw (11, 10, 16'h0020); // *QWC = r11 dut.u_bios.mem[16] = enc_ori(11, 0, 16'h0001); // r11 = CHCR.start dut.u_bios.mem[17] = enc_sw (11, 10, 16'h0000); // *CHCR = r11 dut.u_bios.mem[18] = 32'h0000_000C; // syscall (halt) // Zero pad the rest so undefined memory reads stay quiet. for (build_i = 19; build_i < 1024; build_i++) dut.u_bios.mem[build_i] = 32'h0000_0000; errors = 0; saw_in_ready_low = 1'b0; in_ready_low_cycles = 0; saw_raster_overflow = 1'b0; rst_n = 1'b0; core_go = 1'b0; repeat (10) @(posedge clk); rst_n = 1'b1; repeat (4) @(posedge clk); @(negedge clk); core_go = 1'b1; @(negedge clk); core_go = 1'b0; // Wait for the EE bootlet to finish + DMAC/GIF/raster to drain. wait (core_halt == 1'b1); repeat (4) @(posedge clk); wait (dma_done_seen == 1'b1); repeat (10) @(posedge clk); if (dut.xfer_busy == 1'b1) wait (dut.xfer_busy == 1'b0); if (dut.u_gs.raster_active == 1'b1) wait (dut.u_gs.raster_active == 1'b0); repeat (40) @(posedge clk); // ----------------------------------------------------- // Acceptance check 1 — backpressure was observed. // ----------------------------------------------------- if (!saw_in_ready_low) begin $error("[bp] in_ready never went low — backpressure did not assert despite 8 sprites in flight"); errors = errors + 1; end if (in_ready_low_cycles == 0) begin // Same as above but counted explicitly so the // pass-message can report the stall duration. errors = errors + 1; end // ----------------------------------------------------- // Acceptance check 2 — no raster_overflow. // ----------------------------------------------------- if (saw_raster_overflow) begin $error("[bp] raster_overflow latched during stress — at least one primitive was dropped"); errors = errors + 1; end // ----------------------------------------------------- // Acceptance check 3 — every sprite's center pixel // landed in VRAM with the right color. // ----------------------------------------------------- for (build_s = 0; build_s < N_SPRITES; build_s++) begin probe_x_v = sprite_x0(build_s) + SPRITE_W/2; probe_y_v = sprite_y0(build_s) + SPRITE_H/2; probe_addr = ref_addr_psmct32(probe_x_v, probe_y_v); probe_expected = {8'hFF, sprite_b(build_s), sprite_g(build_s), sprite_r(build_s)}; probe_got = dut.u_vram.mem[probe_addr >> 2]; if (probe_got !== probe_expected) begin $error("[bp] sprite %0d center (%0d,%0d) got 0x%08x expected 0x%08x", build_s, probe_x_v, probe_y_v, probe_got, probe_expected); errors = errors + 1; end end // ----------------------------------------------------- // Status latches. // ----------------------------------------------------- if (!core_halt) begin $error("[bp] core_halt low at end"); errors = errors + 1; end if (!dma_done_seen) begin $error("[bp] dma_done_seen never latched"); errors = errors + 1; end if (!frame_seen) begin $error("[bp] frame_seen never latched"); errors = errors + 1; end $display("[tb_gs_raster_backpressure_stress] sprites=%0d FIFO_DEPTH=%0d in_ready_low_cycles=%0d errors=%0d", N_SPRITES, dut.u_gs.FIFO_DEPTH, in_ready_low_cycles, errors); if (errors == 0) $display("[tb_gs_raster_backpressure_stress] PASS"); else $display("[tb_gs_raster_backpressure_stress] FAIL"); $finish; end // Watchdog — 8 sprites × 1200 raster cycles each + scanout + slack. initial begin #30_000_000; $error("[tb_gs_raster_backpressure_stress] TIMEOUT"); $finish; end endmodule : tb_gs_raster_backpressure_stress