Module 4: Introduction to Verilog HDL ยท 4.4 Structural Modeling

Gate Primitives

This section is structural modelling: describing a circuit by naming its parts and wiring them together, as a schematic does. The smallest parts are the gate primitives built into the language, and they are also what a synthesis tool writes out when it produces a netlist, so being able to read them is a debugging skill as much as a design one.

The primitives

Verilog has AND, OR, NOT, NAND, NOR, XOR and XNOR built in, plus buffers and tri-state drivers. They are instantiated like modules but never declared, and there is one rule that differs from module instantiation: the output comes first, and connections are positional only.

wire a, b, c;
wire y_and, y_or, y_not, y_nand, y_nor, y_xor, y_xnor, y_and3;

and  g_and  (y_and,  a, b);      // y = a & b
or   g_or   (y_or,   a, b);
not  g_not  (y_not,  a);         // one input only
nand g_nand (y_nand, a, b);
nor  g_nor  (y_nor,  a, b);
xor  g_xor  (y_xor,  a, b);
xnor g_xnor (y_xnor, a, b);

and  g_and3 (y_and3, a, b, c);   // any number of inputs

Every primitive except not takes two or more inputs; and g4 (y, a, b, c, d); is a 4-input AND and the tool builds it as a tree if the library has no such cell. not takes exactly one.

The instance name is optional. and (cout, a, b); is legal and you will see it in generated netlists, where compactness matters more than readability. In anything you write, name the instances, because a named instance can be found in a waveform viewer.

Buffers and tri-state drivers

buf    g_buf  (buf_out, a_in);     // y = a; a drive-strength hint
bufif1 g_tri1 (y, data, en);       // y = data when en is 1, z otherwise
bufif0 g_tri0 (y, data, en);       // y = data when en is 0
notif1 g_ni1  (y, data, en);       // inverting, enabled high
notif0 g_ni0  (y, data, en);       // inverting, enabled low

The tri-state primitives drive z when disabled, which is how a shared bus works at the board level. Inside a modern FPGA there are no internal tri-state buses and a floating net is a design error; use these only to model I/O pads.

An SR latch

The cross-coupled NAND latch from section 3.1 is the circuit that structural style suits best, because the code is the schematic:

`timescale 1ns / 1ps
`default_nettype none

module sr_latch (
    input  wire s_n,    // set, active low
    input  wire r_n,    // reset, active low
    output wire q,
    output wire q_n
);
    nand g1 (q,   s_n, q_n);
    nand g2 (q_n, r_n, q);
endmodule

The wiring is circular: q feeds g2 and q_n feeds g1. The simulator handles the loop by re-evaluating until nothing changes, which is exactly the feedback that makes a latch a latch.

$\overline{S}$ $\overline{R}$ $Q$ $\overline{Q}$
1 1 hold hold memory
0 1 1 0 set
1 0 0 1 reset
0 0 1 1 forbidden

A full adder

$$sum = a \oplus b \oplus c_{in} \qquad c_{out} = ab + (a \oplus b), c_{in}$$

`timescale 1ns / 1ps
`default_nettype none

module full_adder_gates (
    input  wire a, b, cin,
    output wire sum, cout
);
    wire s1;   // a xor b
    wire c1;   // a and b
    wire c2;   // (a xor b) and cin

    xor g1 (s1,   a,  b);
    xor g2 (sum,  s1, cin);
    and g3 (c1,   a,  b);
    and g4 (c2,   s1, cin);
    or  g5 (cout, c1, c2);
endmodule

Five gates, and you can draw the schematic from the code line by line (Figure 1).

Full adder from primitives
Figure 1: The five-gate full adder, one primitive per gate

Where primitives are used

A primitive maps to one cell of the target library with no interpretation, so a structural description is useful when the circuit has to match a reference schematic exactly, when you are modelling a latch or another feedback structure, in testbench models that need # delays on individual gates, and for learning, because writing a circuit gate by gate makes you think at the level synthesis works at.

Production RTL does not use them for logic. assign sum = a + b; is one line where the gate version of a 16-bit adder is eighty, and the one line gives the tool freedom to choose a faster adder. What you will do with primitives, throughout your career, is read them: a post-synthesis netlist is a file of exactly these instantiations, usually wrapped in library cell names like AND2X1 and NAND2BX2, and an equivalence checker compares your RTL against that file gate by gate.

Practice Exercise

Build a 2-to-1 mux, $y = \overline{sel},a + sel,b$, from primitives.

Solution
`timescale 1ns / 1ps
`default_nettype none

module mux2to1 (
    input  wire a, b,
    input  wire sel,     // 0 selects a, 1 selects b
    output wire y
);
    wire sel_n, and_a, and_b;

    not g_inv  (sel_n, sel);
    and g_and0 (and_a, sel_n, a);
    and g_and1 (and_b, sel,   b);
    or  g_or   (y,     and_a, and_b);
endmodule

With sel at 0, and_a passes a and and_b is 0; with sel at 1 the other way round.

Gates are the atoms. The next article is about the molecules: instantiating modules inside modules, and the port-connection style that makes it safe.

โ† Precedence and Width Module Instantiation โ†’