// retroDE_ps2 — tb_vram_normalize_write (Ch155)
//
// Focused unit TB for the `vram_normalize_pkg::normalize_write`
// function. Exercises all 4 PSM lanes per Codex Ch155 framing:
//   - PSMCT32 at word-aligned addresses (no shifting needed; the
//     "happy path" + a deliberate misuse test that the function
//     correctly drops the write when CT32 sees an unaligned addr).
//   - PSMCT16 at low and high halfword lanes (byte_addr[1] ∈ {0,1}).
//   - PSMT8 across all 4 byte lanes (byte_addr[1:0] ∈ {0,1,2,3}).
//   - PSMT4 splice with old-byte preservation, low and high nibble,
//     across all 4 byte lanes.
//
// The TB drives the function directly (no RTL pipeline) and asserts
// the (write_addr, write_data, write_be) tuple matches expectation.

`timescale 1ns/1ps

import vram_normalize_pkg::*;

module tb_vram_normalize_write;

    int errors;
    initial errors = 0;

    task automatic check(
        input string         label,
        input norm_out_t     got,
        input logic [31:0]   exp_addr,
        input logic [31:0]   exp_data,
        input logic [3:0]    exp_be
    );
        if (got.write_addr !== exp_addr || got.write_data !== exp_data || got.write_be !== exp_be) begin
            $error("[%s] addr=0x%08x exp=0x%08x  data=0x%08x exp=0x%08x  be=%b exp=%b",
                   label,
                   got.write_addr, exp_addr,
                   got.write_data, exp_data,
                   got.write_be,   exp_be);
            errors = errors + 1;
        end
    endtask

    initial begin
        norm_out_t r;

        // ------------------------------------------------------------
        // PSMCT32 — word-aligned input must round-trip cleanly with
        // be=4'b1111 + payload preserved.
        // ------------------------------------------------------------
        r = vram_normalize_pkg::normalize_write(32'h0000_0000, PSM_PSMCT32, 32'hDEAD_BEEF, 1'b0, 8'h00);
        check("CT32-w0", r, 32'h0000_0000, 32'hDEAD_BEEF, 4'b1111);

        r = vram_normalize_pkg::normalize_write(32'h0000_0204, PSM_PSMCT32, 32'h1234_5678, 1'b0, 8'h00);
        check("CT32-w1", r, 32'h0000_0204, 32'h1234_5678, 4'b1111);

        // CT32 misuse: unaligned addr → function MUST drop the write
        // (be=0000). Codex Ch155: "no sub-32-bit writer is allowed
        // to hand an unaligned address directly to vram_bram_stub".
        r = vram_normalize_pkg::normalize_write(32'h0000_0001, PSM_PSMCT32, 32'hCAFE_BABE, 1'b0, 8'h00);
        check("CT32-misuse-1", r, 32'h0000_0000, 32'h0000_0000, 4'b0000);
        r = vram_normalize_pkg::normalize_write(32'h0000_0202, PSM_PSMCT32, 32'hCAFE_BABE, 1'b0, 8'h00);
        check("CT32-misuse-2", r, 32'h0000_0200, 32'h0000_0000, 4'b0000);

        // ------------------------------------------------------------
        // PSMCT16 — halfword writes at low (byte_addr[1]=0) and high
        // (byte_addr[1]=1) lanes. byte_addr[0] must be 0.
        // ------------------------------------------------------------
        r = vram_normalize_pkg::normalize_write(32'h0000_0000, PSM_PSMCT16, 32'h0000_ABCD, 1'b0, 8'h00);
        check("CT16-lo-w0",  r, 32'h0000_0000, 32'h0000_ABCD, 4'b0011);

        r = vram_normalize_pkg::normalize_write(32'h0000_0002, PSM_PSMCT16, 32'h0000_1234, 1'b0, 8'h00);
        check("CT16-hi-w0",  r, 32'h0000_0000, 32'h1234_0000, 4'b1100);

        r = vram_normalize_pkg::normalize_write(32'h0000_0100, PSM_PSMCT16, 32'h0000_AAAA, 1'b0, 8'h00);
        check("CT16-lo-w64", r, 32'h0000_0100, 32'h0000_AAAA, 4'b0011);

        r = vram_normalize_pkg::normalize_write(32'h0000_0102, PSM_PSMCT16, 32'h0000_BBBB, 1'b0, 8'h00);
        check("CT16-hi-w64", r, 32'h0000_0100, 32'hBBBB_0000, 4'b1100);

        // CT16 misuse: byte-address[0] = 1 → drop.
        r = vram_normalize_pkg::normalize_write(32'h0000_0001, PSM_PSMCT16, 32'h0000_FFFF, 1'b0, 8'h00);
        check("CT16-misuse-byte", r, 32'h0000_0000, 32'h0000_0000, 4'b0000);

        // ------------------------------------------------------------
        // PSMT8 — all 4 byte lanes.
        // ------------------------------------------------------------
        r = vram_normalize_pkg::normalize_write(32'h0000_0000, PSM_PSMT8, 32'h0000_0011, 1'b0, 8'h00);
        check("T8-b0", r, 32'h0000_0000, 32'h0000_0011, 4'b0001);

        r = vram_normalize_pkg::normalize_write(32'h0000_0001, PSM_PSMT8, 32'h0000_0022, 1'b0, 8'h00);
        check("T8-b1", r, 32'h0000_0000, 32'h0000_2200, 4'b0010);

        r = vram_normalize_pkg::normalize_write(32'h0000_0002, PSM_PSMT8, 32'h0000_0033, 1'b0, 8'h00);
        check("T8-b2", r, 32'h0000_0000, 32'h0033_0000, 4'b0100);

        r = vram_normalize_pkg::normalize_write(32'h0000_0003, PSM_PSMT8, 32'h0000_0044, 1'b0, 8'h00);
        check("T8-b3", r, 32'h0000_0000, 32'h4400_0000, 4'b1000);

        r = vram_normalize_pkg::normalize_write(32'h0000_0205, PSM_PSMT8, 32'h0000_0099, 1'b0, 8'h00);
        check("T8-b1-w129", r, 32'h0000_0204, 32'h0000_9900, 4'b0010);

        // ------------------------------------------------------------
        // PSMT4 — nibble splice with old-byte preservation. Lane 0,
        // both nibbles. Lane 1+, both nibbles. The function returns a
        // full-byte write of the spliced byte; the BRAM module's
        // byte-WE then commits that single byte.
        // ------------------------------------------------------------

        // Byte lane 0, low nibble: old_byte=0xAB → new=0xA5 (5 in low,
        // A preserved in high). byte_addr[1:0]=00 selects byte 0.
        r = vram_normalize_pkg::normalize_write(32'h0000_0000, PSM_PSMT4, 32'h0000_0005, 1'b0, 8'hAB);
        check("T4-b0-lo", r, 32'h0000_0000, 32'h0000_00A5, 4'b0001);

        // Byte lane 0, high nibble: old_byte=0xAB → new=0x5B (5 in
        // high, B preserved in low).
        r = vram_normalize_pkg::normalize_write(32'h0000_0000, PSM_PSMT4, 32'h0000_0005, 1'b1, 8'hAB);
        check("T4-b0-hi", r, 32'h0000_0000, 32'h0000_005B, 4'b0001);

        // Byte lane 1, low nibble: old_byte=0xCD → new=0xC9.
        r = vram_normalize_pkg::normalize_write(32'h0000_0001, PSM_PSMT4, 32'h0000_0009, 1'b0, 8'hCD);
        check("T4-b1-lo", r, 32'h0000_0000, 32'h0000_C900, 4'b0010);

        // Byte lane 1, high nibble: old_byte=0xCD → new=0x9D.
        r = vram_normalize_pkg::normalize_write(32'h0000_0001, PSM_PSMT4, 32'h0000_0009, 1'b1, 8'hCD);
        check("T4-b1-hi", r, 32'h0000_0000, 32'h0000_9D00, 4'b0010);

        // Byte lane 2 / lane 3 splice — lane select is the only
        // difference; verify the byte goes to the right lane.
        r = vram_normalize_pkg::normalize_write(32'h0000_0002, PSM_PSMT4, 32'h0000_000F, 1'b0, 8'hF0);
        check("T4-b2-lo", r, 32'h0000_0000, 32'h00FF_0000, 4'b0100);

        r = vram_normalize_pkg::normalize_write(32'h0000_0003, PSM_PSMT4, 32'h0000_0007, 1'b1, 8'h0E);
        check("T4-b3-hi", r, 32'h0000_0000, 32'h7E00_0000, 4'b1000);

        // T4 splice that preserves the OTHER nibble exactly: critical
        // for the Ch106 nibble RMW semantics. old_byte=0xF0 + low
        // nibble write of 0x3 → new byte must be 0xF3 (preserves
        // upper nibble). Same for high splice 0x3 over 0x0F → 0x3F.
        r = vram_normalize_pkg::normalize_write(32'h0000_0010, PSM_PSMT4, 32'h0000_0003, 1'b0, 8'hF0);
        check("T4-preserve-upper", r, 32'h0000_0010, 32'h0000_00F3, 4'b0001);
        r = vram_normalize_pkg::normalize_write(32'h0000_0010, PSM_PSMT4, 32'h0000_0003, 1'b1, 8'h0F);
        check("T4-preserve-lower", r, 32'h0000_0010, 32'h0000_003F, 4'b0001);

        // ------------------------------------------------------------
        // Final report.
        // ------------------------------------------------------------
        $display("[tb_vram_normalize_write] errors=%0d", errors);
        if (errors == 0) $display("[tb_vram_normalize_write] PASS");
        else             $display("[tb_vram_normalize_write] FAIL");
        $finish;
    end

endmodule : tb_vram_normalize_write
