// retroDE_ps2 — tb_hdmi_i2c_wake_smoke (Ch165 Medium + Ch166 NACK watchdog + Ch167 byte-sequence lock)
//
// Focused, accelerated bring-up TB for the Ch165 ADV7513 I²C
// wake-up FSM (`I2C_HDMI_Config` → `I2C_Controller`). The
// existing Ch149 board TB only smoke-tests the wrapper and
// can't observe the LUT walk because at the production I²C
// clock divider (50 MHz / 20 kHz = 2500) the full 38-entry
// walk takes ~125 ms simulated (controller-clock period
// ~100 µs × 33 phases per byte × 38 bytes) — far longer
// than the 5 ms board-TB runtime. This TB instantiates
// `I2C_HDMI_Config` directly with a tiny `CLK_Freq / I2C_Freq`
// ratio so the LUT walks in microseconds, then exercises:
//   Phase 1 — `LUT_INDEX` advances 0 → LUT_SIZE−1 (38-entry walk).
//   Phase 1 — `READY` (= `hdmi_init_done`) rises after the walk.
//   Phase 1 — Ch167 byte-sequence lock: a bus-level decoder samples
//             SDA on each SCL rising edge between START/STOP and
//             assembles all 38 transactions as 24-bit
//             {dev_addr, reg, data} tuples; in parallel a snoop on
//             `mI2C_GO` rises captures the FSM-intent payload from
//             `u_dut.mI2C_DATA[23:0]`. After the walk completes the
//             test asserts the captured wire bytes match the
//             FSM-intent bytes one-for-one and that every dev_addr
//             is 8'h72 (ADV7513 write address).
//   Phase 2 — `HDMI_TX_INT` low retriggers the FSM (`LUT_INDEX`
//             back to 0; `READY` falls until the second walk).
//             Bus capture is disabled here so the retrigger walk
//             doesn't overflow the captured/expected logs.
//   Phase 3 — Open-drain shape check: SDA is never `'x` (would
//             indicate two drivers contending for strong-HIGH).
//   Phase 1-3 — `ERROR` (Ch166 NACK watchdog) stays LOW on the
//             healthy bus.
//   Phase 4 — Force `u_dut.mI2C_ACK = 1` to simulate a slave that
//             never ACKs. The FSM retries the same LUT entry; after
//             NACK_LIMIT (overridden to 4) consecutive retries
//             `ERROR` must latch HIGH. Releasing the force must
//             NOT clear `ERROR` (sticky semantics).
//
// Bus model — Ch167 switched the legacy pulldown(sda) to
// pullup(sda) plus a minimal slave-ACK driver: after each START,
// count SCL rising edges; on the 8th, 17th, and 26th edge the
// slave drives SDA strong-LOW for one ACK clock so the master
// FSM sees ACK=0 and advances. Pullup is required because the
// master encodes a 1-bit by *releasing* SDA (1'bz), and a
// pulldown would mask every released bit as 0 — preventing
// byte-level decode.
//
// Slave model: the TB pulls SDA LOW unconditionally (weak
// pulldown). The master drives SDA either strong-LOW (sending
// 0 or releasing to high-Z while the chip ACKs); on
// release-cycles the pulldown wins and the master reads ACK=0
// → the FSM advances every byte. Data correctness on the bus
// is NOT modeled — this TB only verifies FSM progress + the
// open-drain release behavior, which is exactly the audit's
// scope (`Medium — verify the LUT walk + READY + SDA + INT
// retrigger`).

`timescale 1ns/1ps

module tb_hdmi_i2c_wake_smoke;

    // 100 MHz TB clock. The wake-up FSM divides this internally,
    // but the parameter overrides below collapse the divider to
    // ~2 cycles per I²C controller-clock toggle.
    logic clk;
    initial clk = 1'b0;
    always  #5 clk = ~clk;

    logic rst_n;
    logic hdmi_tx_int;

    // I²C bus modeling. The bus floats high through external
    // pull-ups in real hardware. Ch167 byte-sequence-lock work
    // requires the line to read each transmitted bit faithfully:
    // master drives strong-LOW for 0 and releases (1'bz) for 1,
    // so a pull-up is needed to bring 1's up to 1'b1 (pulldown
    // would mask every released bit as 0). The minimal I²C slave
    // model below drives SDA strong-LOW during the three ACK
    // windows of every transaction so the master FSM sees ACK=0
    // and advances to the next LUT entry.
    wire scl;
    wire sda;
    pullup(sda);

    // ----------------------------------------------------------
    // Bus-edge detection (used by the slave-ACK driver and the
    // byte-sequence decoder). Sampled on the TB master clock.
    // ----------------------------------------------------------
    reg scl_d, sda_d;
    always_ff @(posedge clk) begin
        scl_d <= scl;
        sda_d <= sda;
    end
    wire scl_rise = scl && !scl_d;
    wire sda_fall_during_scl_high = scl && !sda &&  sda_d;
    wire sda_rise_during_scl_high = scl &&  sda && !sda_d;

    // ----------------------------------------------------------
    // Minimal I²C slave-ACK driver. The master's I2C_Controller
    // FSM lives at u_dut.u0; phase encoding is a 6-bit register
    // with PH_ACK0=11, PH_ACK1=20, PH_ACK2=29 (see
    // rtl/platform/I2C_Controller.v parameters). Drive SDA
    // strong-LOW for the entire ACK phase so the master sees
    // ACK=0 regardless of where it samples within the phase.
    // Outside ACK phases SDA is 1'bz so the master's data bits
    // (released → 1'b1, driven → 1'b0) are visible to the
    // byte decoder. This is far more robust than counting SCL
    // edges (which races at ACK boundaries when input-clock
    // resolution is only 2× the controller-clock period).
    // ----------------------------------------------------------
    wire slave_in_ack_phase =
        (u_dut.u0.phase == 6'd11) ||  // PH_ACK0
        (u_dut.u0.phase == 6'd20) ||  // PH_ACK1
        (u_dut.u0.phase == 6'd29);    // PH_ACK2
    assign sda = slave_in_ack_phase ? 1'b0 : 1'bz;

    // ----------------------------------------------------------
    // Ch167 byte-sequence lock. Bus-level decoder: between
    // START and STOP, sample SDA on each SCL rising edge and
    // assemble three 8-bit data bytes (skipping the ACK windows
    // at edges 8, 17, 26). On STOP, append the 24-bit
    // {dev_addr, reg, data} payload to a captured[] log. A
    // parallel snoop on `mI2C_GO` rising edges captures the
    // FSM's intent (`u_dut.mI2C_DATA[23:0]`) into expected[].
    // After the first LUT walk completes (Phase 1), the test
    // asserts captured[i] === expected[i] for i = 0..LUT_SIZE-1.
    // ----------------------------------------------------------
    bit          bus_capture_enable;
    initial      bus_capture_enable = 1'b1;
    typedef enum logic [0:0] { DEC_IDLE, DEC_TXN } dec_state_t;
    dec_state_t  dec_state;
    int          dec_bit_count;
    logic [23:0] dec_payload;
    int          captured_count;
    logic [23:0] captured [0:63];

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            dec_state      <= DEC_IDLE;
            dec_bit_count  <= 0;
            dec_payload    <= 24'h0;
            captured_count <= 0;
        end else if (bus_capture_enable) begin
            case (dec_state)
                DEC_IDLE: begin
                    if (sda_fall_during_scl_high) begin
                        dec_state     <= DEC_TXN;
                        dec_bit_count <= 0;
                        dec_payload   <= 24'h0;
                    end
                end
                DEC_TXN: begin
                    if (sda_rise_during_scl_high &&
                        !slave_in_ack_phase &&
                        dec_bit_count >= 27) begin
                        // STOP — record the captured payload.
                        if (captured_count < 64)
                            captured[captured_count] <= dec_payload;
                        captured_count <= captured_count + 1;
                        dec_state     <= DEC_IDLE;
                    end else if (scl_rise && dec_bit_count < 27) begin
                        // Map SCL-rise position within transaction
                        // to a bit slot in the 24-bit payload
                        // (MSB-first per byte). Edges 8/17/26 are
                        // ACK windows - sample but discard.
                        case (dec_bit_count)
                            0:  dec_payload[23] <= sda;
                            1:  dec_payload[22] <= sda;
                            2:  dec_payload[21] <= sda;
                            3:  dec_payload[20] <= sda;
                            4:  dec_payload[19] <= sda;
                            5:  dec_payload[18] <= sda;
                            6:  dec_payload[17] <= sda;
                            7:  dec_payload[16] <= sda;
                            // 8 = ACK0
                            9:  dec_payload[15] <= sda;
                            10: dec_payload[14] <= sda;
                            11: dec_payload[13] <= sda;
                            12: dec_payload[12] <= sda;
                            13: dec_payload[11] <= sda;
                            14: dec_payload[10] <= sda;
                            15: dec_payload[9]  <= sda;
                            16: dec_payload[8]  <= sda;
                            // 17 = ACK1
                            18: dec_payload[7]  <= sda;
                            19: dec_payload[6]  <= sda;
                            20: dec_payload[5]  <= sda;
                            21: dec_payload[4]  <= sda;
                            22: dec_payload[3]  <= sda;
                            23: dec_payload[2]  <= sda;
                            24: dec_payload[1]  <= sda;
                            25: dec_payload[0]  <= sda;
                            // 26 = ACK2
                            default: ;
                        endcase
                        dec_bit_count <= dec_bit_count + 1;
                    end
                end
            endcase
        end
    end

    // FSM-intent snoop: capture u_dut.mI2C_DATA on each rising
    // edge of u_dut.mI2C_GO. The FSM raises GO once per LUT
    // entry's first attempt (and again on each retry, but
    // expected_count is gated to LUT_SIZE so retries don't
    // overflow).
    reg          mi2c_go_d;
    int          expected_count;
    logic [23:0] expected_seq [0:63];
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            mi2c_go_d      <= 1'b0;
            expected_count <= 0;
        end else begin
            mi2c_go_d <= u_dut.mI2C_GO;
            if (u_dut.mI2C_GO && !mi2c_go_d && bus_capture_enable) begin
                if (expected_count < 64)
                    expected_seq[expected_count] <= u_dut.mI2C_DATA[23:0];
                expected_count <= expected_count + 1;
            end
        end
    end

    // The DUT. Tiny CLK_Freq/I2C_Freq ratio (≈ 1) collapses the
    // controller-clock divider so the LUT walks in microseconds
    // instead of tens of milliseconds. NACK_LIMIT shrunk from
    // production 16 → 4 so Phase 4 (forced-NACK error trigger)
    // finishes in microseconds rather than tens of ms.
    logic ready;
    logic dut_error;
    I2C_HDMI_Config #(
        .CLK_Freq    (2),
        .I2C_Freq    (1),
        .LUT_SIZE    (38),
        .NACK_LIMIT  (4)
    ) u_dut (
        .iCLK       (clk),
        .iRST_N     (rst_n),
        .I2C_SCLK   (scl),
        .I2C_SDAT   (sda),
        .HDMI_TX_INT(hdmi_tx_int),
        .READY      (ready),
        .ERROR      (dut_error)
    );

    // ----------------------------------------------------------
    // Phase 1: reset, watch the FSM walk LUT_INDEX 0 → 37.
    // ----------------------------------------------------------
    int  errors;
    int  lut_index_max_seen;
    int  ready_rise_cycles;
    bit  saw_lut_full_walk;
    bit  saw_ready_rise;
    bit  saw_error_during_happy_path;

    // Hoisted out of the Phase-2/Phase-3/Phase-4 initial block below so
    // iverilog 12 (which rejects mid-block declarations after executable
    // statements) accepts the file. Module-scope `bit`/`int` initialize
    // to 0 / 1'b0.
    bit  saw_restart;
    int  lut_idx_after_int;
    int  sda_high_obs;
    bit  saw_error_rise;

    initial begin
        errors             = 0;
        lut_index_max_seen = 0;
        ready_rise_cycles  = 0;
        saw_lut_full_walk  = 1'b0;
        saw_ready_rise     = 1'b0;
        rst_n              = 1'b0;
        hdmi_tx_int        = 1'b1;  // deasserted (active-low)
        repeat (10) @(posedge clk);
        rst_n = 1'b1;
    end

    // Track the highest LUT_INDEX ever observed, and whether the
    // ERROR output ever rose during the "happy path" (Phases 1-3,
    // before the explicit NACK-injection Phase 4 begins).
    bit happy_path_active;
    initial happy_path_active = 1'b1;

    always_ff @(posedge clk) begin
        if (rst_n) begin
            if (int'(u_dut.LUT_INDEX) > lut_index_max_seen)
                lut_index_max_seen <= int'(u_dut.LUT_INDEX);
            if (u_dut.LUT_INDEX == u_dut.LUT_SIZE - 1)
                saw_lut_full_walk <= 1'b1;
            if (ready && !saw_ready_rise) begin
                saw_ready_rise    <= 1'b1;
                ready_rise_cycles <= 0;
            end else if (saw_ready_rise) begin
                ready_rise_cycles <= ready_rise_cycles + 1;
            end
            if (happy_path_active && dut_error)
                saw_error_during_happy_path <= 1'b1;
        end
    end

    // ----------------------------------------------------------
    // Slave-side ACK behavior is implicit in the SDA pulldown:
    // when the master releases SDA at the 9th bit, the bus reads
    // LOW, the controller's mI2C_ACK samples 0, and the wake-up
    // FSM advances to LUT_INDEX+1. Sanity-check that the bus is
    // actually toggling (master drives SCL) — if SCL never
    // toggles, the FSM is stuck and we'd time out below.
    int scl_edge_count;
    initial scl_edge_count = 0;
    always @(scl) if (rst_n) scl_edge_count = scl_edge_count + 1;

    // ----------------------------------------------------------
    // Phase 2: once READY, pull HDMI_TX_INT LOW briefly and
    // assert the LUT walk restarts (LUT_INDEX returns to 0;
    // READY falls).
    // ----------------------------------------------------------
    initial begin
        // Wait for the first LUT walk to complete.
        wait (rst_n);
        fork
            begin
                wait (ready == 1'b1);
            end
            begin
                #5_000_000;  // 5 ms wall budget
                $error("Phase 1: READY never asserted in 5 ms (LUT walk stuck or too slow)");
                errors = errors + 1;
            end
        join_any
        disable fork;

        // Phase 1 asserts. LUT_INDEX walks 0..LUT_SIZE-1 inside the
        // case() loop, then the post-step increments it to LUT_SIZE
        // (38) on the cycle the FSM falls through to the READY-high
        // branch — so the max value ever observed is LUT_SIZE, not
        // LUT_SIZE-1. The full-walk evidence comes from the separate
        // `saw_lut_full_walk` flag (LUT_INDEX == LUT_SIZE-1).
        if (lut_index_max_seen != 38) begin
            $error("Phase 1: LUT_INDEX max seen = %0d, expected 38 (LUT_SIZE)", lut_index_max_seen);
            errors = errors + 1;
        end
        if (!saw_lut_full_walk) begin
            $error("Phase 1: never observed LUT_INDEX == LUT_SIZE-1 (37)");
            errors = errors + 1;
        end
        if (!ready) begin
            $error("Phase 1: READY low after wait — fork race");
            errors = errors + 1;
        end
        if (scl_edge_count < 100) begin
            $error("Phase 1: only %0d SCL edges seen — bus is stuck", scl_edge_count);
            errors = errors + 1;
        end

        // ------------------------------------------------------
        // Ch167 byte-sequence lock. Compare every captured
        // 24-bit transaction (assembled from the wire) against
        // the FSM-intent snapshot (assembled at mI2C_GO rise).
        // Both should be exactly LUT_SIZE entries long; mismatch
        // means either the controller miswired the data path or
        // the FSM tried to send something unexpected.
        // ------------------------------------------------------
        // Settle a few extra cycles so the last STOP gets recorded.
        repeat (32) @(posedge clk);
        if (captured_count != int'(u_dut.LUT_SIZE)) begin
            $error("Ch167: captured %0d transactions on the wire, expected %0d",
                   captured_count, u_dut.LUT_SIZE);
            errors = errors + 1;
        end
        if (expected_count != int'(u_dut.LUT_SIZE)) begin
            $error("Ch167: snooped %0d FSM-intent payloads, expected %0d",
                   expected_count, u_dut.LUT_SIZE);
            errors = errors + 1;
        end
        for (int i = 0; i < int'(u_dut.LUT_SIZE); i++) begin
            if (captured[i] !== expected_seq[i]) begin
                $error("Ch167: txn %0d mismatch: bus=24'h%06h fsm=24'h%06h",
                       i, captured[i], expected_seq[i]);
                errors = errors + 1;
            end
        end
        // First byte of every transaction must be 8'h72 = ADV7513
        // write address (7-bit 0x39 << 1, R/W=0). Cross-check the
        // captured stream directly so we lock the dev_addr too.
        for (int i = 0; i < int'(u_dut.LUT_SIZE); i++) begin
            if (captured[i][23:16] !== 8'h72) begin
                $error("Ch167: txn %0d dev_addr=8'h%02h, expected 8'h72",
                       i, captured[i][23:16]);
                errors = errors + 1;
            end
        end

        // Disable bus capture for the rest of the test —
        // Phase 2 retriggers the LUT (would overflow captured[])
        // and Phase 4 forces NACKs (broken bus by design).
        bus_capture_enable = 1'b0;

        // Phase 2: HDMI_TX_INT retrigger. Pull INT low while
        // READY is high; the FSM should reset LUT_INDEX to 0
        // and re-walk.
        @(posedge clk); hdmi_tx_int = 1'b0;
        // Hold long enough for the FSM to sample it.
        repeat (8) @(posedge clk);
        hdmi_tx_int = 1'b1;

        // Wait for LUT_INDEX to dip back below the max (proves
        // the FSM restarted) and then for READY to come back up.
        saw_restart      = 1'b0;
        fork
            begin
                wait (u_dut.LUT_INDEX == 0);
                saw_restart = 1'b1;
            end
            begin
                #5_000_000;
                $error("Phase 2: LUT_INDEX never returned to 0 after HDMI_TX_INT pulse");
                errors = errors + 1;
            end
        join_any
        disable fork;

        if (!saw_restart) begin
            // (already errored above)
        end else begin
            // Wait for the second walk to complete.
            fork
                begin
                    wait (ready == 1'b1 && u_dut.LUT_INDEX >= u_dut.LUT_SIZE);
                end
                begin
                    #5_000_000;
                    $error("Phase 2: READY did not reassert after retrigger walk");
                    errors = errors + 1;
                end
            join_any
            disable fork;
        end

        // Phase 3: open-drain shape check. With the Ch167 pullup
        // + slave-ACK bus model, the line is HIGH at idle and
        // LOW only when the master drives a 0 or when the slave
        // drives the ACK. The relevant violation is that no two
        // drivers ever simultaneously drive strong-HIGH (which
        // would resolve to 'x' on a tri-state wire). Sample SDA
        // repeatedly and assert it is never 'x'.
        // (Reusing `sda_high_obs` as the violation counter.)
        sda_high_obs = 0;
        repeat (50) begin
            @(posedge clk);
            if (sda === 1'bx) sda_high_obs = sda_high_obs + 1;
        end
        if (sda_high_obs > 0) begin
            $error("Phase 3: SDA was 'x' on %0d/50 sample cycles (driver conflict / open-drain violation)",
                   sda_high_obs);
            errors = errors + 1;
        end

        // Happy-path ERROR check: across Phases 1-3 the bus was
        // healthy (pulldown → ACK=0), so the NACK watchdog must
        // never have fired.
        if (saw_error_during_happy_path) begin
            $error("Happy path: ERROR rose during normal walk (NACK watchdog false-positive)");
            errors = errors + 1;
        end
        if (dut_error) begin
            $error("Happy path: ERROR is HIGH at end of Phase 3 (expected LOW)");
            errors = errors + 1;
        end

        // ----------------------------------------------------------
        // Phase 4: forced-NACK error trigger. The healthy bus
        // produced ACK=0 every byte (master sees the pulldown
        // win the release window). Force `mI2C_ACK = 1'b1` to
        // simulate a slave that doesn't ACK — every transaction
        // bounces back to mSetup_ST=0 and retries the same LUT
        // entry. After NACK_LIMIT consecutive retries (overridden
        // to 4 above) the watchdog must latch ERROR.
        //
        // We also pulse HDMI_TX_INT so the FSM restarts the walk
        // at LUT_INDEX=0 → predictable retry counter starting
        // point.
        happy_path_active = 1'b0;
        @(posedge clk); hdmi_tx_int = 1'b0;
        repeat (8) @(posedge clk);
        hdmi_tx_int = 1'b1;
        wait (u_dut.LUT_INDEX == 0);
        force u_dut.mI2C_ACK = 1'b1;

        saw_error_rise = 1'b0;
        fork
            begin
                wait (dut_error == 1'b1);
                saw_error_rise = 1'b1;
            end
            begin
                #5_000_000;
                $error("Phase 4: ERROR never asserted within 5 ms after forced NACK");
                errors = errors + 1;
            end
        join_any
        disable fork;

        // ERROR is sticky. Releasing the force (slave starts ACKing
        // again) must NOT clear it — only iRST_N would.
        release u_dut.mI2C_ACK;
        repeat (200) @(posedge clk);
        if (!dut_error) begin
            $error("Phase 4: ERROR was not sticky — went LOW after force released");
            errors = errors + 1;
        end

        $display("[tb_hdmi_i2c_wake_smoke] lut_index_max=%0d saw_full_walk=%0b saw_ready_rise=%0b scl_edges=%0d saw_error_rise=%0b errors=%0d",
                 lut_index_max_seen, saw_lut_full_walk, saw_ready_rise,
                 scl_edge_count, saw_error_rise, errors);
        if (errors == 0) $display("[tb_hdmi_i2c_wake_smoke] PASS");
        else             $display("[tb_hdmi_i2c_wake_smoke] FAIL");
        $finish;
    end

    // Hard timeout — bound the whole test.
    initial begin
        #20_000_000;
        $error("[tb_hdmi_i2c_wake_smoke] hard timeout");
        $finish;
    end

endmodule : tb_hdmi_i2c_wake_smoke
