Logic gates can be used for mathematical calculation and comparison. Half adder, full adder, half subtractor, full subtractor, multipliers, adder- subtractors are some very well defined combinational logic circuits that perform basic addition, subtraction, division, and multiplication. These circuits can be modeled or can be implemented in any hardware descriptive language.
After reading this post, you’ll be able to
- Understand structural modeling.
- Become familiar with the half-subtractor and full-subtractor circuits.
- Design Half Subtractor and Full Subtractor in Verilog HDL.
- Simulate the circuits and generate RTL schematics.
Contents
Understanding the subtractor circuits
A subtractor circuit is a digital combinational circuit that is responsible for the subtraction of two or three-bit numbers. There are two different types of subtractors.
- Half- subtractor: subtracts two numbers and generates a difference output and a borrow output.
- Full- subtractor: performs subtraction of two bits, one is minuend, and the other is subtrahend, taking into account borrow of the previous adjacent lower minuend bit.
Here’s a detailed explanation of the half subtractor and full subtractor circuits if you would like to acquaint yourself with their working.
The general structure of the Verilog Code
The code structure of Verilog is as follows:
- Module Declaration
- Input-Output Delecaration
- Sensitivity list
- Procedural/ assignment/ continuous statements
- Ending module
module module_name(port_list);
declarations;
statements;
endmodule
* Note: module
and endmodule
are the keywords defined in Verilog IEEE 1134.
Verilog supports three abstractions of modeling:
- Behavioral modeling
- Dataflow modeling
- Structural Modeling
In this post, we will be writing the Verilog code for the half subtractor and full subtractor using structural modeling. This type of modeling gives an idea about the actual elemental circuit involved in the system. It defines a circuit by explicitly showing how to construct it using logic gates, their predefined modules, and the connections between them.
The Half-subtractor circuit
Let’s begin.
For the half- subtractor, suppose we have to subtract two numbers, say A and B, minuend and subtrahend respectively. So these will be the inputs to the half – subtractor circuit and the output generated will be a difference bit Diff and a borrow bit Borrow. Since we have two input variables, the maximum number of possible inputs can be calculated by using 2^n, where n is the number of inputs.
So we have 2^2 = 4 combinations for A and B i. e. 00 01 10 11.
Check out its circuit diagram.
Logic Diagram of Half- Subtractor
Verilog Code for Half Subtractor
To write the Verilog code, first, we need to analyze the logic diagram of half- subtractor. Especially when we are considering structural modeling. We can see three logic gates being used in the circuit. An XOR gate, an AND gate, and a NOT gate. So we’ll structurize these particular modules. Each module will define the functionality of one of the three logic gates using the assign
statement.
- The first module is for the XOR gate. The first two arguments in bracket a1, b1 are the input to the XOR gate, and the third one is output c1. After i/o declaration, it’s expression is assigned to the output variable c1 using
assign
statement.
- Point to be noted here, the LHS entity of the
assign
statement must be a wire or vector net; it can’t be a register.
module xor_gate(a1, b1, c1); input a1, b1; output c1; assign c1 = a1 ^ b1; endmodule
- Similarly, we will do the same for the rest of the two gates.
//NOT gate
module not_gate(a3, b3);
input a3;
output b3;
assign b3 = ~ a3;
endmodule
//AND gate module and_gate(a2, b2, c2); input a2, b2; output c2; assign c2 = a2 & b2; endmodule
- After defining three modules, each for NOT, AND, XOR gate, we need to specify a module for HALF SUBTRACTOR which will contain module instantiation of these individual modules.
- Module instantiation consists of module_name followed by instance_name and port_association_list. This module instance is useful if we want to call the same module several times in the same program.
- One additional thing is done here; a temporary
wire
X is declared. This is due to the presence of an intermediate signal which is emerging from the output of NOT gate. u1, u2, u3 are the name of the instances for XOR, AND, and NOT gates, respectively.
module half_subtractor(a, b, difference, borrow);
input a, b;
output difference, borrow;
wire x;
xor_gate u1(a, b, difference);
and_gate u2(x, b, borrow);
not_gate u3(a, x);
endmodule
The final Verilog code for the half- subtractor is:
module xor_gate(a1, b1, c1); input a1, b1; output c1; assign c1 = a1 ^ b1; endmodule module and_gate(a2, b2, c2); input a2, b2; output c2; assign c2 = a2 & b2; endmodule module not_gate(a3, b3); input a3; output b3; assign b3 = ~ a3; endmodule module half_subtractor(a, b, difference, borrow); input a, b; output difference, borrow; wire x; xor_gate u1(a, b, difference); and_gate u2(x, b, borrow); not_gate u3(a, x); endmodule
Testbench for Half Subtractor
A simple testbench will instantiate a Unit Under Test (UUT) and drive the inputs. Your testbench should be able to test all the possible input conditions across every corner of your project. A good testbench should be self-checking in nature. It should generate input and automatically compare them with the expected results with that of the actual observations.
- For writing the testbench, we’ll first add the timescale directive. Now, what are directives in Verilog? There are compiler directives, which are instructions to the Verilog compiler. They start with a grave accent
`
and do not end with a semicolon. - Timescale directive is used for specifying the unit of time used in further modules and the time resolution. The time resolution is the precision factor that determines the degree of accuracy of the time unit in the modules.
- In the code below, the reference time is one nanosecond, and time resolution is one picosecond.
`timescale reference_time_unit/time_resolution
- Next is the
reg
andwire
declaration. The register(reg) type holds the value until a next value is being driven by the clock pulse onto it and is always underinitial
oralways
block. It is used to apply a stimulus to the input. Hence A for first input and B for second input are declared as registers. - Wires(wire) are declared for the variables which are passive in nature. Their values don’t change, and they can’t be assigned inside
always
andinitial
block. Hence difference and borrow variables are declared as wires.
module top; reg A, B; wire Difference, Borrow;
- Next is the module UUT instantiation. The test bench applies stimulus to the Device Under Test DUT. To do this, the DUT must be instantiated under the testbench. The syntax for instantiation is given below. Port mapping is the linking of testbench’s modules with that of the design modules.
name_of_module name_of_instance(port_map)
- Then comes the initialize block with keyword
initial
. This block of statements will give the stimulus to the input variables. Here, value 0 is given to the input signals at the very first time and the code gets finished executing after a delay of 100 ns;$finish
statement is used for stopping the simulation after a specified delay.
initial
begin
A=0;
B=0;
#100 $finish;
end
- We can also consider system tasks for dumping the variables incorporated.
$dumpfile
and$dumpvars
are used in this code. $dumpfile
is used to dump the changes in the values of net and registers in a VCD file (value change dump file). This particular attribute dumps all the changes that take place inside the variables (used in the program) throughout the simulation of the code. The syntax for$dumpfile
is:
$dumpfile("name_of_the _file.vcd");
$dumpvars
is used to specify which variables should be dumped in the filename specified by the argument. The simplest way to use it is without any arguments.- Now, it depends on the user whether he wants to display the simulation result on the TCL console or not. There are three ways to display the change in variable/ signal.
$display
: shows immediate values of signals.$monitor
: displays the value of the signal whenever its value changes.$strobe
: displays the value of the signal at the end of the current time.
- I have used
$monitor
since our output changes on the change in the input value. It is always executed inside analways
block. The sensitivity list contains the ports to which the following statements are sensitive. In most of the cases, input variables constitute the sensitivity list.
always @(A or B)
$monitor("At TIME(in ns)=%t, A=%d B=%d Difference = %d Borrow = %d", $time, A, B, Difference, Borrow);
The final testbench code for half subtractor:
`timescale 1ns / 1ps module top; reg A, B; wire Difference, Borrow; half_subtractor instantiation(.a(A), .b(B), .difference(Difference), .borrow(Borrow)); initial begin $dumpfile("xyz.vcd"); $dumpvars; A=0; B=0; #100 $finish; end always #10 A=~A; always #5 B=~B; always @(A or B) $monitor("At TIME(in ns)=%t, A=%d B=%d Difference = %d Borrow = %d", $time, A, B, Difference, Borrow); endmodule
RTL Hardware Schematic for Half Subtractor
The RTL schematic is a design abstraction which models a synchronous digital circuit in terms of the flow of digital signals. Click on the elaborated design under RTL analysis, and you’ll get the schematic as shown below.
Simulation Waveform
I am using VIVADO 2019.1 software for the verification and testing of these circuits. This software gives us the simulation waveform, the RTL schematic, and has the console output for the user interface output. Once you’ve coded the logic diagram, you have to simulate both the design code as well as the testbench code.
The following window will appear, provided no errors or critical warning shows up after compilation. You can add/ move/ remove the signals/ variables according to your needs. You can cross-check this waveform with the truth table. This is executed till 100 ns, which is the delay for $finish
statement.
TCL Console Output
Since we have used $monitor
statement in testbench, the output console shows each clock transition and time control event change.
The Full-subtractor circuit
The full subtractor is one of the essential combinational logic circuits. It subtracts two one-bit numbers; one is minuend, other is subtrahend along with the borrow of the previous minuend bit.
Logic Diagram of Full- Subtractor
Can you notice the two half-subtractors in the above circuit?
As you can see from the above diagram, the full subtractor contains two individual half subtractor circuits, with the outputs of AND gate ORed together for the final borrow out Bout. Although there exists a simple logic diagram, in this article, we’re interested in using half subtractor for implementation of full subtractor.
Verilog Code for Full Subtractor using Half Subtractor:
For the coding part, as said earlier, we need to take a look at the logic diagram for the structural style of modeling. The logic diagram includes an AND gate and two half subtractor circuits, which are further an OR, XOR, AND, and NOT gate combination. Since in structural modeling, we define different modules for each basic elemental structure, we will start defining modules for each gate first.
- This module is for the OR gate.
- INPUT: a0, b0 OUTPUT: c0
module or_gate(a0, b0, c0); input a0, b0; output c0; assign c0 = a0 | b0; endmodule
We’ll repeat for the XOR, AND, and NOT gate.
module xor_gate(a1, b1, c1); input a1, b1; output c1; assign c1 = a1 ^ b1; endmodule
module and_gate(a2, b2, c2); input a2, b2; output c2; assign c2 = a2 & b2; endmodule
module not_gate(a3, b3); input a3; output b3; assign b3 = ~ a3; endmodule
Finally, we will combine these gate specific modules into a single module. For that, we will use module instantiation. Now instantiation is used when we want to repeat a particular function/ module for different sets of input. We’ll first construct a half subtractor, as discussed before, and then will use that module of half subtractor for implementing a full subtractor.
module half_subtractor(a4, b4, c4, d4); // module declaration input a4, b4; // input variable output c4, d4; // output variable wire x; // temporary variable xor_gate u1(a4, b4, c4); // XOR gate instance u1; a4, b4 will act as input and c4 will be the output and_gate u2(x, b4, d4); // AND gate instance u2; x, b4 will act as input and d4 will be the output not_gate u3(a4, x); // NOT gate instance u3; a4 will act as input and x will be the output endmodule
Using this half_subtractor module for implementing a full subtractor, we need the OR gate for combining the outputs for the Bout variable.
module full_subtractor(A, B, Bin, D, Bout); // port declaration input A, B, Bin; // input ports output D, Bout; // output ports wire p, q, r; // for intermediate signals, we use wire data type for temporary variables half_subtractor u4(A, B, p, q); // half_subtractor instance u4; A and B will act as input and p, q will be the intermediate outputs half_subtractor u5(p, Bin, D, r); // half_subtractor instance u5; wire p and Bin as input, D as final output and r as temporary output or_gate u6(q, r, Bout); // or_gate instance u6; wire q and r as intermediate input and Bout as the final output endmodule
The full Verilog code for the full-subtractor is:
module or_gate(a0, b0, c0); input a0, b0; output c0; assign c0 = a0 | b0; endmodule module xor_gate(a1, b1, c1); input a1, b1; output c1; assign c1 = a1 ^ b1; endmodule module and_gate(a2, b2, c2); input a2, b2; output c2; assign c2 = a2 & b2; endmodule module not_gate(a3, b3); input a3; output b3; assign b3 = ~ a3; endmodule module half_subtractor(a4, b4, c4, d4); input a4, b4; output c4, d4; wire x; xor_gate u1(a4, b4, c4); and_gate u2(x, b4, d4); not_gate u3(a4, x); endmodule module full_subtractor(A, B, Bin, D, Bout); input A, B, Bin; output D, Bout; wire p, q, r; half_subtractor u4(A, B, p, q); half_subtractor u5(p, Bin, D, r); or_gate u6(q, r, Bout); endmodule
Testbench Code for Full Subtractor
The structure of the Verilog code for the testbench of full subtractor is almost the same as that for half subtractor. The only difference that gets showed up here is the change in the number of input and output ports.
`timescale 1ns / 1ps module top; reg a, b, bin; wire d, bout; full_subtractor instantiation(.A(a), .B(b), .Bin(bin), .D(d), .Bout(bout)); initial begin $dumpfile("xyz.vcd"); $dumpvars; a=0; b=0; bin=0; #100 $finish; end always #40 a=~a; always #20 b=~b; always #10 bin=~bin; always @(a or b or bin) $monitor("At TIME(in ns)=%t, A=%d B=%d Bin=%d D = %d Bout = %d", $time, a, b, bin, d, bout); endmodule
RTL Hardware Schematic for Full Subtractor using Half Subtractor
The hardware schematic for half subtractor is shown below:
Simulation Waveform
The simulation window opens up like the image below. We can append the signals according to our convenience. This waveform should verify the truth-table. This is executed till 100 ns, which is the delay for $finish
statement.
TCL Console Output
The final output on the TCL screen looks like this. We are looking at the transition of variables after every change in the input due to the $monitor
function.
I hope this post was sufficient for you to fully grasp the process of writing the Verilog code for half and full subtractors. If you have any queries regarding any of the concepts we saw above, let us know in the comments section.