In this article, we will be writing the VHDL code for a 2-bit binary multiplier using all the three modeling techniques. We will write the code, testbench and will also create the RTL schematics for the same.
Contents
Binary multiplier (2-bit)
A multiplier is a circuit that takes two numbers as input and produces their product as an output. So a binary multiplier takes binary numbers as inputs and produces a result in binary. Before moving forward, lets quickly recap binary multiplication first.
0 x 0 = 0
0 x 1 = 0
1 x 0 = 0
1 x 1 = 1
So there’s always a confusion in students. Especially for students who have studied microprocessors like 8085 in their curriculum. Because in the programming of microprocessors like 8085, we use a technique called “Repetitive addition” for multiplication.
For example, to multiply 5 x 4, you just need to either add ‘4’ five times or add ‘5’ four times. It seems easy at first, but it is a very inefficient technique as it takes a lot of time to execute. Just imagine multiplying numbers of the order of millions or billions. Also, programs that have loops are not easy to implement in hardware.
So we use the “Parallel Binary Multiplier” method for multiplication. Because it is way more efficient. Moreover, it is similar to the method that we use to perform multiplication of decimal numbers. We have covered the 2-bit binary multiplier in detail in our digital electronics course.
In this article, we will focus more on the VHDL code of the circuit. So for reference, we’re using the equations and logic circuit of the 2-bit multiplier, as shown below.
Equations
P(0) <= A(0) AND B(0); P(1) <= (A(1) AND B(0)) XOR (A(0) AND B(1)); P(2) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) XOR (A(1) AND B(1)); P(3) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) AND (A(1) AND B(1));
Circuit diagram
Dataflow Modeling
As we know that in the dataflow modeling style, we describe the flow of data through every gate using equations. So let’s start writing a VHDL program using dataflow modeling.
As we have been doing from the start of this VHDL course, in the beginning, we have to include the IEEE library and use its standard logic library.
library ieee; use ieee.std_logic_1164.all;
After including the library, we need to define an entity in which we define our input and output ports of the circuit.
entity multiply is port( A, B : in bit_vector(1 downto 0); P: out bit_vector(3 downto 0) ); end multiply;
In the above code “multiply” is the name of the entity and in ports, we have created two input ports of 2-bit each using A, B : bit_vector(1 downto 0);
this creates two bit_vector having bits A(0), A(1) and B(0), B(1) and a 4-bit output port using P: out bit_vector(3 downto 0)
having bits P(0), P(1), P(2), P(3). Then we end the entity using the end
keyword.
Now we move forward to create architecture for the above entity.
architecture dataflow of multiply is begin
In the above code, architecture
is the keyword used to define architecture. “dataflow” is the name of the architecture here, and it can be anything but must be a valid identifier. Then we specify the name of the entity, for which we are writing the architecture, i.e., multiply.
Now that we have completed the entity-architecture pair, we use the begin
keyword after which we start writing the code for the architecture, if we have to define any component or signal, we define it before the keyword begin
.
P(0) <= A(0) AND B(0); P(1) <= (A(1) AND B(0)) XOR (A(0) AND B(1)); P(2) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) XOR (A(1) AND B(1)); P(3) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) AND (A(1) AND B(1));
Now, talking about equations for P(0), it is pretty self-explanatory, just an AND gate.
Let’s get the circuit down here once again.
But in P(1), we have to do a sum of two bits coming from two AND gates, as shown in the figure. So we use XOR operation on them because we also know that inside a half adder, the sum is produced by the XOR gate. Let’s get the circuit diagram of a half-adder to simplify the process of understanding the equations for us. Check out the sum output below; it is the EX-OR of the two inputs.
Now look at P(2), it looks confusing at first. But let’s simplify it. We can see in the half adder’s diagram above that the carry output of a half adder is obtained by ANDing the two inputs. So let’s do that first.
We get the output of the first half adder as (A(1) AND B(0)) AND (A(0) AND B(1))
. But the job is not done yet. P(2) is actually the output of the SUM component of the second half adder. As we saw earlier, the sum component of the half adder is basically the EXORing of its two inputs.
The first input is (A(1) AND B(0)) AND (A(0) AND B(1))
and the second input is (A(1) AND B(1))
. Thus P(1) is equal to (A(1) AND B(0)) AND (A(0) AND B(1)) XOR (A(1) AND B(1))
.
If you understood the formation of equation P(2), then P(3) is the same, just instead of XOR we used AND.
end architecture;
We end the architecture using the end
keyword.
VHDL program of 2-bit multiplier using dataflow modeling
library ieee; use ieee.std_logic_1164.all; entity multiply is port (A, B : in bit_vector(1 downto 0); P : out bit_vector(3 downto 0) ); end multiply; architecture dataflow of multiply is begin P(0) <= A(0) AND B(0); P(1) <= (A(1) AND B(0)) XOR (A(0) AND B(1)); P(2) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) XOR (A(1) AND B(1)); P(3) <= ((A(1) AND B(0)) AND (A(0) AND B(1))) AND (A(1) AND B(1)); end architecture;
RTL schematic of a 2-bit multiplier using dataflow modeling
Behavioral Modeling
As its name suggests, in this modeling, we define the behavior of the entity using sequential statements.
So we will talk only about the architecture here, the architecture of a 2-bit multiplier in behavioral style modeling is shown below.
We will start writing the architecture using architecture
keyword and a label and then bind it to the entity and use begin keyword to write inside the architecture.
architecture behavioral of multiply_behav is begin
Then we start a process
, it contains a set of instructions that will be executed sequentially, and if the program has multiple processes, then all processes will run concurrently.
process(A,B) is begin
Arguments passed to the process are called its sensitivity list. Like in process(A,B)
, (A, B) is the sensitivity list, and whenever the value of either A or B changes, the process will be triggered, and all statements inside it will be executed. Here also begin
keyword is used to start writing inside the process. process
command is used in behavioral modeling.
Now we will use case statements in combination with if/else to construct the logics for a 2-bit binary multiplier.
We will look into one case only, and the rest are similar to write.
case A is when "00" => if B="00" then P<="0000"; elsif B="01" then P<="0000"; elsif B="10" then P<="0000"; else P<="0000"; end if;
In the above code, we select ‘A’ as case, and when A=”00″ is true, we enter in its substatements where we use if-elsif conditional statements to generate output. For, eg. A=”00″ is fixed for this case, and if B=”00″, then the product should also be “0000” so we write that value to the output port. Similarly, we cover all values of B for all cases of A.
VHDL program of 2-bit multiplier using behavioral modeling
library ieee; use ieee.std_logic_1164.all; entity multiply_behav is port (A, B : in bit_vector(1 downto 0); P : out bit_vector(3 downto 0) ); end multiply_behav; architecture behavioral of multiply_behav is begin process(A,B) is begin case A is when "00" => if B="00" then P<="0000"; elsif B="01" then P<="0000"; elsif B="10" then P<="0000"; else P<="0000"; end if; when "01" => if B="00" then P<="0000"; elsif B="01" then P<="0001"; elsif B="10" then P<="0010"; else P<="0011"; end if; when "10" => if B="00" then P<="0000"; elsif B="01" then P<="0010"; elsif B="10" then P<="0100"; else P<="0110"; end if; when "11" => if B="00" then P<="0000"; elsif B="01" then P<="0011"; elsif B="10" then P<="0110"; else P<="1001"; end if; end case; end process; end architecture;
RTL schematic of a 2-bit multiplier using behavioral modeling
Structural Modeling
In structural modeling, we describe the circuit by interconnections of individual components of the circuit.
Here also entity remains almost the same, but there is a small change
entity multiply_struct is port (A, B : in bit_vector(1 downto 0); P : buffer bit_vector(3 downto 0) ); end multiply_struct;
For an output port, instead of using out bit
we have used buffer
, this is because out bit
cannot be read by the circuit that precedes it. We will explain it in detail while explaining the architecture. The rest of the entity is the same. Now let’s move to the architecture.
We start writing the architecture for the above entity in the same manner as before.
architecture structural of multiply_struct is
Now, look at the circuit once more.
We need some AND gates and Half adders to realize the circuit. So now we define components that will be used in the architecture. Then we will declare the architecture of the multiplier and define the components using the component
keyword in VHDL.
AND Gate
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity AND2 is port( A,B: in BIT; x : out BIT); end AND2; architecture behavioral of AND2 is begin x <= A and B; end behavioral;
Half Adder
library ieee; use ieee.std_logic_1164.all; entity half_adder is port (a, b : in BIT; sum, carry : out BIT ); end half_adder; architecture arch of half_adder is begin sum <= a xor b; carry <= a and b; end arch;
After declaring components’ entity-architecture pairs, we will declare the multiplier’s entity and architecture pair and declare the components.
entity multiply_struct is port (A, B : in bit_vector(1 downto 0); P : buffer bit_vector(3 downto 0) ); end multiply_struct; architecture structural of multiply_struct is component AND2 port( A,B: in BIT; X : out BIT); end component; component half_adder port (A, B : in BIT; sum, carry : out BIT); end component;
Now, we need to initialize some signals because, as we know that to interconnect components, we have to use signals.
We define four signals of bit type. signal S1,S2,S3,S4:BIT;
Now comes the part of the main architecture.
As usual, we start with begin
keyword and instantiate the components using component instantiation statements.
And the components are interconnected through signals.
Let’s focus on one instantiation, and the rest of all are the same.
In the below line of code, A1
is the label of the instantiation and ‘AND2’ is the component that is called here. Then we use another keyword port map
, which is used to bind the port/signal to the port of the component’s entity.
A1: AND2 port map(A(0),B(0),P(0));
Here, A(0), B(0), and P(0) are mapped to the Input1, Input2, and Output of the AND gate, respectively.
Let’s look at one more instantiation:
A2: AND2 port map(A(1),B(0),S1);
Here we mapped A(1), B(0), S1 to the inputs and output of AND gate, but why we have used a signal(S1), can you guess?
This is because we had to connect the output of one component to the input of another component instead of the output port. So to carry it, we need a signal which is used for interconnections of components in structural modeling.
Now, according to our circuit, we can frame the following line of codes.
begin A1: AND2 port map(A(0),B(0),P(0)); A2: AND2 port map(A(1),B(0),S1); A3: AND2 port map(A(0),B(1),S2); A4: AND2 port map(A(1),B(1),S3); H1: half_adder port map(S1,S2,P(1),S4); H2: half_adder port map(S4,S3,P(2),P(3)); end architecture;
Then we end the architecture, using end
keyword.
VHDL program of 2-bit multiplier using structural modeling
library ieee; use ieee.std_logic_1164.all; entity AND2 is port( A,B: in BIT; x : out BIT); end AND2; architecture behavioral of AND2 is begin x <= A and B; end behavioral; entity half_adder is port (a, b : in BIT; sum, carry : out BIT ); end half_adder; architecture arch of half_adder is begin sum <= a xor b; carry <= a and b; end arch; entity multiply_struct is port (A, B : in bit_vector(1 downto 0); P : buffer bit_vector(3 downto 0) ); end multiply_struct; architecture structural of multiply_struct is component AND2 port( A,B: in BIT; X : out BIT); end component; component half_adder port (A, B : in BIT; sum, carry : out BIT); end component; signal S1,S2,S3,S4:BIT; begin A1: AND2 port map(A(0),B(0),P(0)); A2: AND2 port map(A(1),B(0),S1); A3: AND2 port map(A(0),B(1),S2); A4: AND2 port map(A(1),B(1),S3); H1: half_adder port map(S1,S2,P(1),S4); H2: half_adder port map(S4,S3,P(2),P(3)); end architecture;
RTL schematic of a 2-bit multiplier using structural modeling
Testbench
A testbench is a special VHDL program written to test the working of another VHDL program. It basically injects the provided values into its input ports and reads its output ports and shows as waveforms.
It has a similar structure as of a VHDL program but has a blank entity and uses an entity a component which is the entity of program under test.
Now, let’s write a testbench for our 2-bit multiplier. One thing you should understand and remember that testbench for all modeling styles is the same.
We start the testbench by including the necessary library, which is the same as the program under test.
library ieee; use ieee.std_logic_1164.all;
Then we create a blank entity as testbench does not define actual hardware.
entity multiply_behav_tb is end multiply_behav_tb;
Now we write the architecture of the testbench and before begin
we declare the component and initialize signals.
architecture tb of multiply_behav_tb is component multiply_behav is port (A, B : in bit_vector(1 downto 0); P : out bit_vector(3 downto 0) ); end component; signal A, B : bit_vector(1 downto 0); signal P : bit_vector(3 downto 0); begin
Then we map the ports of the testbench to the ports of the entity under test so that it can inject and read values from them.
UUT : multiply_behav port map ( A => A, B => B, P => P);
Then we start a process
, and give it a label(‘Force’ in this case) and we define a constant time period to use later for delays, and begin
the process
.
Force:process constant period: time := 20 ns; begin
Now we can finally inject values to inputs. Generally, we try to give all possible input combinations, here we do the same. And after every input, we provide a delay.
A <= "00"; B <= "00"; wait for period; A <= "00"; B <= "01"; wait for period; A <= "00"; B <= "10"; wait for period; A <= "00"; B <= "11"; wait for period; A <= "01"; B <= "00"; wait for period; A <= "01"; B <= "01"; wait for period; A <= "01"; B <= "10"; wait for period; A <= "01"; B <= "11"; wait for period; A <= "10"; B <= "00"; wait for period; A <= "10"; B <= "01"; wait for period; A <= "10"; B <= "10"; wait for period; A <= "10"; B <= "11"; wait for period; A <= "11"; B <= "00"; wait for period; A <= "11"; B <= "01"; wait for period; A <= "11"; B <= "10"; wait for period; A <= "11"; B <= "11"; wait for period;
Then we use a wait
statement to terminate the process
and end process
to kill it, and one more end
to finish the architecture.
wait; end process; end tb;
Full testbench code for the 2-bit multiplier
library ieee; use ieee.std_logic_1164.all; entity multiply_behav_tb is end multiply_behav_tb; architecture tb of multiply_behav_tb is component multiply_behav is port (A, B : in bit_vector(1 downto 0); P : out bit_vector(3 downto 0) ); end component; signal A, B : bit_vector(1 downto 0); signal P : bit_vector(3 downto 0); begin UUT : multiply_behav port map ( A => A, B => B, P => P); Force:process constant period: time := 20 ns; begin A <= "00"; B <= "00"; wait for period; A <= "00"; B <= "01"; wait for period; A <= "00"; B <= "10"; wait for period; A <= "00"; B <= "11"; wait for period; A <= "01"; B <= "00"; wait for period; A <= "01"; B <= "01"; wait for period; A <= "01"; B <= "10"; wait for period; A <= "01"; B <= "11"; wait for period; A <= "10"; B <= "00"; wait for period; A <= "10"; B <= "01"; wait for period; A <= "10"; B <= "10"; wait for period; A <= "10"; B <= "11"; wait for period; A <= "11"; B <= "00"; wait for period; A <= "11"; B <= "01"; wait for period; A <= "11"; B <= "10"; wait for period; A <= "11"; B <= "11"; wait for period; wait; end process; end tb;
Simulation Result (waveform)
As always, if you have any queries, we would love to address them. Just drop in a comment in the comments section below.