同步计数器的仿真环境
模拟环境
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
我们可以看到计数器按预期工作。
使用 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'
注意传递给 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
以及它声明的 stop
和 finish
程序:
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