Richtige Taktgenerierung für VHDL-Testbenches

7

In vielen Prüfständen sehe ich das folgende Muster für die Taktgenerierung:

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

In anderen Fällen sehe ich:

clk <= not clk after 10 ns;

Letzteres gilt als besser, da es geplant wird, bevor ein Prozess ausgeführt wird, und somit Signale, die synchron zur Clk-Kante geändert werden, ordnungsgemäß behandelt werden. Die folgenden Abschnitte des LRM scheinen diese Theorie zu unterstützen:

Seite 169: 12.6.4 Der Simulationszyklus Ein Simulationszyklus besteht aus folgenden Schritten:

  • b) Jedes aktive explizite Signal im Modell wird aktualisiert. (Infolgedessen können Ereignisse auf Signalen auftreten.)

Dies sollten die Signale mit einem neuen projizierten Wert sein, wie z. B. Signale, die durch die verzögert werden after.

  • d) Wenn P für jeden Prozess P derzeit für ein Signal S empfindlich ist und in diesem Simulationszyklus ein Ereignis auf S aufgetreten ist, wird P fortgesetzt.

Das wäre der größte Teil der zu simulierenden Logik

  • e) Jeder nicht verschobene Prozess, der im aktuellen Simulationszyklus fortgesetzt wurde, wird ausgeführt, bis er angehalten wird.

Und jetzt werden alle Prozesse ausgeführt, die von a angehalten wait forwerden.

TL; DR:

  • Ist die afterMethode der Methode immer überlegen wait for?
  • Hilft es, die Probleme beim synchronen Setzen von Eingangssignalen zu vermeiden?
Karsten Becker
quelle
Die richtige Lösung besteht natürlich darin, die Eingangssignale nicht gleichzeitig mit der Flanke zu ändern. Dies ist jedoch nicht der Punkt dieser Diskussion hier.
Karsten Becker
Was meinst du mit "Signale synchron zu clk geändert"? o Sie meinen Signale, die sich auch bei Vielfachen von 10 ns (bis wait fors) ändern, oder Signale, die von der Flanke des Clk ausgelöst werden und daher einen Delta-Zyklus später sind?
Martin Thompson
Haben Sie auch einen Hinweis auf die Behauptung, dass die afterVersion besser ist? Mir wurde noch nie beigebracht, dass es besser ist, ich bevorzuge es einfach, da es eine einzelne Zeile ist :)
Martin Thompson
Sie können dieses Beispiel hier ansehen : edaplayground.com/x/J8_ Die Clock-Entität zeigt das schlechte Verhalten, die Clock2-Entität scheint es zu beheben, bis Clock3 es erneut bricht.
Karsten Becker
Und ja, ich meine, die Eingänge mit steigender Flanke mit wait untiloder ändern wait for.
Karsten Becker

Antworten:

6

Der Simulator kann nicht wirklich dafür verantwortlich gemacht werden, dass er sich manchmal so verhält, als ob die Uhr unmittelbar nach oder unmittelbar vor der Änderung des Eingangs passiert wäre, wenn Sie beide clkund Eingänge mit zuweisen wait for. Zu fragen, ob ein Stil dem anderen überlegen oder unterlegen ist, ist in gewisser Weise die falsche Frage. Sie müssen das Verhalten nicht mehrdeutig angeben, wenn Sie eine deterministische und nicht mehrdeutige Ausgabe wünschen.

Was ich jahrelang gemacht habe und für mich ziemlich fehlerfrei gearbeitet habe (für synchrone Designs), ist, die ihnen vorangestellten Eingaben mit wait until rising_edge(clk)oder zuzuweisen wait until falling_edge(clk). Wie Sie das generieren, clkwird unwichtig. Für einfache Testbenches der afterEinzeiler macht den Job gut und prägnant, aber bietet nicht die Flexibilität eines processmit wait foroder wait untilAussagen.

Ich habe ein kleines einfaches Verfahren, das mir gute Dienste geleistet hat:

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;

Was ich in einem aufbewahre tb_pkg.vhd, den ich immer usein Testbenches habe.

Ein Anwendungsbeispiel könnte sein:

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;

Einige Designer weisen ihre Stimulussignale an der gegenüberliegenden Kante dem zu, für das das zu testende Gerät empfindlich ist. Ich persönlich mache das nicht gerne, weil es nicht so ist, wie der Rest der Schaltung simuliert, wo sich die Signale an der Triggerflanke ändern. Aber an diesem Ansatz ist sicherlich nichts auszusetzen. Das obige Verfahren kann auch dafür verwendet werden.

Apalopohapa
quelle
Ich ziehe es vor, die Signale an der gegenüberliegenden Kante zu ändern, da dies näher an den meisten Datenblättern liegt. Auch rein optisch könnte es so aussehen, als ob die Ausgabe auf den Signalen basiert, die nach dem Umgang mit der Uhr angelegt wurden. Aber ich mag deine kleine Funktion wirklich. Gute Idee.
Karsten Becker
3

Ich habe diesen Thread vor langer Zeit gelesen, hatte aber keine Zeit zu antworten, bis ich in den Ruhestand ging.

Es ist interessant zu sehen, wie verschiedene Leute ihre Testbench-Uhren erstellen und anhand welcher Kriterien beurteilt wird, wie "gut" sie sind. Für mich würde ich mir den Code ansehen und fragen: Wie vielseitig ist er? Wie schwer ist es zu ändern, wenn sich die Taktrate ändert? Oder wenn ich die Testbench in einer Back-Annotated-Gate-Simulation verwenden möchte?

Hier ist mein sehr langer Weg, um sicherzustellen, dass Änderungen einfach sind und die Testbench vielseitig einsetzbar ist:

Die Taktrate, die Dateneinrichtungszeit und die Datenhaltezeiten sollten als Generika oder Konstanten definiert werden, zum Beispiel:

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;

Generieren Sie dann Kanten für die Testbench, die zum Bereitstellen von Daten, Entfernen von Daten sowie für "Ereignisse" zum Anstieg und Abfall der Uhr verwendet werden sollen:

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;

Es kann dann ein Stimulus angewendet werden (Gehen eines Eingangs mit dem für dieses Beispiel verwendeten vld / rdy-Protokoll):

  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;

Alle Eingangsdatenübergänge sind weit entfernt von der aktiven (ansteigenden) Taktflanke. Es fängt sogar Fehler wie eine zusätzliche Inversion in der Uhr ab.

Es ist elegant, lesbar, vielseitig und leicht zu ändern. Es gibt keine Literale, die von der Taktfrequenz oder der Datenbreite abhängen. So würde ich es schreiben.

J. Corona
quelle