VHDL coding tips and tricks: 2010

Saturday, December 4, 2010

Tips for running a successful simulation in Xilinx ISim.

Though I have given enough examples for learning VHDL I didn't write much about using the software till now. In this article I will cover some basics about running your simulation in Xilinx ISim. This article will point out some basic mistakes people do when simulating their code in ISim.
For explaining, I have just used one of my earlier example in the post : Explaining testbench code using a counter design. Lets go step by step, see the images for easier understanding of the steps. Open the images in a new tab in your browser if they are not clear enough.

1)Once the coding is done( I mean both the testbench and the design to be tested) make sure you select the top entity(testbench code) in the Xilinx window as shown below. Many people just select any other file and click the compilation button.
Note down the red markings in the image below. Points to be noted are:



  • Choose View  as simulation.
  • Select the top entity i.e. the testbench. If the wrong file is selected for simulation then the waveform in ISim will be blank and you will see no waveform.
  • Double click on the Behavioral check syntax for compiling the design or for finding out any syntax errors.
  • If the above step is successful then double click on Simulate Behavioral Model. If there are syntax errors in step 3 then you may have to check your code.


2)Now ISim will open in a new window with waveforms. Note down the toolbar at the bottom. Check the below image for knowing what each button does. You can also hover your mouse over the button and they will display the function of that button.

3)Mostly the signals in the wavforms will be displayed as binary numbers or integers. But you can change this basic setting. See the image below. 


Click on the signal which you want to change the display format. Go to radix and then select the format. Some options available are Binary, Hexadecimal, octal etc. Note that depending on your code , you have to change the display format. My code was a counter, so unsigned decimal was the best format in this case.

4)Another interesting thing you can do is adding the internal signals to the waveform which is not displayed by default. By default ISim displays only the signals which are declared in the testbench code. But if there are many sub entities then you may need to see them for debugging purpose. See the image below for how to do it.


  • Go to the Instance and process names on the left side of the ISim window. 
  • Select the Instance name whose internal signals you want to observe. 
  • All the signals declared in that particular instance will be displayed on the immediate right tab now, under simulation objects.
  • Now select the signals you want to display. You can use keyboard short cuts like shift  and Ctrl for selecting multiple signal names.
  • Now drag and drop these select signals into the immediate right tab under signal Name in waveform window.
  • For updating these signal values you have to restart the simulation and run it again. 


5)You may have noticed that in ISim, all the additional signals you added in step (4) are reset when you close the ISim window. This is little bit annoying since you have to add all the internal signals again. But you need not worry about it. Follow the steps:

  • Add the required signals into the waveform as described in step 4.
  • Save the waveform file by clicking, Ctrl + . Give an appropriate name to the wave file.
  •  Now close ISim and go back to the Xilinx ISE window.
  • Right click on simulate behavioral model. 
  • Choose the option Process properties.
  • A new window will open as shown below in the image.
  • Select the check box, Use custom waveform configuration file. 
  • Choose the waveform file you just saved in the  custom waveform configuration file.







Thats it for now. Hope these explain the things better. Thanks.

Monday, November 8, 2010

Synthesis Error : Wait for statement unsupported.

   This article is written as per the request of one of my readers who had some problem trying out the code given in this website. You may have seen this error in Xilinx ISE, "Wait for statement unsupported". And you may have wondered why the error is coming even after you are sure of writing a syntactically correct VHDL code.
  Check out the below code. It is just a testbench plus design code for full adder. As you can see we have 3 input bits and we apply all the 8 combinations of inputs with a 1 ns delay between them. For applying this delay we use the "wait for" statement. This is supported by VHDL.

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY tb IS
END tb;

ARCHITECTURE behavior OF tb IS
   --Inputs
   signal bit1 : std_logic := '0';
   signal bit2 : std_logic := '0';
   signal bit3 : std_logic := '0';
    --Outputs
   signal sum : std_logic;
   signal carry : std_logic;

BEGIN

    --Gates
    gate_inst : process
    begin
        sum <= bit1 xor bit2 xor bit3;
        carry <= (bit1 and bit2) or (bit3 and bit2) or (bit1 and bit3);
        wait for 1 ns;
    end process;   
       
   
   -- Stimulus process
   stim_proc: process
   begin       
        bit1<='0'; bit2<='0';  bit3<='0';  wait for 1 ns;
        bit1<='0'; bit2<='0';  bit3<='1';  wait for 1 ns;
        bit1<='0'; bit2<='1';  bit3<='0';  wait for 1 ns;
        bit1<='0'; bit2<='1';  bit3<='1';  wait for 1 ns;
        bit1<='1'; bit2<='0';  bit3<='0';  wait for 1 ns;
        bit1<='1'; bit2<='0';  bit3<='1';  wait for 1 ns;
        bit1<='1'; bit2<='1';  bit3<='0';  wait for 1 ns;
        bit1<='1'; bit2<='1';  bit3<='1';  wait for 1 ns;
      wait;
   end process;

END;

   Now when you synthesize this code you will get the above mentioned error. This is just because of  the simple fact that all VHDL codes which are syntactically right are need not synthesisable. Synthesis is the process of converting the logic described by your code in terms of actual digital circuit components.A statement like "wait for 1 ns" cannot be described in terms of real hardware and hence it is not synthesisable.
   You may ask why then such a keyword is available if it is not synthesisable.The reason is that there are plenty of situations where you just want to simulate( use the simulation software in your PC for verification, not running it in real FPGA hardware) the design. In those situations "wait for" statement is very useful, especially in case of testbench codes.
   Also, sometimes when you add a new source in Xilinx ISE ( i think versions after 12.1) its default property "View Association" is set as "Implementation".This makes the file invisible in the "simulation" mode in ISE. Once you change this "View association" to "All" the file will be visible under all the views. For this right click on the file name in Xilinx ISE window and click on "source properties". You can see the "View Association" option now. Now do "Behavioral Check syntax" under the "Simulation" view. The errors must have gone now.

I hope the things are clear now.

Sunday, October 31, 2010

Sequence detector using state machine in VHDL

   Some readers were asking for more examples related with state machine and some where asking for codes related with sequence detector.This article will be helpful for state machine designers and for people who try to implement sequence detector circuit in VHDL.
   I have created a state machine for non-overlapping detection of a pattern "1011" in a sequence of bits. The state machine diagram is given below for your reference.

The VHDL code for the same is given below. I have added comments for your easy understanding.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

--Sequence detector for detecting the sequence "1011".
--Non overlapping type.
entity seq_det is
port(   clk : in std_logic;  --clock signal
        reset : in std_logic;   --reset signal
        seq : in std_logic;    --serial bit sequence   
        det_vld : out std_logic  --A '1' indicates the pattern "1011" is detected in the sequence.
        );
end seq_det;

architecture Behavioral of seq_det is

type state_type is (A,B,C,D);  --Defines the type for states in the state machine
signal state : state_type := A;  --Declare the signal with the corresponding state type.

begin

process(clk)
begin
    if( reset = '1' ) then     --resets state and output signal when reset is asserted.
        det_vld <= '0';
        state <= A;
    elsif ( rising_edge(clk) ) then   --calculates the next state based on current state and input bit.
        case state is
            when A =>   --when the current state is A.
                det_vld <= '0';
                if ( seq = '0' ) then
                    state <= A;
                else   
                    state <= B;
                end if;
            when B =>   --when the current state is B.
                if ( seq = '0' ) then
                    state <= C;
                else   
                    state <= B;
                end if;
            when C =>   --when the current state is C.
                if ( seq = '0' ) then
                    state <= A;
                else   
                    state <= D;
                end if;
            when D =>   --when the current state is D.
                if ( seq = '0' ) then
                    state <= C;
                else   
                    state <= A;
                    det_vld <= '1';   --Output is asserted when the pattern "1011" is found in the sequence.
                end if;    
            when others =>
                NULL;
        end case;
    end if;
end process;   

end Behavioral;

If you check the code you can see that in each state we go to the next state depending on the current value of inputs.So this is a mealy type state machine.
The testbench code used for testing the design is given below.It sends a sequence of bits "1101110101" to the module. The code doesnt exploit all the possible input sequences. If you want another sequence to be checked then edit the testbench code.  If it is not working as expected, let me know.

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY blog_cg IS
END blog_cg;

ARCHITECTURE behavior OF blog_cg IS

   signal clk,reset,seq,det_vld : std_logic := '0';
   constant clk_period : time := 10 ns;

BEGIN

    -- Instantiate the Unit Under Test (UUT)
   uut: entity work.seq_det PORT MAP (
          clk => clk,
          reset => reset,
          seq => seq,
          det_vld => det_vld
        );

   -- 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 : Apply the bits in the sequence one by one.
   stim_proc: process
   begin       
        seq <= '1';             --1
      wait for clk_period;
        seq <= '1';             --11
      wait for clk_period;
        seq <= '0';             --110
      wait for clk_period;
        seq <= '1';             --1101
      wait for clk_period;
        seq <= '1';             --11011
      wait for clk_period;
        seq <= '1';             --110111
      wait for clk_period;
        seq <= '0';             --1101110
      wait for clk_period;
        seq <= '1';             --11011101
      wait for clk_period;
        seq <= '0';             --110111010
      wait for clk_period;
        seq <= '1';             --1101110101
      wait for clk_period;
      wait;        
   end process;

END;

The simulated waveform is shown below:

Note:- The code was simulated using Xilinx 12.1 version. The results may vary slightly depending on your simulation tool.

How to use coe file for initializing BRAM

   I hope you have gone through my previous post about how to create a BRAM using core generator.If you are not familiar with the steps then please read it here.This post is a continuation of the above mentioned article. Normally we initialize all the memory locations in a BRAM to zero using the "Fill Remaining memory locations" option in core generator. But this article will tell you how to use coe file as an initialization vector.Follow the steps:
1)Create a BRAM of depth = 16 and read and write width = 8 bits.
2)Check the "Load Init File" under the "Memory Initialization" option. Also select the file named "bram_contents.coe".
3)Before clicking on the generate button create a new file named bram_contents.coe and paste the following contents into it:
memory_initialization_radix=10;
memory_initialization_vector=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16;
In the above text radix means the raidx of the data. I have written decimal data in the vector so my radix is "10". If you want to give the memory contents in binary form then change the coe file like this:
memory_initialization_radix=2;
memory_initialization_vector=0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111;
4)Now click on the generate button.

Now simulate the BRAM with the following testbench code given below:

library IEEE;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity testbench is
end testbench;

architecture Behavioral of testbench is

--temporary signal declarations.
signal ena : std_logic := '0';
signal wea : std_logic_VECTOR(0 downto 0):="0";
signal dina,douta : std_logic_VECTOR(7 downto 0) := (others => '0');
signal addra : std_logic_VECTOR(3 downto 0) := (others => '0');
signal clk : std_logic := '0';

begin

--Instantiating BRAM.
BRAM : entity work.BRAM_test
    port map(
    clka => clk,  --clock for writing data to RAM.
    ena => ena,   --Enable signal.
    wea => wea,   --Write enable signal for Port A.
    addra => addra, --4 bit address for the RAM.
    dina => dina,   --8 bit data input to the RAM.
    douta => douta);  --8 bit data output from the RAM.

--Simulation process.
process
begin
    wait for 1 ns;
    addra <= X"0";  --reset the address value for reading from memory location "0"
    --reading all the 16 memory locations in the BRAM.
    for i in 0 to 15 loop
        ena <= '1';  --Enable RAM always.
        wea <= "0";
        wait for 2 ns;
        addra <= addra + "1";
    end loop;
    wait;
end process;  

--Clock generation - Generates 500 MHz clock with 50% duty cycle.
process
begin
    clk <= '1';
    wait for 1 ns;  --"ON" time.
    clk <= '0';
    wait for 1 ns;  --"OFF" time.
end process;  
 
end Behavioral;

The above code is used for reading the BRAM addresses from 0 to 15 which should display the contents of coe file. Check the given waveform for verification.
Note :- This is just one use of coe file.In different IP cores we can use coe file in different ways. For example coe file can be used to give the filter coefficients for FIR IP core.For testing purpose I have used Xilinx ISE 12.1 version and BRAM version 4.1. The options in the core generator tool may vary slightly depending on the version you are using.

Thursday, October 7, 2010

Design and simulation of BRAM using Xilinx Core generator

   BRAM(Block Random access memory) is an advanced memory constructor that generates area and performance-optimized memories using embedded block RAM resources in Xilinx FPGAs.I hope you have already gone through the Core generator introductory tutorial before.If you haven't please read those articles here.

   For creating a custom BRAM, run Core Generator software:
1)Create a new project and select the required device details for which you want to generate the model.
2)Select "View by Function" category.
3)Select "Memories & Storage Elements" and then "RAMs & ROMs".
4)Double click on the "Block Memory Generator(version 4.1)" and a new window will open similar to the one shown below:

   Now we can start customizing the BRAM as per our requirements. For testing purpose I am going to generate a single port RAM with size 256*8 bit size.The settings I have used are given below:
1)Component Name:BRAM_test
2)Uncheck "Use byte write enable".
3)Select the algorithm as "Low Power".
4)Go to next page.
5)Select write width and read width as "8".
6)Set write depth as "256" which is the number of locations in RAM.
7)Operating mode is "No change".
8)Go to next page.
9)Uncheck all options except "Fill Remaining memory locations" with "0".If you want you can initialize the RAM with a coe file also. Using coe file to initialize BRAM will be explained through another article.
10)Keep other options as "default" and click on "generate" button.The software will now start generating the necessary files such as ngc,vhd,xco etc.

Now close core generator software and open Xilinx ISE.Create a new project with the same device details you have used to create the core generator project.Add BRAM_test.xco or BRAM_test.ngc to this project. Create a new VHDL file in this project directory and name it as BRAM_main.vhd. This code will act like a testbench code for testing the BRAM you have generated. In general you can see how to make it work after going through this example.

The contents of BRAM_main.vhd is displayed below:

library IEEE;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity testbench is  
end testbench;

architecture Behavioral of testbench is

--temporary signal declarations.
signal ena : std_logic := '0';
signal wea : std_logic_VECTOR(0 downto 0):="0";
signal addra,dina,douta : std_logic_VECTOR(7 downto 0) := (others => '0');
signal clk : std_logic := '0';

begin

--Instantiating BRAM.
BRAM : entity work.BRAM_test
    port map(
    clka => clk,  --clock for writing data to RAM.
    ena => ena,   --Enable signal.
    wea => wea,   --Write enable signal for Port A.
    addra => addra, --8 bit address for the RAM.
    dina => dina,   --8 bit data input to the RAM.
    douta => douta);  --8 bit data output from the RAM.

--Simulation process.
process
begin
    wait for 1 ns;
    --Writing to all the memory locations of the BRAM.Set wea "1" for this.
    for i in 0 to 255 loop
        ena <= '1';  --Enable RAM always.
        wea <= "1";
        wait for 2 ns;
        addra <= addra + "1";
        dina <= dina + "1";
    end loop;  
    addra <= X"00";  --reset the address value for reading from memory location "0"
    --reading all the 256 memory locations in the BRAM.
    for i in 0 to 255 loop
        ena <= '1';  --Enable RAM always.
        wea <= "0";
        wait for 2 ns;
        addra <= addra + "1";
    end loop;
    wait;
end process;   

--Clock generation - Generates 500 MHz clock with 50% duty cycle.
process
begin
    clk <= '1';
    wait for 1 ns;  --"ON" time.
    clk <= '0';
    wait for 1 ns;  --"OFF" time.
end process;   
   
end Behavioral;

The signals used in the port mapping can be checked by viewing the BRAM_test.vhd file generated by the core generator tool.Simply copy all those signal names for instantiating the BRAM component in your design.Also remember that you have to either add the .xco file or the .ngc file to the project, for simulation to work.

The simulated waveform is shown below:


Note :- This is the most simplest BRAM. The design will get complicated when you go from single port to dual port RAM's.But the basic idea remains the same.By reading the documentation supplied by Xilinx you can explore more settings used in the GUI tool.For testing purpose I have used Xilinx ISE 12.1 version and BRAM version 4.1. The options in the core generator tool may vary slightly depending on the version you are using.

Sunday, October 3, 2010

A 3 bit Magnitude Comparator using logic gates

   I have been getting lot of requests asking for VHDL code for digital comparators. In this post I have shared a 3 bit comparator which is designed using basic logic gates such as XNOR, OR, AND etc. The code was tested using  a testbench code which tested the design for all the 81 combinations of inputs.

See the code below:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity comparator is
port( a,b : in unsigned(2 downto 0);  --3 bit numbers to be compared
        a_eq_b : out std_logic;  --a equals b
        a_le_b : out std_logic;  --a less than b
        a_gt_b : out std_logic   --a greater than b
      );    
end comparator;

architecture gate_level of comparator is

signal temp1,temp2,temp3,temp4,temp5,temp6,temp7,temp8,temp9 : std_logic := '0';

BEGIN

temp1 <= not(a(2) xor b(2));  --XNOR gate with 2 inputs.
temp2 <= not(a(1) xor b(1));  --XNOR gate with 2 inputs.
temp3 <= not(a(0) xor b(0));  --XNOR gate with 2 inputs.
temp4 <= (not a(2)) and b(2);
temp5 <= (not a(1)) and b(1);
temp6 <= (not a(0)) and b(0);
temp7 <= a(2) and (not b(2));
temp8 <= a(1) and (not b(1));
temp9 <= a(0) and (not b(0));

a_eq_b <= temp1 and temp2 and temp3;  -- for a equals b.
a_le_b <= temp4 or (temp1 and temp5) or (temp1 and temp2 and temp6); --for a less than b
a_gt_b <= temp7 or (temp1 and temp8) or (temp1 and temp2 and temp9); --for a greater than b

end gate_level;

The testbench code for testing the design is given below:

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY tb IS
END tb;

ARCHITECTURE behavior OF tb IS
   --Inputs
   signal a : unsigned(2 downto 0) := (others => '0');
   signal b : unsigned(2 downto 0) := (others => '0');
    --Outputs
   signal a_eq_b : std_logic;
   signal a_le_b : std_logic;
   signal a_gt_b : std_logic;

    signal i,j : integer;

BEGIN

    -- Instantiate the Unit Under Test (UUT)
   uut: entity work.comparator PORT MAP (
          a => a,
          b => b,
          a_eq_b => a_eq_b,
          a_le_b => a_le_b,
          a_gt_b => a_gt_b
        );

   -- Stimulus process
   stim_proc: process
   begin       
        for i in 0 to 8 loop
            for j in 0 to 8 loop
                a <= to_unsigned(i,3); --integer to unsigned type conversion
                b <= to_unsigned(j,3);
                wait for 10 ns;
            end loop;
        end loop;  
   end process;

END;

A part of the simulation waveform is given below:

Note:- For viewing the full results simulate the program yourself. The code was tested using Xilinx ISE 12.1 version. But it will work with almost all the compilers.The code is also synthesisable.

Tuesday, September 14, 2010

How to use "generate" keyword for multiple instantiation?

   Generate statement is a concurrent statement used in VHDL to describe repetitive structures.You can use generate statement in your design to instantiate multiple modules in two ways: the FOR-way and the IF-way.
   FOR-way is explained through a PISO(parallel in serial out) register example I have discussed earlier.The original post can be accessed here. I have modified the Gate level modeling code available in that post using "generate" statement.

--Library declaration.
library ieee;
use ieee.std_logic_1164.all;
--4 bit Parallel In Serial Out shift register(LSB is out first)
entity PISO is
port ( Serial_out : out std_logic;
       Parallel_In : in std_logic_vector(3 downto 0);
       --Load=1 means register is loaded parallely and Load=0 means right shift by one bit.
       Load : in std_logic;
       Clk : in std_logic
     );
end PISO;
architecture gate_level of PISO is
signal D,Q,Load_value : std_logic_vector(3 downto 0):="0000";
signal i : integer := 0;
begin
Load_value <= Parallel_In;
Serial_out <= Q(3);

--entity instantiation of the D flipflop using "generate".
F :  --label name
   for i in 0 to 3 generate   --D FF is instantiated 4 times.
    begin  --"begin" statement for "generate"
        FDRSE_inst : entity work.example_FDRSE port map   --usual port mapping
          (Q => Q(i),
          CLK => Clk,
          CE => '1',
          RESET => '0',
          D => D(i),
          SET => '0');
   end generate F;  --end "generate" block.
--The D inputs of the flip flops are controlled with the load input.
--Two AND gates with a OR gate is used for this.
D(0) <= Load_value(3) and Load;
D(1) <= (Load_value(2) and Load) or (Q(0) and not(Load));
D(2) <= (Load_value(1) and Load) or (Q(1) and not(Load));
D(3) <= (Load_value(0) and Load) or (Q(2) and not(Load));
end gate_level; 

I hope the above code is self explanatory.The for loop is used to instantiate as many number of modules as we want to instantiate.Next I will give an example on the IF-way using a Johnson counter example. The Johnson counter is explained with the help of VHDL code in one of my previous posts.Check it out here.I have modified the code using flip-flops available in this post.
The modified code is given below:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity johnson_counter is
port (
        DAT_O : out unsigned(3 downto 0);
        RST_I : in std_logic;
        CLK_I : in std_logic
        );
end johnson_counter;
architecture Behavioral of johnson_counter is
signal D,Q : unsigned(3 downto 0):="0000";
signal not_Q4 : std_logic:='0';
begin
not_Q4 <= not Q(3);
DAT_O <= Q;
F : for i in 0 to 3 generate
    begin
        F0 : if ( i = 0 ) generate  --The IF condition to for the "first" FF only.
            begin U1 : entity work.example_FDRSE port map --usual port mapping        
            (Q => Q(0),
          CLK => CLK_I,
          CE => '1',
          RESET => RST_I,
          D => not_Q4,
          SET => '0');
             end generate F0;
        F1 : if ( i /= 0 ) generate --generating the rest of the three FF's.
            begin U2 : entity work.example_FDRSE port map   --usual port mapping
         (Q => Q(i),
          CLK => CLK_I,
          CE => '1',
          RESET => RST_I,
          D => Q(i-1),
          SET => '0');
             end generate F1;
    end generate F;    
   
end Behavioral;


   So as you can see from the examples using "generate" keyword makes your design simple to understand  and saves your time.

Monday, September 13, 2010

Measure the time period/Frequency of an input Pulse

   This article is about how to find the time period of an input pulse using a simple counter and some adders.In some applications you may have a pulse input which has unknown or varying frequency.And you may need to find out this frequency.In this design I have two parts.
   First part is a counter which keeps on incrementing at the system clock frequency.When this counter reaches its maximum value it resets automatically and counts up again.The second part is triggered at every positive edge of the pulse input.So this part is triggered once every clock cycle of the pulse.In the second part we simply subtracts the last count from the current count to get the time period of the pulse in terms of the time period of the system clock.
For example if you see the simulation waveform I have attached at the bottom of this page you can see how this works:
1) pulse goes to '1' at 10 ns. Prev_Count is made "1000" which is the curr_count at 10 ns.
2) pulse goes to '0' at 110 ns. Nothing happens here. But all this time curr_count keeps on counting with the system clock.
3) pulse goes to '1' at 160 ns. Now we calculate (Curr_count - Prev_count ) = (16000 - 1000) =15000. This is how we get 15000 as the time period of the pulse input in terms of system clock. For getting the exact time period is ns you have to multiply the time period of system clock with the calculated value. Here we multiply 15000 with 10 ps = 150 ns.
4)For all this to work with good precision the frequency of the system clock should be very high compared to the frequency of pulse input. Otherwise the module will output the time period as "0" and a '1' will be asserted in the ERR_O output.
5)Whenever we calculate the time period of the pulse we update the prev_count with the current value of count.
6)Note that at time 1460.004 ns, at the new positive edge of pulse cycle we have a new time period calculated which is 130000 in relative terms or 1300 ns in absolute scale.
     The code for the design is given below:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity pulse_counter is
port ( DAT_O : out unsigned(47 downto 0);
         ERR_O : out std_logic;  --This is '1' if the pulse freq is more than clk freq.
         Pulse_I : in std_logic;  
       CLK_I : in std_logic
     );
end pulse_counter;
architecture Behavioral of pulse_counter is
signal Curr_Count,Prev_Count : unsigned(47 downto 0):=(others => '0');
begin
--Increment Curr_Count every clock cycle.This is the max freq which can be measured by the module.
process(CLK_I)
begin
    if( rising_edge(CLK_I) ) then
        Curr_Count <= Curr_Count + 1;
    end if;
end process;
--Calculate the time period of the pulse input using the current and previous counts.
process(Pulse_I)
begin
    if( rising_edge(Pulse_I) ) then
    --These different conditions eliminate the count overflow problem
    --which can happen once the module is run for a long time.
       if( Prev_Count < Curr_Count ) then
            DAT_O <= Curr_Count - Prev_Count;
            ERR_O <= '0';
        elsif( Prev_Count > Curr_Count ) then
        --X"F_F" is same as "1111_1111".
        --'_' is added for readability.
            DAT_O <= X"1_0000_0000_0000" - Prev_Count + Curr_Count;    
            ERR_O <= '0';
        else
         DAT_O <= (others => '0');
            ERR_O <= '1';  --Error bit is inserted here.
        end if;    
        Prev_Count <= Curr_Count;  --Re-setting the Prev_Count.
    end if;
end process;
end Behavioral;

The testbench code used for testing the design is given below:

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY tb IS
END tb;
ARCHITECTURE behavior OF tb IS
   --Inputs
   signal Pulse_I : std_logic := '0';
   signal CLK_I : std_logic := '0';
    --Outputs
   signal DAT_O : unsigned(47 downto 0);
   signal ERR_O : std_logic;
   -- Clock period definitions
   constant CLK_I_period : time := 10 ps;
BEGIN
    -- Instantiate the Unit Under Test (UUT)
   uut: entity work.pulse_counter PORT MAP (
          DAT_O => DAT_O,
          ERR_O => ERR_O,
          Pulse_I => Pulse_I,
          CLK_I => CLK_I
        );
   -- Clock process definitions
   CLK_I_process :process
   begin
        CLK_I <= '0';
        wait for CLK_I_period/2;
        CLK_I <= '1';
        wait for CLK_I_period/2;
   end process;
   -- Stimulus process
   stim_proc: process
   begin       
      wait for 10 ns;
        --1  (time period is 15000*10 ps here)
        Pulse_I <= '1';
        wait for 100 ns;
        Pulse_I <= '0';
        wait for 50 ns;
        Pulse_I <= '1';
        --2  (Error because freq of pulse is less than system clock)
        wait for 3 ps;
        Pulse_I <= '0';
        wait for 1 ps;
        Pulse_I <= '1';
        --3  (time period is 130000*10 ps here)
        wait for 300 ns;
        Pulse_I <= '0';
        wait for 1000 ns;
        Pulse_I <= '1';    
      wait;
   end process;
END;

The simulation wave form is shown below:
  Markers are added in the waveform for clearly understanding the positive edges of the pulse wave.Go through the wave form along with the codes and the explanation given above.

Example : 4 bit Johnson Counter with testbench

    A Johnson counter is a digital circuit which consists of a series of flip flops connected together in a feedback manner.The circuit is special type of shift register where the complement output of the last flipflop is fed back to the input of first flipflop.This is almost similar to ring counter with a few extra advantages.When the circuit is reset all the flipflop outputs are made zero. For n-flipflop Johnson counter we have a MOD-2n counter. That means the counter has 2n different states.
The circuit diagram for a 3 bit Johnson counter is shown below:
 The VHDL code for 4 bit Johnson counter is shown below:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity johnson_counter is
port (
        DAT_O : out unsigned(3 downto 0);
        RST_I : in std_logic;
        CLK_I : in std_logic
        );
end johnson_counter;

architecture Behavioral of johnson_counter is

signal temp : unsigned(3 downto 0):=(others => '0');

begin

DAT_O <= temp;

process(CLK_I)
begin
    if( rising_edge(CLK_I) ) then
        if (RST_I = '1') then
            temp <= (others => '0');
        else
            temp(1) <= temp(0);
            temp(2) <= temp(1);
            temp(3) <= temp(2);
            temp(0) <= not temp(3);
        end if;
    end if;
end process;
   
end Behavioral;

The testbench code used for testing the design is given below:

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY tb2 IS
END tb2;

ARCHITECTURE behavior OF tb2 IS
   --Inputs
   signal RST_I : std_logic := '0';
   signal CLK_I : std_logic := '0';
    --Outputs
   signal DAT_O : unsigned(3 downto 0);
   -- Clock period definitions
   constant CLK_I_period : time := 1 ns;

BEGIN

    -- Instantiate the Unit Under Test (UUT)
   uut: entity work.johnson_counter PORT MAP (
          DAT_O => DAT_O,
          RST_I => RST_I,
          CLK_I => CLK_I
        );

   -- Clock process definitions
   CLK_I_process :process
   begin
        CLK_I <= '0';
        wait for CLK_I_period/2;
        CLK_I <= '1';
        wait for CLK_I_period/2;
   end process;

   -- Stimulus process
   stim_proc: process
   begin       
        RST_I <= '1';
      wait for 2 ns;   
        RST_I <= '0';
        wait for 2 ns; 
        RST_I <= '1';
        wait for 1 ns; 
        RST_I <= '0';
      wait;
   end process;

END;

The simulation waveform is given below: