A chip comes back from the fab and the ADC never produces a reading. The digital logic is fine and the ADC is fine. The enable pin that the digital side drives is active-low in the analog block and active-high in the RTL, and every simulation used a stub ADC model that ignored its enable. The bug lived on the line between two teams, and no simulation ever looked at it. How does a digital DV engineer verify that line without a SPICE simulator in every regression?
A transistor-level (SPICE) simulator solves the circuit's equations continuously; it is accurate and orders of magnitude slower than an event-driven digital simulator. Running SPICE on a whole SoC for every test is out of the question. Real-number modelling (RNM) is the compromise: analog blocks are written as event-driven models whose signals carry real values (volts, amps), updated only when something changes or at a chosen time step. The digital simulator runs them at nearly digital speed.
It is like a flight simulator for pilots versus a wind tunnel for engineers. The flight simulator is not exact about the air, but it is exact enough about what the pilot sees and does, and it runs in real time. An RNM ADC is not exact about transistor noise, but it is exact enough about what the digital side sees: a code, a ready flag, a settling time, a threshold. That is the boundary you verify.
Approach | What runs | Speed | Used for |
|---|---|---|---|
Digital stub | A model that ignores analog behaviour ( | Fastest | Early bring-up only; it hides boundary bugs like the enable story |
Real-number model | Event-driven | Near-digital | Regressions of the mixed-signal boundary |
AMS co-simulation | Digital simulator plus an analog (SPICE or FastSPICE) solver on part of the design | Much slower | Selected scenarios, model validation, sign-off of critical paths |
The model below is a 4-bit DAC driving an RC filter, with a comparator on the output. It runs three checks you will meet on real projects: monotonicity (each code step raises the output; the worst step error is the DNL, differential non-linearity, in LSBs), settling (the output reaches the target within half an LSB inside a time spec) and a threshold (when the comparator trips). The second DAC has a mismatched MSB element, the classic cause of a non-monotonic step at mid-scale.
// A real-number model of a 4-bit DAC driving an RC filter, and the three checks a
// digital DV engineer usually owns at an analog boundary: monotonicity, settling, threshold.
module top;
localparam real VREF = 1.0;
localparam real LSB = VREF / 16.0;
localparam real TAU = 20.0; // filter time constant, ns
localparam real T_SETTLE_SPEC = 100.0;
// Static transfer function of a binary-weighted DAC. msb_w is the MSB weight as a
// fraction of VREF: ideal is 0.5; a mismatched MSB element makes it smaller.
function automatic real dac(input bit [3:0] code, input real msb_w);
return VREF * (code[0] * 0.0625 + code[1] * 0.125 + code[2] * 0.25 + code[3] * msb_w);
endfunction
// Monotonicity and worst DNL (step size error in LSB) over all codes.
function automatic bit monotonic(input real msb_w, output real worst_dnl, output int at_code);
bit ok = 1;
worst_dnl = 0.0; at_code = 0;
for (int c = 1; c < 16; c++) begin
real step = dac(c[3:0], msb_w) - dac(c[3:0] - 4'd1, msb_w);
real dnl = step / LSB - 1.0;
if (step <= 0.0) ok = 0;
if ((dnl < 0 ? -dnl : dnl) > (worst_dnl < 0 ? -worst_dnl : worst_dnl)) begin
worst_dnl = dnl; at_code = c;
end
end
return ok;
endfunction
real vout = 0.0, target = 0.0;
bit cmp_out;
real vth = 0.5; // comparator threshold
// The analog part: a first-order RC step response, integrated every 1 ns.
always #1 vout = vout + (target - vout) * (1.0 / TAU);
// A comparator on the filtered output: the digital side only ever sees this bit.
always @(vout) cmp_out = (vout > vth);
initial begin
real dnl; int c; time t0, t_settle, t_cross;
bit ok;
ok = monotonic(0.5, dnl, c);
$display("ideal DAC: monotonic=%0b worst DNL=%0.2f LSB at code %0d", ok, dnl, c);
ok = monotonic(0.42, dnl, c);
$display("MSB weight 0.42: monotonic=%0b worst DNL=%0.2f LSB at code %0d", ok, dnl, c);
// Settling: full-scale step, time until |vout - target| < LSB/2.
#5 t0 = $time; target = dac(4'hF, 0.5); t_cross = 0;
while ((target - vout) > LSB / 2.0) begin
#1;
if (t_cross == 0 && cmp_out) t_cross = $time - t0;
end
t_settle = $time - t0;
$display("step 0 -> 15: settled to %0.4f V (target %0.4f) in %0t ns, spec %0.0f ns: %s",
vout, target, t_settle, T_SETTLE_SPEC, t_settle <= T_SETTLE_SPEC ? "PASS" : "FAIL");
$display("comparator (vth=%0.2f V) switched %0t ns after the step", vth, t_cross);
$finish;
end
endmoduleideal DAC: monotonic=1 worst DNL=0.00 LSB at code 0
MSB weight 0.42: monotonic=0 worst DNL=-1.28 LSB at code 8
step 0 -> 15: settled to 0.9073 V (target 0.9375) in 67 ns, spec 100 ns: PASS
comparator (vth=0.50 V) switched 15 ns after the stepMonotonicity. With the MSB weight at 0.42 instead of 0.5, code 8 (0.42 V) is lower than code 7 (0.4375 V): a DNL of −1.28 LSB, which means a step that goes backwards. A control loop driving this DAC could oscillate around mid-scale.
Settling. A first-order filter settles to half an LSB from a full-scale step in τ·ln(15/16 ÷ 1/32) = τ·ln 30 ≈ 68 ns; the model, integrating in 1 ns steps, gives 67 ns, inside the 100 ns spec.
Threshold. The comparator trips 15 ns after the step, matching τ·ln(0.9375/0.4375) ≈ 15.2 ns. The digital side sees only that bit, so this is the timing its logic must tolerate.
Tolerance, never equality. Every check compares reals against a band (half an LSB, a time spec). An == on a real value is almost always a bug in an analog check.
Plain real variables cannot have several drivers. Real circuits do: two current sources into one node, a pull-up and a driver on one line. SystemVerilog (IEEE 1800-2012 and later) adds user-defined nettypes: a net whose value is any type, with a resolution function that combines all drivers. Verilog-AMS has wreal for the same purpose, and commercial simulators support it in mixed flows. Vendors ship packages of predefined nettypes (for example a voltage-and-impedance struct) so that models from different teams connect.
// not-run: Verilator 5.048 and Icarus 13 do not support nettype (both reject it); commercial simulators do
module top;
// A user-defined net that carries a real value and resolves several drivers by summing them.
function automatic real rsum(input real d[]);
rsum = 0.0;
foreach (d[i]) rsum += d[i];
endfunction
nettype real rnet with rsum;
rnet vin;
real a = 0.0, b = 0.0;
assign vin = a;
assign vin = b;
initial begin
a = 0.6; b = 0.3;
#1 $display("vin = %0.2f V (two drivers summed)", vin);
$finish;
end
endmoduleIn UVM terms, an analog input is just another interface. An analog driver takes an item ("ramp vin from 0.2 V to 0.8 V over 2 us") and sets a real on the interface in small steps; an analog monitor samples a real value on an event or at a fixed rate, or watches the digital outputs the analog block produces; the scoreboard compares against a model with tolerances. Sequences describe stimulus in physical units, which reads well and makes coverage meaningful ("input in each eighth of the range").
// snippet: an analog driver that ramps a real-valued input in steps
task drive_item(vin_item it);
real v = vif.vin;
int steps = it.ramp_ns / it.step_ns;
for (int i = 1; i <= steps; i++) begin
#(it.step_ns * 1ns);
vif.vin = v + (it.volts - v) * i / steps;
end
`uvm_info("AMS_DRV", $sformatf("vin at %0.3f V", vif.vin), UVM_HIGH)
endtaskAccellera's UVM-MS working group defines a methodology for exactly this: a small library and an analog bridge between UVM proxy classes and the analog nets, so that analog drivers and monitors become reusable VIP like digital agents. Check the current release on Accellera's site before quoting its API; the concept is what interviewers ask about. Many teams build the same pattern themselves with interfaces and real-valued signals, as above.
Block | What the digital side drives | What you check |
|---|---|---|
ADC | Enable, start, sample clock, calibration | Code for a known input within tolerance, conversion time, ready/valid timing, behaviour at range ends |
DAC | Codes, update strobe | Monotonicity, settling within spec, glitch-free updates |
PLL | Divider and band settings, enable | Lock within the spec time, lock-detect behaviour, the clock switch-over the digital side does after lock |
Regulators, bandgap, POR | Enables, trim codes | Power-up order, ready flags, reset release only after supplies are good |
Comparators, sensors | Thresholds, trims | Trip points, hysteresis, the interrupt or flag they raise |
Most real escapes on this boundary are not subtle analog effects. They are connectivity and convention: an inverted enable, a swapped bus order, a trim register wired to the wrong block, a ready flag the digital side never waits for. Those are exactly the bugs that a real-number model with the right pin behaviour finds and a stub hides.
A team's PLL model locked in 20 us, and every regression passed. On silicon the PLL needed longer in one band, and the digital sequencer, which switched the system clock to the PLL after a fixed 25 us, sometimes switched before lock. The RNM had been written from an early spec and never compared with the circuit after a design change. The fixes were two. First, the sequencer was changed to wait for the lock-detect flag instead of a fixed time, which should have been a requirement and a check from the start. Second, the team added model validation: an AMS co-simulation regression that runs the same stimulus on the model and on the transistor-level circuit and compares lock time, codes and thresholds within tolerance whenever either changes. A real-number model is only evidence while it matches the circuit.
if (code != expected_code) uvm_error(...), where expected_code = $floor(vin / lsb). It fails rarely at random inputs. Is the ADC model wrong?Probably not. For inputs very close to a code boundary, tiny floating-point differences decide which side the value falls on, so the model and the expectation can disagree by one code. The check should accept the expected code plus or minus one LSB near boundaries (or the tolerance the spec gives), and coverage should still record which codes were reached.
Anything the stub does not model: inverted enables or strobes, a wrong bit order on the code bus, missing update strobes, timing assumptions (the digital side using the output before it settles) and non-monotonic steps that a control loop depends on. A real-number model costs little speed and keeps those visible; stubs belong only in tests that do not touch the block.
target - vout with half an LSB rather than waiting until vout == target?A first-order RC response approaches its target exponentially and never reaches it exactly, so an equality wait would never end. The meaningful spec is settling within a band, here half an LSB, because a downstream ADC or comparator cannot tell finer differences apart.
Next: this topic covered four ways a project stretches the UVM bench. The rest of the Industry Practice section looks at the other verification work that surrounds it.