// retroDE_ps2 — tb_osd_platform_cell_adapter (Ch245)
// ============================================================================
// Focused unit-test for the Ch245 char-BRAM read adapter that translates
// platform osd_overlay's 11-bit cell address into our existing
// tile_ram_cdc 10-bit 32-bit-word index + low/high cell select.
//
// The adapter logic lives inline in
// `rtl/top/de25_nano_psmct32_raster_demo_top.sv`. This TB replicates
// it exactly inside the TB body and exercises it against a small
// shadow memory pre-populated with known cells.
//
// Pack convention (`set_cell` helper that lived in the retired
// tb_osd_overlay_stub.sv used this exact mapping; the bridge tile-
// write decode still produces the same packed layout, so retrodesd
// continues to write into 32-bit words that we unpack here):
//   word_idx = row * 32 + (col >> 1)
//   col[0]=0 → cell lives in word[15:0]  (low half)
//   col[0]=1 → cell lives in word[31:16] (high half)
//
// Adapter behavior (matches the inline logic at de25_nano_*.sv):
//   shadow_word_idx = char_rd_addr[10:1]
//   cell_half_sel   = char_rd_addr[0]
//   cell_data_w     = cell_half_sel ? word[31:16] : word[15:0]
//   char_rd_data    = registered(cell_data_w)  -- 1-cycle latency
//
// iverilog 12 in this codebase doesn't support task-local declarations,
// so the TB body inlines the writes and reads. Each cell write
// directly mutates `shadow_mem`; each read drives `char_rd_addr` and
// then samples `char_rd_data` after a clock.
// ============================================================================

`timescale 1ns/1ps

module tb_osd_platform_cell_adapter;

    logic clk = 1'b0;
    always #5 clk = ~clk;  // 100 MHz

    // Mock 32-bit shadow (the design-domain side of tile_ram_cdc).
    logic [31:0] shadow_mem [0:1023];

    // Adapter inputs (from osd_overlay.char_rd_addr).
    logic [10:0] char_rd_addr;

    // Adapter outputs (drives osd_overlay.char_rd_data).
    wire  [9:0]  word_idx     = char_rd_addr[10:1];
    wire         half_sel     = char_rd_addr[0];
    wire  [31:0] word_data_w  = shadow_mem[word_idx];
    wire  [15:0] cell_data_w  = half_sel ? word_data_w[31:16]
                                         : word_data_w[15:0];
    logic [15:0] char_rd_data;
    always_ff @(posedge clk) char_rd_data <= cell_data_w;

    // ------------------------------------------------------------------
    // Assertion helper
    // ------------------------------------------------------------------
    int errors;
    task automatic check_eq16(input string label, input logic [15:0] got, input logic [15:0] expected);
        if (got !== expected) begin
            $error("[%s] got 0x%04x expected 0x%04x", label, got, expected);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // Test sequence — pack convention writes are done by directly
    // computing the word index + half-select inline. Reads drive
    // char_rd_addr and wait one clock for the adapter register.
    // ------------------------------------------------------------------
    integer i;

    initial begin
        char_rd_addr = '0;
        errors       = 0;
        for (i = 0; i < 1024; i = i + 1) shadow_mem[i] = 32'd0;

        repeat (4) @(posedge clk);

        // §1 — (col=0, row=0) low half. attr=0x0F (white), char=0xC9.
        //      word_idx = 0*32 + 0 = 0. Pack into low half.
        shadow_mem[0] = {16'h0000, 16'h0FC9};

        // §2 — (col=1, row=0) high half. attr=0x8F (transp_bg=1), char=0x20.
        //      word_idx = 0*32 + 0 = 0 (col>>1=0). Pack into high half
        //      WITHOUT disturbing the low half from §1.
        shadow_mem[0] = {16'h8F20, shadow_mem[0][15:0]};

        // §3 — (col=2, row=3) low half. word_idx = 3*32 + 1 = 97.
        //      Cell = 16'h1234.
        shadow_mem[97] = {16'h0000, 16'h1234};

        // §4 — (col=31, row=7) high half (col[0]=1). word_idx = 7*32 + 15 = 239.
        //      Cell = 16'hFEED.
        shadow_mem[239] = {16'hFEED, 16'h0000};

        // Read §1 — char_rd_addr = {row=0, col=0} = 11'd0. Expect 0x0FC9.
        @(negedge clk); char_rd_addr = 11'd0;
        @(posedge clk); #1;
        check_eq16("cell(0,0)_low_half",  char_rd_data, 16'h0FC9);

        // Read §2 — char_rd_addr = {row=0, col=1} = 11'd1. Expect 0x8F20.
        @(negedge clk); char_rd_addr = 11'd1;
        @(posedge clk); #1;
        check_eq16("cell(1,0)_high_half", char_rd_data, 16'h8F20);

        // Read §3 — char_rd_addr = {row=3, col=2} = {5'd3, 6'd2} = 11'h0C2.
        @(negedge clk); char_rd_addr = {5'd3, 6'd2};
        @(posedge clk); #1;
        check_eq16("cell(2,3)_low_half_row3", char_rd_data, 16'h1234);

        // Read §4 — char_rd_addr = {row=7, col=31} = {5'd7, 6'd31}.
        @(negedge clk); char_rd_addr = {5'd7, 6'd31};
        @(posedge clk); #1;
        check_eq16("cell(31,7)_high_half_row7_corner", char_rd_data, 16'hFEED);

        // Adjacent unwritten cells must read 0.
        @(negedge clk); char_rd_addr = {5'd1, 6'd1};
        @(posedge clk); #1;
        check_eq16("cell(1,1)_unwritten", char_rd_data, 16'h0000);

        @(negedge clk); char_rd_addr = {5'd7, 6'd30};
        @(posedge clk); #1;
        check_eq16("cell(30,7)_low_neighbor_of_corner", char_rd_data, 16'h0000);

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

    initial begin
        #2_000_000;
        $error("[tb_osd_platform_cell_adapter] TIMEOUT");
        $finish;
    end

endmodule : tb_osd_platform_cell_adapter
