Basics of Verilog
Verilog is based on modular programming, i.e., the designs are written in small modules and then it can be combined to make a complex design. Modules can be thought of as classes, expect the fact that modules are not dynamic, i.e., objects cannot be made of modules as we can do for classes in OOPs language. But modules can be instantiated multiple times and can thus be reused just like classes.
From hardware point of view, every circuit will have a input and output port. Thus, for every module we have to also write the different I/O ports associated with it.
Verilog Template
Every module in verilog follows a common template. Steps to write Verilog module:
- First a module is declared using
module
keyword followed by the name of the module. At the end of module statement;
is a must. Each module statemtent will also have aendmodule
associated with it, which marks the end of the module.
module [module_name];
// code goes here
endmodule
- If module have I/O pins then the ports are defined after the module name using simple brackets
()
.
module [module_name]([port_list]);
// code goes here
endmodule
- Sometimes, we may need to use some internal variables, i.e., variables which are not directly accessed from outside the modules. So we need to declare these internal variables.
- If we want to use some other module inside our module then we instantiate that module and properly connect with them. The syntax to instantiate a module is:
[module name] [identifier] ([port-connection])
- Now we write the code for this module.
So the final template looks like:
module [module_name]([port_list]);
[internal_variables]
[instantiation of a another module if required]
// code related to the module goes here
endmodule
Example
In this example, we will design a 1-bit half adder. For half adder, there are 2 inputs, in this case, A & B and 2 outputs, sum & carry.
// Step-1: Declare the half_adder module
module half_adder(input A, B,
output sum, carry)
// Step-2: Internal variable (optional)
reg [1:0] temp;
// Step-3: As this is simple design thus no other module is required.
// Step-4: Module logic goes below
always @(A or B) begin
temp <= A+B;
end
assign sum = temp[0];
assign carry = temp[1];
endmodule
Testbench
To run any code, we need to provide some inputs to the code. As in Verilog we are representing hardware, thus the inputs and outputs will be in the form of signals. To give the input as signals we need to make a test bench, from which we can provide inputs to our design and get the output. In technical terms, the design which is tested is known as Device under Test (DUT) or Device under verification (DUV).
Steps to write the testbench:
- Declare a module for testbench as we do in design. But for testbench module, there is commonly no I/O ports required as we will generate the input for our design in this module.
- Declare internal variables which will act as the input/output to our design.
- Instantiate the design module and connect the internal variables with correct I/O ports.
- Generate different inputs.
Example
Let's see how the test bench for the half adder module will be like.
// Step-1: Declaring the tesbench module
module tb;
// Step-2: define internal variables for DUT
// In this case as there are 2 i/p and 2 o/p
// thus total 4 variables will be required
reg a, b; // 2 input variables (any valid name can be used)
wire sum, carry;// 2 output variables (any valid name can be used)
// Step-3: instantiate half_adder module and provide a name
half_adder dut(a, b, sum, carry);
// Step-4: Generate inputs for the dut.
initial begin
$monitor("Input a=%b, b=%b & Output sum=%b, carry=%b", a, b, sum, carry);
a=0; b=0;
#10 a=0; b=1;
#10 a=1; b=0;
#10 a=1; b=1;
#20 $finish;
end
endmodule
Output of the half adder test bench
Input a=0, b=0 & Output sum=0, carry=0
Input a=0, b=1 & Output sum=1, carry=0
Input a=1, b=0 & Output sum=1, carry=0
Input a=1, b=1 & Output sum=0, carry=1