ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
527 lines
22 KiB
Systemverilog
527 lines
22 KiB
Systemverilog
// 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
|