How to design Parameterizable Multiplier and Ripple carry adder

Parameterizable Multiplier using Ripple carry adder

1.1          Aim
The aim of this project is to implement a parameterizable multiplier using for/generate statement to describe repeating components/assignments. The size of the multiplier should be adjustable using parameter data width (possible values are 2, 3, 4, … , 8) that would enable multiplication of binary digits. Below is the list of steps to follow in implementing this task:
·     Describe a parameterizable ripple-carry adder. It should be possible to adjust its size using parameter data width (possible values are 2, 3, 4, ... , 8).
·  Use parameterizable adder as a component to describe a parameterizable multiplier that implements multiplication using columnar addition. It should be possible to adjust its size using parameter data width (possible values are 2, 3, 4, ... , 8).
          1.2  Background
Here it’s needed to use the for/generate statement in other to eradicate the possibility of reusing code repeatedly within an architecture

 1.3 Workflow

Implementation of Ripple carry adder

According to the first task in this laboratory, a parameterizable  4-bit ripple-carry adder is designed by instantiating a full adder four times. Fugure 1:

I have started the implementation by declaring the port and also assigned generic "datawidth" right above the Port within the entity :
           Listing 1
           
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity ripple_adder is
generic (datawidth: in integer);
    Port ( a : in STD_LOGIC_vector(datawidth-1 downto 0);
           b : in STD_LOGIC_vector(datawidth-1 downto 0);
           cin : in STD_LOGIC;
           cout : out STD_LOGIC;
           sum : out STD_LOGIC_vector(datawidth-1 downto 0));
end ripple_adder;

architecture Behavioral of ripple_adder is
component full_adder is
 port (
    a, b : in STD_LOGIC; 
    cin1 : in std_logic;           
    sum1, cout1 : out std_logic);          
end component;

signal carry: std_logic_vector(a'length-1 downto 0);
begin 
gen: for g in b'range generate
genlsb: if g = 0 generate
 fa_lsb: full_adder port map ( a=>a(0), b =>b(0), cin1 =>cin, sum1=>sum(0), cout1 => carry(1));
 end generate;
 genmid: if (g>0) and (g< a'length-1) generate
 fa_mid: full_adder port map(a => a(g), b => b(g), cin1 => carry(g), sum1 =>sum(g), cout1=> carry(g+1));
 end generate;
 genmsb : if g = a'length-1 generate
 fa_msb: full_adder port map(a=>a(g), b=>b(g), cin1=>carry(g), sum1 =>sum(g), cout1=>cout);
 end generate;
 end generate;
end Behavioral;

As it shown from Figure 1 a  ripple carry adder is considered an adder with its carry ripple via every bit from the right to the left. Cin represents the carry in bit and cout represent the carry out bit and S represent the sum outputs whereas A and B are inputs. In this implementation, generate statements are used for instantiating the full adder components. I have also used generic to defined the data width needed as an integer value within the entity.
I have also considered (encapsulation of full adder as component within the architecture of the ripple carry adder) the usefulness of full adder that adds 3 bits, namely a , b and cin and produce two outputs of sum and cout.
I used  “b’range” to access the range from b’left downto b’right. Since both a and b have the same range therefore using b’range to access the range of b is the same as accessing the range of a.
for g in b’range generate
The meaning of this statement is to use g to iterate through the possible range of b whereby using of if statement to set the initial value for each necessary port is possible and further allows the port mapping with those values set.
E.g. I have generated the least significant by further declaration of  if statement and right under the statement I applied the values within the full adder port mapping:
  genlsb: if g = 0 generate
fa_lsb: full_adder port map ( a=>a(0), b =>b(0), cin =>cin, sum=>sum(0), cout => carry(1));
 end generate;
Moreover there are two assignment to take note the cin and the cout, these two are different from other assignments in that line because of the way they carry their bits even if a and b are set to 0, cin carries bit in and cout carries bit out and serves as input of another full adder figure 1.1 below shows:

Ripple carry adder Testbench

I have practically used the same method for generating the waveform for both multiplier and the ripple carry adder, I will explain more about this logic under the multiplier testbench, below shows the part of the code for the Ripple carry adder testbench and figure 1.2 shows the simulation waveform of the ripple carry adder:

Listing 2:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity ripple_adder_tb is
--  Port ( );
end ripple_adder_tb;
architecture Behavioral_rapper of ripple_adder_tb is
component ripple_adder is
generic (datawidth:integer :=4);
    Port ( a : in STD_LOGIC_vector(datawidth-1 downto 0);
           b : in STD_LOGIC_vector(datawidth-1 downto 0);
           cin : in STD_LOGIC;
           cout : out STD_LOGIC;
           sum : out STD_LOGIC_vector(datawidth-1 downto 0));
end component;
constant datawidth : integer := 4; 
signal a_tb, b_tb : std_logic_vector(datawidth-1 downto 0);
signal cin_tb: std_logic_vector(datawidth-datawidth downto 0);
signal cout_tb : std_logic;
signal  sum_tb : std_logic_vector(datawidth-1 downto 0);
signal sum_and_carry : STD_LOGIC_VECTOR (datawidth downto 0);
begin
riple_adder: ripple_adder
generic map (datawidth=>datawidth)
port map      (a=>a_tb,b=>b_tb,cin=>cin_tb(0),sum=>sum_tb, cout=>cout_tb );
sum_and_carry(datawidth-1 downto 0)<=sum_tb;
sum_and_carry(datawidth) <= cout_tb;
process
constant period: time := 10ns;
begin
for c in 0 to 1 loop
  cin_tb<=std_logic_vector(to_unsigned(c,1));
  for i in 0 to 2**datawidth-1 loop
      a_tb <= std_logic_vector(to_unsigned(i,datawidth));
        for j in 0 to 2**datawidth-1 loop
        b_tb<= std_logic_vector(to_unsigned(j,datawidth));
        wait for period;  
        assert(sum_and_carry = std_logic_vector(to_unsigned(i,datawidth + 1) + to_unsigned(j,datawidth + 1) + to_unsigned(c ,1)))
        report  "test failed for specified input combination" 
        severity error ;
     end loop;
 end loop;
end loop;
wait;
end process;
end Behavioral_rapper;

Figure 1.2 Waveform for ripple carry adder

               1.4   Implementation of Parameterizable Multiplier

Here, I have implemented a parameterizable multiplier that permits multiplication using columnar addition. It is possible to adjust its size using parameter data width (possible values are 2, 3, 4, ... , 8).
Figure 1.4 below shows the multiplication process on how this multiplier should work

Figure 1.4  Example of Multiplication Using Columnar Addition.
I have started the implementation by declaring the port and also assigned generic datawidth right above the Port within the entity :

Listing 3
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Multiplier is
generic (datawidth_m: integer:= 4);
    Port ( aa : in STD_LOGIC_vector(datawidth_m-1 downto 0);
           bb : in STD_LOGIC_vector(datawidth_m-1 downto 0);
           product : OUT STD_LOGIC_vector((2*datawidth_m)-1 downto 0));
end Multiplier;

architecture Behavioral of Multiplier is
component ripple_adder is

generic (datawidth: integer:= datawidth_m);
    Port ( a : in STD_LOGIC_vector(datawidth-1 downto 0);
           b : in STD_LOGIC_vector(datawidth-1 downto 0);
           cin : in STD_LOGIC;
           cout : out STD_LOGIC;
           sum : out STD_LOGIC_vector(datawidth-1 downto 0));
end component;

type r_adder is array (0 to datawidth_m-1) of std_logic_vector(datawidth_m-1 downto 0);
signal Partial_Product, Partial_Sum, b_signal : r_adder;
type carry is array (0 to datawidth_m-1) of std_logic;
signal Partial_Carry : carry;

begin
--getting the partial products
getting_Partial_Sums:for i in 0 to datawidth_m-1 generate
   inner_PP: for m in 0 to datawidth_m-1 generate
     Partial_Sum(i)(m)<= aa(i) and bb(m);    
   end generate;
end generate;

Partial_Product(0)<=Partial_Sum(0);
Partial_Carry(0)<='0';

using_the_ripple_adder: for f in 1 to datawidth_m-1 generate
b_signal(f)<= Partial_Carry(f-1)&Partial_Product(f-1)(datawidth_m-1 downto 1);
  dut: component ripple_adder
  port map
          (     
             cin=>'0', --RN
             cout=>Partial_Carry(f), -- RN
             a=>Partial_sum(f), --RN 
             b=> b_signal(f),
             sum=>Partial_Product(f) --RN
         );  
end generate;
product( (2*datawidth_m)-1 downto datawidth_m-1 )<= Partial_Carry(datawidth_m-1)&Partial_Product(datawidth_m-1);

get_the_rest:for t in 0 to datawidth_m-2 generate
product(t) <= Partial_Product(t)(0);
end generate;
end Behavioral;

I have used constrained vector arrays in the for/generate description of the multiplier because of the inputs and outputs of the ripple-carry adders.

type r_adder is array (0 to datawidth_m-1) of std_logic_vector(datawidth_m-1 downto 0);


 1.5          Multiplier Testbench

The testbench stimulus process help to show the logical behavior of the multiplier code in a simulated form with the help of assert statement. The testbench encapsulates the behavioral port of the multiplier source file as a component. Whereas, it also permits signal declaration. Mapping its declared signal with the encapsulated component of the behavioral source file values for input and output is possible right within the unit under test.
Meanwhile right under the keyword “begin” situated within the stimulus, the assignment of all possible bits to signals begins which is generated using for…loop and wrapped the signal with assigned values coming from the std_logic_vector. On the other hand the keyword “wait” stop the test indefinitely whereas the “wait for” is used to wait for a specific period of time before the continuation of the next stimulus process. 
In a nutshell, the correctness of the multiplier was carried out by the stimulus waveform which was implemented by testbench code.
Below shows the code that generated the waveform:

 Listing 4:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity MultiplierTb is
--  Port ( );
end MultiplierTb;
architecture Behavioral of MultiplierTb is
component Multiplier is
Generic (datawidth_m:integer:=4); 
   Port (  aa : in STD_LOGIC_VECTOR (datawidth_m-1 downto 0);
           bb : in STD_LOGIC_VECTOR (datawidth_m-1 downto 0);
           Product : out STD_LOGIC_VECTOR ((2*datawidth_m-1) downto 0));
end component;
signal datawidth : integer:=4;  
signal a_tb : STD_LOGIC_VECTOR (datawidth-1 downto 0);
signal b_tb : STD_LOGIC_VECTOR (datawidth-1 downto 0);
signal Product_tb : STD_LOGIC_VECTOR ((2*datawidth-1) downto 0);
begin
multiplier_tb: Multiplier
generic map (datawidth_m=>datawidth)
port map(
aa=>a_tb,
bb=>b_tb,
Product=>Product_tb);
process
begin
for k in 0 to 2**datawidth-1 loop
  a_tb<=std_logic_vector(to_unsigned(k,datawidth));
  for h in 0 to 2**datawidth-1 loop
    b_tb<=std_logic_vector(to_unsigned(h,datawidth));
    wait for 10 ns;
    assert(Product_tb = std_logic_vector(to_unsigned(k,datawidth)*to_unsigned(h,datawidth))) 
    report  "test failed for this specified input combination" 
     severity error ;
 end loop;
end loop;
wait;    
end process;
end Behavioral;

In these lines of code, I have used for..loop to generate and apply every possible input combination to be assigned to each signals for the multiplication purpose.


Figure 1.3  Waveform for Multiplier
Conclusion
In this article, I have shown how to implement both ripple carry adder and parameterizable multiplier for addition and multiplication of binary numbers to get correct products. I have shown the usefulness of the generic, for…loop and constrained vector arrays.

Conclusively, the correctness of this code has been tested both on simulation and the FPGA board given all the necessary binary outputs correctly. 


2 comments:

Note: only a member of this blog may post a comment.

New Post

New style string formatting in Python

In this section, you will learn the usage of the new style formatting. Learn more here . Python 3 introduced a new way to do string formatti...