View Course Path

Verilog Design Units – Data types and Syntax in Verilog

A Hardware Description Language, abbreviated as HDL, is a language used to describe a digital system. For example, a network switch, a microprocessor, or a memory, or a simple flip-flop. This means that, by using an HDL, one can describe any (digital) hardware at any level. VHDL is an HDL. And so is Verilog.

In this article, we will first discuss some basic constructs and syntax in Verilog, which provide the necessary framework for Verilog programming. Then we learn about the different data types available in Verilog. Then, we move on to learn about the module and port declarations from the Verilog point of view. These are some important topics to know about for Verilog coding purposes.

Keywords

Have you noticed some words turn red in Vivado GUI? Those are reserved words in Verilog known as keywords. We can use them to define language constructs.

Verilog provides us with a set of keywords. These keywords have a predefined purpose that is understood by Verilog compilers across the board. For example, for defining a module, we use the keyword module. Whenever you use that keyword, the compiler expects you to define a module.

gotyoufam
Keywords are like a comfortable language element that you use to communicate with the compiler, and the compiler winks at you and mouths, “I got you fam.”

Always write keywords using lowercase letters. Why? Unlike VHDL, Verilog is a case sensitive language.  For example:

input wire // wire is a keyword
input WIRE // WIRE is not a keyword

We can see some commonly used keywords in Verilog from the table below:

always else input not
and end integer or
assign endmodule module output
begin for nand parameter
case If nor real
posedge negedge forever repeat
reg wire endcase initial

Identifiers

An identifier is a unique name, which identifies an object. They are case-sensitive made up of alphanumeric characters, underscore, or a dollar sign.

We can start identifiers using alphanumeric or underscore. It’s not possible to name identifiers beginning with a dollar sign since it is reserved for naming system tasks. Also starting with numbers is not advisable. Let’s see how we can use identifiers in Verilog:

reg value //value is an identifier
input CLK // CLK is an identifier
input $time //not an identifier but a system task
output out1 //out1 is an identifier
output 123abc // invalid identifier:-starting with numbers

Another type of identifier that exists is the escape identifier. Escaped identifiers start with a backslash and end with white space (i.e., space, tab, newline).

Escaped identifiers can contain any printable characters. The backslash and white space are not part of the identifier. For example:

/a+b
/**my_name**

Number Specifications

We can use two types of number specification in Verilog:

Sized numbers

The format for writing sized number is:

<size>’base format><number>

The size specifies the number of bits in the number. It is written in decimal only.

The base format is used for representing which base we use to represent our number. Legal base formats are binary(‘B or ‘b), decimal(‘d or ‘D), octal(‘O or ‘o) or hexadecimal(‘h or ‘H).

The number is specified as consecutive digits from 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f. Only a subset of these digits is legal for each base. Let’s see the legal digits for each base.

BASE LEGAL DIGITS
Binary 0,1
Decimal 0,1,2,3,4,5,6,7,8,9
Hexadecimal 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
octal 0,1,2,3,4,5,6,7

Let’s see how we can specify sized numbers in Verilog:

4'b1111 // 4-digit binary number
12'habc // 12-bit hexadecimal number
16'd255 //16-bit decimal number
16'hDEF //16-bit haxadecimal number. Uppercase legal for number specification

Unsized number

Numbers without <size> have a default size of 32 bits. The default size differs depending on the machine and simulator. When no base is specified, it is decimal by default. For example:

23456 //32-bit decimal number by default
'h5c //32-bit hexadecimal number 
'o21 //32-bit octal number

It is recommended to use sized numbers. As a designer, we always thrive to use our memory allocation efficiently to build efficient hardware.

Isn’t it better to write 5'b10101 than 'b10101?

The unsized format will take up 32-bit of memory, which is a total waste in this case. Hence, we prefer sized number specifications.

X or Z values

For representing ‘unknown’ and ‘high impedance,’ Verilog uses x and z. These are important for modeling real circuits. An x or z sets four bits in the hexadecimal base, three bits in the octal base, and one bit in the binary base. For example:

12'h13x //12-bit hex number; 4 least significant bits unknown
6'hx //6-bit hex number
32'bz// 32 bit high impedance number

Comments

For describing or documenting a model, we use comments. This part of the code will be skipped during execution. For example:

c = a+b // values of a and b are added and result is stored in c

We can write comments in two ways. When we want to skip one full line during execution, we specify the comment using //. Verilog jumps from that point to the end of the line.

To write multiline comments, we start with /* and end it with */.

We can see how comments are used in Verilog:

//This is a one-line comment

/* this is a 
multi-line comment */

/*this is /*not a */ legal comment */
//Note:- Multi-line comments cannot be nested

/*this is a // legal comment */
//one-line comments can be embedded in multi-line comments

That’s all about some basic conventions and elementary syntax in Verilog. Now, let’s see what the different data types available in Verilog are.

Data types

Data types in Verilog inform the compiler whether to act as a transmission line (like a wire) or store data (like a flip flop). This depends on what group of data type is declared. There are two groups: NET and REGISTER. Let’s discuss them.

Net

Recall that the entire purpose of an HDL like Verilog is to describe circuits. So physical circuit elements that you see need to be describable by the HDL.

A Net represents connections between hardware elements. Nets don’t store values. They have the values of the drivers.

Examples of net using AND gate
Examples of net using AND gate

For instance, in the figure, the net out connected to the output is driven by the driving output value A&B.

A net data type must be used when a signal is:

Types of Nets

Wire

The keyword wire is the most commonly used net in modeling circuits. When used in the code, it exhibits the same property as an electrical wire used for making connections. Let’s see how we can declare wire by describing a simple AND gate in Verilog:

module and_gate;

input wire A,B;
output wire C;

assign C = A & B;

endmodule

Tri

This is another type of net that has identical syntax and function as wire.

Then, what’s the difference?

A wire net can be used for nets that are driven by a single gate or continuous assignment. Whereas, the tri net can be used where multiple drivers drive a net.

Let’s see an example where tri is declared:

2:1 mux using tristate buffer
2:1 mux using tristate buffers

The above diagram shows a 2:1 mux constructed using tri-state buffers. Let’s see how we can write the Verilog code for the above circuit.

module 2:1_mux(out, a,b, control);

output out;
input a,b,control;
tri out;
wire a,b,control;

bufif0 b1(out, a, control); //b1 drives a when control = 0;z otherwise 
bufif1 b2(out, b, control); //b2 drives b when control = 1;z otherwise 

endmodule

Have you noticed something?

We have declared out as tri because it is driven by multiple drivers as per the logic circuit.

We have instantiated tri-state buffers bufif1 and bufif0 to implement the functionality of the MUX.

The net driven by b1 and b2 works in a complementary manner. When b1 drives a, b2 is tristated; when b2 drives a, b1 is tristated. Interesting, isn’t it?

Are you hearing about the tri-state buffer for the first time? Don’t worry. Here we go.

What are tri-state buffers?

Do you know what a buffer is?

It is a gate which functions exactly opposite to the NOT gate. That is, it transfers the signal to the output from the input exactly as it is. These buffers are used to implement delays in circuits. The logic symbol of a buffer gate is given below:

 

buffer
buffer gate

We can instantiate buffer using gate primitives:

buf(out,in)

When we add a control signal to a buffer, we get tri-state buffers. These gates will propagate only if their control signal is asserted. If the control signal is de-asserted, they propagate z(high impedance, tri-state).

These gates are used when a signal is to be driven only when the control signal is asserted. Such a situation is applicable when multiple drivers drive the signal.

There are two types of the tristate buffer:

tristate buffer with an active-low control signal(bufif0): It will propagate the input to the output only if the control signal is asserted as 0. Else it propagates z.

tristate buffer with an active-high control signal(bufif1): It will propagate the input to the output only if the control signal is asserted as 1. Else it propagates z.

The logic symbols of bufif0 and bufif1 are shown below

bufif0
bufif0

 

bufif1
bufif1

For declaring tri-state buffers, Verilog gate primitives are available as shown:

bufif0(out, in, ctrl);
bufif1(out, in, ctrl);

supply0 and supply1

In every circuit we build, there are two crucial elements – power supply and ground. Hence, we use supply0 and supply1 nets to represent these two. The supply1 is used to model power supply. Whereas, the supply0 is for modeling ground.

These nets have constant logic values and strength level supply, which is the strongest of all strength levels.

Let’s see how we can use them:

supply1 Vcc; // all nets connected to Vcc are connected to power supply
supply0 GND; // all nets connected to GND are connected to ground

Variable Datatypes

The variable datatypes are responsible for the storage of values in the design. It is used for the variables, whose values are assigned inside the always block. Also, the input port can not be defined as a variable group. reg and integer are examples of the variable datatypes. For designing purposes, we commonly use reg.

Registers

Isn’t that hardware registers made from flip flops? No.

In Verilog, the term register means a variable that can hold value. It can retain value until another value is placed. Unlike a net, a register does not need a driver. It doesn’t need a clock as hardware registers do.

Register datatype is commonly declared using the keyword reg. Let’s see how we can declare reg in Verilog:

reg reset // declare a variable reset that can hold it's value

initial 
begin 
reset = 1'b1; //initialize the reset variable to 1 to reset the digital circuit
#10 reset = 1'b0; //After 10 time units, reset is deasserted.
end

Integer, Real and Time Register Data Types

Integer

An integer is a general-purpose register data type used for manipulating quantities. The integer register is a 32-bit wide data type. So what makes it different from reg? The reg store values as unsigned quantities, whereas integer stores signed values.

To declare an integer in Verilog, we use the keyword integer. For example:

integer counter; //general purpose variable used as a counter;
initial
counter = -1 // A negative one is stored in the counter.

Real

The real register is a 64-bit wide data type. In order to store decimal notation(eg 3.14) or scientific notation(eg: 3e6 which is 3 X 106 ), we use the keyword real. Real numbers cannot have a range declaration, and their default value is 0.

Let’s see how we can declare them Verilog:

real delta; // define a real variable called delta

initial
begin
delta = 4e10; //assigned scientific notation
delta = 2.13 //assigned a decimal value of 2.13
end

If a real value is assigned to an integer, it gets rounded off to the nearest integer. For example:

integer i;

initial
begin

i = delta // i gets the value 2 (rounded value of 2.13);
end

Time registers

Since timing is an essential factor in designing digital circuits, we need something to keep track of it. Hence, the keyword time helps us to record the simulation time. The default size of time-register is implementation-specific but should be at least 64 bit.

To get the current simulation time, the $time system function is invoked.

Let’s see how:

time save_sim_time; //Define a time variable save_sim_time
initial
begin

save_sim_time = $time; // Save the current simulation time. 
end

The realtime register is also a time register which will record current simulation time. For example

realtime save_sim_time; //Defining the variable

initial
begin
save_sim_time = $realtime // The system task $realtime will invode real value of the current simulation time
end

Scalar and Vector datatypes

1-bit is declared as a scalar datatype. It has only one bit. When we declare a wire or reg as a scalar, we write them as:

wire n;
reg d1;

Vector data types are multi-bit. We either use [<MSB bit number> : <LSB bit number>] or [<LSB bit number> : <MSB bit position>] to represent them. For example, we can declare a 4-bit wire or reg as:

wire [0:3] a;
reg [3:0] d0;

Want to see how wire and reg in scalar data type look like?

scalar and Vector data type representation in Verilog
Scalar and Vector Data type representation in Verilog

We have now learned about data types in Verilog. Now we move on to learn about the most important topics in Verilog; the module. We’ll learn how to declare it and what are the essential components associated with it.

Module declaration

The module forms the building block of a Verilog design. A module definition always starts with the keyword module and ends with endmodule. There are five components within a module. They are:

components in Verilog module
Components in the Verilog module

All these components except the module, module_name, and endmodule can be mixed and matched as per design needs.

To get a better understanding, we can see the Verilog code of 2:1 Multiplexer:

//This code is used to illustrate different components in Verilog

//module and port list
//mux_21 module
module mux_21(s,a,b,out)

//port declarations
input s,a,b;
output out;

//using assign statement and conditional operator in dataflow modeling
assign s? a:b;

//endmodule statement
endmodule

Have you noticed something?  There are no variable declarations, instantiation of lower gates, or behavioral blocks mentioned in the above module.

Let’s see the test bench treating the above DUT.

//module name 
//mux_21_tb module
module mux_21_tb;

//declaration of reg,wire and other variables
reg S, A, B;
wire OUT;

//Instantiate lower level modules
//In this case, instantiate the mux_21 module
mux_21 dut(.s(S), .a(A), .b(B), .out(OUT));

//behavioral block initial
initial
begin
$monitor("simtime = %g, S = %b, A = %b, B = %b, OUT = %b", $time, s, a, b, out); 
#5 s = 0;
#5 a = 0; b = 0;
#5 s = 1;
#5 a = 0; b = 1;
#5 a = 1; b = 0;
#5 s = 0;
end

//endmodule statement
endmodule

The test bench module above contains the variable declaration, behavioral block, and instantiation of lower blocks.  But there is no port declaration and dataflow statements in this module.

What can we understand from that?

It’s not compulsory that all the five components should be incorporated in the module. The components except for the module, module_name, and endmodule are mixed and matched according to what our design demands.

IO Port Declaration

Let’s take a small piece of code from the mux_21 module

module mux_21(s,a,b,out);
input s,a,b;
output out;

The above code has specified that there are ports in our mux_21 module.

Wait? What are the ports?

Ports are what declares the interface of the module. The ports model the pins of the hardware components. For example, our mux_21 has four ports communicating with its internal circuitry.

Depending on their direction, ports can be input, output, or inout. As per our mux_21 module, s, a, and b are input ports, while out is the output port.

What about the ports in the mux_21_tb (testbench) module? It is a stimulus module. Hence we use reg and wire declarations. Declaring ports is not applicable in this case.

Syntax

We follow the below format for declaring I/O ports:

<port_direction> <port datatype> <port size> <port name>

Let’s see some examples of how I/O ports are declared:

D flip flop

input clk,d,rest;
output reg q, qbar;

If the data type is not specified, it is declared as wire implicitly.

4-bit Full Adder

input [3:0] A,B; 
input Cin;
output [3:0] SUM;
output Cout;

We can also declare ports using ANSI C style. In this style, the complete information of the port is specified along with the module and port declarations. Such an example is given below:

module full_adder_4(input [3:0] A, B,
                    input Cin,
                    output [3:0] SUM,
                    output Cout);
//module internals
.........

endmodule

Module Instantiation

There are two approaches in design: Top-down and Bottom-up approach.

In the top-down approach, we build the top-level block and identify the sub-blocks until we come to leaf-cells, which cannot be divided further. In the bottom-up approach, the leaf cells are first designed, then the lower blocks, which is then combined to form the top-level blocks.

Let’s say we need to build a full adder. When we use the top-down approach, we first design the full adder using two half adders, and then we design the half adders using logic gates. The bottom-up approach suggests that we design the half adder from the logic gates and then build the full adder using these half adder blocks.

In short, we have understood we can build a top-level module from lower-level modules and vice-versa. To do that, Verilog has provided us with a feature-Module Instantiation. This enables us to nest lower modules to form a top-level module.

This feature is mostly used in two levels of abstraction:- Structural and Gate-Level Modeling.

The syntax for module instantiation.

To nest one module to another, we follow the below syntax.

<lower module name> <instance name> (<ports from top-level module>)

You will get a better idea with the example of describing a full adder:

The Full-Adder 

To build a full adder, we need two half adders.

Hence, let’s build a half adder using gate primitives.

First, as for any Verilog code, we declare the module and ports:

module ha(sum,carry,a,b);

input a,b;
output sum,carry;
......
endmodule

A half adder requires an XOR gate for summing two inputs and an AND gate to generate carry. Hence using gate primitives, we write as

xor(sum,a,b);
and(carry,a,b);

So, our Half adder module will be like this:

module ha(sum,carry,a,b);

input a,b;
output sum,carry

xor(sum,a,b);
and(carry,a,b);

endmodule

Now, we can use this to build a full adder from the ha module referring the logic diagram:

full adder using two half adders
full adder using two half adders

As usual, carry out the module and port declaration:

module fa(SUM,Cout,A,B,Cin);

input A,B,Cin;
output SUM, Cout;

....

endmodule

Now let’s instantiate the ha module to the full adder module:

ha ha1(sum1,c1,A,B);
ha ha2(SUM,c2,sum1,Cin);
or(Cout,c1,c2);

Hence, our code is:

module fa(SUM,Cout,A,B,Cin);

input A,B,Cin;
output SUM, Cout;



ha ha1(sum1,c1,A,B);
ha ha2(SUM,c2,sum1,Cin);
or(Cout,c1,c2);
endmodule

Have you noticed something?

When we instantiated, we connected the ports in the fa module the same order as the ports declared in the ha module. This is what we call instantiation by port order.

There’s another way of nesting to modules.  Let’s see how we can connect the same ha modules to build fa module using this style

ha ha1(.sum(sum1), .carry(c1), .a(A), .b(B));
ha ha2(.sum(SUM), .carry(c2), .a(sum1), .b(Cin));
or(Cout, c1, c2);

Unlike the former, this style doesn’t mind if the order is messed up. But, we have mentioned the names of all the physical connections related to both modules. Hence, this style is called instantiation by port name.

I hope you had fun learning about the syntax used in Verilog and the various data types in the HDL. If you have any doubts, feel free to drop comments.