VHDL coding tips and tricks: testbench
Showing posts with label testbench. Show all posts
Showing posts with label testbench. 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...

Sunday, November 29, 2020

Writing a Gate Level VHDL design (and Testbench) from Scratch

    In this video I want to show you how you can take a logic circuit diagram and write the corresponding VHDL code along with its testbench. 




The VHDL codes presented in the video are given below:

xor_gate.vhd:


library ieee;
use ieee.std_logic_1164.all;

entity xor_gate is
    port (
        A,B : in std_logic;
        C : out std_logic
    );
end entity;


architecture gate_level of xor_gate is

signal An,Bn,t1,t2 : std_logic := '0';

begin

An <= not A;
Bn <= not B;
t1 <= An and B;
t2 <= Bn and A;

C <= t1 or t2;

end architecture;

tb_xor.vhd:


library ieee;
use ieee.std_logic_1164.all;

entity tb_xor is
end entity;

architecture behav of tb_xor is

component xor_gate is
    port (
        A,B : in std_logic;
        C : out std_logic
    );
end component;

signal A,B,C : std_logic := '0';

begin

UUT : xor_gate port map (A,B,C);

stimulus : process
begin
    A <= '0';
    B <= '0';
    wait for 100 ns;
    A <= '0';
    B <= '1';
    wait for 100 ns;
    A <= '1';
    B <= '0';
    wait for 100 ns;
    A <= '1';
    B <= '1';
    wait;
end process;    

end architecture;


The Logic circuit diagram is given below:





Simulation Waveform from Modelsim:






Wednesday, February 5, 2020

Online Automatic Testbench Generator For VHDL and Simulation Using Xilinx Vivado

Hello guys, I am back here with another video.

If you are someone like me, who suddenly started using the Xilinx Vivado tool after using Xilinx ISE for a long time, then you might have noticed that Vivado currently doesn't support the Automatic testbench generation. 

This is such a major disappointment for many of us. But luckily there are many online tools which does more or less the same. In this Video, I used the Doulos tool for creating testbenches for my VHDL designs. Once generated I tested the codes using the latest Vivado 2019.2 version. 

Hope this is useful for you. Enjoy!



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.