Electronic – VHDL testbench variable clock/wave generation

testingvhdl

A testbench for one sub-entity in my system currently defines a helper process to generate a clock-like waveform at the command of the main stimulus process. A simplified version of it is:

shared variable gen_period : time := 10 us;
signal gen_all : boolean := false;  -- wavegen enabled
signal gen_A : boolean := false;    -- run this signal
signal A_in : std_logic;            -- manual input (wavegen disabled)
signal A_gen : std_logic;           -- generated output (internal)
signal A_out : std_logic;           -- final output
...
A_generate: process
    if gen_A then
        A_gen <= '1';
        wait for gen_period/2;
        A_gen <= '0';
        wait for gen_period/2;
    else
        A_gen <= '0';
        wait for 1 ns;
    end if;
end process;

A_out <= A_gen when gen_all else A_in;

The full system has a few more signals, but this is the basic idea. gen_all applies to all signals and allows a manual signal pattern to be applied when disabled, while gen_A is basically a clock enable for that specific signal. The other important feature is that the period of the clock is variable.

Putting this all together it means that the main testbench stimulus process can either manually generate edges or can just request a pulse train at a specific interval and then wait for a longer time (typically gen_period * N) to generate multiple pulses. It works quite well.


Now though I'm interested in generalising this a bit, in particular to instantiate several such independent generators in the testbench for a higher-level design that contains multiple of the original component. However I'm having trouble finding the right way to do so.

My first attempt was to wrap the code above into its own entity, declaring all but A_gen as ports of the entity. Doing this required changing gen_period from a variable to a signal, since AFAIK ports must be signals.

Unfortunately, back in the original testbench stimulus process, whenever it tried to assign a new period it had to do this as a signal assignment, which didn't take effect until later. Additionally the wait for gen_period * 5 calls appeared to be using the original value for gen_period rather than one just assigned. (This is not surprising behaviour for signals, but in this case it's undesired.)

Is there a better way to encapsulate this functionality, and retain instant behaviour of the variable? The code is only going to be used in a testbench, so it does not need to be synthesisable. I'm using Xilinx ISim.

(I realise that I can do a for .. generate to instantiate multiple instances in the higher level testbench, but this doesn't allow me to share the code between separate testbenches, which is also desirable to avoid duplication.)

I can live with having only one instance of the gen_period variable shared between all generators in the high level testbench, but I would prefer to have one per generator instance.


Since the answers seem to be focusing on "clock" rather than "waveform", it seems that I need to clarify the usage a bit more. This is inside a testbench:

stim_proc : process
begin
    -- (reset and other setup instructions here)

    -- generate "slow" pulse train
    gen_all <= true;
    gen_period := 250 ns;
    gen_A <= true;
    wait for gen_period * 20;
    gen_A <= false;
    wait for gen_period;
    -- (perform tests on logic for slow pulses here)

    -- generate "fast" pulse train
    gen_period := 10 ns;
    gen_A <= true;
    wait for gen_period * 50;
    gen_A <= false;
    wait for gen_period;
    -- (perform tests on logic for fast pulses here)

    -- generate asymmetric pulses
    gen_all <= false;
    A_in <= '1';
    wait for 100 ns;
    A_in <= '0';
    wait for 200 ns;
    A_in <= '1';
    wait for 150 ns;
    A_in <= '0';
    wait for 50 ns;
    -- (perform tests on logic for asymmetric pulses here)

    -- etc
end process;

These are the simple ones; there are a few more complex ones involving multiple signals happening simultaneously, but that's not really important for this question. The important point is that this is all sequential code and both the pulse generation and the waiting need to act on the most recently set period, not any delayed value.

Best Answer

You could use a procedure with an out parameter. The procedure can be declared in a package and called with different parameters for different 'clock' signals.

Here is an example of a clock_gen procedure from VHDL-extras:

subtype duty_cycle is real range 0.0 to 1.0;

procedure clock_gen( signal Clock : out std_ulogic; signal Stop_clock : in boolean;
  constant Clock_period : in delay_length; constant Duty : duty_cycle := 0.5 ) is
  constant HIGH_TIME : delay_length := Clock_period * Duty;
  constant LOW_TIME  : delay_length := Clock_period - HIGH_TIME;
begin
  Clock <= '0';

  while not Stop_clock loop
    wait for LOW_TIME;
    Clock <= '1';
    wait for HIGH_TIME;
    Clock <= '0';
  end loop;
end procedure;

The constant input period can be exchanged with a variable for simulation environments.

Usage example:

architecture rtl of my_entity is
  signal SimStop      : boolean     := false;
  signal my_clock1    : std_ulogic;
  signal my_clock2    : std_ulogic;
begin

  clock_gen(my_clock1, SimStop, 10.000 ns);
  clock_gen(my_clock2, SimStop,  6.666 ns);

  -- ...

  process
  begin
    --
    -- some stimuli
    --

    wait for 10 ns;
    SimStop := true;
    wait;
  end process;
end architecture;

Example to generate a waveform with an generic waveform:

type T_TIME_VECTOR is array(NATURAL range <>) of TIME;

constant myWaveform : T_TIME_VECTOR  := (
  -- generate "slow" pulse train
  20 * 250 ns,
  250 ns,
  -- generate "fast" pulse train
  50 * 10 ns,
  10 ns
);

procedure generateWaveform(signal Wave : out BOOLEAN; Waveform: T_TIME_VECTOR; InitialValue : BOOLEAN) is
  variable State : BOOLEAN := InitialValue;
begin
  Wave <= State;
  for i in Waveform'range loop
    wait for Waveform(i);
    State := not State;
    Wave  <= State;
  end loop;
end procedure;

signal mySignal : BOOLEAN;

-- begin of architecture
generateWaveform(mySignal, myWaveform);