Verilog's built-in primitives, such as and, nand and not, cover the basic gates, but they are too limited for more complex cells like a multiplexer or a flip-flop. Verilog lets you define your own primitives, called User Defined Primitives (UDPs), which describe combinational or sequential behavior with a truth table instead of gates or procedural code.
12 min read | Beginner Level
What You'll Learn
- The rules for declaring a UDP and the symbols used in its table
- How to write a combinational UDP, using a 2:1 mux as the example
- How to write level-sensitive and edge-sensitive sequential UDPs, such as a latch and a flip-flop
- Where UDPs are used in practice, and the common mistakes when writing their tables
A UDP has a few rules that make it different from a module:
- It is declared with
primitive ... endprimitive, outside of any module. - It has exactly one output, which must be the first port in the port list, and can have many inputs. Simulators must support at least 9 inputs for sequential UDPs and 10 for combinational ones.
- All ports are 1-bit scalars. Vectors and
inoutports are not allowed. - The output can only be 0, 1 or x, never z. A z on an input is treated as x.
- The behavior is described in a state table between
tableandendtable. Any input combination that is not listed in the table produces x.
Verilog UDP Symbols
The table is written with the following symbols:
| Symbol | Comments |
|---|---|
| 0 | Logic 0 |
| 1 | Logic 1 |
| x | Unknown, can be either logic 0 or 1. Can be used as input/output or current state of sequential UDPs |
| ? | Logic 0, 1 or x. Cannot be output of any UDP |
| - | No change, only allowed in output of a UDP |
| ab | Change in value from a to b where a or b is either 0, 1, or x |
| * | Same as ??, indicates any change in input value |
| r | Same as 01 -> rising edge on input |
| f | Same as 10 -> falling edge on input |
| p | Potential positive edge on input; either 0->1, 0->x, or x->1 |
| n | Potential falling edge on input; either 1->0, x->0, 1->x |
A transition is written in parentheses, such as (01) or (0?). The symbols -, transitions and edge letters can only be used in sequential UDPs.
Combinational UDP Example
In a combinational UDP, each row of the table lists the values of the inputs, in the same order as in the port list, followed by a colon and the output value. The 2:1 multiplexer below selects a when sel is 0 and b when sel is 1. The ? marks the input that is not selected, because its value does not affect the output.
// Output should always be the first signal in port list
primitive mux (out, sel, a, b);
output out;
input sel, a, b;
table
// sel a b out
0 1 ? : 1;
0 0 ? : 0;
1 ? 0 : 0;
1 ? 1 : 1;
x 0 0 : 0;
x 1 1 : 1;
endtable
endprimitive
The last two rows handle an unknown select: if a and b are equal, the output is known even though sel is not. Any combination missing from the table, such as sel = x with a = 0 and b = 1, gives x.
A UDP is instantiated like a gate primitive. Connections are by position only; named port connections such as .out(out) are not allowed.
module tb;
reg sel, a, b;
reg [2:0] dly;
wire out;
integer i;
// Instantiate the UDP - note that UDPs cannot
// be instantiated with port name connection
mux u_mux ( out, sel, a, b);
initial begin
a <= 0;
b <= 0;
$monitor("[T=%0t] a=%0b b=%0b sel=%0b out=%0b", $time, a, b, sel, out);
// Drive a, b, and sel after different random delays
for (i = 0; i < 10; i = i + 1) begin
dly = $random;
#(dly) a <= $random;
dly = $random;
#(dly) b <= $random;
dly = $random;
#(dly) sel <= $random;
end
end
endmodule
xcelium> run [T=0] a=0 b=0 sel=x out=0 [T=4] a=1 b=0 sel=x out=x [T=5] a=1 b=1 sel=x out=1 [T=10] a=1 b=1 sel=1 out=1 [T=15] a=0 b=1 sel=1 out=1 [T=28] a=0 b=0 sel=1 out=0 [T=33] a=0 b=0 sel=0 out=0 [T=38] a=1 b=0 sel=0 out=1 [T=40] a=1 b=1 sel=0 out=1 [T=51] a=1 b=1 sel=1 out=1 [T=54] a=0 b=0 sel=1 out=0 [T=62] a=1 b=0 sel=1 out=0 [T=67] a=1 b=1 sel=1 out=1 [T=72] a=0 b=1 sel=1 out=1 [T=80] a=0 b=1 sel=0 out=0 [T=84] a=0 b=0 sel=0 out=0 [T=85] a=1 b=0 sel=0 out=1 xmsim: *W,RNQUIE: Simulation is complete.
The first three lines show the x handling. sel is not assigned until T=10, so it is x. At T=0 both data inputs are 0, the row x 0 0 : 0 matches, and the output is 0. At T=4, a and b differ, no row matches, and the output becomes x. At T=5 they are equal again, and the output is 1. From T=10 on, out follows b whenever sel is 1 and a whenever sel is 0.

Quick Check
If the row x 1 1 : 1; were removed from the table, what would the output be at T=5 in the log?
Show Answer
x. With sel = x, a = 1 and b = 1, no row would match, and an unlisted combination always produces x.
Sequential UDP Example
A sequential UDP has a memory: its output depends on the inputs and on the current value of the output. Its table therefore has an extra field for the current state, between the inputs and the next output:
// inputs : current state : next state
The output of a sequential UDP must also be declared as a reg. Its starting value can be set with an initial statement inside the primitive, such as initial q = 0;; without it, the output starts as x. Sequential UDPs are either level-sensitive or edge-sensitive.
Level-Sensitive UDPs
A level-sensitive UDP reacts to the current values of its inputs, like a latch. In the D latch below, q follows d while clk is 1. While clk is 0, the - in the output column means that q keeps its current value.
primitive d_latch (q, clk, d);
output q;
input clk, d;
reg q;
table
// clk d q q+
1 1 : ? : 1;
1 0 : ? : 0;
0 ? : ? : -;
endtable
endprimitive
module tb;
reg clk, d;
reg [1:0] dly;
wire q;
integer i;
d_latch u_latch (q, clk, d);
always #10 clk = ~clk;
initial begin
clk = 0;
$monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);
#10; // To see the effect of X
for (i = 0; i < 50; i = i+1) begin
dly = $random;
#(dly) d <= $random;
end
#20 $finish;
end
endmodule
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=1 [T=13] clk=1 d=0 q=0 [T=14] clk=1 d=1 q=1 [T=17] clk=1 d=0 q=0 [T=20] clk=0 d=1 q=0 [T=28] clk=0 d=0 q=0 [T=30] clk=1 d=1 q=1 [T=38] clk=1 d=0 q=0 [T=39] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=42] clk=0 d=0 q=1 [T=47] clk=0 d=1 q=1 [T=50] clk=1 d=0 q=0 [T=55] clk=1 d=1 q=1 [T=59] clk=1 d=0 q=0 [T=60] clk=0 d=0 q=0 [T=61] clk=0 d=1 q=0 [T=64] clk=0 d=0 q=0 [T=67] clk=0 d=1 q=0 [T=70] clk=1 d=0 q=0 [T=73] clk=1 d=1 q=1 [T=74] clk=1 d=0 q=0 [T=77] clk=1 d=1 q=1 [T=79] clk=1 d=0 q=0 [T=80] clk=0 d=0 q=0 [T=84] clk=0 d=1 q=0 [T=86] clk=0 d=0 q=0 [T=87] clk=0 d=1 q=0 [T=90] clk=1 d=1 q=1 [T=91] clk=1 d=0 q=0 [T=100] clk=0 d=0 q=0 [T=110] clk=1 d=0 q=0 Simulation complete via $finish(1) at time 111 NS + 0
While clk is 1, every change of d appears on q immediately, for example at T=13, 14 and 17. While clk is 0, from T=40 to T=50, d changes but q holds 1. At T=20, clk falls in the same time step in which d changes to 1; the clock change is processed first, so the latch is already closed and q keeps 0.

Edge-Sensitive UDPs
An edge-sensitive UDP reacts to a transition on an input, written as (ab) or with an edge letter. The D flip-flop below loads d on the rising edge of clk and ignores everything else. Each row can contain at most one transition.
primitive d_flop (q, clk, d);
output q;
input clk, d;
reg q;
table
// clk d q q+
// obtain output on rising edge of clk
(01) 0 : ? : 0;
(01) 1 : ? : 1;
(0?) 1 : 1 : 1;
(0?) 0 : 0 : 0;
// ignore negative edge of clk
(?0) ? : ? : -;
// ignore data changes on steady clk
? (??) : ? : -;
endtable
endprimitive
The rows (0?) 1 : 1 : 1 and (0?) 0 : 0 : 0 reduce x pessimism: if clk goes from 0 to x, it may or may not be a rising edge, but when d already equals q, the result is the same either way, so q can stay known instead of becoming x.
module tb;
reg clk, d;
reg [1:0] dly;
wire q;
integer i;
d_flop u_flop (q, clk, d);
always #10 clk = ~clk;
initial begin
clk = 0;
$monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);
#10; // To see the effect of X
for (i = 0; i < 20; i = i+1) begin
dly = $random;
repeat(dly) @(posedge clk);
d <= $random;
end
#20 $finish;
end
endmodule
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=x [T=20] clk=0 d=1 q=x [T=30] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=50] clk=1 d=1 q=1 [T=60] clk=0 d=1 q=1 [T=70] clk=1 d=0 q=1 [T=80] clk=0 d=0 q=1 [T=90] clk=1 d=1 q=0 [T=100] clk=0 d=1 q=0 [T=110] clk=1 d=1 q=1 [T=120] clk=0 d=1 q=1 [T=130] clk=1 d=1 q=1 [T=140] clk=0 d=1 q=1 [T=150] clk=1 d=0 q=1 [T=160] clk=0 d=0 q=1 [T=170] clk=1 d=0 q=0 [T=180] clk=0 d=0 q=0 [T=190] clk=1 d=0 q=0 [T=200] clk=0 d=0 q=0 [T=210] clk=1 d=1 q=0 [T=220] clk=0 d=1 q=0 [T=230] clk=1 d=1 q=1 [T=240] clk=0 d=1 q=1 [T=250] clk=1 d=1 q=1 [T=260] clk=0 d=1 q=1 [T=270] clk=1 d=1 q=1 [T=280] clk=0 d=1 q=1 [T=290] clk=1 d=1 q=1 [T=300] clk=0 d=1 q=1 [T=310] clk=1 d=1 q=1 [T=320] clk=0 d=1 q=1 [T=330] clk=1 d=1 q=1 [T=340] clk=0 d=1 q=1 [T=350] clk=1 d=1 q=1 [T=360] clk=0 d=1 q=1 [T=370] clk=1 d=0 q=1 [T=380] clk=0 d=0 q=1 [T=390] clk=1 d=0 q=0 [T=400] clk=0 d=0 q=0 [T=410] clk=1 d=1 q=0 [T=420] clk=0 d=1 q=0 [T=430] clk=1 d=1 q=1 [T=440] clk=0 d=1 q=1 [T=450] clk=1 d=1 q=1 [T=460] clk=0 d=1 q=1 [T=470] clk=1 d=1 q=1 [T=480] clk=0 d=1 q=1 Simulation complete via $finish(1) at time 490 NS + 0
The testbench changes d with a nonblocking assignment right after a rising edge, so the flip-flop always samples the old value of d, and q follows d one clock cycle later. For example, d becomes 0 at T=70, and q becomes 0 at the next rising edge, T=90. At T=10, d was still x when the clock rose, no row matched, and q stayed x until the edge at T=30.

When a table contains both level-sensitive and edge-sensitive rows, the level-sensitive rows take priority. This is how an asynchronous reset is added to a flip-flop: a row such as 0 ? ? : ? : 0; for an active-low reset input overrides any clock edge.
Synthesis result: Most synthesis tools do not accept UDPs. A UDP describes behavior as a table for simulation, and synthesis needs RTL or library cells instead. Write synthesizable designs with always blocks and assign, which infer the same mux, latch and flip-flop.
Quick Check
In the flip-flop log, d changes to 1 at T=410. At which time does q become 1, and why not at T=410?
Show Answer
At T=430. The change of d at T=410 is a nonblocking assignment that happens after the rising edge at T=410 has already been evaluated, so the flip-flop sees it at the next rising edge. Between edges, the row ? (??) : ? : - ignores the change.
Standard Cell Simulation Models
The Verilog models of standard cell libraries, used for gate-level simulation of a synthesized netlist, are one of the main places UDPs are still used. A flip-flop cell with scan, set and reset is described as a UDP so that it simulates fast and handles x and z values in a well-defined, carefully reduced way. The cell's timing is then added with a specify block and back-annotated delays.
Controlling X Pessimism
During reset or power-up, many signals are x. A model that makes everything x as soon as any input is x can hide real behavior and cause false failures. UDP tables let library writers add rows like x 1 1 : 1 in the mux, which keep the output known whenever the result does not depend on the unknown input.
Common Beginner Mistakes
Mistake #1: Not putting the output first
Wrong: primitive mux (sel, a, b, out);
Right: primitive mux (out, sel, a, b);
Why? The first port of a UDP is always its single output. The table columns then follow the order of the remaining ports, so the order of the port list and the table must match.
Mistake #2: Leaving combinations out of the table by accident
Wrong: A mux table without the x rows, expecting the output to stay known while sel is x
Right: List every combination whose output is known, including those with x inputs
Why? Any input combination that is not in the table gives x. There is no warning, so a missing row only shows up as unexpected x values in simulation.
Mistake #3: Conflicting rows
Wrong: Two rows such as 1 ? 0 : 0; and 1 1 ? : 1;, which both match sel = 1, a = 1, b = 0 with different outputs
Right: Make sure each input combination leads to only one output
Why? The compiler reports an error for rows that overlap with different outputs. Overlapping rows with the same output are allowed.
Mistake #4: Using a vector port or z output
Wrong: input [1:0] sel; or a table row with z as output
Right: Use one scalar input per bit, and only 0, 1, x or - as outputs
Why? UDP ports are always 1-bit scalars, and a UDP cannot drive z. For a 4:1 mux with a 2-bit select, declare sel1 and sel0 as two separate inputs.
Practice Exercise
Challenge: Toggle Flip-Flop UDP
Write an edge-sensitive UDP t_flop (q, clk, t) that toggles q on a rising edge of clk when t is 1, keeps q when t is 0, ignores falling clock edges and changes of t, and starts at 0.
Hint
The next value depends on the current value of q, so the toggle needs two rows: one for q = 0 and one for q = 1. Use initial q = 0; for the starting value.
Solution
primitive t_flop (q, clk, t);
output q;
input clk, t;
reg q;
initial q = 0;
table
// clk t q q+
(01) 0 : ? : -; // Hold
(01) 1 : 0 : 1; // Toggle 0 -> 1
(01) 1 : 1 : 0; // Toggle 1 -> 0
(?0) ? : ? : -; // Ignore falling edge
? (??) : ? : -; // Ignore changes of t
endtable
endprimitive
With t held at 1, q goes 0, 1, 0, 1 on successive rising edges, which divides the clock frequency by 2. If q were x, the row for t = 1 would not match and q would stay x, which is why the initial statement matters.
Summary
In this tutorial, you learned:
- A UDP is declared with
primitive ... endprimitiveoutside modules, has one scalar output first in the port list, and any number of scalar inputs - Its behavior is a table; combinations that are not listed produce x, and the output can never be z
- Combinational UDP rows map inputs to an output, and
?marks inputs that do not matter - Sequential UDPs declare the output as
reg, add a current-state column, and use-for "no change" - Level-sensitive UDPs model latches, edge-sensitive UDPs use transitions such as
(01)to model flip-flops, and level rows take priority over edge rows - UDPs are mainly used in cell library simulation models and are generally not synthesizable