// retroDE_ps2 — tb_tile_ram_cdc (Ch229)
// ============================================================================
// Focused unit-test TB for the design-domain side of the Ch229 tile-RAM
// CDC. Drives synthetic bridge-side signals at one clock and verifies the
// shadow RAM in the design clock domain receives the write correctly.
//
// Uses two distinct clocks at different frequencies (100 MHz bclk,
// 33 MHz dclk) to exercise a real CDC scenario without taking forever.
// Bridge writes are spaced ≥ 6 dclk cycles apart (well above the 2-FF
// synchronizer's settling time) per the CDC contract documented in the
// `tile_ram_cdc.sv` header.
//
// Verifies:
//   1. Reset state: shadow RAM reads return 0 everywhere.
//   2. Single write at base (index 0) → propagates to shadow read.
//   3. Single write at mid (index 0x200) → propagates.
//   4. Single write at end (index 0x3FF) → propagates.
//   5. Multiple writes to the same index → final value wins.
//   6. Multiple writes to distinct indices → each lands in its slot.
//   7. Read of unwritten indices stays 0 across all the above.
//   8. Toggle-based event reception: writes happen on toggle EDGES,
//      not on toggle value; the receiver doesn't double-count a static
//      toggle value.
// ============================================================================

`timescale 1ns/1ps

module tb_tile_ram_cdc;

    // Two distinct clocks: 100 MHz bridge, 33 MHz design (3x ratio).
    logic bclk;
    logic dclk;
    initial bclk = 1'b0;
    initial dclk = 1'b0;
    always #5    bclk = ~bclk;     // 100 MHz
    always #15   dclk = ~dclk;     // 33 MHz

    logic breset_n;
    logic dreset_n;

    // Bridge-side write ports.
    logic        bclk_wr_toggle;
    logic [9:0]  bclk_wr_index;
    logic [31:0] bclk_wr_data;

    // Design-side read ports.
    logic [9:0]  dclk_rd_index;
    wire  [31:0] dclk_rd_data;

    // Ch230 — diagnostic counter output.
    wire [15:0] too_close_count;

    tile_ram_cdc u_dut (
        .bclk                   (bclk),
        .breset_n               (breset_n),
        .bclk_wr_toggle         (bclk_wr_toggle),
        .bclk_wr_index          (bclk_wr_index),
        .bclk_wr_data           (bclk_wr_data),
        .dclk                   (dclk),
        .dreset_n               (dreset_n),
        .dclk_rd_index          (dclk_rd_index),
        .dclk_rd_data           (dclk_rd_data),
        .tile_wr_too_close_count(too_close_count)
    );

    int errors;

    task automatic check_eq(input string label, input logic [31:0] got, input logic [31:0] expected);
        if (got !== expected) begin
            $error("[%s] got 0x%08x expected 0x%08x", label, got, expected);
            errors = errors + 1;
        end
    endtask

    // Read shadow_mem[idx] via the read port. Lookup is combinational,
    // so just drive the index and sample one dclk cycle later (to
    // mirror the read-pipeline timing the overlay uses).
    task automatic shadow_read(input logic [9:0] idx, output logic [31:0] data);
        dclk_rd_index = idx;
        @(posedge dclk);
        data = dclk_rd_data;
    endtask

    // Bridge-side write: latch index + data, then toggle. Hold for
    // several dclk cycles to give the 2-FF synchronizer + edge
    // detector time to fire and the shadow write to land.
    task automatic bridge_write(input logic [9:0] idx, input logic [31:0] data);
        @(posedge bclk);
        bclk_wr_index  <= idx;
        bclk_wr_data   <= data;
        bclk_wr_toggle <= ~bclk_wr_toggle;
        // Wait long enough for the dclk side to see + sample the
        // toggle edge: 2-FF sync (2 dclk) + 1 extra cycle for the
        // edge detector + 1 for the shadow write. Round up to 6
        // dclk cycles for slack.
        repeat (6) @(posedge dclk);
    endtask

    logic [31:0] rd;
    logic [15:0] count_before_burst;

    initial begin
        errors          = 0;
        breset_n        = 1'b0;
        dreset_n        = 1'b0;
        bclk_wr_toggle  = 1'b0;
        bclk_wr_index   = 10'd0;
        bclk_wr_data    = 32'd0;
        dclk_rd_index   = 10'd0;

        repeat (4) @(posedge bclk);
        breset_n = 1'b1;
        @(posedge dclk);
        dreset_n = 1'b1;
        repeat (4) @(posedge dclk);

        // ----------------------------------------------------------
        // 1. Reset state: every shadow read returns 0.
        // ----------------------------------------------------------
        shadow_read(10'h000, rd); check_eq("rst@0x000", rd, 32'd0);
        shadow_read(10'h001, rd); check_eq("rst@0x001", rd, 32'd0);
        shadow_read(10'h200, rd); check_eq("rst@0x200", rd, 32'd0);
        shadow_read(10'h3FF, rd); check_eq("rst@0x3FF", rd, 32'd0);

        // ----------------------------------------------------------
        // 2. Single write at base.
        // ----------------------------------------------------------
        bridge_write(10'h000, 32'hDEADBEEF);
        shadow_read(10'h000, rd); check_eq("wr@0x000",  rd, 32'hDEADBEEF);
        shadow_read(10'h001, rd); check_eq("nb@0x001",  rd, 32'd0);
        shadow_read(10'h3FF, rd); check_eq("nb@0x3FF",  rd, 32'd0);

        // ----------------------------------------------------------
        // 3. Single write at mid.
        // ----------------------------------------------------------
        bridge_write(10'h200, 32'h12345678);
        shadow_read(10'h200, rd); check_eq("wr@0x200",  rd, 32'h12345678);
        // Previous write still there.
        shadow_read(10'h000, rd); check_eq("wr@0x000_persist", rd, 32'hDEADBEEF);

        // ----------------------------------------------------------
        // 4. Single write at end.
        // ----------------------------------------------------------
        bridge_write(10'h3FF, 32'hCAFEF00D);
        shadow_read(10'h3FF, rd); check_eq("wr@0x3FF",  rd, 32'hCAFEF00D);
        // Previous writes still there.
        shadow_read(10'h000, rd); check_eq("wr@0x000_persist2", rd, 32'hDEADBEEF);
        shadow_read(10'h200, rd); check_eq("wr@0x200_persist",  rd, 32'h12345678);

        // ----------------------------------------------------------
        // 5. Multiple writes to the same index → last wins.
        // ----------------------------------------------------------
        bridge_write(10'h100, 32'h11111111);
        bridge_write(10'h100, 32'h22222222);
        bridge_write(10'h100, 32'h33333333);
        shadow_read(10'h100, rd); check_eq("wr@0x100_last_wins", rd, 32'h33333333);

        // ----------------------------------------------------------
        // 6. Many distinct writes (eight different slots).
        // ----------------------------------------------------------
        bridge_write(10'h010, 32'hAAAA0010);
        bridge_write(10'h020, 32'hAAAA0020);
        bridge_write(10'h040, 32'hAAAA0040);
        bridge_write(10'h080, 32'hAAAA0080);
        bridge_write(10'h150, 32'hAAAA0150);
        bridge_write(10'h153, 32'hAAAA0153);
        bridge_write(10'h333, 32'hAAAA0333);
        bridge_write(10'h3FE, 32'hAAAA03FE);
        shadow_read(10'h010, rd); check_eq("wr@0x010", rd, 32'hAAAA0010);
        shadow_read(10'h020, rd); check_eq("wr@0x020", rd, 32'hAAAA0020);
        shadow_read(10'h040, rd); check_eq("wr@0x040", rd, 32'hAAAA0040);
        shadow_read(10'h080, rd); check_eq("wr@0x080", rd, 32'hAAAA0080);
        shadow_read(10'h150, rd); check_eq("wr@0x150", rd, 32'hAAAA0150);
        shadow_read(10'h153, rd); check_eq("wr@0x153", rd, 32'hAAAA0153);
        shadow_read(10'h333, rd); check_eq("wr@0x333", rd, 32'hAAAA0333);
        shadow_read(10'h3FE, rd); check_eq("wr@0x3FE", rd, 32'hAAAA03FE);

        // ----------------------------------------------------------
        // 7. Unwritten slots are still 0 amid all the writes above.
        // ----------------------------------------------------------
        shadow_read(10'h007, rd); check_eq("nb@0x007", rd, 32'd0);
        shadow_read(10'h0FF, rd); check_eq("nb@0x0FF", rd, 32'd0);
        shadow_read(10'h2FF, rd); check_eq("nb@0x2FF", rd, 32'd0);

        // ----------------------------------------------------------
        // 8. Toggle is event-based — a static toggle value does not
        //    re-trigger writes. Drive bclk_wr_index to a fresh value
        //    but leave the toggle unchanged → shadow MUST NOT update.
        // ----------------------------------------------------------
        @(posedge bclk);
        bclk_wr_index <= 10'h000;
        bclk_wr_data  <= 32'hBEEFCAFE;
        // Do NOT toggle. Wait several cycles.
        repeat (10) @(posedge dclk);
        // Slot 0x000 still holds DEADBEEF.
        shadow_read(10'h000, rd); check_eq("static_toggle_no_write", rd, 32'hDEADBEEF);

        // ----------------------------------------------------------
        // 9. Ch230 — too-close-write diagnostic counter. Up to this
        //    point every bridge_write() spaced its toggle at least
        //    6 dclk apart, well above MIN_DCLK_GAP=3, so the
        //    counter must still be 0.
        // ----------------------------------------------------------
        check_eq("too_close_count@safe_writes", {16'd0, too_close_count}, 32'd0);

        // Now deliberately violate the rate: flip toggle on
        // consecutive bclk edges (1 bclk apart) ten times. Each
        // resulting wr_pulse on dclk lands within < 3 dclk of the
        // previous one (because 1 bclk @ 100 MHz = 10 ns vs 1 dclk
        // @ 33 MHz ≈ 30 ns), incrementing the saturating counter.
        // The number of counted "too close" events is timing-
        // dependent (some bclk-domain flips will merge in the
        // synchronizer), so check the count is non-zero rather than
        // a specific value.
        for (int i = 0; i < 10; i++) begin
            @(posedge bclk);
            bclk_wr_index  <= 10'h300 + i[9:0];
            bclk_wr_data   <= 32'h0BADCAFE + 32'(i);
            bclk_wr_toggle <= ~bclk_wr_toggle;
        end
        repeat (10) @(posedge dclk);
        if (too_close_count == 16'd0) begin
            $error("[too_close_count_nonzero] counter stayed at 0 after fast writes");
            errors = errors + 1;
        end

        // Saturation behavior: counter must not wrap. Push it past
        // many more fast-write events and confirm it stops at
        // 0xFFFF eventually (or stays at its current value if
        // saturated). Easier to assert: monotonic non-decreasing
        // across additional bursts.
        count_before_burst = too_close_count;
        for (int j = 0; j < 20; j++) begin
            @(posedge bclk);
            bclk_wr_toggle <= ~bclk_wr_toggle;
        end
        repeat (10) @(posedge dclk);
        if (too_close_count < count_before_burst) begin
            $error("[too_close_count_monotonic] counter decreased: was 0x%04x, now 0x%04x",
                   count_before_burst, too_close_count);
            errors = errors + 1;
        end

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

    initial begin
        #5_000_000;
        $error("[tb_tile_ram_cdc] TIMEOUT");
        $finish;
    end

endmodule : tb_tile_ram_cdc
