
前一段时间,在专业FPGA开发人员公司的讨论中,引发了有关通过面试的讨论。 在那里问什么问题,可以问什么。 我提出了两个问题:
- 给出一个不使用延迟的同步代码示例,这将在建模和在实际设备中工作时产生不同的结果
- 延迟更正此代码。
在提出这个问题之后,进行了热烈的讨论,结果,我决定更详细地考虑这个问题。
我已经在
上一篇文章中谈到了这个问题。 现在更详细。 这是一个示例文本:
library IEEE; use IEEE.STD_LOGIC_1164.all; entity delta_delay is end delta_delay; architecture delta_delay of delta_delay is signal clk1 : std_logic:='0'; signal clk2 : std_logic; alias clk3 : std_logic is clk1;
为了简单起见,所有代码都放在一个组件中。
信号
clk1和
a是测试曝光信号。
clk1是100 MHz的时钟频率,信号
a保持两个时钟周期为0,四个时钟周期为1。信号
a相对于
clk1的上升沿具有1 nc的延迟。 这两个信号足以描述问题。
不同的合成代码选项可以不加注释和建模。
考虑第一个选项,这是没有延迟并且使用时钟频率的重新分配的合成代码。
这是选项1的仿真结果:

该图从视觉上显示了时钟信号
clk1和
clk2一致,但实际上
clk2相对于
clk1延迟了delta delay的值。 信号
c比信号
b滞后一个时钟周期。 这是正确的。 但是信号
d必须与信号
c一致,但这不会发生。 它可以更早地工作。
让我们记住什么是delta delay。 这是一个基本概念,它基于事件模拟器的工作,我们在对逻辑电路进行建模时会使用它。
模拟器具有模型时间的概念。 系统中的所有事件都将附加到该模型时间。 让我们看一下时钟频率的形成:
clk1 <= not clk1 after 5 ns;
假设现在我们仅建模
clk1 ,没有其他信号。
在初始时间
clk1为0,在声明信号时设置。 模拟器看到了将信号反相的要求。 after关键字给出指令,以相对于当前模型时间在5 ns内分配一个新值。 模拟器看到了这一点,并指出在时间5 ns时,
clk1的值将为1。尽管这是将来的模型,但它可能仍会改变。 接下来,模拟器扫描剩余的信号。 模拟器将看到,在模型时间的给定时刻,一切都已完成,他可以计算下一个时刻。 问题出现了-下一个时刻是什么? 原则上,可能有不同的选择。 例如,Simulink具有固定音高模式。 在这种情况下,模型时间将增加一定量,并且计算将继续。
数字电路仿真系统的功能有所不同。 他们继续进行下一个事件,他们已经将其放置在将来的模型时间轴上。 在这种情况下,将为5 ns。 模拟器将看到
clk1已更改,并将为其计算一个新值,该值将为0,该值还将在时间轴上放置5 ns的延迟。 即 这将是10 ns。 因此,该过程将继续进行,直到指定的模拟时间结束为止。
现在让我们添加信号
a和
b 。
在此过程中分配了信号
a 。 对于信号
b ,使用时的条件构造。 上升边缘(
clk1 )函数解析
clk1并在前端固定时返回
true ,即 前一个值为0,当前值为1。
在模型时间5 ns时,
clk1将改变。 它将等于1,并且在10 ns的瞬间将创建一个将其设置为0的事件。但这是稍后的事件。 当我们仍处于5 ns的时刻时,我们继续进行计算。 模拟器上线
b<=a when rising_edge(clk1);
由于有一个依赖于
clk1的函数
,因此模拟器将计算该函数的值,看它返回true并赋值
b<=a;
在这里最有趣的部分开始-当有必要更改
b的值时。 现在似乎需要对此进行更改。 但是我们有并行的流程。 也许我们仍然需要
b的值来计算其他信号。 这就是增量延迟的概念。 这是模型时间偏移的最小值。 该值甚至没有时间维度。 这只是一个三角洲。 但是可以有很多。 如此之多,以至于模拟器只是错误地停止或冻结。
因此,将在5 ns +1的时刻设置新的
b值(1是第一个增量延迟)。 模拟器将看到在5 ns的时刻已经没有什么可计算,并且会转到下一个时刻,这将是5 ns +1。 目前,rising_edge(ckl1)不起作用。 b的值将设置为1。之后,模拟器将转至10 nc时刻。
现在让我们添加信号
c ,
d ,看看它们为什么不同。
最好考虑模型延迟25 ns的时刻,并考虑到增量延迟
三角洲 | clk1 | clk2 | 重新(clk1) | 重新(clk2) | b | ç | d |
---|
0 | 1个 | 0 | 是真的 | 错误的 | 0 | 0 | 0 |
---|
1个 | 1个 | 1个 | 错误的 | 是真的 | 1个 | 0 | 0 |
---|
2 | 1个 | 0 | 错误的 | 错误的 | 1个 | 0 | 1个 |
---|
注意:重新-上升边缘
该表显示,在触发rising_edge(
clk2 )函数的时刻,
b的值已经为1。因此,它将被分配给信号
d 。
基于常识,这不是我们期望的代码行为。 毕竟,我们只是将信号
clk1重新分配给
clk2,并期望信号
c和
d相同。 但是按照模拟器的逻辑,事实并非如此。 这是
主要功能。 当然,FPGA项目的开发人员必须知道此功能,因此这是采访的一个很好且必要的问题。
综合过程中会发生什么? 但是合成器将遵循常识,它将使信号
clk2和
clk1成为一个信号,因此
c和
d也将相同。 通过某些合成器设置,它们也将被组合为一个信号。
当在实际设备中进行建模和工作会导致不同的结果时,就是这种情况。 我想指出,产生不同结果的原因是模拟器和合成器的逻辑不同。 这是基本的区别。 这与时间限制无关。 如果您的模型和模型中的项目显示不同的结果,请检查一下,也许是这样的设计
clk2 <= clk1
现在第二个问题是延迟修复此代码。
这是选项2。可以不加注释和建模。
这是结果。

结果是正确的。 怎么了 让我们重新制作一张表格,间隔为25-36 ns
时间 | 三角洲 | clk1 | clk2 | 重新(clk1) | 重新(clk2) | b | ç | d |
---|
25 | 0 | 1个 | 0 | 是真的 | 错误的 | 0 | 0 | 0 |
---|
25 | 1个 | 1个 | 1个 | 错误的 | 是真的 | 0 | 0 | 0 |
---|
26 | 0 | 1个 | 1个 | 错误的 | 错误的 | 1个 | 0 | 0 |
---|
35 | 0 | 1个 | 0 | 是真的 | 错误的 | 1个 | 0 | 0 |
---|
35 | 1个 | 1个 | 1个 | 错误的 | 是真的 | 1个 | 0 | 0 |
---|
36 | 0 | 1个 | 1个 | 错误的 | 错误的 | 1个 | 1个 | 1个 |
---|
可以看出,在前沿
clk1 ,
clk2的时刻
b的值没有变化。 信号变化超过边沿响应区域的瞬间就是1 ns的延迟。 这段代码越来越接近现实。 在实际电路中,触发器需要一段时间才能触发,信号也要传播一段时间。 该时间应小于时钟频率的周期,实际上,这是跟踪器所做的,也是时间分析检查的内容。
造成错误的原因是时钟信号通过出现延迟延迟的通常分配重新分配。 但是,VHDL语言具有别名构造。 这使您可以为信号取一个不同的名称。 这是公告:
alias clk3 : std_logic is clk1;
在示例文本中,您可以取消注释选项3-它可以正常工作。
这个例子是用VHDL编写的。 也许这只是这种语言的问题? 但是,这是Verilog中的相同选项。
隐藏文字 `timescale 1 ns / 1 ps module delta_delay_2 (); reg clk1 = 1'b0; reg clk2; wire clk3; reg a = 1'b0; reg b; reg c; reg d; initial begin forever clk1 = #5 ~clk1; end initial begin repeat(10) begin #20 a = 1'b1; #60 a = 1'b0; end end
- 选项1-无延迟。 它不能正常工作。
- 选项2-有延迟。 它可以正常工作。
- 选项3-通过电线重新分配。 它可以正常工作。
Verilog具有reg和wire的概念。 在这种情况下,通过电线重新分配时钟信号看起来更加自然。 这类似于VHDL中的别名分配。 这在某种程度上减轻了问题的压力,但是您仍然需要知道这一点。
Verilog还具有阻塞和非阻塞分配的概念。 信号分配
b和
c可以用另一种方式编写:
always @(posedge clk1) begin c = b; b = a; end
您可以这样做:
always @(posedge clk1) begin b = a; c = b; end
根据行的顺序,结果将有所不同。
回到访谈的话题,我想再次强调,这些问题是为了理解问题的实质。 从对问题的理解中,可以得出各种结论,例如,使用哪种代码样式。 就个人而言,我总是使用延迟分配。
样本文件在这里。