VERILOG SERIES · MODULE 15

Switch Level — Delays, Strengths & Trireg — VLSI Trainers
Verilog Series · Module 15

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.

📈
Rise Delay (tr)
Time for drain to transition to logic 1 (or any transition to a higher value) after the switch conducts.
📉
Fall Delay (tf)
Time for drain to transition to logic 0 (or any transition to a lower value) after the switch conducts.
Turn-Off Delay (tz)
Time for drain to transition to z (high-impedance) when the switch turns OFF. Unique to switch primitives.
🔬
Simulation Only
All delay annotations are ignored by synthesis tools. They model post-layout extracted parasitics for accurate timing simulation.
Switch delays vs gate delays: Switch primitives support a turn-off delay (tz) as the third delay value — the time to float the output to 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:

#d
Single
Same for rise, fall, turn-off
#(r,f)
Rise / Fall
Separate 0→1 and 1→0 delays
#(r,f,z)
Rise/Fall/Off
Add turn-off (→z) delay
#(min:typ:max)
Corners
Process variation: slow/typ/fast
Fig 1 — All delay formats on switch primitives
// ── 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 transitionDelay usedExample (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
Fig 2 — Delay waveform: nmos #(3, 5, 2) showing all three transitions
gate source drain t=0 t=10 t=20 t=30 t=40 t=50 t=60 = 1 z z t_r=3 t_z=2

💡 Delay Examples

Fig 3 — CMOS inverter with realistic process delays

Inverter with asymmetric rise/fall delays modelling PMOS/NMOS sizing difference
`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

Series NMOS stack has longer fall delay — charge must discharge through two 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:

7
Supply
supply
VDD / GND power rails — highest possible strength. Cannot be overridden.
6
Strong
strong
Default strength for gate primitives and assign statements. Most RTL signals.
5
Pull
pull
Pull-up / pull-down resistors. pullup / pulldown primitives drive at this level.
4
Weak
weak
Weak drivers — overridden by pull, strong, and supply drivers.
3
Large
large
Capacitive — large trireg node retaining charge.
2
Medium
medium
Capacitive — medium trireg node (default trireg size).
1
Small
small
Capacitive — small trireg node retaining charge at minimum strength.
0
High-Z
highz
Floating / undriven — net has no active driver. Lowest possible state.
Driving vs Capacitive strengths: Levels 4–7 (weak → supply) are driving strengths — they represent an active source pushing the net. Levels 1–3 (small → large) are capacitive strengths — they represent charge stored on a 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:

nmos
Primitive
gate keyword
(str1, str0)
Strengths
for driving 1 and driving 0
#(r,f,z)
Delays
optional timing
inst_name
Instance
unique identifier
(ports);
Port List
drain, source, gate
Fig 5 — Complete instantiation syntax with strength and 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

KeywordLevelCategoryDrive for 1Drive for 0
supply 7Driving supply1 supply0
strong 6Driving strong1 strong0
pull 5Driving pull1 pull0
weak 4Driving weak1 weak0
large 3Capacitive large1 large0
medium 2Capacitive medium1 medium0
small 1Capacitive small1 small0
highz 0High-Z highz1 highz0

💡 Strength Examples

Fig 6 — Pseudo-NMOS with weak pull-up

rpmos as weak pull-up, nmos as strong pull-down — strength resolution determines output
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

I2C SDA line — pull-up at pull strength, open-drain nmos at strong
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.

Driven State
0 or 1
At least one active driver is connected. Value and strength determined by driver(s). Behaves like wire.
Capacitive State
last val
All drivers disconnected (z). Retains previous value at capacitive strength (small/medium/large). Models charge storage.
Contention State
x
Two equal-strength drivers drive opposite values, OR a capacitive node and a driving source conflict at equal strength.
Fig 8 — trireg declaration and size specification
// ── 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
trireg vs wire when undriven: A regular 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

Fig 9 — trireg state transitions: driven → capacitive → decay
DRIVEN strong 0/1 driver→z CAPACITIVE medium 0/1 decay time DECAYED x (unknown) new driver → returns to DRIVEN
Fig 10 — trireg in a dynamic precharge circuit
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:

🏆
Stronger Wins
The driver with the higher strength level determines the net value. The weaker driver’s value is suppressed.
Equal Strength Conflict
Two drivers at the same strength driving opposite values produce x at that strength level. A design error.
🔋
Capacitive vs Drive
A driving source vs a trireg capacitive node: if driving strength > capacitive strength → driver wins. If equal → x.
🔗
Same Value — Resolved
Two drivers pushing the same value at any strength → no conflict. Result is that value at the higher strength.
Fig 11 — Resolution rules: three scenarios
// ── 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 → supplystrongpullweakhighz
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)
Equal-strength contention always produces x. Even if both sources are at a driving strength (e.g., two strong0 vs strong1 drivers), the conflict is 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

Using trireg to model capacitive storage in a dynamic memory cell
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

Charge sharing: large node charges small node via a pass transistor
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

Dynamic MUX: output node is capacitive, models pass-gate behaviour
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

TopicKey RuleApplies 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
Fig 15 — Complete switch-level cell with delay, strength, and trireg
`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
Switch-level modelling takeaway: At this level, every detail matters — the strength of each driver, the delay through each transistor, and whether nodes are capacitive or resistive. This fidelity makes switch-level simulation the most accurate pre-layout verification method for custom cells, SRAM, and dynamic logic. The trade-off is simulation speed — switch-level simulations run 10–100× slower than gate or RTL level, which is why they are reserved for critical cell characterisation rather than full-chip verification.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top