A plain RTL simulation never goes metastable: the flop model resolves every edge deterministically. To exercise CDC logic in simulation you must write the uncertainty into the model yourself, and then read the results knowing exactly what the model proves.
The standard teaching model watches for data changes inside a setup/hold window around the clock edge. Outside the window the flop is exact; inside it the output resolves randomly to the old or the new value. The randomness stands in for silicon's dice roll, and a fixed seed or an explicit stimulus pattern keeps the test repeatable.
The testbench below uses that model for both synchroniser flops. It drives one hundred changes just inside the setup window, counts the window hits on the first flop, and checks that the second-stage output is always a clean 0 or 1 and finally equals the input.
`timescale 1ns/1ps
// A flip-flop that models metastability: a data change inside the setup/hold
// window around the clock edge resolves randomly to the old or the new value.
module meta_ff #(parameter real TCQ = 0.3, TSU = 0.15, THD = 0.1) (
input logic clk, d,
output logic q
);
realtime t_d = -100.0, t_clk = -100.0;
int meta_events = 0;
initial q = 1'b0;
always @(d) begin
t_d = $realtime;
if ($realtime - t_clk < THD) begin
meta_events++; q <= #TCQ $urandom_range(0, 1);
end
end
always @(posedge clk) begin
t_clk = $realtime;
if ($realtime - t_d < TSU) begin
meta_events++; q <= #TCQ $urandom_range(0, 1);
end else q <= #TCQ d;
end
endmodule
// The standard two-flop synchroniser built from the model above.
module sync2 (input logic clk, d, output logic q);
logic q1;
meta_ff f1 (.clk, .d, .q(q1)); // may go metastable and resolve randomly
meta_ff f2 (.clk, .d(q1), .q(q)); // samples one period later: settled in this model; the silicon residual is the MTBF argument
endmodule
If the model drives X into the first flop instead of a random 0/1, optimistic simulators propagate X through everything downstream and the test drowns in X, while pessimistic flows mask real bugs. The random old/new model avoids both traps: it keeps the simulation two-valued while still exercising either resolution.
Question | Simulation answers it? |
|---|---|
Does the logic handle either resolution of a window hit? | Yes: this is what the testbench above checks. |
Does the protocol survive late resolution? | Partly: the model resolves within the cycle, so it cannot show multi-cycle hangs. |
What is the MTBF? | No: MTBF comes from the analytical model with library parameters, never from a testbench. |
Is the crossing structurally correct? | No: that is the structural tool's job (flop count, clocks, no logic in the path). |
No. It shows the downstream logic tolerates either resolution of a hit. The failure rate is set by how often silicon is still unresolved a full period later, which only the MTBF model addresses.
X would flood the simulation with unknowns the design would never actually produce, hiding whether the logic handles the two real outcomes. Random 0/1 exercises both genuine resolutions while staying two-valued.