vhdlvhdl入门


备注

VHDL是VHSIC(超高速集成电路)HDL(硬件描述语言)的复合首字母缩写。作为硬件描述语言,它主要用于描述或建模电路。 VHDL是描述电路的理想语言,因为它提供了易于描述并发和顺序行为的语言结构,以及消除建模并发行为时引入的歧义的执行模型。

VHDL通常在两种不同的上下文中进行解释:用于模拟和合成。当解释用于合成时,代码被转换(合成)到被建模的等效硬件元素。在合成期间通常只有VHDL的子集可用,并且支持的语言结构不是标准化的;它是所用合成引擎和目标硬件设备的功能。当VHDL被解释为模拟时,所有语言结构都可用于对硬件行为进行建模。

版本

发布日期
IEEE 1076-1987 1988年3月31日
IEEE 1076-1993 1994年6月6日
IEEE 1076-2000 二零零零年一月三十零日
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 2009-01-26

同步计数器的仿真环境

模拟环境

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
 

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

GTKWave波形

使用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 。所有其他进程将监视此信号,并在它成为true 时永久wait 停止:

  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
 

你好,世界

有许多方法可以打印经典的“Hello world!” VHDL中的消息。最简单的可能是:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;
 

安装或设置

可以模拟或合成VHDL程序。模拟与其他编程语言中的大多数执行类似。综合将VHDL程序转换为逻辑门网络。许多VHDL仿真和综合工具是商业电子设计自动化(EDA)套件的一部分。它们还经常处理其他硬件描述语言(HDL),如Verilog,SystemVerilog或SystemC。存在一些免费和开源应用程序。

VHDL仿真

GHDL可能是最成熟的免费和开源VHDL模拟器。根据使用的后端,它有三种不同的风格: gccllvmmcode 。以下示例显示如何在GNU / Linux操作系统下使用GHDL( mcode 版本)和Modelsim(Mentor Graphics的商业HDL模拟器)。与其他工具和其他操作系统非常相似。

你好,世界

创建一个包含以下内容的文件hello_world.vhd

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;
 

VHDL编译单元是一个完整的VHDL程序,可以单独编译。 实体是VHDL编译单元,用于描述数字电路的外部接口,即其输入和输出端口。在我们的示例中, entity 名为hello_world ,为空。我们正在建模的电路是一个黑盒子,没有输入也没有输出。 体系结构是另一种类型的编译单元。它们始终与entity 相关联,它们用于描述数字电路的行为。一个实体可以具有一个或多个体系结构来描述实体的行为。在我们的示例中, 实体仅与一个名为arc 体系结构相关联,该体系结构仅包含一个VHDL语句:

  assert false report "Hello world!" severity note;
 

该声明将在模拟开始时执行并打印Hello world! 标准输出上的消息。然后模拟将结束,因为没有更多的事情要做。我们编写的VHDL源文件包含两个编译单元。我们可以将它们分成两个不同的文件但我们不能将它们中的任何一个拆分为不同的文件:编译单元必须完全包含在一个源文件中。请注意,此架构无法合成,因为它没有描述可以直接转换为逻辑门的函数。

使用GHDL分析并运行程序:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!
 

gh_work 目录是GHDL存储它生成的文件的位置。这就是--workdir=gh_work 选项所说的。分析阶段检查语法正确性并生成描述源文件中找到的编译单元的文本文件。运行阶段实际上编译,链接和执行程序。请注意,在mcode 版本中,不会生成任何二进制文件。每次我们模拟它时都会重新编译程序。 gccllvm 版本的行为有所不同。另请注意, ghdl -r 不会使用VHDL源文件的名称,例如ghdl -a ,而是编译单元的名称。在我们的例子中,我们传递entity 的名称。由于它只有一个相关的architecture ,因此无需指定要模拟的architecture

使用Modelsim:

$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
#    Time: 0 ns  Iteration: 0  Instance: /hello_world
...
 

vlibvmapvcomvsim 是Modelsim提供的四个命令。 vlib 创建一个目录( ms_work ),其中将存储生成的文件。 vmapvlib 创建的vlib 与逻辑名称( work )相关联。 vcom 编译VHDL源文件,默认情况下,将结果存储在与work 逻辑名关联的目录中。最后, vsim 模拟程序并生成与GHDL相同的输出。再次注意, vsim 要求的不是源文件,而是已编译的编译单元的名称。 -c 选项告诉模拟器以命令行模式而不是默认的图形用户界面(GUI)模式运行。 -do 选项用于在加载设计后传递TCL脚本以执行。 TCL是EDA工具中经常使用的脚本语言。 -do 选项的值可以是文件的名称,或者像我们的示例中一样,是一串TCL命令。 run -all; quit 指示模拟器运行模拟直到它自然结束 - 或永远永远 - 然后退出。

信号与变量,VHDL仿真语义的简要概述

这个例子涉及VHDL语言最基本的方面之一:模拟语义。它适用于VHDL初学者,并提供了一个简化的视图,其中省略了许多细节(推迟的过程,VHDL过程接口,共享变量......)对真正的完整语义感兴趣的读者应参考语言参考手册(LRM)。

信号和变量

大多数经典命令式编程语言都使用变量。它们是价值容器。赋值运算符用于在变量中存储值:

a = 15;
 

并且可以读取当前存储在变量中的值并在其他语句中使用:

if(a == 15) { print "Fifteen" }
 

VHDL也使用变量,它们与大多数命令式语言具有完全相同的角色。但VHDL还提供了另一种价值容器:信号。信号还存储值,也可以分配和读取。可以存储在信号中的值的类型(几乎)与变量中的相同。

那么,为什么有两种价值容器呢?这个问题的答案是必不可少的,也是语言的核心。理解变量和信号之间的差异是在尝试用VHDL编程之前要做的第一件事。

让我们在一个具体的例子中说明这种差异:交换。

注意:以下所有代码段都是进程的一部分。我们稍后会看到什么是流程。

    tmp := a;
    a   := b;
    b   := tmp;
 

交换变量ab 。执行这些指令3之后,新内容a 是旧的内容b ,反之亦然。与大多数编程语言一样,需要第三个临时变量( tmp )。如果我们想要交换信号而不是变量,我们会写:

    r <= s;
    s <= r;
 

要么:

    s <= r;
    r <= s;
 

结果相同,无需第三个临时信号!

注意:VHDL信号赋值运算符<= 与变量赋值运算符不同:=

让我们看一下第二个例子,其中我们假设print 子程序打印其参数的十进制表示。如果a 是整数变量且其当前值为15,则执行:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);
 

将打印:

5
 

如果我们在调试器中逐步执行此操作,我们可以看到从最初的15到30,25和最后5 a 变化值。

但如果s 是整数信号且其当前值为15,则执行:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
 

将打印:

15
3
 

如果我们在调试器中逐步执行此操作,则在wait 指令之后才会看到s 任何值更改。而且, s 的最终值不会是15,30,25或5而是3!

这种看似奇怪的行为是由于数字硬件的基本并行性质,我们将在以下部分中看到。

排比

VHDL是硬件描述语言(HDL),它本质上是并行的。 VHDL程序是并行运行的顺序程序的集合。这些顺序程序称为进程:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;
 

这些过程就像它们正在建模的硬件一样永远不会结束:它们是无限循环。执行完最后一条指令后,继续执行第一条指令。

与支持一种或另一种并行形式的任何编程语言一样,调度程序负责决定在VHDL模拟期间执行哪个进程(以及何时执行)。此外,该语言为进程间通信和同步提供了特定的结构。

调度

调度程序维护所有进程的列表,并为每个进程记录其当前状态,该状态可以是running ,可run-able 或已suspendedrunning 状态中最多只有一个进程:当前正在执行的进程。只要当前正在运行的进程不执行wait 指令,它就会继续运行并阻止执行任何其他进程。 VHDL调度程序不是抢占式的:每个进程都有责任暂停自身并让其他进程运行。这是VHDL初学者经常遇到的问题之一:自由运行过程。

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;
 

注意:变量a 在本地声明,而信号sr 在其他地方声明,在更高的级别。 VHDL变量是声明它们的进程的本地变量,并且其他进程无法看到它们。另一个进程也可以声明一个名为a 的变量,它不会与进程P3 变量相同。

一旦调度程序恢复P3 过程,模拟将停滞不前,模拟当前时间将不再进行,停止此操作的唯一方法是终止或中断模拟。原因是P3 没有wait 语句,因此将永远保持running 状态,循环其3条指令。没有其他进程将永远不会有机会运行,即使它是run-able

即使包含wait 语句的进程也会导致同样的问题:

  P4: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    if a = 16 then
      wait on s;
    end if;
    r <= a;
  end process P4;
 

注意:VHDL相等运算符是=

如果在信号s 值为3时恢复过程P4 ,则它将永远运行,因为a = 16 条件永远不会为真。

让我们假设我们的VHDL程序不包含这样的病态过程。当正在运行的进程执行wait 指令时,它立即被挂起并且调度程序将其置于suspended 状态。 wait 指令还带有进程再次变为可run-able 的条件。例:

    wait on s;
 

意味着暂停我直到信号s 的值改变 。调度程序记录此条件。然后,调度器选择之间的另一个进程run-able ,把它放在running 状态,并执行它。并且在所有可run-able 进程被执行和暂停之前重复相同的操作。

重要说明:当多个进程可run-able ,VHDL标准未指定调度程序应如何选择运行哪个进程。结果是,取决于模拟器,模拟器的版本,操作系统或其他任何东西,同一VHDL模型的两个模拟可以在一个点上做出不同的选择并选择要执行的不同过程。如果这个选择对模拟结果有影响,我们可以说VHDL是非确定性的。由于非确定性通常是不可取的,因此程序员有责任避免非确定性情况。幸运的是,VHDL负责这一点,这就是信号进入图片的地方。

信号和进程间通信

VHDL使用两个特定的特征来避免非确定性:

  1. 进程只能通过信号交换信息
  signal r, s: integer;  -- Common to all processes
...
  P5: process
    variable a: integer; -- Different from variable a of process P6
  begin
    a := s + 1;
    r <= a;
    a := r + 1;
    wait on s;
  end process P5;

  P6: process
    variable a: integer; -- Different from variable a of process P5
  begin
    a := r + 1;
    s <= a;
    wait on r;
  end process P6;
 

注意:VHDL注释从-- 延伸到行尾。

  1. 在执行过程期间,VHDL信号的值不会改变

每次分配信号时,调度程序都会记录指定的值,但信号的当前值保持不变。这是与分配后立即获取新值的变量的另一个主要区别。

让我们看一下上面的过程P5 的执行,并假设当调度程序恢复它时a=5s=1r=0 。执行指令后a := s + 1; ,变量a 的值发生变化,变为2(1 + 1)。执行下一条指令时r <= a; 它是分配给r a (2)的新值。但r 是一个信号,的电流值r 仍为0。因此,在执行时a := r + 1; ,变量a 取(立即)值1(0 + 1),而不是直觉所说的3(2 + 1)。

当将信号r 真正把它的新的价值?当调度程序执行所有可运行的进程时,它们都将被暂停。这也称为: 在一个三角形循环之后 。只有这样,调度程序才会查看已分配给信号的所有值,并实际更新信号的值。 VHDL仿真是执行阶段和信号更新阶段的交替。在执行阶段,信号的值被冻结。象征性地,我们说在执行阶段和随后的信号更新阶段之间经过了时间的增量 。这不是实时的。 增量周期没有物理持续时间。

由于这种延迟的信号更新机制,VHDL是确定性的。进程只能与信号通信,并且在执行进程期间信号不会发生变化。因此,进程的执行顺序无关紧要:它们的外部环境(信号)在执行期间不会改变。让我们在前面的例子中用过程P5P6 ,其中初始状态是P5.a=5P6.a=10s=17r=0 并且调度程序决定先运行P5 而下一步运行P6 。下表显示了两个变量的值,即执行每个过程的每条指令后的信号的当前值和下一个值:

过程/指令 P5.a P6.a s.current s.next r.current r.next
初始状态 10 17 0
P5 / a := s + 1 18 10 17 0
P5 / r <= a 18 10 17 0 18
P5 / a := r + 1 1 10 17 0 18
P5 / wait on s 1 10 17 0 18
P6 / a := r + 1 1 1 17 0 18
P6 / s <= a 1 1 17 1 0 18
P6 / wait on r 1 1 17 1 0 18
信号更新后 1 1 1 18

在相同的初始条件下,如果调度程序决定先运行P6 而下一步运行P5

过程/指令 P5.a P6.a s.current s.next r.current r.next
初始状态 10 17 0
P6 / a := r + 1 1 17 0
P6 / s <= a 1 17 1 0
P6 / wait on r 1 17 1 0
P5 / a := s + 1 18 1 17 1 0
P5 / r <= a 18 1 17 1 0 18
P5 / a := r + 1 1 1 17 1 0 18
P5 / wait on s 1 1 17 1 0 18
信号更新后 1 1 1 18

正如我们所看到的,在执行我们的两个进程之后,无论执行顺序如何,结果都是相同的。

这种反直觉的信号分配语义是VHDL初学者经常遇到的第二类问题的原因:由于延迟了一个delta周期,显然不起作用的分配。在调试器中逐步运行进程P5 ,在为r 分配18并且a 已分配r + 1 ,可以预期a 值为19,但调试器固执地说r=0a=1 ...

注意:在同一执行阶段可以多次分配相同的信号。在这种情况下,它是决定信号下一个值的最后一个赋值。其他任务完全没有效果,就像它们从未被执行过一样。

是时候检查我们的理解了:请回到我们的第一个交换示例并尝试理解原因:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;
 

实际上交换信号rs 而不需要第三个临时信号,为什么:

  process
  begin
    ---
    r <= s;
    s <= r;
    ---
  end process;
 

将完全相同。尝试理解为什么,如果s 是整数信号且其当前值为15,我们执行:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;
 

信号s 的两个第一个分配没有效果,为什么s 最终分配为3,为什么两个打印值为15和3。

物理时间

为了对硬件进行建模,能够对某些操作所花费的物理时间进行建模非常有用。以下是如何在VHDL中完成此操作的示例。该示例为同步计数器建模,它是一个完整的,自包含的VHDL代码,可以编译和模拟:

-- File counter.vhd
entity counter is
end entity counter;

architecture arc of counter is
  signal clk: bit; -- Type bit has two values: '0' and '1'
  signal c, nc: natural; -- Natural (non-negative) integers
begin
  P1: process
  begin
    clk <= '0';
    wait for 10 ns; -- Ten nano-seconds delay
    clk <= '1';
    wait for 10 ns; -- Ten nano-seconds delay
  end process P1;

  P2: process
  begin
    if clk = '1' and clk'event then
      c <= nc;
    end if;
    wait on clk;
  end process P2;

  P3: process
  begin
    nc <= c + 1 after 5 ns; -- Five nano-seconds delay
    wait on c;
  end process P3;
end architecture arc;
 

在过程P1wait 指令不用于等待信号的值改变,就像我们到目前为止看到的那样,而是等待给定的持续时间。该过程模拟时钟发生器。信号clk 是我们系统的时钟,周期为20 ns(50 MHz)并具有占空比。

进程P2 对寄存器进行建模,如果刚出现clk 的上升沿,则将其输入nc 的值分配给其输出c ,然后等待clk 的下一个值更改。

过程P3 模拟增量器,该增量器将其输入c 的值(以1递增)分配给其输出nc ...,物理延迟为5 ns。然后它等待,直到其输入c 的值改变。这也是新的。到目前为止,我们总是分配信号:

  s <= value;
 

由于前面部分解释的原因,我们可以隐含地翻译成:

  s <= value; -- after delta
 

这个小型数字硬件系统可以用下图表示:

同步计数器

随着物理时间的引入,并且知道我们还有以三角形测量的符号时间,我们现在有一个二维时间我们将表示T+D ,其中T 是以纳秒为单位测量的物理时间和D 数字增量(没有实际持续时间)。

完整的图片

我们尚未讨论VHDL仿真的一个重要方面:在执行阶段之后,所有进程都处于suspended 状态。我们非正式地说,调度程序然后更新已分配的信号的值。但是,在我们的同步计数器示例中,它是否应同时更新信号clkcnc ?物理延误怎么样?接下来会发生什么事情,所有进程都处于suspended 状态而没有处于可run-able 状态?

完整(但简化)的模拟算法如下:

  1. 初始化
    • 将当前时间Tc 设置为0 + 0(0 ns,0 delta-cycle)
    • 初始化所有信号。
    • 执行每个进程,直到它挂起wait 语句。
      • 记录信号分配的值和延迟。
      • 记录恢复过程的条件(延迟或信号变化)。
    • 计算下一次Tn 最早的时间:
      • 进程的恢复时间wait for <delay> 暂停。
      • 下一次信号值应改变的时间。
  1. 模拟周期
    • Tc=Tn
    • 更新需要的信号。
    • 将所有正在等待其中一个已更新信号的值更改的进程置于可run-able 状态。
    • wait for <delay> 语句暂停的所有进程置于可run-able 状态,并且恢复时间为Tc
    • 执行所有可运行的进程,直到它们挂起。
      • 记录信号分配的值和延迟。
      • 记录恢复过程的条件(延迟或信号变化)。
    • 计算下一次Tn 最早的时间:
      • 进程的恢复时间wait for <delay> 暂停。
      • 下一次信号值应改变的时间。
    • 如果Tn 为无穷大,则停止模拟。否则,开始一个新的模拟循环。

手动模拟

最后,让我们现在在上面介绍的同步计数器上手动执行简化的仿真算法。我们任意决定,当几个进程可运行时,顺序将是P3 > P2 > P1 。下表表示初始化和第一个模拟周期期间系统状态的演变。每个信号都有自己的列,其中指示了当前值。当执行信号分配时,调度值将附加到当前值,例如a/b@T+D 如果当前值为a ,下一个值将为b 时间T+D (物理时间加上delta周期) 。最后3列表示恢复暂停进程的条件(必须更改的信号名称或进程恢复的时间)。

初始化阶段:

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 0 + 0
初始化所有信号 0 + 0 '0' 0 0
P3/nc<=c+1 after 5 ns 0 + 0 '0' 0 0/1 @ 5 + 0
P3/wait on c 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/if clk='1'... 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/end if 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/wait on clk 0 + 0 '0' 0 0/1 @ 5 + 0 clk c
P1/clk<='0' 0 + 0 '0'/ '0' @ 0 + 1 0 0/1 @ 5 + 0 clk c
P1/wait for 10 ns 0 + 0 '0'/ '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
下次计算 0 + 0 0 + 1 '0'/ '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

模拟周期#1

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 0 + 1 '0'/ '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
更新信号 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
下次计算 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

注意:在第一个模拟周期中没有执行阶段,因为我们的3个进程都没有满足其恢复条件。 P2 等待的价值变化clk 和出现在了交易 clk ,但是随着新旧值是相同的,这不是一个值的变化

模拟周期#2

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
更新信号 5 + 0 '0' 0 1 10 + 0 clk c
下次计算 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

注意:同样,没有执行阶段。 nc 更改,但没有进程正在等待nc

模拟周期#3

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 10 + 0 '0' 0 1 10 + 0 clk c
更新信号 10 + 0 '0' 0 1 10 + 0 clk c
P1/clk<='1' 10 + 0 '0'/ '1' @ 10 + 1 0 1 clk c
P1/wait for 10 ns 10 + 0 '0'/ '1' @ 10 + 1 0 1 20 + 0 clk c
下次计算 10 + 0 10 + 1 '0'/ '1' @ 10 + 1 0 1 20 + 0 clk c

模拟周期#4

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 10 + 1 '0'/ '1' @ 10 + 1 0 1 20 + 0 clk c
更新信号 10 + 1 '1' 0 1 20 + 0 clk c
P2/if clk='1'... 10 + 1 '1' 0 1 20 + 0 c
P2/c<=nc 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/end if 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
下次计算 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

模拟周期#5

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
更新信号 10 + 2 '1' 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 '1' 1 二分之一@ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 '1' 1 二分之一@ 15 + 0 20 + 0 clk c
下次计算 10 + 2 15 + 0 '1' 1 二分之一@ 15 + 0 20 + 0 clk c

注意:可以认为nc 更新将安排在15+2 ,而我们安排在15+0 。当向当前时间( 10+2 )添加非零物理延迟(此处为5 ns )时,增量周期消失。实际上,delta周期仅用于区分具有相同物理时间T 不同模拟时间T+0T+1 ....一旦物理时间改变,就可以重置增量周期。

模拟周期#6

操作 Tc Tn clk c nc P1 P2 P3
设置当前时间 15 + 0 '1' 1 二分之一@ 15 + 0 20 + 0 clk c
更新信号 15 + 0 '1' 1 2 20 + 0 clk c
下次计算 15 + 0 20 + 0 '1' 1 2 20 + 0 clk c

注意:同样,没有执行阶段。 nc 更改,但没有进程正在等待nc

模拟...切换到英语继续阅读

同步计数器

-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
  port(
    clock: in  bit;    -- We are using the rising edge of CLOCK
    reset: in  bit;    -- Synchronous and active HIGH
    data:  out natural -- The current value of the counter
  );
end entity counter;

-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
  -- The internal signals we use to count. Natural is another standard
  -- type. VHDL is not case sensitive.
  signal current_value: natural;
  signal NEXT_VALUE:    natural;
begin
  -- A process is a concurrent statement. It is an infinite loop.
  process
  begin
    -- The wait statement is a synchronization instruction. We wait
    -- until clock changes and its new value is '1' (a rising edge).
    wait until clock = '1';
    -- Our reset is synchronous: we consider it only at rising edges
    -- of our clock.
    if reset = '1' then
      -- <= is the signal assignment operator.
      current_value <= 0;
    else
      current_value <= next_value;
    end if;
  end process;

  -- Another process. The sensitivity list is another way to express
  -- synchronization constraints. It (approximately) means: wait until
  -- one of the signals in the list changes and then execute the process
  -- body. Sensitivity list and wait statements cannot be used together 
  -- in the same process.
  process(current_value)
  begin
    next_value <= current_value + 1;
  end process;

  -- A concurrent signal assignment, which is just a shorthand for the
  -- (trivial) equivalent process.
  data <= current_value;
end architecture sync;