Complete coverage of time delays in switch primitives, instantiation with explicit drive strengths and delays, and how the simulator resolves strength contention β especially with capacitive trireg nets.
In real silicon, transistor switches do not change state instantaneously. The time taken for the output to respond after the input changes is called the propagation delay. Verilog allows you to model these delays directly on switch primitives β giving a more accurate simulation of circuit timing at the transistor level.
Switch primitive delays model the time it takes for the drain to respond after a change on the gate or source β capturing both the transistor switching time and interconnect RC delay.
z when the gate turns the transistor OFF. Gate primitives also support three delays, but their third value is specifically for floating outputs of tri-state gates (bufif1, etc.). Switch turn-off delay is the same concept applied at transistor level.Delays are specified between the primitive keyword and the instance name using the # specifier. All three values are optional and follow the same format as gate delays:
// ββ Single delay β same for all transitions βββββββββββββββββββ nmos #5 n1(out, in, g); // 5 units for any change pmos #3 p1(out, in, g); // 3 units for any change // ββ Two delays β (rise_delay, fall_delay) ββββββββββββββββββββ nmos #(3, 5) n2(out, in, g); // rise=3, fall=5 pmos #(2, 4) p2(out, in, g); // rise=2, fall=4 // ββ Three delays β (rise, fall, turn-off to z) ββββββββββββββββ nmos #(2, 4, 1) n3(out, in, g); // rise=2, fall=4, turn-off=1 pmos #(3, 2, 1) p3(out, in, g); // rise=3, fall=2, turn-off=1 // ββ cmos (4-port) β same delay forms apply βββββββββββββββββββ cmos #(2, 3, 1) c1(out, data, nctrl, pctrl); // ββ Bidirectional switches β symmetric delays βββββββββββββββββ tranif1 #(2, 3) t1(a, b, ctrl); // rise=2, fall=3 (both directions) tran #1 t2(a, b); // 1-unit delay in both directions // ββ Min:Typ:Max β process corners ββββββββββββββββββββββββββββ nmos #(1:2:3, 2:3:5, 1:1:2) n4(out, in, g); // rise: 1-2-3 (min:typ:max) // fall: 2-3-5 // turn-off: 1-1-2 // ββ With timescale ββββββββββββββββββββββββββββββββββββββββββββ `timescale 1ns/100ps nmos #(0.3, 0.5, 0.1) n5(out, in, g); // 300ps, 500ps, 100ps
Switch primitive delays have specific rules for which delay value applies to which transition. Understanding these rules is essential for accurate switch-level timing simulation.
| Output transition | Delay used | Example (r=3, f=5, z=1) |
|---|---|---|
| β 1 (rising) | Rise delay (tr) | Output rises to 1 after 3 time units |
| β 0 (falling) | Fall delay (tf) | Output falls to 0 after 5 time units |
| β z (turn-off) | Turn-off delay (tz) | Output floats after 1 time unit |
| β x (unknown) | min(tr, tf) | Output goes x after min(3,5) = 3 units |
| 1 β 0 (via z) | tz then tf | Gate turns off β z after 1, then pulled to 0 |
`timescale 1ns/100ps module inv_delayed ( input in, output out ); supply1 VDD; supply0 GND; // PMOS slower (wider device, more gate capacitance) // Turn-off at 0.15ns when input goes high pmos #(0.5, 0.3, 0.15) p1(out, VDD, in); // rise=0.5ns (outβ1), fall=0.3ns (outβ0), off=0.15ns (outβz) // NMOS faster (smaller device, less gate capacitance) nmos #(0.3, 0.2, 0.1) n1(out, GND, in); // rise=0.3ns, fall=0.2ns, off=0.1ns endmodule // Final output delay = max of conducting transistor delays // When in=1: nmos conducts, outβ0 after 0.2ns // When in=0: pmos conducts, outβ1 after 0.5ns
`timescale 1ns/1ps module nand2_delay ( input a, b, output y ); supply1 VDD; supply0 GND; wire mid; // PMOS pull-up β parallel, faster turn-on pmos #(0.4, 0.2, 0.1) pa(y, VDD, a); pmos #(0.4, 0.2, 0.1) pb(y, VDD, b); // NMOS pull-down β series stack, slower fall (mid node RC) nmos #(0.3, 0.6, 0.15) na(y, mid, a); nmos #(0.3, 0.6, 0.15) nb(mid, GND, b); endmodule
Every signal in a Verilog switch-level model carries both a value (0, 1, x, z) and a drive strength. The strength indicates how strongly a driver is driving the net. When multiple sources simultaneously drive the same net, the strength levels determine which driver wins through a process called strength resolution.
Understanding drive strengths is critical at the switch level because: the chain of switches between VDD/GND and the output node determines the effective drive strength; weak drivers model resistive paths; and capacitive nodes (trireg) retain charge when all active drivers disconnect.
Verilog defines 8 named drive strength levels, numbered 0 (weakest) to 7 (strongest). Each level has a keyword used in instantiation:
trireg node that gradually dissipates when the driver turns off.Drive strengths can be specified explicitly on gate and switch primitive instantiations. The syntax places the strength specification between the primitive keyword and the optional delay:
// ββ Strength only βββββββββββββββββββββββββββββββββββββββββββββ nmos (strong1, strong0) n1(out, in, g); // explicit strong (default) pmos (pull1, pull0) p1(out, in, g); // pull strength nmos (weak1, weak0) n2(out, in, g); // weak β overridable // ββ Strength + delay ββββββββββββββββββββββββββββββββββββββββββ nmos (strong1, strong0) #(2,3,1) n3(out, in, g); pmos (strong1, strong0) #(3,2,1) p3(out, in, g); // ββ Gate primitives with strength (same syntax) βββββββββββββββ and (strong1, strong0) g1(y, a, b); buf (pull1, highz0) g2(y, a); // drive 1 at pull, float for 0 // ββ Supply strength: cannot be overridden βββββββββββββββββββββ nmos (supply1, supply0) n4(out, in, g); // max strength // ββ Mixed strength β asymmetric βββββββββββββββββββββββββββββββ pmos (strong1, highz0) pu(net, VDD, 1'b0); // Drives strong1 when gate=0; output is highz when gate=1 // Models an always-on strong pull-up through pmos
| Keyword | Level | Category | Drive for 1 | Drive for 0 |
|---|---|---|---|---|
| supply | 7 | Driving | supply1 | supply0 |
| strong | 6 | Driving | strong1 | strong0 |
| pull | 5 | Driving | pull1 | pull0 |
| weak | 4 | Driving | weak1 | weak0 |
| large | 3 | Capacitive | large1 | large0 |
| medium | 2 | Capacitive | medium1 | medium0 |
| small | 1 | Capacitive | small1 | small0 |
| highz | 0 | High-Z | highz1 | highz0 |
module pseudo_nmos ( input in, output out ); supply1 VDD; supply0 GND; // Weak pull-up load (rpmos β strength reduced by 1 = pull β weak) rpmos (weak1, highz0) load(out, VDD, 1'b0); // gate tied low=always ON // Strong pull-down driver nmos (strong1, strong0) drv(out, GND, in); // Resolution: // in=1: nmos ON β strong0 beats weak1 β out = 0 β // in=0: nmos OFF β only weak1 driver β out = 1 (weak) β endmodule
module i2c_sda_bus ( inout sda, input dev_a_lo, dev_b_lo // 1 = device wants to pull SDA low ); supply0 GND; // External pull-up resistor β pull strength (level 5) pullup r_pull(sda); // pull1 on sda // Device A open-drain: drives strong0 when enabled nmos (strong1, strong0) dev_a(sda, GND, dev_a_lo); // Device B open-drain: drives strong0 when enabled nmos (strong1, strong0) dev_b(sda, GND, dev_b_lo); // Result: // dev_a_lo=0, dev_b_lo=0 β only pull1 β sda = 1 (pull strength) // dev_a_lo=1 or dev_b_lo=1 β strong0 beats pull1 β sda = 0 β // Wired-AND: sda=1 only when ALL devices release endmodule
The trireg net is a special net type that models capacitive charge storage. Unlike a regular wire (which goes to z when all drivers disconnect), a trireg retains its last driven value at a reduced capacitive strength when all active drivers turn off.
This models the behaviour of real CMOS nodes that store charge on gate capacitances β the classic example is a dynamic logic node that is precharged and then evaluated.
// ββ Basic trireg declaration ββββββββββββββββββββββββββββββββββ trireg node; // medium capacitance (default) trireg (large) big_cap; // large capacitance β stronger retention trireg (medium) med_cap; // medium capacitance (explicit) trireg (small) sml_cap; // small capacitance β weakest retention // ββ Vector trireg βββββββββββββββββββββββββββββββββββββββββββββ trireg [7:0] data_node; // 8-bit capacitive bus trireg (large) [3:0] addr_node; // large cap 4-bit bus // ββ trireg with delay (charge decay) βββββββββββββββββββββββββ trireg #(0, 0, 50) dyn_node; // charge decays to x after 50 units // trireg delays: (charge_rise, charge_fall, charge_decay) // decay: time from driven to capacitive before x appears
wire driven to z by all sources becomes z immediately. A trireg driven to z by all sources retains its last value at capacitive strength β it only becomes x after a specified charge-decay time (or never, if no decay is specified).module dynamic_precharge ( input clk, eval, output dyn_out ); supply1 VDD; supply0 GND; wire clkn; trireg (medium) dyn_node; // capacitive node stores charge not inv1(clkn, clk); // Phase 1 β Precharge: clk=0 β pmos ON β node charged to VDD pmos pre(dyn_node, VDD, clk); // ON when clk=0 // Phase 2 β Evaluate: clk=1 β pmos OFF β nmos may discharge nmos ev(dyn_node, GND, eval); // ON when eval=1 buf b1(dyn_out, dyn_node); // Timeline: // clk=0, eval=x : pmos ON β dyn_node driven to VDD (strong1) // clk=1, eval=0 : pmos OFF β dyn_node CAPACITIVE β retains 1 // clk=1, eval=1 : nmos ON β drives 0 β node discharges to 0 endmodule
When two or more drivers simultaneously drive the same net with different values, the simulator applies the following resolution rules to determine the net’s final value and strength:
x at that strength level. A design error.trireg capacitive node: if driving strength > capacitive strength β driver wins. If equal β x.// ββ Scenario 1: Different strengths, different values βββββββββ assign (strong1, strong0) net = 1'b1; // strong 1 β strength 6 assign (pull1, pull0) net = 1'b0; // pull 0 β strength 5 // Result: net = strong1 (strength 6 beats strength 5) β // ββ Scenario 2: Same strength, conflicting values β x βββββββββ assign (strong1, strong0) net = 1'b1; // strong 1 assign (strong1, strong0) net = 1'b0; // strong 0 // Result: net = strong-x (conflict!) β // ββ Scenario 3: Same strength, same value β resolved ββββββββββ assign (pull1, pull0) net = 1'b1; // pull 1 assign (pull1, pull0) net = 1'b1; // pull 1 // Result: net = pull1 (no conflict) β // ββ Scenario 4: Drive vs trireg capacitive ββββββββββββββββββββ trireg (medium) cap_net; // medium capacitance (level 2) // cap_net retained medium1 from previous drive assign (pull1, pull0) cap_net = 1'b0; // pull0 (level 5) // pull5 > medium2 β driver wins β cap_net = pull0 β
The Verilog simulator uses these tables to resolve contested net values. The result depends on both the strength levels and the values being driven.
Row = first driver strength, Column = second driver strength (both driving opposite values)
| β Driver A \ Driver B β | supply | strong | pull | weak | highz |
|---|---|---|---|---|---|
| supply | x(su) | A wins | A wins | A wins | A wins |
| strong | B wins | x(st) | A wins | A wins | A wins |
| pull | B wins | B wins | x(pu) | A wins | A wins |
| weak | B wins | B wins | B wins | x(wk) | A wins |
| highz | B wins | B wins | B wins | B wins | z |
When a driver and a capacitive trireg drive opposite values
| Driver strength \ trireg size | large (3) | medium (2) | small (1) |
|---|---|---|---|
| supply (7) | Driver wins | Driver wins | Driver wins |
| strong (6) | Driver wins | Driver wins | Driver wins |
| pull (5) | Driver wins | Driver wins | Driver wins |
| weak (4) | x (conflict) | Driver wins | Driver wins |
| large cap (3) | x (conflict) | Large wins | Large wins |
| medium cap (2) | Large wins | x (conflict) | Medium wins |
| small cap (1) | Large wins | Medium wins | x (conflict) |
strong-x β an unknown signal at strong strength. Any downstream logic receiving this x may produce further x propagation.module dram_cell ( inout bit_line, input word_line ); // Storage node β large capacitance holds charge without refresh trireg (large) #(0, 0, 200) storage; // charge_rise=0, charge_fall=0, decay_to_x=200 time units // Access transistor β bidirectional to model read and write tranif1 access(bit_line, storage, word_line); // Write: word_line=1 β bit_line drives storage (driven state) // Read: word_line=1 β storage drives bit_line (capacitive state) // Hold: word_line=0 β storage holds value for 200 time units // after 200 units β storage becomes x (refresh needed!) endmodule
module charge_share( input share_en ); trireg (large) c_large; // holds strong(er) charge trireg (small) c_small; // smaller capacitance // When share_en=1: the two nodes connect via pass transistor tranif1 pass(c_large, c_small, share_en); // Contention analysis when share_en=1: // c_large = large1 (level 3) // c_small = small0 (level 1) β opposite value! // large (3) > small (1) β large WINS β both nodes = large1 // This is the charge-sharing hazard in dynamic logic: // A floating node can corrupt a precharged node if they share charge endmodule
module mux_dynamic ( input a, b, sel, output out ); wire sel_n; trireg (medium) #(0, 0, 100) mux_node; // decays to x after 100 units not g1(sel_n, sel); // Pass gate A: passes when sel=1 tranif1 tga(mux_node, a, sel); // Pass gate B: passes when sel=0 tranif0 tgb(mux_node, b, sel); buf b1(out, mux_node); // When sel=1: a drives mux_node β out = a // When sel=0: b drives mux_node β out = b // During sel transition: mux_node temporarily capacitive // β retains last value up to 100 time units β then x endmodule
| Topic | Key Rule | Applies to |
|---|---|---|
| Single delay (#d) | Same delay for rise, fall, and turn-off transitions | All switch primitives |
| Two delays (#r,f) | Separate rise (β1) and fall (β0) delays | All switch primitives |
| Three delays (#r,f,z) | Add turn-off delay (βz) as third value | All switch primitives |
| Min:Typ:Max | Each delay can be a range for process corners | All primitives |
| Strength spec | (str1, str0) placed after keyword, before delay | Gates and switches |
| Higher strength wins | The driver with higher level determines net value | All nets |
| Equal strength conflict | Opposite values at same strength β x at that strength | All nets |
| trireg driven | Active driver β behaves like wire | trireg |
| trireg capacitive | All drivers z β retains last value at cap strength | trireg |
| trireg decay | After decay time β value becomes x | trireg with #(,,decay) |
| Charge sharing | Two connected trireg nodes: larger capacitance wins | trireg + tran/tranif |
`timescale 1ns/100ps module complete_switch_cell ( input clk, data_in, input sel, output out ); supply1 VDD; supply0 GND; wire clkn, sel_n; // ββ Internal capacitive nodes βββββββββββββββββββββββββββββ trireg (large) #(0,0,500) precharge_node; // 500ns decay trireg (medium) #(0,0,200) eval_node; // 200ns decay not inv_clk (clkn, clk); not inv_sel (sel_n, sel); // ββ Precharge phase (clk=0) βββββββββββββββββββββββββββββββ pmos (strong1, highz0) #(0.2,0.1,0.05) pc(precharge_node, VDD, clk); // ββ Evaluate phase (clk=1): pass data through tranif ββββββ tranif1 (strong1,strong0) #(0.15,0.15,0.05) tg1(eval_node, precharge_node, sel); nmos (strong1, strong0) #(0.1,0.2,0.05) n1(eval_node, GND, data_in); // ββ Output buffer βββββββββββββββββββββββββββββββββββββββββ buf #(0.1, 0.1) b1(out, eval_node); endmodule