For this post I thought it might be interesting to take a look at Verilog user-defined primitives (UDPs). Recall that some time ago I built an Exclusive Or circuit (XOR) using the built-in Verilog operator. I’ll build the same circuit using Verilog user-defined primitives.
A Verilog UDP allows you to specify a circuit by defining its truth table. Verilog case statements are more flexible and as such seem to be preferred by most over UDPs. Nevertheless, I can imagine situations where defining a simple circuit by its truth table would be convenient. So, let’s begin to explore how UDPs work.
Exclusive Or
First, let’s recall how the XOR operation works. The behavior of XOR is defined by the following truth table.
Input A | Input B | Output D |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
Then, rather than the familiar module / endmodule keywords, you declare a UDP via the keywords primitive / endprimitive. For reasons that will become clear later, I name the new primitive xor_gate which is the same name as the circuit from the earlier post.
You declare signals (or ports) for primitives in the same way as for modules. As with the earlier XOR circuit, I declare the inputs to be a and b and the output to be d. Note that with UDPs the output must come first in the list of port declarations. While this ordering requirement may seem strange, I suspect it is likely a remnant from much earlier versions of Verilog.
Note that with UDPs the output must come first in the list of port declarations.
To specify the body of a UDP you use the table / endtable keywords. For each input combination you must include a row in the table which provides the inputs and the resulting output. With that background information, creating the XOR primitive becomes relatively straightforward. The code for the primitive is shown below.
primitive xor_gate(output d, input a, input b);
table
// a b : d
0 0 : 0;
1 0 : 1;
0 1 : 1;
1 1 : 0;
endtable
endprimitive
Testing the Primitive
Instantiating a primitive looks just like instantiating a module. And since I named the primitive xor_gate, I can use the test code from the original XOR project to the new primitive. That code is repeated below.
// test-bench for the XOR user-defined primitive
module xor_test();
localparam T=20;
reg clk, a, b;
wire d;
// instantiate our primitive
xor_gate gate(.a(a), .b(b), .d(d));
// generate clock signal
always
begin
clk = 1'b1;
#(T/2);
clk = 1'b0;
#(T/2);
end
// generate our test inputs
initial
begin
@(negedge clk);
a = 1'b0;
b = 1'b0;
#(T);
@(negedge clk);
a = 1'b1;
b = 1'b0;
#(T);
@(negedge clk);
a = 1'b0;
b = 1'b1;
#(T);
@(negedge clk);
a = 1'b1;
b = 1'b1;
#(10*T);
$stop;
end
endmodule
Now, when I run a simulation, I observe the waveform shown below. Looking at the timeline starting at 20ns, you should see that we match the four input and output combinations defined by the XOR truth table.
And with that, we’ve finished our first look at Verilog user-defined primitives. As always, the source code is available on github. If you have feedback, questions or suggestions, please leave me a comment!