VHDL coding tips and tricks: synthesisable
Showing posts with label synthesisable. Show all posts
Showing posts with label synthesisable. Show all posts

Sunday, April 3, 2022

Digital Clock (With ability to Set time) And Testbench in VHDL

    More than a decade back I had written a Digital Clock module in this blog, which was when I just started learning VHDL. Obviously it had its own shortcomings and through this post, I wanted to rectify these shortcomings. Plus add the ability to set time.

The codes are shared below. They are commented to help you understand the logic. For a detailed understanding watch the video. Also please make sure to like the video if it was helpful and subscribe to my YouTube channel for more such videos in the future. 




Digital Clock:

--Declare the libraries
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;  --we need this one because we are using "unsigned" data type.


entity digital_clock is
 --frequency of the clock passed as a generic parameter.
generic( CLOCK_FREQ : integer := 50000000	); 
port(	
		Clock : in std_logic;  --system clock
		reset : in std_logic;  --resets the time
		inc_secs : in std_logic;  --set a pulse here to increment the seconds by 1.
		inc_mins : in std_logic;  --set a pulse here to increment the minutes by 1.
		inc_hrs : in std_logic;  --set a pulse here to increment the hours by 1.
		seconds :out unsigned(5 downto 0);  --seconds output
		minutes :out unsigned(5 downto 0);  --minutes output
		hours :out unsigned(4 downto 0)  --hours output
		);
end digital_clock;

architecture Behavioral of digital_clock is

--temperory signals as we cant directly perform arithmetic operations on outputs
signal secs, mins, hrs : integer := 0;
--counter used for getting the 1 sec duration from the system Clock.
signal counter : integer := 0; begin process(Clock, reset) begin if(reset = '1') then --reset the time. secs <= 0; mins <= 0; hrs <= 0; counter <= 0; elsif(rising_edge(Clock)) then --increment the seconds. also increment mins and hours if needed. if(inc_secs = '1') then if(secs = 59) then secs <= 0; if(mins = 59) then mins <= 0; if(hrs = 23) then hrs <= 0; else hrs <= hrs+1; end if; else mins <= mins+1; end if; else secs <= secs + 1; end if; --increment the minutes. also increment hours if needed. elsif(inc_mins = '1') then if(mins = 59) then mins <= 0; if(hrs = 23) then hrs <= 0; else hrs <= hrs+1; end if; else mins <= mins+1; end if; --increment the hours. elsif(inc_hrs = '1') then if(hrs = 23) then hrs <= 0; else hrs <= hrs+1; end if; end if; --regular operation of the clock if(counter = CLOCK_FREQ-1) then --counting CLOCK_FREQ times takes 1 second. counter <= 0; --check and change values of secs, mins and hours if(secs = 59) then secs <= 0; if(mins = 59) then mins <= 0; if(hrs = 23) then hrs <= 0; else hrs <= hrs+1; end if; else mins <= mins+1; end if; else secs <= secs + 1; end if; else counter <= counter+1; end if; end if; end process; --The internal integer signals are converted into unsigned format. --The size of the output unsigned signal is assigned via the 2nd parameter(5 or 6 bits)
seconds <= to_unsigned(secs, 6);
minutes <= to_unsigned(mins, 6);
hours <= to_unsigned(hrs, 5);

end Behavioral;

Testbench:


LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
--the below library is used for finishing the simulation after we are done. 
--Otherwise it will run continuously.
library std;
use std.env.finish;
 
ENTITY tb_digitalClock IS
END tb_digitalClock;
 
ARCHITECTURE behavior OF tb_digitalClock IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT digital_clock
    generic( CLOCK_FREQ : integer := 50000000	); 
    PORT(
         Clock : IN  std_logic;
         reset : IN  std_logic;
         inc_secs : IN  std_logic;
         inc_mins : IN  std_logic;
         inc_hrs : IN  std_logic;
         seconds : OUT  unsigned(5 downto 0);
         minutes : OUT  unsigned(5 downto 0);
         hours : OUT  unsigned(4 downto 0)
        );
    END COMPONENT;
    

   --Inputs
   signal Clock : std_logic := '0';
   signal reset : std_logic := '0';
   signal inc_secs : std_logic := '0';
   signal inc_mins : std_logic := '0';
   signal inc_hrs : std_logic := '0';

 	--Outputs
   signal seconds : unsigned(5 downto 0);
   signal minutes : unsigned(5 downto 0);
   signal hours : unsigned(4 downto 0);

   -- Clock period definitions
   constant Clock_period : time := 10 ns;
	
	--Clock frequency in Hz. Use a smaller value for testbench. 
  --When testing on board it need to be set as 50 million, 100 million etc.
	constant CLOCK_FREQ : integer := 10;
 
BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: digital_clock 
		GENERIC MAP(CLOCK_FREQ => CLOCK_FREQ)
		PORT MAP (
          Clock => Clock,
          reset => reset,
          inc_secs => inc_secs,
          inc_mins => inc_mins,
          inc_hrs => inc_hrs,
          seconds => seconds,
          minutes => minutes,
          hours => hours
        );

   -- Clock process definitions
   Clock_process :process
   begin
		Clock <= '0';
		wait for Clock_period/2;
		Clock <= '1';
		wait for Clock_period/2;
   end process;
 

   -- Stimulus process
   stim_proc: process
   begin		
		reset <= '1';
      -- hold reset state for 100 ns.
      wait for 100 ns;	
		reset <= '0';
      wait for Clock_period*CLOCK_FREQ*60*60*25;  --run the clock for 25 hours
		
		--increment seconds
		inc_secs <= '1';	wait for Clock_period;
		inc_secs <= '0';	
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds once.
		
		--increment seconds 60 times
		inc_secs <= '1';	wait for Clock_period*60;
		inc_secs <= '0';	
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds.
		
		--increment minutes
		inc_mins <= '1';	wait for Clock_period;
		inc_mins <= '0';	
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing minutes once.
--increment minutes 60 times inc_mins <= '1'; wait for Clock_period*60; inc_mins <= '0';
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing minutes.
--increment hours inc_hrs <= '1'; wait for Clock_period; inc_hrs <= '0';
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing hours once.
--increment hours 25 times inc_hrs <= '1'; wait for Clock_period*25; inc_hrs <= '0';
    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing hours.
--apply reset reset <= '1'; --wait for 100 Clock cycles and then finish the simulation. --with the current settings it will run around 9 ms of simualtion time. wait for Clock_period*100; finish; end process; END;


So what next?

    As an extension of this project I want to show you how you can convert the time into BCD format and then connect a BCD to 7 segment converter to it. This will be useful for those who want to try this clock on a real FPGA board. Look forward to it in the next post.

So until next time...

Tuesday, December 8, 2020

Synthesizable Clocked Square Root Calculator In VHDL

    Long back I had shared VHDL function for finding the square root of a number. This function too was synthesisable, but as it was a function, it was purely combinatorial. If you want to find the square root of a relatively larger number, then the resource usage was very high.

    In such cases, it makes sense to use a clocked design. Such a clocked design enables us to reuse one set of resources over and over. The advantage of such a design is that it uses far less resources while the disadvantage being the low speed. 

    For example, in the design I have shared in this post, to find the square root of a N-bit number, you need to wait N/2 clock cycles. 

    The code is written based on Figure (8) from this paper: A New Non-Restoring Square Root Algorithm and Its VLSI Implementations.

    The codes are well commented, so I wont write much about how it works here. Please refer to the block diagram from the paper in case you have some doubts.

Let me share the codes now:

square_root.vhd:


--Synthesisable Design for Finding Square root of a number.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity square_root is
    generic(N : integer := 32);
    port (
        Clk : in std_logic;     --Clock
        rst : in std_logic;     --Asynchronous active high reset.
        input : in unsigned(N-1 downto 0);  --this is the number for which we want to find square root.
        done : out std_logic;   --This signal goes high when output is ready
        sq_root : out unsigned(N/2-1 downto 0)  --square root of 'input'
    );
end square_root;

architecture Behav of square_root is

begin

    SQROOT_PROC : process(Clk,rst)
        variable a : unsigned(N-1 downto 0);  --original input.
        variable left,right,r : unsigned(N/2+1 downto 0):=(others => '0');  --input to adder/sub.r-remainder.
        variable q : unsigned(N/2-1 downto 0) := (others => '0');  --result.
        variable i : integer := 0;  --index of the loop. 
    begin
        if(rst = '1'then  --reset the variables.
            done <= '0';
            sq_root <= (others => '0');
            i := 0;
            a := (others => '0');
            left := (others => '0');
            right := (others => '0');
            r := (others => '0');
            q := (others => '0');
        elsif(rising_edge(Clk)) then
            --Before we start the first clock cycle get the 'input' to the variable 'a'.
            if(i = 0then  
                a := input;
                done <= '0';    --reset 'done' signal.
                i := i+1;   --increment the loop index.
            elsif(i < N/2then --keep incrementing the loop index.
                i := i+1;  
            end if;
            --These statements below are derived from the block diagram.
            right := q & r(N/2+1) & '1';
            left := r(N/2-1 downto 0) & a(N-1 downto N-2);
            a := a(N-3 downto 0) & "00";  --shifting left by 2 bit.
            if ( r(N/2+1) = '1'then   --add or subtract as per this bit.
                r := left + right;
            else
                r := left - right;
            end if;
            q := q(N/2-2 downto 0) & (not r(N/2+1));
            if(i = N/2then    --This means the max value of loop index has reached. 
                done <= '1';    --make 'done' high because output is ready.
                i := 0--reset loop index for beginning the next cycle.
                sq_root <= q;   --assign 'q' to the output port.
                --reset other signals for using in the next cycle.
                left := (others => '0');
                right := (others => '0');
                r := (others => '0');
                q := (others => '0');
            end if;
        end if;    
    end process;

end architecture;


Testbench: tb.vhd


--Testbench for out square root calculator design.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

--empty entity as its a testbench
entity tb is
end tb;

architecture sim of tb is

    --Declare the component which we want to test.
    component square_root is
        generic(N : integer := 32);
        port (
            Clk : in std_logic;
            rst : in std_logic;
            input : in unsigned(N-1 downto 0);
            done : out std_logic;
            sq_root : out unsigned(N/2-1 downto 0)
        );
    end component;

    constant clk_period : time := 10 ns;    --set the clock period for simulation.
    constant N : integer := 16;    --width of the input.
    signal Clk,rst,done : std_logic := '0';
    signal input : unsigned(N-1 downto 0) := (others => '0');
    signal sq_root : unsigned(N/2-1 downto 0) := (others => '0');
    signal error : integer := 0;    --this indicates the number of errors encountered during simulation.
    

begin

    Clk <= not Clk after clk_period / 2;    --generate clock by toggling 'Clk'.

    --entity instantiation.
    DUT : entity work.square_root generic map(N => N)
             port map(Clk,rst,input,done,sq_root);

    --Apply the inputs to the design and check if the results are correct. 
    --The number of inputs for which the results were wrongly calculated are counted by 'error'.     
    SEQUENCER_PROC : process
        variable actual_result,i : integer := 0;
    begin
        --First we apply reset input for one clock period.
        rst <= '1';
        wait for clk_period;
        rst <= '0';
        --Test the design for all the combination of inputs.
        --Since we have (2^16)-1 inputs, we test all of them one by one. 
        while(i <= 2**N-1loop
            input <= to_unsigned(i,N);  --convert 'i' from integer to unsigned format.
            wait until done='1';    --wait until the 'done' output signal goes high.
            wait until falling_edge(Clk);   --we sample the output at the falling edge of the clock.
            actual_result := integer(floor(sqrt(real(i)))); --Calculate the actual result.
            --if actual result and calculated result are different increment 'error' by 1.
            if (actual_result /= to_integer(sq_root)) then  
                error <= error + 1;
            end if
            i := i+1;   --increment the loop index.
        end loop;
        reset <= '1';   --all inputs are tested. Apply reset
        input <= (others => '0');   --reset the 'input'
        wait;
    end process;

end architecture;


Simulation Waveform from ModelSim:



        To reach the end of the testbench, you need to simulate only for 5.5 msec of simulation time.


Wednesday, October 25, 2017

How to create a Floating Point IP using CORE Generator on Xilinx ISE

As you learn VHDL, soon or later, you will do projects which require you to do operations on floating point(FP) numbers. In most of the programming languages, dealing with real numbers is as easy as dealing with integers. But not so in VHDL or Verilog.

If you want the design to be synthesisable, then the real numbers has to be stored in floating or fixed point format in hardware. Arithmetic operations on these numbers aren't so easy, if you have to write the code from scratch.

Fortunately, tools like Xilinx/Altera have IP's, which deals with these numbers. These IP's can be easily customized too. Xilinx grouped all these IP's under the CORE Generator system. In this post, I want to show you how to create a simple FP multiplier and how to simulate it.

Let's go step by step.

1) Create a new project in Xilinx ISE.

2) Right click on the design sources window, in ISE and click on "New Source".

3) Next select source type as IP(CORE Generator & Architecture Wizard). Enter the file name, and change the file location if needed. Click Next.

4) A new window will open up saying creating selector for specified device. After some moments, a window will show all the IP's available in coregen.
Click on + sign of Math Functions. 
Then click on + sign of Floating Point.
Select the normal Floating-Point IP and click Next.

5) Upon this, a window will be opened where you can customize the selected IP.  As you can see, the left side will show the IP symbol with input and output signals, and on right side, we have the parameters of IP which can be customized. Select the settings as shown below and then click Generate at the end.




 


So what kind of IP have we created here? Its a FP multiplier with 2 inputs and one output of single precision FP numbers. There is also a Clock input and RDY (Ready) output. A high on RDY signal shows that the current output is valid. 

6) Coregen will generate all the files which are required for simulating or synthesizing the IP in your project. Upon completion, the CORE Generator creates and adds to the project, a file called component_name.xco. This is a file that records all the customization parameters used to create the core and the project options in effect when the core was generated. Double clicking on this file, will open the customization window, and you will be able to edit the IP later on if needed.

7) When the View mode is Implementation, select the .xco file, and then you can see various options under the Design tab down. Double click on the View HDL Instantiation Template. A file named component_name.vho will open, which shows the component declaration. This is useful, if we want to instantiate the IP later on.


8) Now we want to test this IP. For that purpose, I have written a testbench. Create a New Source in the same project, and copy and paste the following code in there. You can name the file as tb.

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY tb IS
END tb;

ARCHITECTURE behavior OF tb IS 

    -- Component Declaration for the Unit Under Test (UUT) 
    COMPONENT ip_test
    PORT(
         a : IN  std_logic_vector(31 downto 0);
         b : IN  std_logic_vector(31 downto 0);
         clk : IN  std_logic;
         result : OUT  std_logic_vector(31 downto 0);
         rdy : OUT  std_logic
        );
    END COMPONENT;
   
   --Inputs
   signal a : std_logic_vector(31 downto 0) := (others => '0');
   signal b : std_logic_vector(31 downto 0) := (others => '0');
   signal clk : std_logic := '0';
    --Outputs
   signal result : std_logic_vector(31 downto 0);
   signal rdy : std_logic;
   -- Clock period definitions
   constant clk_period : time := 10 ns;

BEGIN

    -- Instantiate the Unit Under Test (UUT)
   uut: ip_test PORT MAP (
          a => a,
          b => b,
          clk => clk,
          result => result,
          rdy => rdy
        );

   -- Clock process definitions
   clk_process :process
   begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
   end process;


   -- Stimulus process
   stim_proc: process
   begin        
      a <= x"4148F5C3";  --12.56
        b <= x"42C80000";  --100.0
        --result should be 1256 = x"449D0000"
      wait;
   end process;

END;

9) Double click on Simulate Behavioral Model. The ISIM window will open with the simulation waveform. The waveform should look like this:


Analysis:

The inputs applied were 12.56 and 100.0. These needed to be converted to FP representation before. I used an online tool for this. The result was also confirmed using the same tool.

Have you noticed the 6 clock cycle delay between the input and output? This is called latency of the design. If you remember, while creating the IP using coregen, we could have changed this value on Page 3. A lower latency will reduce the maximum clock frequency of the design and might also increase the usage of resources. Think carefully before changing the default value.

The tool used for this post is Xilinx ISE 14.6 and ISIM for simulation.