// 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