// retroDE_ps2 — tb_gs_scanout_psm16 (Ch94) // // Locks the contract for DISPFB1.PSM=PSMCT16 (RGB5A1) scanout. // gs_pcrtc_stub now selects byte-offset shift and color-decode // path based on the PSM field in DISPFB1: PSMCT32 (=0) reads 4 // bytes/pixel and uses {A, B, G, R} byte order; PSMCT16 (=2) // reads 2 bytes/pixel and unpacks RGB5A1 with 5→8 bit-replicate // expansion. // // gs_stub's raster channel still emits PSMCT32 only, so this TB // bypasses gs_stub entirely — it instantiates only `vram_stub` // (so we have a real RAM) and `gs_pcrtc_stub` (so we exercise // the scanout PSM path), and drives privileged-register-shaped // inputs from TB-side regs. That isolates the test to the // scanout PSM decode. // // Setup: // VRAM populated with a 4×4 RGB5A1 sprite, color // (R5, G5, B5) = (0x10, 0x18, 0x08) → 16-bit pixel 0x2310, // row stride 128 bytes (FBW=1 → 64 pixels/row × 2 bytes/pixel). // PSM = PSMCT16 (DISPFB1[19:15] = 0x02). // DISPFB1.FBP=0, FBW=1. DBX=DBY=0. // DISPLAY1 covers full 16×8 active area, MAGH=MAGV=0 (1×). // // Expected: // R8 = {0x10, 0x10[4:2]} = {0x10, 3'b100} = 8'h84 // G8 = {0x18, 0x18[4:2]} = {0x18, 3'b110} = 8'hC6 // B8 = {0x08, 0x08[4:2]} = {0x08, 3'b010} = 8'h42 // At displayed (0..3, 0..3): (R, G, B) = (0x84, 0xC6, 0x42). // At every other displayed pixel: (0, 0, 0). `timescale 1ns/1ps module tb_gs_scanout_psm16; 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; // Privileged-register-shaped TB regs feeding pcrtc directly. logic [63:0] pmode_q; logic [63:0] dispfb1_q; logic [63:0] display1_q; // VRAM write side: TB-driven (no gs_stub raster source). logic vram_we; logic [31:0] vram_waddr; logic [31:0] vram_wdata; // VRAM read side: pcrtc drives the address. 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 (4'b1111), .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); @(negedge clk); vram_we = 1'b1; vram_waddr = addr; vram_wdata = data; @(posedge clk); @(negedge clk); vram_we = 1'b0; vram_waddr = 32'd0; vram_wdata = 32'd0; endtask // Build a packed RGB5A1 16-bit value: A[15] B[14:10] G[9:5] R[4:0]. function automatic logic [15:0] psm16_pack(input logic [4:0] r5, input logic [4:0] g5, input logic [4:0] b5, input logic a1); return {a1, b5, g5, r5}; endfunction // Sprite color. localparam logic [4:0] R5 = 5'h10; localparam logic [4:0] G5 = 5'h18; localparam logic [4:0] B5 = 5'h08; // Expected expanded 8-bit values (5→8 bit-replicate). localparam logic [7:0] EXP_R = {R5, R5[4:2]}; localparam logic [7:0] EXP_G = {G5, G5[4:2]}; localparam logic [7:0] EXP_B = {B5, B5[4:2]}; localparam int SPRITE_W = 4; localparam int SPRITE_H = 4; // DISPFB1 PSM = PSMCT16 (5'h02), FBP=0, FBW=1. // FBP[8:0] = 0 // FBW[14:9] = 1 → 0x200 // PSM[19:15] = 2 → 0x10000 localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0001_0200; // DISPLAY1 covers full active area, MAGH=MAGV=0. localparam logic [63:0] DISPLAY1_VAL = (64'(PCRTC_H_ACTIVE - 1) << 32) // DW | (64'(PCRTC_V_ACTIVE - 1) << 44); // DH localparam logic [63:0] PMODE_EN1 = 64'h0000_0000_0000_0001; 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; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Populate VRAM with the 4×4 PSMCT16 sprite. FBW=1 // means 64 pixels/row × 2 bytes = 128 bytes/row stride. // Row Y starts at byte Y*128. Each row stores 4 PSMCT16 // pixels = 8 bytes = 2 32-bit writes. for (int y = 0; y < SPRITE_H; y++) begin logic [15:0] pix; logic [31:0] pair; int row_base; pix = psm16_pack(R5, G5, B5, 1'b0); pair = {pix, pix}; // two PSMCT16 pixels packed row_base = y * 128; // Pixels (0,1) at byte row_base..row_base+3. vram_write32(row_base, pair); // Pixels (2,3) at byte row_base+4..row_base+7. vram_write32(row_base + 32'd4, pair); end // Configure scanout. Reset has dispfb1_q=display1_q=0, so // we must explicitly write both before enabling EN1. dispfb1_q = DISPFB1_VAL; display1_q = DISPLAY1_VAL; @(posedge clk); // Cross-check the PSM field landed at [19:15]=2. if (dispfb1_q[19:15] !== 5'h02) begin $error("DISPFB1.PSM=%0d (expected 2 = PSMCT16)", 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; if (x < SPRITE_W && y < SPRITE_H) begin er = EXP_R; eg = EXP_G; eb = EXP_B; 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)", x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb); errors = errors + 1; end end end $display("[tb_gs_scanout_psm16] sprite=%0dx%0d RGB5A1=(%02x,%02x,%02x) → RGB8=(%02x,%02x,%02x)", SPRITE_W, SPRITE_H, R5, G5, B5, EXP_R, EXP_G, EXP_B); if (errors == 0) $display("[tb_gs_scanout_psm16] PASS"); else $display("[tb_gs_scanout_psm16] FAIL"); $finish; end initial begin #5000000; $error("[tb_gs_scanout_psm16] timeout"); $finish; end endmodule : tb_gs_scanout_psm16