同步计数器的仿真环境

模拟环境

VHDL 设计(被测设计或 DUT)的仿真环境是另一种 VHDL 设计,至少:

  • 声明对应于 DUT 的输入和输出端口的信号。
  • 实例化 DUT 并将其端口连接到声明的信号。
  • 实例化驱动连接到 DUT 输入端口的信号的过程。

可选地,仿真环境可以实例化除 DUT 之外的其他设计,例如,接口上的流量生成器,用于检查通信协议的监视器,DUT 输出的自动验证器……

对仿真环境进行分析,阐述和执行。大多数模拟器提供了选择一组信号进行观察,绘制图形波形,在源代码中放置断点,步入源代码的可能性……

理想情况下,仿真环境应该可用作稳健的非回归测试,也就是说,它应该自动检测违反 DUT 规范的情况,报告有用的错误消息并保证 DUT 功能的合理覆盖。当这样的仿真环境可用时,可以在 DUT 的每次更改时重新运行它们以检查它是否仍然在功能上是正确的,而不需要对模拟迹线进行繁琐且容易出错的视觉检查。

在实践中,设计理想的甚至是好的模拟环境是一项挑战。它经常比设计 DUT 本身更难,甚至更难。

在此示例中,我们为同步计数器示例提供了一个模拟环境。我们将展示如何使用 GHDL 和 ModelSim 以及如何观察使用图形的波形来运行它 GTKWave与 GHDL 并采用 ModelSim 内置的波形显示器。然后我们讨论模拟的一个有趣方面:如何阻止它们?

同步计数器的第一个仿真环境

同步计数器有两个输入端口和一个输出端口。一个非常简单的模拟环境可能是:

-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;

architecture sim of counter_sim is

  -- One signal per port of the DUT. Signals can have the same name as
  -- the corresponding port but they do not need to.
  signal clk:  bit;
  signal rst:  bit;
  signal data: natural;

begin

  -- Instantiation of the DUT
  u0: entity work.counter(sync)
  port map(
    clock => clk,
    reset => rst,
    data  => data
  );

  -- A clock generating process with a 2ns clock period. The process
  -- being an infinite loop, the clock will never stop toggling.
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
  end process;

  -- The process that handles the reset: active from beginning of
  -- simulation until the 5th rising edge of the clock.
  process
  begin
    rst  <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst  <= '0';
    wait; -- Eternal wait. Stops the process forever.
  end process;

end architecture sim;

用 GHDL 模拟

让我们用 GHDL 编译和模拟这个:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"

然后错误消息告诉我们两个重要的事情:

  • GHDL 分析器发现我们的设计实例化了一个名为 counter 的实体,但是这个实体在库 work 中找不到。这是因为我们没有在 counter_sim 之前编译 counter。在编译实例化实体的 VHDL 设计时,必须始终在最高级别之前编译底层(层次结构设计也可以自上而下编译,但前提是它们实例化 component,而不是实体)。
  • 我们的设计使用的 rising_edge 函数没有定义。这是因为这个函数是在 VHDL 2008 中引入的,我们没有告诉 GHDL 使用这个版本的语言(默认情况下它使用 VHDL 1993,容忍 VHDL 1987 语法)。

让我们修复这两个错误并启动模拟:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C

请注意,分析模拟需要 --std=08 选项。另请注意,我们在实体 counter_sim,架构 sim 上启动了模拟,而不是在源文件上。

由于我们的模拟环境具有永无止境的过程(生成时钟的过程),因此模拟不会停止,我们必须手动中断它。相反,我们可以使用 --stop-time 选项指定停止时间:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time

因此,模拟并没有告诉我们很多关于 DUT 的行为。让我们转储文件中信号的值变化:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time

(忽略错误消息,这是需要在 GHDL 中修复的东西,并且没有任何后果)。已创建 counter_sim.vcd 文件。它包含 VCD(ASCII) 格式模拟期间的所有信号变化。GTKWave 可以向我们展示相应的图形波形:

$ gtkwave counter_sim.vcd

我们可以看到计数器按预期工作。

StackOverflow 文档

使用 Modelsim 进行模拟

与 Modelsim 的原理完全相同:

$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'

StackOverflow 文档

注意传递给 vsim-voptargs="+acc" 选项:它可以防止模拟器优化 data 信号,并允许我们在波形上看到它。

优雅地结束模拟

使用两个模拟器,我们必须中断永无止境的模拟或使用专用选项指定停止时间。这不是很方便。在许多情况下,很难预测模拟的结束时间。当达到特定条件时,例如,当计数器的当前值达到 20 时,从仿真环境的 VHDL 代码内部停止仿真会更好。这可以通过在处理重置的进程:

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    loop
      wait until rising_edge(clk);
      assert data /= 20 report "End of simulation" severity failure;
    end loop;
  end process;

只要 data 与 20 不同,模拟就会继续。当 data 达到 20 时,模拟崩溃并显示错误消息:

$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
  from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed

请注意,我们仅重新编译了仿真环境:它是唯一改变的设计,它是顶级的。如果我们只修改了 counter.vhd,我们将不得不重新编译两个:counter.vhd 因为它改变而 counter_sim.vhd 因为它取决于 counter.vhd

使用错误消息破坏模拟并不是很优雅。在自动解析模拟消息以确定是否通过自动非回归测试时,它甚至可能是一个问题。更好,更优雅的解决方案是在达到条件时停止所有进程。例如,这可以通过添加 boolean 模拟结束(eof)信号来完成。默认情况下,它在模拟开始时初始化为 false。当时间结束模拟时,我们的一个过程将把它设置为 true。所有其他过程将监视此信号,并在它将成为 tihuan 时停止一个永恒的 true

  signal eos:  boolean;
...
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
    if eos then
      report "End of simulation";
      wait;
    end if;
  end process;

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    eos <= true;
    wait;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation

最后但并非最不重要的是,VHDL 2008 中引入了更好的解决方案,标准包 env 以及它声明的 stopfinish 程序:

use std.env.all;
...
  process
  begin
    rst    <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst    <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    finish;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns