7
\$\begingroup\$

In many test benches I see the following pattern for clock generation:

process
begin
    clk <= '0';
    wait for 10 NS;
    clk <= '1';
    wait for 10 NS;
end process;

On other cases I see:

clk <= not clk after 10 ns;

The later is said to be better, because it is scheduled before any process is executed, and thus signals that are changed synchronously to the clk edge are handled properly. The following sections from the LRM may seem to support this theory:

Page 169: 12.6.4 The simulation cycle A simulation cycle consists of the following steps:

  • b) Each active explicit signal in the model is updated. (Events may occur on signals as a result.)

This should be the signals with a new projected value such as signals delayed by the after.

  • d) For each process P, if P is currently sensitive to a signal S and if an event has occurred on S in this simulation cycle, then P resumes.

That would be most of the logic to be simulated

  • e) Each nonpostponed process that has resumed in the current simulation cycle is executed until it suspends.

And now all processes that are suspended by a wait for are executed.

TL;DR:

  • Is the after method always superior to the wait for method?
  • Does it help to prevent the problems synchronously set input signals?
\$\endgroup\$
6
  • \$\begingroup\$ Of course the proper solution is to not change input signals at the same time as the edge. But this is not the point of this discussion here. \$\endgroup\$ – Karsten Becker Jan 9 '15 at 10:52
  • \$\begingroup\$ What do you mean by "signals changed synchronously to clk"? o you mean signals which also change at multiples of 10ns (through wait fors) or signals which are triggered by the edge of the clk and hence are a delta-cycle later? \$\endgroup\$ – Martin Thompson Jan 10 '15 at 16:34
  • \$\begingroup\$ Also, do you have a reference to the claim that the after version is better? I've never been taught it's better, I just prefer it as it's a single line :) \$\endgroup\$ – Martin Thompson Jan 10 '15 at 16:35
  • \$\begingroup\$ You can check out this example here: edaplayground.com/x/J8_ The clock entity shows the bad behaviour, the clock2 entity seems to fix it, until clock3 breaks it again. \$\endgroup\$ – Karsten Becker Jan 10 '15 at 22:22
  • \$\begingroup\$ And yes I mean changing the inputs with the rising edge by using wait until or wait for. \$\endgroup\$ – Karsten Becker Jan 10 '15 at 22:30
6
\$\begingroup\$

The simulator can't really be blamed for sometimes acting like the clock happened right after or right before the input changes, if you assign both clk and inputs using wait for. Asking if one style is superior or inferior to the other is, in a way, the wrong question. You need to specify behaviour in a non-ambiguous way, if you desire a deterministic and non-ambiguous output.

What I've done for years and has worked for me pretty flawlessly (for synchronous designs) is to assign the inputs preceding them with wait until rising_edge(clk) or wait until falling_edge(clk). How you generate the clk becomes unimportant. For simple testbenches the after one-liner does the job nicely and succinctly, but does not offer the flexibility of a process with wait for or wait until statements.

I have a little simple procedure that has served me well:

procedure wait_until_rising_edges(signal clk : in std_logic; n : in integer) is
begin
    for i in 1 to n loop
        wait until rising_edge(clk);
    end loop;
end procedure;

Which I keep in a tb_pkg.vhd that I always use in testbenches.

A use example could be:

some_stim_proc : process
begin
    some_signal <= '0';
    wait_until_rising_edges(clk,900);
    some_signal <= '1';
    wait_until_rising_edges(clk,100);
end process;

Some designers assign their stimulus signals at the opposite edge to what the unit under test is sensitive to. I personally don't like doing this because it is not how the rest of the circuit will simulate, where signals change at the 'trigger' edge. But there is certainly nothing wrong with that approach. The above procedure can also be used for it.

\$\endgroup\$
1
  • \$\begingroup\$ I rather prefer to change the signals on the opposite edge, because that is closer to what most data sheets look like. Also purely from an optical perspective, it might look like as as if the output is based on the signals that were applied after the clock was handled. But I really like your little function. Neat idea. \$\endgroup\$ – Karsten Becker Jan 11 '15 at 14:00
3
\$\begingroup\$

I read this thread a long time ago, but haven't had time to respond until becoming retired.

It's interesting to see the ways different people create their testbench clocks and the criteria which is used to judge how "good" they are. For me, I'd look at the code and ask: how versatile is it? How hard is it to modify if the clock rate changes? Or if I want to use the testbench in a back-annotated gate simulation?

So here's my very long way to make sure modifications are easy and the testbench is versatile:

The clock rate, data setup time, and data hold times should be defined as generics or constants, for example:

generic (
  CLK_CYCLE_TIME  : time     := 10 ns;
  CLK_HIGH_TIME   : time     := 5 ns;
  DATA_SETUP_TIME : time     := 4 ns;
  DATA_HOLD_TIME  : time     := 4 ns;

Then, generate edges for the testbench to use for providing data, removing data, and for clock rise and clock fall "events":

begin
  -- generate events for data setup, clock rise, data hold and clock fall times:
  data_setup_event <= transport not data_setup_event after CLK_CYCLE_TIME;
  clk_rise_event   <= transport     data_setup_event after DATA_SETUP_TIME;
  clk_fall_event   <= transport     clk_rise_event   after CLK_HIGH_TIME;
  data_hold_event  <= transport     clk_rise_event   after DATA_HOLD_TIME;

  -- actual clock signal generation:
  clk_gen_p:process is
  begin
    wait on clk_rise_event;
    clk <= '1';
    wait on clk_fall_event;
    clk <= '0';
  end process;

Stimulus may then applied (walking one input with vld/rdy protocol used for this example):

  apply_stimulus_p : process is
    variable v_walking_one : std_logic_vector(i_dat'range) := std_logic_vector(to_unsigned(1, i_dat'length));
  begin
    wait on data_setup_event;
    rst <= '1';
    wait on data_hold_event;
    rst <= '0';

    for i in 0 to TCOUNT-1 loop
      wait on data_setup_event;
      i_vld       <= '1';
      i_dat       <= v_walking_one;
      i_side_pipe <= std_logic_vector(to_unsigned(i, i_side_pipe'length));

      wait on clk_rise_event;
      while i_rdy /= '1' loop
        wait on clk_rise_event;
      end loop;

      wait on data_hold_event;
      i_vld       <= '0';
      i_dat       <= (others => 'X');
      i_side_pipe <= (others => 'X');

      -- rotate the walking one:
      v_walking_one := rotate_right(v_walking_one);
    end loop;
    wait;
  end process;

All input data transitions are well away from the active (rising) clock edge. It even catches errors like an extra inversion in the clock.

It's elegant, readable, versatile, and easily modifiable. There are no literals that depend on the clock frequency or data width. This is how I would write it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.