Verilog Operators and the Assign Keyword for Combinational Logic Circuits

Share on facebook
Share on twitter
Share on linkedin

Table of Contents

In this post, we look at some of the basic techniques which allow us to model simple digital circuits. We will look in particular at continuous assignment using the assign keyword and the verilog operators. These are fundamental to modelling logic circuits in verilog.

There are two main classes of digital circuit which we can model in verilog – combinational and sequential

Combinational logic is the simplest of the two, consisting solely of basic logic gates, such as ANDs, ORs and NOTs. When the circuit input changes, the output changes almost immediately (there is a small delay as signals propagate through the circuit).

In contrast, sequential circuits use a clock and require storage elements such as flip flops. As a result, output changes are synchronized to the circuit clock and are not immediate.

In this post, we will consider how we design combinational logic circuits in verilog. In the next post, we will discuss the techniques we use to model basic sequential circuits.

Combinational Logic Circuits

Basic combinational logic circuits are one of the simplest elements to model in verilog.

We use two fundamental verilog concepts to model these circuits – operators and continuous assignment. We will discuss both of of these topics in more detail shortly.

However, both of these concepts are fairly intuitive if we have some previous knowledge of other programming languages.

Therefore, let’s look at a basic example to show how simple it is to model combinational logic in verilog.

To demonstrate how we do this, let’s consider a simple two input AND gate circuit as shown below.

A two input and gate

The code snippet below shows the implementation of this circuit using verilog.

assign and_out = a & b;

As we can see from this example, it is extremely simple to model basic logic gates in verilog.

Let’s take a deeper look at continuous assignment and the verilog operators.

Continuous Assignment in Verilog

In verilog, we use continuous assignment to drive data onto net types.

We can actually use two different methods to implement continuous assignment in verilog.

The first of these is known as explicit continuous assignment. This is most commonly used method for continuous assignment in verilog.

We can also use implicit continuous assignment, or net declaration assignment as it is also known. This method is less common but it can allow us to write less code.

Let’s look at both of these techniques in more detail.

Explicit Continuous Assignment

As we have already seen, we normally use the assign keyword when we want to use continuous assignment in verilog. This approach is know as explicit continuous assignment.

The verilog code below shows the general syntax for continuous assignment using the assign keyword.

assign <variable> = <value>;

The <variable> field in the code above is the name of the signal which we are assigning data to. We can only use continuous assignment to assign data to net type variables.

The <value> field can be a fixed value or we can create an expression using the operators we discuss later in this post. If we include any other signals in this expression, they can be either variable or net types.

When we use continuous assignment, the <variable> value changes whenever one of the signals in the <value> field changes state.

We have already seen an example of continuous assignment when we looked at the 2 input and gate. This code is repeated below.

assign and_out = a & b;

Whenever either the a or b signal changes states, the value of and_out is assigned a new value based on the outcome of the logical expression

Net Declaration Assignment

We can also use implicit continuous assignment in our verilog designs. This approach is also commonly known as net declaration assignment in verilog.

When we use net declaration, we can place a continuous assignment in the statement which declares our signal. This can allow us to reduce the amount of code we have to write.

To use net declaration assignment in verilog, we use the = symbol to assign a value to a signal when we declare it.

The code snippet below shows the general syntax we use for net declaration assignment.

<type> <variable> = <value>;

The variable and value fields have the same function for both explicit continuous assignment and net declaration assignment.

As an example, the verilog code below shows how we would code a 2 input and gate using net declaration assignment.

and_out = a & b;

Verilog Operators

When we design digital circuits, we normally need to process data in some way.

This processing can be extremely simple, as is the case with simple logic gates. However, we may also need to perform complex logical or mathematical operations on our data.

In any case, verilog provides us with a number of operators which allow us to perform a wide range of different calculations or operations on our data.

In most instances when we use verilog operators, we create boolean expressions or logic circuit which we want to synthesize. However, there are also some operators which we can’t use to write synthesizable code.

Let’s take a closer look at the various different types of operator which we can use in our verilog code.

Bit Wise Operators in Verilog

We use the bit wise operators to combine a number of single bit inputs into a single bit output.

We most commonly use the bit wise operators to model logic gates in verilog. Infact, we have already seen the bit wise and operator (&) in use in the example of a 2 input and gate.

The table below shows the full list of bit wise operators which we can use in verilog.

A table showing the different bit wise operators which can be used in verilog.

The verilog code below shows how we use each of these operators in practise.

// Returns the value not a
y = ~a;

// Returns the value of a and b
y = a & b;

// Returns the value of a or b
y = a | b;

// Returns the value of a nor b
y = a ~| b;

// Returns the value of a nand b
y = a ~& b;

// Returns the value of a xor b
y = a ^ b;

// returns the value of a xnor b
y = a ~| b;

To show how we use the bit wise operators to model logic gates, let’s consider a basic example. For this example, we will model the basic three input and gate shown below.

A three input and gate.

As we model logic gates using continuous assignment in verilog, we will also need to use the assign keyword in this example.

The code snippet below shows how we would implement this circuit using verilog.

assign and_out = a & b & c;

This example shows how simple it is to model logic gates using the bit wise operators. If we wanted to change the function of this logic gate, we would simply change the operator which we use.

Mixing Bit Wise Operators in Verilog

Combinatorial logic circuits almost always feature more than one type of gate. Therefore, verilog allows us to mix different bit wise operators within a single statement.

To demonstrate this concept, let’s consider a circuit featuring an and gate and an or gate. The circuit diagram below shows this circuit.

A logic circuit with the function (a and b) or c.

The code snippet below shows the implementation of this circuit using Verilog.

assign logic_out = (a & b) | c;

This code is simple to understand as it makes use of the operators we have already discussed.

However, it is important to use brackets when modelling circuits with multiple logic gates, as shown in the above example.

Not only does this ensure that the design works as intended, it also makes the intention of the code easier to understand.

Arithmetic Operators in Verilog

We use arithmetic operators to perform basic mathematic functions on our variables. These operators should already be familiar as they are mostly replications of common mathematic symbols.

These operators require some consideration when we use them with synthesizable code.

The plus, minus and multiplication operators can all be synthesised by most modern tools.

However, this can often result in sub-optimal logical performance. As a result, it can be necessary to design logic circuits which specifically perform these functions. Alternatively, we may wish to use DSP blocks within our FPGA to perform these operations more efficiently.

We should never use the modulus or divide operators for synthesizable code as most tools will be unable to handle them.

The table below shows the full list of arithmetic operators in Verilog.

A table showing the different arithmetic operators which can be used in verilog and system verilog

The code snippet below shows how we use each of these operators in practise.

// Returns the value of a plus b
y = a + b;

// Returns the value of a minus b
y = a - b;

// Returns the value of a multiplied by b
y = a * b;

// Returns the value of a divided by b
y = a / b;

// Returns the modulus of a divided by b
y = a % b;

Relational Operators in Verilog

We use relational operators to compare the value of two different variables in verilog. The result of this comparison returns either logical 1 or 0, representing true and false respectively.

These operators are similar to what we would see in other programming languages such as C or Java.

Most of these operators are also commonly used in basic mathematics expressions so they should already feel familiar.

The table below shows the full list of relational operators in Verilog.

A table showing the different relational operators which can be used in verilog and system verilog

The verilog code below shows how we use each of the relational operators in practise.

// 1 if a is greater than b
y = a > b;

// 1 if a is greater than or equal to b
y = a >= b; 

// 1 if a is less than b
y = a < b;  

// 1 if a is less than or equal to b
y = a <= b; 

// 1 if a is equal to b
y = a == b; 

// 1 if a is not equal to b
y = a != b;

Logical Operators in Verilog

The verilog logical operators are similar to the bit-wise operators we have already seen.

However, rather than using these operators to model gates we use them to combine relational operators. As a result, we can build more complex expressions which can perform multiple comparisons.

As with relational operators, these expressions return either a 1 (true) or 0 (false).

There are only three logical operators which we can use in verilog. Again, these are similar to operators which are used in languages such as C or Java.

The table below shows the full list of logical operators in Verilog.

A table showing the different logical operators which can be used in verilog and system verilog

The verilog code below shows how we use each of the logical operators in practise.

Again, it is important that we use parentheses to separate the different elements in our expressions when using these operators.

// Returns 1 if a is greater than b but less than c
y = (a > b) &amp;&amp; (a < c);

// Returns 1 if a is greater than b or less than c
y = (a > b) || (a < c);

// Returns 1 if a is not great than b
y = !(a > b);

Other Operators

In addition to the operators we have already seen, there are a few extra operators which we can use for specific logical functions.

The table below shows the full list of special operators which are available in verilog.

A table showing the verilog shift operators and the conditional operator.

When designing digital circuits, we frequently make use of shift operations. As a result, verilog provides us with a simple technique for implementing these functions.

The shift operator actually requires two arguments. The first of these is the name of the signal which we want to shift. The second argument is the number of bits we want to shift.

After the signal has been shifted by the required number of bits, the blank positions are filled with 0.

The code snippet below shows how we use the shift operators in practise.

// Shift the a signal left by 3 bits
a = a << 3;

// Shift the b signal right by 8 bits
b = b >> 8;

We typically use the conditional operator to model the behaviour of multiplexors in Verilog.

This operator may already be familiar as it is also used in the C programming languages. However, we discuss its use in more detail in the following section.

Modelling Multiplexors in Verilog

Multiplexors are another component which are commonly used in combinational digital circuits.

In verilog, there are a number of ways we can model these components.

One of these methods uses a construct known as an always block. We normally use this when we are modelling sequential logic circuits, which is the topic of the next post in this series. Therefore, we will look at this approach in more detail the next blog post.

In the rest of this post, we will look at the other methods we can use to model multiplexors.

Verilog Conditional Operator

As we previously saw, there is a conditional operator which we can use in Verilog. This functions in the same way as the conditional operator in the C programming language.

To use the conditional operator, we write a logical expression before the ? operator which is then evaluated to see if it is true or false.

The output is assigned to one of two values depending on whether the expression is true or false.

The verilog code below shows the general syntax which the conditional operator uses. From this example, it is clear how we can create a basic two to one multiplexor using this operator.

output = <expression> ? <value if true> : <value if false>;

Nested Conditional Operators

Although this is not common, we can also write code to build larger multiplexors by nesting conditional operators.

To show how this is done, let’s consider a basic 4 to 1 multiplexor as shown in the circuit below.

A four input multiplexor

To model this in verilog using the conditional operator, we must consider the multiplexor to be a pair of two input multiplexors.

This means one multiplexor will select between inputs A and B whilst the other selects between C and D. Both of these multiplexors use the LSB of the address signal as the address pin.

assign mux1 = addr[0] ? b : a;
assign mux2 = addr[0] ? d : c;

To create the full four input multiplexor, we would then need another multiplexor.

This takes the outputs from the first two multiplexors and uses the MSB of the address signal to select between them.

The code snippet below shows the simplest way to do this. This code uses the signals mux1 and mux2 which we defined in the last example.

assign q = addr[1] ? mux2 : mux1;

However, we could easily remove the mux1 and mux2 signals from this code and instead use nested conditional operators.

This reduces the amount of could that we would have to write without affecting the functionality.

The code snippet below shows how we would write this.

assign q = addr[1] ? (addr[0] ? d : c) : (addr[0] ? b : a);

As we can see from this example, when we use conditional operators to model multiplexors in verilog, the code can quickly become difficult to understand. Therefore, we should only use this method to model small multiplexors.

Arrays as Multiplexors

It is also possible for us to use verilog arrays to build simple multiplexors.

To do this we combine all of the multiplexor inputs into a single array type and use the address to point at an element.

To get a better idea of how this works in practise, let’s consider a basic four to one multiplexor as an example.

The first thing we must do is combine our input signals into an array. There are two ways in which we can do this.

Firstly, we can declare an array and then assign all of the individual bits, as shown in the verilog code below.

// Assigning individual bits in the vector
assign in_vec[0] = a;
assign in_vec[1] = b;
assign in_vec[2] = c;
assign in_vec[3] = d;

Alternatively we can use array literals, which allows us to assign the entire array in one line of code.

To do this, we use a pair of curly braces – { } – and list the elements we wish to include in the array inside of them.

When we use array literals we can also declare and assign the variable in one statement, as long as we use a net type.

The verilog code below shows how we can use array literals to populate an array.

// Using vector assignment
assign in_vec = {d, c, b, a};

// Declare and assign the vector in one line
wire [3:0] in_vec = {d, c, b, a};

As verilog is a loosely typed language, we can use the two bit addr signal as if it were an integer type. This signal then acts as a pointer that determines which of the four elements to select.

The code snippet below demonstrates this method in practise. As the mux output is a wire, we must use continuous assignment in this instance.

assign mux_out = in_vec[addr];

Exercises

What is the difference between implicit and explicit continuous assignment?

show answer

When we use implicit continuous assignment we assign the variable a value when we declare. When we use explicit continuous assignment we use the assign keyword to assign a value.

hide answer

Which type of operators do we use to model logic gates in verilog?

show answer

We use the bit wise operators to model logic gates in verilog.

hide answer

Two of the arithmetic operators should not be used with synthesizable code – name them.

show answer

The division and modulus operators can’t be synthesized.

hide answer

What is the difference between the bit wise and logical operators?

show answer

The bit wise operators work on individual bits whereas the logical operators are used to combine logical expressions.

hide answer

Which operators do use to model shift registers in verilog.

show answer

We use the two shift operators – << for left shifts and >> for right shifts.

hide answer

Write the code for a 2 to 1 multiplexor using any of the methods discussed we discussed.

show answer
// Using conditional operator
assign mux_out = addr ? b : a;
 
// Using an array
wire in_arr [1:0] = {a, b} 
assign mux_out = in_arr[addr];
hide answer

Write the code for circuit below using both implicit and explicit continuous assignment.

show answer
// Using explicit continuous assignment
wire logic_out;
assign logic_out = (a | b) & c;
 
// Using implicit continuous assignment
wire logic_out = (a | b) & c;
hide answer

Enjoyed this post? Why not share it with others

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

ENJOYING THIS ARTICLE?

Why not join our mailing list and be the first to hear about our latest FPGA tutorials

Close