Levels of Design Description in Verilog
Understand the four abstraction levels Verilog supports — from transistor switches all the way up to behavioral system descriptions.
🏗 Overview — The Four Levels
The components of a target design can be described at different levels of abstraction using Verilog’s constructs. Each level trades off detail for compactness. One of Verilog’s key strengths is that all four can be freely mixed in the same design module.
⚡ 1. Circuit / Switch Level
- The switch is the basic building block — it represents a MOS transistor.
- Switches can be combined to form inverters and other gates at the next level of abstraction.
- Verilog has MOS switch primitives built in:
nmos,pmos,cmos,tran, etc. - Used to build basic circuits: inverters, logic gates, 1-bit dynamic and static memories.
- Suitable for performance-critical or analog-adjacent circuits where transistor-level behavior matters.
Fig 2 — CMOS Inverter at Switch Level
module inverter ( output out, input in, input vdd, gnd ); // PMOS pulls output high when input is low pmos p1(out, vdd, in); // NMOS pulls output low when input is high nmos n1(out, gnd, in); endmodule
VDD
|
[pmos p1] ← gate = in
|
in ──┤── out ──→ output
|
[nmos n1] ← gate = in
|
GND
🔗 2. Gate Level
- All basic gates are available as built-in “Primitives” —
and,or,nand,nor,xor,xnor,not,buf. - Each primitive is defined by its inputs and outputs and can be instantiated directly.
- Also called structural modeling — analogous to building a circuit on a breadboard or PCB. You must know the full circuit topology.
- Hierarchical circuits can be built at this level.
- Limitation: Beyond 20–30 gate primitives, design descriptions become unwieldy and debugging gets laborious.
Fig 3 — AND Gate and NAND Gate at Gate Level
// 2-input AND gate and g1(y, a, b); // y = a & b // 3-input NAND gate nand g2(y, a, b, c); // y = ~(a & b & c) // Chaining gates to build an AOI function wire ab, cd; and g3(ab, a, b); // ab = a & b and g4(cd, c, d); // cd = c & d nor g5(y, ab, cd); // y = ~(ab | cd) → AOI
🌊 3. Data Flow Level
- All logic is described using the
assignkeyword — called a continuous assignment. - Every assignment runs concurrently — any change on the right-hand side immediately updates the output.
- All logic and algebraic operations on signals and variables can be represented here.
- Design descriptions are more compact than gate level — no need to know the gate topology, only the logical relationship.
- Easily synthesizable — tools map assignments directly to logic cells.
assign must be a wire (net), not a reg.
Fig 4 — Data Flow Examples
// Simple logic functions assign y = a & b; // AND assign y = a | b; // OR assign y = ~a; // NOT (inverter) assign y = a ^ b; // XOR // AOI: AND-OR-INVERT assign y = ~((a & b) | (c & d)); // Multiplexer using ternary operator assign out = sel ? data1 : data0; // 4-bit adder with carry assign {cout, sum} = a + b + cin; // All three assignments below run at the same time assign p = a & b; assign q = c | d; assign r = p ^ q; // p and q are always up-to-date
🧠 4. Behavioral Level
- The highest level of design description — essentially describes system behavior.
- Uses
alwaysandinitialprocedural blocks, withif/else,case,for,while— looks like a C program. - Statements are dense in function — a few lines can describe complex behavior.
- Makes development fast and efficient. Ideal for testbenches, complex controllers, algorithms.
- Caution: Not all behavioral constructs are synthesizable. Synthesis tools may produce incorrect or redundant hardware from some constructs.
Fig 5 — Behavioral Examples
// Combinational logic (always @* — sensitive to all inputs) always @(*) begin if (sel) out = data1; else out = data0; end // Sequential logic — D flip-flop with synchronous reset always @(posedge clk) begin if (reset) Q <= 1'b0; else Q <= D; end // Case statement — 4-to-1 mux always @(*) begin case (sel) 2'b00: out = in0; 2'b01: out = in1; 2'b10: out = in2; 2'b11: out = in3; endcase end // Initial block — testbench only (not synthesizable) initial begin clk = 0; reset = 1; #10 reset = 0; #100 $finish; end
🔀 Mixed-Mode Design
One of Verilog’s most powerful characteristics is that all four levels can coexist seamlessly within a single design module. You are not forced to stay at one level.
always @(*)
assign sum = a+b
and / nand / nor
A common real-world pattern:
module top ( ... ); // Behavioral — controller FSM always @(posedge clk) begin state <= next_state; end // Data Flow — combinational datapath assign result = op_a + op_b; // Gate Level — instantiate a critical cell manually nand g_fast(fast_out, p, q); // Module instantiation — structural composition my_adder u1 (.a(x), .b(y), .sum(s)); endmodule
📊 Level Comparison Table
A quick side-by-side reference of all four levels:
| Level | Basic Element | Key Construct | Synthesizable? | Compactness | Typical Use |
|---|---|---|---|---|---|
| Switch | MOS transistor | nmos, pmos |
✅ Yes | Very verbose | Standard cell design |
| Gate | Logic gate | and, nor, xor |
✅ Yes | Verbose | Post-synthesis netlists |
| Data Flow | Logical expression | assign |
✅ Yes | Compact | Combinational datapaths |
| Behavioral | Procedural statement | always, if, case |
⚠️ Mostly | Most compact | Controllers, testbenches |
initial, delays (#10), and some loop patterns are not synthesizable. Use them only in testbenches, not in RTL that will go through synthesis.
⚡ Concurrency in Verilog
In a real electronic circuit, all components are active and changing simultaneously. Verilog simulators are designed to model this parallel reality.
always blocks and assign statements run in parallel — unlike sequential C programs where one line executes after another.`timescale to match your technology node.always @(posedge clk) — concurrency and sequential operation are not mutually exclusive in Verilog.Fig 7 — Concurrent vs Sequential Execution
// These two blocks run CONCURRENTLY — both respond to clk at the same time always @(posedge clk) begin reg_a <= data_in; // Block 1 end always @(posedge clk) begin reg_b <= reg_a; // Block 2 — reg_a here is the OLD value end // ⚠️ Non-blocking (<=) ensures both blocks read old values before any update // This is why flip-flop chains work correctly in Verilog
<= (non-blocking) inside clocked always blocks to correctly model flip-flops. Use = (blocking) in combinational always @(*) blocks. Mixing them incorrectly is a common source of simulation vs synthesis mismatches.
