// retroDE_ps2 — vram_normalize_pkg (Ch155)
//
// Writer-side normalization for `vram_bram_stub`. The new BRAM-friendly
// VRAM (Ch154) requires word-aligned writes (`write_addr[1:0] == 0`)
// with payload pre-shifted into the selected byte lane(s) and
// `write_be` set per byte. Today's writer-side RTL emits at sub-word
// boundaries for PSMCT16 (halfword), PSMT8 (byte), and PSMT4 (nibble);
// this package's `normalize_write` function bridges the contract.
//
// Codex Ch155 framing: "Add a small helper module or function for
// VRAM write normalization: input: natural byte address, PSM,
// pixel/index payload, old byte for PSMT4 if needed; output:
// word-aligned write_addr, shifted write_data, write_be."
//
// Scope (Ch155):
//   - Function is defined + standalone-verified for all 4 PSMs.
//   - NOT yet applied inside `gs_stub.raster_pixel_emit` or
//     `gif_image_xfer_stub`. The PSMT4 case needs a read-then-write
//     pipeline upstream (to source `old_byte`); that's a Ch156+
//     RTL plumbing chapter. CT32/CT16/T8 cases are pure-comb and
//     can be plumbed in as soon as the wiring lands.
//
// Pure-comb function — no RTL pipelining inside the function itself.
// Callers that need a read-then-write pipeline (PSMT4) own that
// pipelining and pass the read result as `old_byte`.

`timescale 1ns/1ps

package vram_normalize_pkg;

    // GS PSM codes (subset relevant to VRAM writes).
    localparam logic [5:0] PSM_PSMCT32 = 6'h00;
    localparam logic [5:0] PSM_PSMCT16 = 6'h02;
    localparam logic [5:0] PSM_PSMT8   = 6'h13;
    localparam logic [5:0] PSM_PSMT4   = 6'h14;

    typedef struct packed {
        logic [31:0] write_addr;   // word-aligned
        logic [31:0] write_data;   // payload shifted to lane
        logic [3:0]  write_be;     // per-byte write enable
    } norm_out_t;

    // ----------------------------------------------------------------
    // normalize_write — pure-comb writer-side normalization.
    //
    // Inputs:
    //   byte_addr   — natural byte address as the legacy writers
    //                 already emit. CT32 callers must already pass
    //                 a word-aligned address. CT16 callers may pass
    //                 a halfword address (byte_addr[1] selects low
    //                 or high halfword). T8/T4 callers may pass any
    //                 byte address.
    //   psm         — GS PSM code (use one of the localparams above).
    //   payload     — payload bits in the LSBs:
    //                   CT32 → payload[31:0] is the full ABGR word.
    //                   CT16 → payload[15:0] is the RGB5A1 halfword.
    //                   T8   → payload[ 7:0] is the byte index.
    //                   T4   → payload[ 3:0] is the nibble index.
    //   nibble_hi   — T4 only. 0 = splice payload[3:0] into the LOW
    //                 nibble of the byte at byte_addr, 1 = HIGH.
    //                 Ignored for CT32/CT16/T8.
    //   old_byte    — T4 only. Current value of mem[byte_addr]; the
    //                 function splices the new nibble into this byte
    //                 to preserve the other nibble. Ignored for
    //                 CT32/CT16/T8.
    //
    // Output: word-aligned write_addr + shifted write_data + write_be.
    //
    // For PSMs other than CT32/CT16/T8/T4 the function returns a
    // dropped write (write_be = 4'b0000, write_data = 32'd0); this
    // matches `vram_stub`'s "unsupported PSMs are silent no-ops"
    // posture (Ch95).
    // ----------------------------------------------------------------
    function automatic norm_out_t normalize_write(
        input logic [31:0] byte_addr,
        input logic [5:0]  psm,
        input logic [31:0] payload,
        input logic        nibble_hi,
        input logic [7:0]  old_byte
    );
        norm_out_t r;

        // Word-aligned base address common to every PSM.
        r.write_addr = byte_addr & ~32'd3;

        unique case (psm)
            // ------------------------------------------------------
            // PSMCT32 — natural 32-bit-aligned write. byte_addr MUST
            // already be word-aligned; if it isn't, the function
            // produces a dropped write so the BRAM module never sees
            // the misuse.
            // ------------------------------------------------------
            PSM_PSMCT32: begin
                if (byte_addr[1:0] != 2'b00) begin
                    r.write_data = 32'd0;
                    r.write_be   = 4'b0000;
                end else begin
                    r.write_data = payload;
                    r.write_be   = 4'b1111;
                end
            end

            // ------------------------------------------------------
            // PSMCT16 — halfword write. byte_addr[1] picks low or
            // high halfword; byte_addr[0] MUST be 0.
            // ------------------------------------------------------
            PSM_PSMCT16: begin
                if (byte_addr[0] != 1'b0) begin
                    r.write_data = 32'd0;
                    r.write_be   = 4'b0000;
                end else if (byte_addr[1] == 1'b0) begin
                    r.write_data = {16'd0, payload[15:0]};
                    r.write_be   = 4'b0011;
                end else begin
                    r.write_data = {payload[15:0], 16'd0};
                    r.write_be   = 4'b1100;
                end
            end

            // ------------------------------------------------------
            // PSMT8 — single byte at any byte address. byte_addr[1:0]
            // selects which of the 4 byte lanes gets the byte.
            // ------------------------------------------------------
            PSM_PSMT8: begin
                unique case (byte_addr[1:0])
                    2'b00: begin
                        r.write_data = {24'd0, payload[7:0]};
                        r.write_be   = 4'b0001;
                    end
                    2'b01: begin
                        r.write_data = {16'd0, payload[7:0], 8'd0};
                        r.write_be   = 4'b0010;
                    end
                    2'b10: begin
                        r.write_data = {8'd0, payload[7:0], 16'd0};
                        r.write_be   = 4'b0100;
                    end
                    2'b11: begin
                        r.write_data = {payload[7:0], 24'd0};
                        r.write_be   = 4'b1000;
                    end
                endcase
            end

            // ------------------------------------------------------
            // PSMT4 — nibble splice. The function takes `old_byte` as
            // the current value of mem[byte_addr] and produces a
            // full-byte write at that address containing the new
            // byte: (old_byte & ~nibble_mask) | (new_nibble in lane).
            //
            // The caller is responsible for sourcing `old_byte` —
            // typically a 1-cycle read of mem[byte_addr] before the
            // write fires. Ch156+ inserts that read pipeline inside
            // gs_stub.raster_pixel_emit + gif_image_xfer_stub.
            //
            // byte_addr[1:0] selects the byte lane in the 32-bit
            // word; nibble_hi selects which nibble of that byte gets
            // the new value.
            // ------------------------------------------------------
            PSM_PSMT4: begin
                logic [7:0] new_byte;
                if (nibble_hi)
                    new_byte = {payload[3:0], old_byte[3:0]};
                else
                    new_byte = {old_byte[7:4], payload[3:0]};

                unique case (byte_addr[1:0])
                    2'b00: begin
                        r.write_data = {24'd0, new_byte};
                        r.write_be   = 4'b0001;
                    end
                    2'b01: begin
                        r.write_data = {16'd0, new_byte, 8'd0};
                        r.write_be   = 4'b0010;
                    end
                    2'b10: begin
                        r.write_data = {8'd0, new_byte, 16'd0};
                        r.write_be   = 4'b0100;
                    end
                    2'b11: begin
                        r.write_data = {new_byte, 24'd0};
                        r.write_be   = 4'b1000;
                    end
                endcase
            end

            // ------------------------------------------------------
            // Unsupported PSM → drop the write. Matches vram_stub's
            // Ch95 stance.
            // ------------------------------------------------------
            default: begin
                r.write_data = 32'd0;
                r.write_be   = 4'b0000;
            end
        endcase

        return r;
    endfunction

endpackage : vram_normalize_pkg
