// retroDE_ps2 — tb_gs_raster_bram_psmt4 (Ch157)
//
// Direct positive proof of the Ch157 PSMT4 RMW pipeline inside
// `top_psmct32_raster_demo_bram` (replaces the Ch156 PSMT4 hard-
// gate that `tb_gs_raster_bram_psmt4_gate` proved). Drives
// gs_stub#(PSMT4_SWIZZLE=0) directly via gif_reg_wr_* and routes
// the registered raster emit signals through the SAME wrapper-
// site RMW pipe shape used in `top_psmct32_raster_demo_bram` —
// 1-cycle read2 + nibble splice + 1-cycle delayed write, with
// `t4_prev_*` forwarding for back-to-back same-byte emits.
//
// **Why LINEAR PSMT4 (`PSMT4_SWIZZLE=0`) here**: in the linear
// addressing the byte address is `(y * FBW * 32) + (x >> 1)` and
// `nibble_hi = x & 1`, so adjacent emits at x=2k and x=2k+1 hit
// the SAME byte address back-to-back. That is exactly the
// forwarding hazard the wrapper's `t4_prev_*` registers cover —
// without forwarding, the second emit would read mem[byte] BEFORE
// the first emit's write committed (NBA semantics inside
// vram_bram_stub) and lose the first nibble.
//
// **Two-nibble adversarial drive**: 16×4 SPRITE split into two
// 8×4 halves with distinct nibble values:
//   - `(0,0)..(7,3)` : nibble 0xA  (R=0x0A in RGBAQ).
//   - `(8,0)..(15,3)`: nibble 0x5  (R=0x05 in RGBAQ).
// The left half writes bytes [0..3], [64..67], [128..131],
// [192..195] each ending at 0xAA (both nibbles=0xA). The right
// half writes bytes [4..7], [68..71], [132..135], [196..199] each
// ending at 0x55. Sentinel preload (0xDEADBEEF) catches stray
// writes outside the swizzled set; the linear-stride separators
// at bytes [8..63] in row 0 are checked to stay at sentinel.

`timescale 1ns/1ps

module tb_gs_raster_bram_psmt4;

    logic clk;
    logic rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ------------------------------------------------------------
    // gs_stub I/O — minimal subset.
    // ------------------------------------------------------------
    logic        gif_reg_wr_en;
    logic [7:0]  gif_reg_num;
    logic [63:0] gif_reg_data;

    logic        raster_pixel_emit;
    logic [31:0] raster_pixel_emit_count;
    logic [63:0] raster_pixel_color_q;
    logic [31:0] raster_pixel_fb_addr_q;
    logic [3:0]  raster_pixel_be_q;
    logic [31:0] raster_pixel_mask_q;
    logic [5:0]  raster_pixel_psm_q;
    logic        raster_active;

    // PSMT4_SWIZZLE=0 — linear PSMT4 addressing for back-to-back
    // same-byte forwarding stress.
    gs_stub #(
        .PSMCT32_SWIZZLE(1'b0),
        .PSMCT16_SWIZZLE(1'b0),
        .PSMT8_SWIZZLE  (1'b0),
        .PSMT4_SWIZZLE  (1'b0)
    ) u_gs (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en  (1'b0),
        .reg_wr_addr(16'd0),
        .reg_wr_data(64'd0),
        .gif_reg_wr_en(gif_reg_wr_en),
        .gif_reg_num  (gif_reg_num),
        .gif_reg_data (gif_reg_data),
        .bg_r(), .bg_g(), .bg_b(),
        .pmode_q(), .dispfb1_q(), .display1_q(),
        .prim_q(), .rgbaq_q(),
        .xyz2_q(), .xyzf2_q(),
        .frame_1_q(), .zbuf_1_q(),
        .tex0_1_q(), .tex0_1_cbp_q(), .tex0_1_cpsm_q(),
        .tex0_1_csm_q(), .tex0_1_csa_q(), .tex0_1_cld_q(),
        .tex0_1_wr_q(),
        .bitbltbuf_q(), .trxpos_q(), .trxreg_q(), .trxdir_q(),
        .trxdir_wr_q(),
        .prim_complete(), .prim_complete_count(),
        .prim_v0_q(), .prim_v1_q(), .prim_v2_q(),
        .prim_color_q(),
        .prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(),
        .prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(),
        .prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(),
        .pixel_emit(), .pixel_emit_count(),
        .pixel_x_q(), .pixel_y_q(), .pixel_color_q(),
        .pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(), .pixel_fb_addr_q(),
        .raster_pixel_emit(raster_pixel_emit),
        .raster_pixel_emit_count(raster_pixel_emit_count),
        .raster_pixel_x_q(), .raster_pixel_y_q(),
        .raster_pixel_color_q(raster_pixel_color_q),
        .raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
        .raster_pixel_be_q(raster_pixel_be_q),
        .raster_pixel_mask_q(raster_pixel_mask_q),
        .raster_pixel_psm_q(raster_pixel_psm_q),
        .raster_active(raster_active),
        .raster_overflow(), .raster_degenerate(),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------
    // Wrapper-site Ch157 RMW pipe (verbatim from
    // top_psmct32_raster_demo_bram). Raster-only path here —
    // xfer engine isn't instantiated.
    // ------------------------------------------------------------
    logic        is_t4_emit;
    logic        t4_nibble_hi;
    logic [3:0]  t4_nibble_value;

    assign is_t4_emit       = raster_pixel_emit &&
                              (raster_pixel_psm_q == vram_normalize_pkg::PSM_PSMT4);
    assign t4_nibble_hi     = (raster_pixel_mask_q[7:0] == 8'hF0);
    assign t4_nibble_value  = t4_nibble_hi ? raster_pixel_color_q[7:4]
                                            : raster_pixel_color_q[3:0];

    vram_normalize_pkg::norm_out_t vram_norm;
    assign vram_norm = vram_normalize_pkg::normalize_write(
        raster_pixel_fb_addr_q,
        raster_pixel_psm_q,
        raster_pixel_color_q[31:0],
        1'b0,
        8'd0
    );

    logic        t4_pipe_valid_q;
    logic [31:0] t4_pipe_addr_q;
    logic        t4_pipe_nibble_hi_q;
    logic [3:0]  t4_pipe_nibble_q;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            t4_pipe_valid_q     <= 1'b0;
            t4_pipe_addr_q      <= 32'd0;
            t4_pipe_nibble_hi_q <= 1'b0;
            t4_pipe_nibble_q    <= 4'd0;
        end else begin
            t4_pipe_valid_q     <= is_t4_emit;
            t4_pipe_addr_q      <= raster_pixel_fb_addr_q;
            t4_pipe_nibble_hi_q <= t4_nibble_hi;
            t4_pipe_nibble_q    <= t4_nibble_value;
        end
    end

    logic        t4_prev_valid_q;
    logic [31:0] t4_prev_addr_q;
    logic [7:0]  t4_prev_new_byte_q;

    logic [31:0] vram_read2_addr;
    logic [31:0] vram_read2_data;
    assign vram_read2_addr = is_t4_emit ? (raster_pixel_fb_addr_q & ~32'd3) : 32'd0;

    logic [7:0]  t4_read_byte_lane;
    always_comb begin
        case (t4_pipe_addr_q[1:0])
            2'b00: t4_read_byte_lane = vram_read2_data[ 7: 0];
            2'b01: t4_read_byte_lane = vram_read2_data[15: 8];
            2'b10: t4_read_byte_lane = vram_read2_data[23:16];
            2'b11: t4_read_byte_lane = vram_read2_data[31:24];
        endcase
    end

    logic [7:0] t4_effective_old_byte;
    assign t4_effective_old_byte = (t4_prev_valid_q && (t4_prev_addr_q == t4_pipe_addr_q))
                                 ? t4_prev_new_byte_q
                                 : t4_read_byte_lane;

    logic [7:0] t4_new_byte;
    always_comb begin
        if (t4_pipe_nibble_hi_q)
            t4_new_byte = {t4_pipe_nibble_q,        t4_effective_old_byte[3:0]};
        else
            t4_new_byte = {t4_effective_old_byte[7:4], t4_pipe_nibble_q};
    end

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            t4_prev_valid_q    <= 1'b0;
            t4_prev_addr_q     <= 32'd0;
            t4_prev_new_byte_q <= 8'd0;
        end else begin
            t4_prev_valid_q    <= t4_pipe_valid_q;
            t4_prev_addr_q     <= t4_pipe_addr_q;
            t4_prev_new_byte_q <= t4_new_byte;
        end
    end

    logic [31:0] t4_write_addr;
    logic [31:0] t4_write_data;
    logic [3:0]  t4_write_be;
    assign t4_write_addr = t4_pipe_addr_q & ~32'd3;
    always_comb begin
        case (t4_pipe_addr_q[1:0])
            2'b00: begin t4_write_data = {24'd0, t4_new_byte};        t4_write_be = 4'b0001; end
            2'b01: begin t4_write_data = {16'd0, t4_new_byte,  8'd0}; t4_write_be = 4'b0010; end
            2'b10: begin t4_write_data = { 8'd0, t4_new_byte, 16'd0}; t4_write_be = 4'b0100; end
            2'b11: begin t4_write_data = {       t4_new_byte, 24'd0}; t4_write_be = 4'b1000; end
        endcase
    end

    logic        vram_we_final;
    logic [31:0] vram_waddr_final;
    logic [31:0] vram_wdata_final;
    logic [3:0]  vram_wbe_final;
    assign vram_we_final    = t4_pipe_valid_q ? 1'b1
                                              : (raster_pixel_emit && !is_t4_emit);
    assign vram_waddr_final = t4_pipe_valid_q ? t4_write_addr  : vram_norm.write_addr;
    assign vram_wdata_final = t4_pipe_valid_q ? t4_write_data  : vram_norm.write_data;
    assign vram_wbe_final   = t4_pipe_valid_q ? t4_write_be    : vram_norm.write_be;

    // ------------------------------------------------------------
    // vram_bram_stub. PSMT4 LINEAR at FBW=2 places page (0,0)
    // bytes at [0..8191]; 8 KiB is enough.
    // ------------------------------------------------------------
    localparam int VRAM_BYTES = 8192;
    localparam int VRAM_WORDS = VRAM_BYTES / 4;
    localparam logic [31:0] SENTINEL = 32'hDEAD_BEEF;

    vram_bram_stub #(.BYTES(VRAM_BYTES)) u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (vram_we_final),
        .write_addr(vram_waddr_final),
        .write_data(vram_wdata_final),
        .write_be  (vram_wbe_final),
        .read_addr (32'd0),
        .read_data (),
        .read_valid(),
        .read2_addr(vram_read2_addr),
        .read2_data(vram_read2_data),
        .read2_valid()
    );

    // ------------------------------------------------------------
    // Drive helpers.
    // ------------------------------------------------------------
    task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
        @(negedge clk);
        gif_reg_wr_en = 1'b1;
        gif_reg_num   = num;
        gif_reg_data  = data;
        @(posedge clk);
        @(negedge clk);
        gif_reg_wr_en = 1'b0;
        gif_reg_num   = 8'd0;
        gif_reg_data  = 64'd0;
    endtask

    function automatic logic [63:0] xyz2_data(input logic [11:0] x_int,
                                              input logic [11:0] y_int);
        return {32'd0, y_int, 4'd0, x_int, 4'd0};
    endfunction

    // ------------------------------------------------------------
    // Linear PSMT4 byte address: stride = FBW * 64 px = 128 px per
    // row; each pixel = 0.5 byte → 64 bytes per row at FBW=2.
    // byte_addr = FBP*2048 + y*FBW*32 + (x >> 1); nibble_hi = x[0].
    // ------------------------------------------------------------
    function automatic int linear_byte_addr(
        input int fbp_v, input int fbw_v, input int x_v, input int y_v);
        return fbp_v * 2048 + y_v * fbw_v * 32 + (x_v >> 1);
    endfunction

    // ------------------------------------------------------------
    // Test stimulus.
    //   16×4 SPRITE in PSMT4 LINEAR at FBP=0/FBW=2:
    //     A: (0,0)..(7,3),  RGBAQ_A → nibble 0xA. 32 emits.
    //     B: (8,0)..(15,3), RGBAQ_B → nibble 0x5. 32 emits.
    //   PSMT4 packs 2 px / byte, so each x=2k/x=2k+1 pair fires
    //   back-to-back at the SAME byte_addr, exercising the
    //   wrapper's t4_prev_* forwarding registers.
    // ------------------------------------------------------------
    localparam logic [7:0]  R_PRIM     = 8'h00;
    localparam logic [7:0]  R_RGBAQ    = 8'h01;
    localparam logic [7:0]  R_XYZ2     = 8'h05;
    localparam logic [7:0]  R_FRAME_1  = 8'h4C;

    localparam logic [63:0] PRIM_SPRITE = 64'd6;
    // FRAME_1: FBP=0, FBW=2, PSM=PSMT4 (=0x14).
    localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_1402_0000;

    // RGBAQ_A: R=0x0A → nibble 0xA in low 4 bits.
    localparam logic [63:0] RGBAQ_A     = 64'h0000_0000_0000_000A;
    localparam logic [3:0]  NIB_A       = 4'hA;
    // RGBAQ_B: R=0x05 → nibble 0x5 in low 4 bits.
    localparam logic [63:0] RGBAQ_B     = 64'h0000_0000_0000_0005;
    localparam logic [3:0]  NIB_B       = 4'h5;

    int errors;

    initial begin
        rst_n          = 1'b0;
        gif_reg_wr_en  = 1'b0;
        gif_reg_num    = 8'd0;
        gif_reg_data   = 64'd0;
        errors         = 0;

        // Sentinel preload — 0xDEADBEEF on every word so any
        // stray write or wrong-byte commit shows up against a
        // known-non-zero background.
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = SENTINEL;
        #1;
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = SENTINEL;

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // ---------- Sprite A — left half, nibble 0xA ----------
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_FRAME_1, FRAME_1_VAL);
        drive_reg(R_RGBAQ,   RGBAQ_A);
        drive_reg(R_XYZ2,    xyz2_data(12'd0, 12'd0));
        drive_reg(R_XYZ2,    xyz2_data(12'd7, 12'd3));

        wait (raster_active == 1'b1);
        wait (raster_active == 1'b0);
        // Drain the RMW pipe — 1 extra cycle for the last T4
        // write to commit.
        repeat (10) @(posedge clk);

        // ---------- Sprite B — right half, nibble 0x5 ----------
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_RGBAQ,   RGBAQ_B);
        drive_reg(R_XYZ2,    xyz2_data(12'd8,  12'd0));
        drive_reg(R_XYZ2,    xyz2_data(12'd15, 12'd3));

        wait (raster_active == 1'b1);
        wait (raster_active == 1'b0);
        repeat (10) @(posedge clk);

        if (raster_pixel_emit_count != 32'(8 * 4 + 8 * 4)) begin
            $error("expected %0d raster emits, got %0d",
                   8 * 4 + 8 * 4, raster_pixel_emit_count);
            errors = errors + 1;
        end

        // Per-pixel nibble verification. For each (x,y), byte_addr
        // is the linear PSMT4 address; nibble_hi picks the upper
        // or lower 4 bits of the byte; expected nibble depends on
        // which half-sprite covers the pixel.
        for (int yy = 0; yy < 4; yy++) begin
            for (int xx = 0; xx < 16; xx++) begin
                int          byte_addr;
                logic        nibble_hi;
                logic [31:0] word;
                logic [7:0]  byte_v;
                logic [3:0]  got, expected;
                byte_addr = linear_byte_addr(0, 2, xx, yy);
                nibble_hi = xx[0];
                word      = u_vram.mem[byte_addr >> 2];
                case (byte_addr[1:0])
                    2'b00: byte_v = word[ 7: 0];
                    2'b01: byte_v = word[15: 8];
                    2'b10: byte_v = word[23:16];
                    2'b11: byte_v = word[31:24];
                endcase
                got      = nibble_hi ? byte_v[7:4] : byte_v[3:0];
                expected = (xx < 8) ? NIB_A : NIB_B;
                if (got !== expected) begin
                    $error("[bram-t4] (%0d,%0d) byte_addr=0x%0h nibble_hi=%0b got=0x%01x expected=0x%01x (byte=0x%02x word=0x%08x)",
                           xx, yy, byte_addr, nibble_hi, got, expected, byte_v, word);
                    errors = errors + 1;
                end
            end
        end

        // Linear-stride separators: bytes [8..63] in row 0 are
        // OUTSIDE the 16-pixel SPRITE; the corresponding words
        // (mem[2..15]) must stay at sentinel.
        for (int wi = 2; wi < 16; wi++) begin
            if (u_vram.mem[wi] !== SENTINEL) begin
                $error("[linear-sep] mem[%0d] (byte 0x%0h) got 0x%08x expected sentinel",
                       wi, wi*4, u_vram.mem[wi]);
                errors = errors + 1;
            end
        end

        $display("[tb_gs_raster_bram_psmt4] raster_emits=%0d errors=%0d",
                 raster_pixel_emit_count, errors);
        if (errors == 0) $display("[tb_gs_raster_bram_psmt4] PASS");
        else             $display("[tb_gs_raster_bram_psmt4] FAIL");
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_gs_raster_bram_psmt4] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_raster_bram_psmt4
