VHDL - How should I create a clock in a testbench?
Asked Answered
S

4

38

How should I create a clock in a testbench? I already have found one answer, however others on stack overflow have suggested that there are alternative or better ways of achieving this:

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY test_tb IS 
END test_tb;

ARCHITECTURE behavior OF test_tb IS

    COMPONENT test
        PORT(clk : IN std_logic;)
    END COMPONENT;

   signal clk : std_logic := '0';
   constant clk_period : time := 1 ns;

BEGIN

   uut: test PORT MAP (clk => clk);       

   -- Clock process definitions( clock with 50% duty cycle is generated here.
   clk_process :process
   begin
        clk <= '0';
        wait for clk_period/2;  --for 0.5 ns signal is '0'.
        clk <= '1';
        wait for clk_period/2;  --for next 0.5 ns signal is '1'.
   end process;

END;

(source here)

Septicidal answered 28/7, 2013 at 2:47 Comment(1)
This method works just fine.Uptrend
C
39

My favoured technique:

signal clk : std_logic := '0'; -- make sure you initialise!
...
clk <= not clk after half_period;

I usually extend this with a finished signal to allow me to stop the clock:

clk <= not clk after half_period when finished /= '1' else '0';

If you use a std_logic item for your finished signal, it can be driven from all the items in your test environment:

signal finished : std_logic;

....
stimulus_process:process
begin
   finished <= '0';
   drive_various_signals_sync_with_clk;
   finished <= '1';
end process;

monitor_process:process
begin
   finished <= '0';
   check_all_signals_until_all_tests_complete;
   finished <= '1';
end process;

Then the clock only stops after all elements have finishde. And when there are no more transactions (on signals) scheduled, your simulation will stop cleanly.


Gotcha alert: Care needs to be taken if you calculate half_period from another constant by dividing by 2. The simulator has a "time resolution" setting, which often defaults to nanoseconds... In which case, 5 ns / 2 comes out to be 2 ns so you end up with a period of 4ns! Set the simulator to picoseconds and all will be well (until you need fractions of a picosecond to represent your clock time anyway!)

Comanche answered 28/7, 2013 at 20:13 Comment(1)
Sir, how do you define the signal finished? I do not understand how can the clock stop at finished?Perfidious
B
23

If multiple clock are generated with different frequencies, then clock generation can be simplified if a procedure is called as concurrent procedure call. The time resolution issue, mentioned by Martin Thompson, may be mitigated a little by using different high and low time in the procedure. The test bench with procedure for clock generation is:

library ieee;
use ieee.std_logic_1164.all;

entity tb is
end entity;

architecture sim of tb is

  -- Procedure for clock generation
  procedure clk_gen(signal clk : out std_logic; constant FREQ : real) is
    constant PERIOD    : time := 1 sec / FREQ;        -- Full period
    constant HIGH_TIME : time := PERIOD / 2;          -- High time
    constant LOW_TIME  : time := PERIOD - HIGH_TIME;  -- Low time; always >= HIGH_TIME
  begin
    -- Check the arguments
    assert (HIGH_TIME /= 0 fs) report "clk_plain: High time is zero; time resolution to large for frequency" severity FAILURE;
    -- Generate a clock cycle
    loop
      clk <= '1';
      wait for HIGH_TIME;
      clk <= '0';
      wait for LOW_TIME;
    end loop;
  end procedure;

  -- Clock frequency and signal
  signal clk_166 : std_logic;
  signal clk_125 : std_logic;

begin

  -- Clock generation with concurrent procedure call
  clk_gen(clk_166, 166.667E6);  -- 166.667 MHz clock
  clk_gen(clk_125, 125.000E6);  -- 125.000 MHz clock

  -- Time resolution show
  assert FALSE report "Time resolution: " & time'image(time'succ(0 fs)) severity NOTE;

end architecture;

The time resolution is printed on the terminal for information, using the concurrent assert last in the test bench.

If the clk_gen procedure is placed in a separate package, then reuse from test bench to test bench becomes straight forward.

Waveform for clocks are shown in figure below.

Waveforms for clk_166 and clk_125

An more advanced clock generator can also be created in the procedure, which can adjust the period over time to match the requested frequency despite the limitation by time resolution. This is shown here:

-- Advanced procedure for clock generation, with period adjust to match frequency over time, and run control by signal
procedure clk_gen(signal clk : out std_logic; constant FREQ : real; PHASE : time := 0 fs; signal run : std_logic) is
  constant HIGH_TIME   : time := 0.5 sec / FREQ;  -- High time as fixed value
  variable low_time_v  : time;                    -- Low time calculated per cycle; always >= HIGH_TIME
  variable cycles_v    : real := 0.0;             -- Number of cycles
  variable freq_time_v : time := 0 fs;            -- Time used for generation of cycles
begin
  -- Check the arguments
  assert (HIGH_TIME /= 0 fs) report "clk_gen: High time is zero; time resolution to large for frequency" severity FAILURE;
  -- Initial phase shift
  clk <= '0';
  wait for PHASE;
  -- Generate cycles
  loop
    -- Only high pulse if run is '1' or 'H'
    if (run = '1') or (run = 'H') then
      clk <= run;
    end if;
    wait for HIGH_TIME;
    -- Low part of cycle
    clk <= '0';
    low_time_v := 1 sec * ((cycles_v + 1.0) / FREQ) - freq_time_v - HIGH_TIME;  -- + 1.0 for cycle after current
    wait for low_time_v;
    -- Cycle counter and time passed update
    cycles_v := cycles_v + 1.0;
    freq_time_v := freq_time_v + HIGH_TIME + low_time_v;
  end loop;
end procedure;

Again reuse through a package will be nice.

Binnings answered 29/7, 2013 at 12:49 Comment(0)
F
14

Concurrent signal assignment:

library ieee;
use ieee.std_logic_1164.all;

entity foo is
end;
architecture behave of foo is
    signal clk: std_logic := '0';
begin
CLOCK:
clk <=  '1' after 0.5 ns when clk = '0' else
        '0' after 0.5 ns when clk = '1';
end;

ghdl -a foo.vhdl
ghdl -r foo --stop-time=10ns --wave=foo.ghw
ghdl:info: simulation stopped by --stop-time
gtkwave foo.ghw

enter image description here

Simulators simulate processes and it would be transformed into the equivalent process to your process statement. Simulation time implies the use of wait for or after when driving events for sensitivity clauses or sensitivity lists.

Falciform answered 28/7, 2013 at 6:11 Comment(0)
P
1

How to use a clock and do assertions

This example shows how to generate a clock, and give inputs and assert outputs for every cycle. A simple counter is tested here.

The key idea is that the process blocks run in parallel, so the clock is generated in parallel with the inputs and assertions.

library ieee;
use ieee.std_logic_1164.all;

entity counter_tb is
end counter_tb;

architecture behav of counter_tb is
    constant width : natural := 2;
    constant clk_period : time := 1 ns;

    signal clk : std_logic := '0';
    signal data : std_logic_vector(width-1 downto 0);
    signal count : std_logic_vector(width-1 downto 0);

    type io_t is record
        load : std_logic;
        data : std_logic_vector(width-1 downto 0);
        count : std_logic_vector(width-1 downto 0);
    end record;
    type ios_t is array (natural range <>) of io_t;
    constant ios : ios_t := (
        ('1', "00", "00"),
        ('0', "UU", "01"),
        ('0', "UU", "10"),
        ('0', "UU", "11"),

        ('1', "10", "10"),
        ('0', "UU", "11"),
        ('0', "UU", "00"),
        ('0', "UU", "01")
    );
begin
    counter_0: entity work.counter port map (clk, load, data, count);

    process
    begin
        for i in ios'range loop
            load <= ios(i).load;
            data <= ios(i).data;
            wait until falling_edge(clk);
            assert count = ios(i).count;
        end loop;
        wait;
    end process;

    process
    begin
        for i in 1 to 2 * ios'length loop
            wait for clk_period / 2;
            clk <= not clk;
        end loop;
        wait;
    end process;
end behav;

The counter would look like this:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all; -- unsigned

entity counter is
    generic (
        width : in natural := 2
    );
    port (
        clk, load : in std_logic;
        data : in std_logic_vector(width-1 downto 0);
        count : out std_logic_vector(width-1 downto 0)
    );
end entity counter;

architecture rtl of counter is
    signal cnt : unsigned(width-1 downto 0);
begin
    process(clk) is
    begin
        if rising_edge(clk) then
            if load = '1' then
                cnt <= unsigned(data);
            else
                cnt <= cnt + 1;
            end if;
        end if;
    end process;
    count <= std_logic_vector(cnt);
end architecture rtl;

Related: https://electronics.stackexchange.com/questions/148320/proper-clock-generation-for-vhdl-testbenches

Profusion answered 21/6, 2016 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.