XOR user-defined primitive waveform

A Look At Verilog User-Defined Primitives

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 AInput BOutput D
000
101
011
110
Truth Table for XOR

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.

XOR user-defined primitive waveform
XOR UDP Waveform

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!

Discover more from FPGA Coding

Subscribe now to keep reading and get access to the full archive.

Continue reading