Switch Level Modelling
The lowest abstraction level in Verilog — model circuits at the MOS transistor level using nmos, pmos, cmos, and bidirectional switch primitives with truth tables, circuit diagrams, and complete examples.
⚡ Introduction
Switch level modelling is the lowest abstraction level available in Verilog. It models circuits as networks of MOS transistor switches connected by nodes — exactly as a circuit designer thinks when drawing a transistor-level schematic.
At this level, the fundamental building block is the transistor switch — a three-terminal device where a gate signal controls whether current flows between drain and source. Verilog provides a complete set of built-in switch primitives that directly correspond to NMOS and PMOS transistors in CMOS technology.
🏗 Where Switch Level Fits
Switch level is the lowest and most detailed abstraction in Verilog — directly below gate level. The same circuit can be described at any of these levels:
// Behavioral always @(*) y = ~a; // Data Flow assign y = ~a; // Gate Level not g1(y, a); // Switch Level — explicit transistors pmos p1(y, vdd, a); // pull-up: ON when a=0 nmos n1(y, gnd, a); // pull-dn: ON when a=1
z through instead of converting it to x — matching real silicon behaviour.
🔌 Basic Transistor Switches
Verilog provides six unidirectional MOS switch primitives — three ideal (fully conducting) and three resistive (partially conducting, lower signal strength). All have the same three-terminal structure:
primitive_name instance_name (drain, source, gate); — drain first, then source, then gate. The gate controls whether drain-source conduct.
nmos n1(drain, source, gate);
pmos p1(drain, source, gate);
cmos c1(out, data, nctrl, pctrl);
rnmos rn1(drain, source, gate);
rpmos rp1(drain, source, gate);
rcmos rc1(out, data, nctrl, pctrl);
🟠 nmos — N-Channel MOSFET Switch
The nmos primitive models an N-channel MOSFET transistor. Its gate terminal is the control input — when the gate is high (logic 1), the transistor turns ON and conducts, passing the source signal to the drain. When the gate is low, the transistor turns OFF and the drain floats to z.
| gate | source | drain (out) | state |
|---|---|---|---|
| 0 | any | z | OFF |
| 1 | 0 | 0 | ON → passes 0 |
| 1 | 1 | 1 | ON → passes 1 |
| 1 | z | z | ON → passes z |
| x | any | x/z | Unknown |
// Syntax: nmos instance_name(drain, source, gate); nmos n1(drain, source, gate); // Example: pull-down in a CMOS inverter supply0 GND; nmos n_inv(out, GND, in); // when in=1 → out pulled to GND (0) // Multiple nmos in series (stacked pull-down) wire mid; nmos na(out, mid, a); // both must be ON to pull out low nmos nb(mid, GND, b); // implements NAND pull-down network
z (floating), it passes z to the drain. This is the key difference from gate primitives — a not gate receiving z produces x, but an nmos switch receiving z passes z through.
🔵 pmos — P-Channel MOSFET Switch
The pmos primitive models a P-channel MOSFET. It is the complement of nmos — the gate signal is inverted in effect: gate = 0 turns the transistor ON, gate = 1 turns it OFF. PMOS transistors form the pull-up network in CMOS logic, connecting the output to VDD when the input is low.
| gate | source | drain (out) | state |
|---|---|---|---|
| 1 | any | z | OFF |
| 0 | 0 | 0 | ON → passes 0 |
| 0 | 1 | 1 | ON → passes 1 |
| 0 | z | z | ON → passes z |
| x | any | x/z | Unknown |
// Syntax: pmos instance_name(drain, source, gate); pmos p1(drain, source, gate); // Example: pull-up in a CMOS inverter supply1 VDD; pmos p_inv(out, VDD, in); // when in=0 → out pulled to VDD (1) // Multiple pmos in parallel (pull-up network for NAND) pmos pa(out, VDD, a); // either one pulls out high pmos pb(out, VDD, b); // if a=0 OR b=0, out=1 (NAND pull-up)
〰️ Resistive Switches — rnmos, rpmos
The resistive switch variants (rnmos, rpmos) behave identically to their ideal counterparts in terms of conduction logic — but when conducting, they reduce the signal strength by one level. A strong input becomes pull, a pull becomes weak, and so on.
🔵 nmos — Ideal (strength preserved)
nmos n1(out, in, g); // in = strong1 → out = strong1 // Signal strength unchanged
〰️ rnmos — Resistive (strength reduced)
rnmos rn1(out, in, g); // in = strong1 → out = pull1 // Strength drops one level
// rnmos: models a resistive pass transistor // Common in dynamic logic and pass-gate networks rnmos rn1(out, in, enable); // Resistive pull-up using rpmos supply1 VDD; rpmos rp1(net, VDD, 1'b0); // always-on weak pull-up (gate tied low) // net has a weak pull-up — any strong driver overrides it // Practical use: pseudo-NMOS inverter // rpmos acts as a weak pull-up load rpmos load(out, VDD, 1'b0); // weak1 pull-up nmos pull(out, GND, in); // strong0 pull-down when in=1 // When in=1: strong0 from nmos beats weak1 from rpmos → out=0 // When in=0: nmos OFF, rpmos drives weak1 → out=1 (weak)
supply → strong → pull → weak → medium → small → highz. A resistive switch drops the strength one step. If the signal is already at the lowest driving strength (weak), it becomes medium (capacitive).
🟢 CMOS Switch (cmos, rcmos)
The cmos primitive is a convenience primitive that combines an nmos and a pmos switch in a single instantiation. It models a CMOS transmission gate — a pass gate that conducts in both polarities, giving full-rail signal transmission for both logic 0 and logic 1.
It has four ports: output (drain), input (source), n-gate control, and p-gate control. For a true complementary switch, ngate and pgate should be complementary (pgate = ~ngate).
| nctrl | pctrl | data | out |
|---|---|---|---|
| 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 |
| 1 | 0 | z | z |
| 0 | 1 | any | z |
| x | x | any | x/z |
nctrl=0, pctrl=1 → switch OFF
// cmos primitive: 4 ports — (out, data, nctrl, pctrl) cmos tg1(out, data, nctrl, pctrl); // Equivalent using separate nmos + pmos: nmos n1(out, data, nctrl); pmos p1(out, data, pctrl); // Resistive version rcmos rtg1(out, data, nctrl, pctrl); // 2-to-1 MUX using two CMOS transmission gates wire sel_n; not g_inv(sel_n, sel); cmos tg_a(out, a, sel, sel_n); // a passes when sel=1 cmos tg_b(out, b, sel_n, sel); // b passes when sel=0
Vdd - Vth, causing a threshold voltage loss. A CMOS transmission gate (nmos + pmos complementary) passes both 0 and 1 at full rail voltage, making it superior for high-speed and low-power signal transmission.
🏗 CMOS Gate Designs at Switch Level
Using nmos and pmos primitives together, complete CMOS logic gates can be built from scratch. Every CMOS gate follows the same complementary topology: a pull-up network (PUN) of PMOS transistors connected to VDD, and a pull-down network (PDN) of NMOS transistors connected to GND.
Fig 6 — CMOS Inverter (complete switch-level model)
module inv_switch ( input in, output out ); supply1 VDD; supply0 GND; pmos p1(out, VDD, in); // pull-up: ON when in=0 → out=1 nmos n1(out, GND, in); // pull-dn: ON when in=1 → out=0 endmodule
Fig 7 — CMOS NAND Gate (switch level)
module nand2_switch ( input a, b, output y ); supply1 VDD; supply0 GND; wire mid; // Pull-up network (PUN) — PMOS in PARALLEL // If a=0 OR b=0 → at least one PMOS ON → y=1 pmos pa(y, VDD, a); // ON when a=0 pmos pb(y, VDD, b); // ON when b=0 // Pull-down network (PDN) — NMOS in SERIES // Only if a=1 AND b=1 → both ON → y=0 nmos na(y, mid, a); // ON when a=1 nmos nb(mid, GND, b); // ON when b=1 endmodule
Fig 8 — CMOS NOR Gate (switch level)
module nor2_switch ( input a, b, output y ); supply1 VDD; supply0 GND; wire mid; // Pull-up network (PUN) — PMOS in SERIES // Only if a=0 AND b=0 → both ON → y=1 pmos pa(mid, VDD, a); // ON when a=0 pmos pb(y, mid, b); // ON when b=0 // Pull-down network (PDN) — NMOS in PARALLEL // If a=1 OR b=1 → at least one ON → y=0 nmos na(y, GND, a); // ON when a=1 nmos nb(y, GND, b); // ON when b=1 endmodule
↔️ Bidirectional Gates — tran, tranif0, tranif1
Unlike nmos/pmos (unidirectional — source to drain only), bidirectional switch primitives pass signals equally in both directions. They model the bidirectional nature of a physical pass transistor where current can flow either way. Bidirectional switches have no source or drain — just two interchangeable terminals and (for conditional versions) a control gate.
tran t1(node_a, node_b);
z when enable = 0. Models a CMOS pass gate with active-high enable.
tranif1 t1(node_a, node_b, enable);
z when enable = 1. Models a CMOS pass gate with active-low enable.
tranif0 t1(node_a, node_b, enable);
rtran rt1(node_a, node_b);
rtranif1 rt1(node_a, node_b, enable);
rtranif0 rt1(node_a, node_b, enable);
Bidirectional Switch Truth Tables
| node_a | node_b (result) |
|---|---|
| 0 | 0 |
| 1 | 1 |
| z | z |
| x | x |
| enable | in | out |
|---|---|---|
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| 1 | z | z |
| 0 | any | z |
| x | any | x/z |
| enable | in | out |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 0 | z | z |
| 1 | any | z |
| x | any | x/z |
💡 Bidirectional Gate Examples
Fig 9 — SRAM bit cell (6-transistor)
module sram_6t ( inout bl, blb, // bit line and bit line bar (complementary) input wl // word line (row select) ); supply1 VDD; supply0 GND; wire q, qn; // internal storage nodes // Cross-coupled inverter pair (storage element) pmos p1(q, VDD, qn); nmos n1(q, GND, qn); // INV1: qn→q pmos p2(qn, VDD, q); nmos n2(qn, GND, q); // INV2: q→qn // Access transistors (controlled by word line) // tranif1: bidirectional — enables read AND write on same path tranif1 t1(q, bl, wl); // q ↔ bl when wl=1 tranif1 t2(qn, blb, wl); // qn ↔ blb when wl=1 endmodule
Fig 10 — Bidirectional I/O pad (inout port)
module bidir_pad ( inout pad, // connects to external pin input data_out, // data to drive onto pad output data_in, // data read from pad input oe // output enable: 1=drive, 0=receive ); // Output path: drive pad when oe=1 tranif1 tx(pad, data_out, oe); // pad ↔ data_out when oe=1 // Input path: always read from pad tran rx(data_in, pad); // data_in always connected to pad // When oe=0: tranif1 disconnected → pad floats to external signal // When oe=1: tranif1 drives pad with data_out endmodule
Fig 11 — Dynamic logic using tran (charge sharing model)
module dynamic_and ( input clk, a, b, output out ); supply1 VDD; supply0 GND; wire clkn, mid; not inv_clk(clkn, clk); // Precharge: when clk=0 (clkn=1), pmos charges out to VDD pmos pre(out, VDD, clk); // ON when clk=0 // Evaluate: when clk=1, nmos PDN can discharge out nmos na(out, mid, a); // stack: a AND b pull out low nmos nb(mid, GND, b); // Keeper: weak pmos holds precharged value if a or b = 0 rpmos keep(out, VDD, clkn); // weak pull-up during evaluate endmodule
🔺 pullup and pulldown Primitives
Verilog provides two convenience primitives for modelling resistor pull-ups and pull-downs — commonly used in open-drain bus topologies (I²C, JTAG, 1-Wire).
🔼 pullup — Resistive pull to VDD
pullup pu1(net); // Connects net to VDD at pull strength // When no strong driver: net = pull1 // Any strong 0 driver: net = 0
🔽 pulldown — Resistive pull to GND
pulldown pd1(net); // Connects net to GND at pull strength // When no strong driver: net = pull0 // Any strong 1 driver: net = 1
// I2C SDA line: open-drain topology // Multiple devices drive low; external resistor pulls high module i2c_bus_sw ( inout sda, input drv_a, drv_b // 0 = device wants to pull low ); // External pull-up resistor (modelled by pullup) pullup r_ext(sda); // pull1 strength — weak // Open-drain drivers: nmos pulls low, no pull-high inside device nmos dev_a(sda, 1'b0, !drv_a); // drv_a=0 → nmos ON → sda=0 nmos dev_b(sda, 1'b0, !drv_b); // drv_b=0 → nmos ON → sda=0 // Result: sda = 0 if any device drives; 1 (weak) otherwise // → open-drain wired-AND behaviour endmodule
📋 Switch Primitives Complete Reference
| Primitive | Type | Ports | Conducts when | Direction | Strength |
|---|---|---|---|---|---|
| nmos | N-MOS | (drain, source, gate) | gate = 1 | Unidirectional | Full (unchanged) |
| pmos | P-MOS | (drain, source, gate) | gate = 0 | Unidirectional | Full (unchanged) |
| cmos | CMOS pair | (out, data, ngate, pgate) | ngate=1, pgate=0 | Unidirectional | Full (unchanged) |
| rnmos | N-MOS (R) | (drain, source, gate) | gate = 1 | Unidirectional | Reduced by 1 level |
| rpmos | P-MOS (R) | (drain, source, gate) | gate = 0 | Unidirectional | Reduced by 1 level |
| rcmos | CMOS pair (R) | (out, data, ngate, pgate) | ngate=1, pgate=0 | Unidirectional | Reduced by 1 level |
| tran | Bidir switch | (node_a, node_b) | Always | Bidirectional | Full (unchanged) |
| tranif1 | Bidir switch | (node_a, node_b, ctrl) | ctrl = 1 | Bidirectional | Full (unchanged) |
| tranif0 | Bidir switch | (node_a, node_b, ctrl) | ctrl = 0 | Bidirectional | Full (unchanged) |
| rtran | Bidir switch (R) | (node_a, node_b) | Always | Bidirectional | Reduced by 1 level |
| rtranif1 | Bidir switch (R) | (node_a, node_b, ctrl) | ctrl = 1 | Bidirectional | Reduced by 1 level |
| rtranif0 | Bidir switch (R) | (node_a, node_b, ctrl) | ctrl = 0 | Bidirectional | Reduced by 1 level |
| pullup | Pull resistor | (net) | Always | Drives pull1 | Pull strength |
| pulldown | Pull resistor | (net) | Always | Drives pull0 | Pull strength |
nmos, pmos, tran) propagate z when conducting. Gate primitives (and, or, buf) convert a z input into x. This is the fundamental modelling difference — use switch level when you need accurate high-impedance and charge-sharing behaviour.
