Switch Level — Delays, Strengths & Trireg
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.
⏱ Time Delays — Introduction
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.
📐 Delay Forms for Switch Primitives
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
📡 Delay Propagation Rules
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 |
💡 Delay Examples
Fig 3 — CMOS inverter with realistic process delays
`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
Fig 4 — CMOS NAND with delay-annotated transistors
`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
💪 Drive Strengths — Introduction
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.
📊 Strength Levels
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.
⚙️ Instantiation with Strengths and Delays
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
Strength Keyword Reference
| 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 |
💡 Strength Examples
Fig 6 — Pseudo-NMOS with weak pull-up
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
Fig 7 — Open-drain bus: multiple drivers, strength resolution
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
🔋 trireg Net — Introduction
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).
📊 trireg States and Transitions
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
⚔️ Strength Contention Resolution
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 ✅
📋 Contention Resolution Tables
The Verilog simulator uses these tables to resolve contested net values. The result depends on both the strength levels and the values being driven.
Table 1 — Two Driving Sources on a wire
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 |
Table 2 — Driving Source vs trireg Capacitive Node
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.
💡 trireg Examples
Fig 12 — SRAM bit cell capacitive node model
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
Fig 13 — Charge sharing between two trireg nodes
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
Fig 14 — trireg with charge decay in a pass-transistor MUX
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
📋 Summary
| 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
