// retroDE_ps2 — tb_gs_scanout_psmt8 (Ch96) // // Locks the contract for DISPFB1.PSM=PSMT8 (=0x13) scanout. // PSMT8 stores 1 byte per pixel in VRAM. Without a CLUT (Ch97 // candidate), gs_pcrtc_stub surfaces the 8-bit index as // grayscale R=G=B=index so the storage lane is visually // verifiable. // // This TB exercises the PSMT8 path WITH a non-trivial display // window AND non-trivial horizontal magnification, proving: // - byte stride = 1 byte/pixel (FBW=1 → 64 bytes/row) // - DISPLAY1.DX/DY/DW/DH still gate the window // - DISPLAY1.MAGH/MAGV still scale the VRAM coord lookup // // gs_stub raster channel doesn't emit PSMT8 yet (Ch97 will // extend the write side), so this TB bypasses gs_stub and // drives vram_stub.write_* directly. // // Setup: // VRAM populated with a 4×4 PSMT8 sprite at indices // row 0: 0x10 0x11 0x12 0x13 at bytes 0..3 // row 1: 0x14 0x15 0x16 0x17 at bytes 64..67 // row 2: 0x18 0x19 0x1A 0x1B at bytes 128..131 // row 3: 0x1C 0x1D 0x1E 0x1F at bytes 192..195 // Each row's 4 bytes are written as one 32-bit word with // write_be=4'b1111. Adjacent bytes don't conflict because the // sprite starts at a 4-byte-aligned address per row. // // DISPFB1: FBP=0, FBW=1, PSM=PSMT8 (0x13), DBX=DBY=0. // DISPLAY1: DX=4, DY=2, DW=7 (8 wide), DH=3 (4 tall), // MAGH=1 (2× horizontal), MAGV=0 (1× vertical). // Window covers displayed (4..11, 2..5) = 8×4 pixels. // With MAGH=1, each VRAM column shows for 2 displayed pixels. // So a 4-VRAM-wide sprite fills the 8-pixel-wide window. // // Expected per displayed (x, y): // if x ∈ [4,11] AND y ∈ [2,5]: // vram_x = (x - 4) / 2 (range 0..3) // vram_y = y - 2 (range 0..3) // idx = 0x10 + (vram_y * 4 + vram_x) // RGB = (idx, idx, idx) (grayscale) // else: RGB = (0, 0, 0) `timescale 1ns/1ps module tb_gs_scanout_psmt8; localparam int PCRTC_H_ACTIVE = 16; localparam int PCRTC_V_ACTIVE = 8; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // TB-driven privileged-register-shaped feeds (no gs_stub). logic [63:0] pmode_q; logic [63:0] dispfb1_q; logic [63:0] display1_q; logic vram_we; logic [31:0] vram_waddr; logic [31:0] vram_wdata; logic [3:0] vram_wbe; logic [31:0] vram_raddr; logic [31:0] vram_rdata; vram_stub #(.BYTES(4096)) u_vram ( .clk(clk), .rst_n(rst_n), .write_en (vram_we), .write_addr(vram_waddr), .write_data(vram_wdata), .write_be (vram_wbe), .write_mask(32'hFFFF_FFFF), .read_addr (vram_raddr), .read_data (vram_rdata), .read2_addr(32'd0), .read2_data() ); logic hsync_o, vsync_o, de_o; logic [7:0] r_o, g_o, b_o; logic pcrtc_ev_valid; trace_pkg::subsys_e pcrtc_ev_subsys; trace_pkg::event_e pcrtc_ev_event; logic [63:0] pcrtc_ev_arg0, pcrtc_ev_arg1; logic [63:0] pcrtc_ev_arg2, pcrtc_ev_arg3; logic [31:0] pcrtc_ev_flags; gs_pcrtc_stub #( .H_ACTIVE(PCRTC_H_ACTIVE), .H_FRONT(1), .H_SYNC(1), .H_BACK(1), .V_ACTIVE(PCRTC_V_ACTIVE), .V_FRONT(1), .V_SYNC(1), .V_BACK(1) ) u_pcrtc ( .clk(clk), .rst_n(rst_n), .pmode_q (pmode_q), .dispfb1_q (dispfb1_q), .display1_q (display1_q), .vram_read_addr(vram_raddr), .vram_read_data(vram_rdata), .clut_enable (1'b0), .clut_csa (5'd0), .clut_read_idx (), .clut_read_data(32'd0), .hsync(hsync_o), .vsync(vsync_o), .de(de_o), .r(r_o), .g(g_o), .b(b_o), .ev_valid(pcrtc_ev_valid), .ev_subsys(pcrtc_ev_subsys), .ev_event(pcrtc_ev_event), .ev_arg0(pcrtc_ev_arg0), .ev_arg1(pcrtc_ev_arg1), .ev_arg2(pcrtc_ev_arg2), .ev_arg3(pcrtc_ev_arg3), .ev_flags(pcrtc_ev_flags) ); logic [7:0] cap_r [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1]; logic [7:0] cap_g [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1]; logic [7:0] cap_b [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1]; logic cap_de[0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1]; int errors; bit capture_armed; initial begin for (int y = 0; y < PCRTC_V_ACTIVE; y++) for (int x = 0; x < PCRTC_H_ACTIVE; x++) begin cap_r[y][x] = 8'd0; cap_g[y][x] = 8'd0; cap_b[y][x] = 8'd0; cap_de[y][x] = 1'b0; end errors = 0; capture_armed = 1'b0; end always_ff @(posedge clk) begin if (rst_n && capture_armed && de_o && (u_pcrtc.vcnt < PCRTC_V_ACTIVE) && (u_pcrtc.hcnt < PCRTC_H_ACTIVE)) begin cap_r [u_pcrtc.vcnt][u_pcrtc.hcnt] <= r_o; cap_g [u_pcrtc.vcnt][u_pcrtc.hcnt] <= g_o; cap_b [u_pcrtc.vcnt][u_pcrtc.hcnt] <= b_o; cap_de[u_pcrtc.vcnt][u_pcrtc.hcnt] <= 1'b1; end end task automatic vram_write32(input logic [31:0] addr, input logic [31:0] data, input logic [3:0] be); @(negedge clk); vram_we = 1'b1; vram_waddr = addr; vram_wdata = data; vram_wbe = be; @(posedge clk); @(negedge clk); vram_we = 1'b0; vram_waddr = 32'd0; vram_wdata = 32'd0; vram_wbe = 4'b0000; endtask localparam int SPRITE_W = 4; localparam int SPRITE_H = 4; localparam logic [7:0] BASE_IDX = 8'h10; // FBW=1 → 64 bytes/row stride for PSMT8 (1 byte/pixel × 64 px/FBW). localparam int ROW_STRIDE = 64; // DISPFB1: FBP=0, FBW=1, PSM=PSMT8 (= 0x13). // FBW [14:9] = 1 → 0x200 // PSM [19:15] = 0x13 → bits 18,17,16,15 = 1,0,0,1; total bit pattern at 19:15 = 5'b10011 → 0x9_8000 // 5'b10011 << 15 = bit15..bit19 → 0x0009_8000 / wait let me recompute // PSM is bits [19:15], 5-bit. Value 0x13 = 5'b10011. // bit15=1, bit16=1, bit17=0, bit18=0, bit19=1 // 0x13 << 15 = 0x13 * 0x8000 = 0x9_8000 localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0009_8200; // DISPLAY1: DX=4, DY=2, DW=7, DH=3, MAGH=1, MAGV=0. // DX [11:0] = 4 // DY [22:12] = 2 → bit 12 // MAGH [26:23] = 1 → bit 23 // MAGV [28:27] = 0 // DW [43:32] = 7 → bit 32 // DH [54:44] = 3 → bit 44 localparam logic [63:0] DISPLAY1_VAL = 64'(12'd4) | (64'(11'd2) << 12) | (64'(4'd1) << 23) | (64'(12'd7) << 32) | (64'(11'd3) << 44); localparam logic [63:0] PMODE_EN1 = 64'h0000_0000_0000_0001; localparam int WIN_DX = 4; localparam int WIN_DY = 2; localparam int WIN_W = 8; // DW + 1 localparam int WIN_H = 4; // DH + 1 localparam int MAGH_FAC = 2; // MAGH + 1 localparam int MAGV_FAC = 1; // MAGV + 1 initial begin rst_n = 1'b0; pmode_q = 64'd0; dispfb1_q = 64'd0; display1_q = 64'd0; vram_we = 1'b0; vram_waddr = 32'd0; vram_wdata = 32'd0; vram_wbe = 4'b0000; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Populate VRAM with the 4×4 PSMT8 sprite. Each row's 4 // indices pack into one 32-bit word: data[7:0]=col0, // [15:8]=col1, [23:16]=col2, [31:24]=col3 (little-endian // byte order matches vram_stub's mem layout). for (int y = 0; y < SPRITE_H; y++) begin logic [7:0] i0, i1, i2, i3; logic [31:0] data; int row_base; i0 = BASE_IDX + 8'(y * SPRITE_W + 0); i1 = BASE_IDX + 8'(y * SPRITE_W + 1); i2 = BASE_IDX + 8'(y * SPRITE_W + 2); i3 = BASE_IDX + 8'(y * SPRITE_W + 3); data = {i3, i2, i1, i0}; row_base = y * ROW_STRIDE; vram_write32(row_base, data, 4'b1111); end // Configure scanout. dispfb1_q + display1_q first; then // PMODE.EN1 last, mirroring the canonical driver order. dispfb1_q = DISPFB1_VAL; display1_q = DISPLAY1_VAL; @(posedge clk); if (dispfb1_q[19:15] !== 5'h13) begin $error("DISPFB1.PSM=0x%02x (expected 0x13 PSMT8)", dispfb1_q[19:15]); errors = errors + 1; end pmode_q = PMODE_EN1; @(posedge clk); @(posedge u_pcrtc.end_of_frame); @(posedge clk); capture_armed = 1'b1; @(posedge u_pcrtc.end_of_frame); @(posedge clk); capture_armed = 1'b0; // Per-pixel verification. for (int y = 0; y < PCRTC_V_ACTIVE; y++) begin for (int x = 0; x < PCRTC_H_ACTIVE; x++) begin logic [7:0] er, eg, eb; bit in_window; int hwin_rel, vwin_rel; int vram_x, vram_y; logic [7:0] idx; bit in_sprite; in_window = (x >= WIN_DX) && (x < WIN_DX + WIN_W) && (y >= WIN_DY) && (y < WIN_DY + WIN_H); if (in_window) begin hwin_rel = x - WIN_DX; vwin_rel = y - WIN_DY; vram_x = hwin_rel / MAGH_FAC; vram_y = vwin_rel / MAGV_FAC; in_sprite = (vram_x < SPRITE_W) && (vram_y < SPRITE_H); idx = BASE_IDX + 8'(vram_y * SPRITE_W + vram_x); end else begin in_sprite = 1'b0; idx = 8'd0; end if (in_sprite) begin er = idx; eg = idx; eb = idx; end else begin er = 8'd0; eg = 8'd0; eb = 8'd0; end if (!cap_de[y][x]) begin $error("(%0d,%0d) DE never asserted", x, y); errors = errors + 1; end if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin $error("(%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x) in_window=%0d in_sprite=%0d", x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb, in_window, in_sprite); errors = errors + 1; end end end $display("[tb_gs_scanout_psmt8] sprite=%0dx%0d window=(%0d,%0d %0dx%0d) MAGH=%0d MAGV=%0d (factors %0dx %0dx)", SPRITE_W, SPRITE_H, WIN_DX, WIN_DY, WIN_W, WIN_H, 1, 0, MAGH_FAC, MAGV_FAC); if (errors == 0) $display("[tb_gs_scanout_psmt8] PASS"); else $display("[tb_gs_scanout_psmt8] FAIL"); $finish; end initial begin #5000000; $error("[tb_gs_scanout_psmt8] timeout"); $finish; end endmodule : tb_gs_scanout_psmt8