再次关于FPGA项目的源代码延迟或关于FPGA开发人员工作面试的简单问题



前一段时间,在专业FPGA开发人员公司的讨论中,引发了有关通过面试的讨论。 在那里问什么问题,可以问什么。 我提出了两个问题:

  1. 给出一个不使用延迟的同步代码示例,这将在建模和在实际设备中工作时产生不同的结果
  2. 延迟更正此代码。

在提出这个问题之后,进行了热烈的讨论,结果,我决定更详细地考虑这个问题。

我已经在上一篇文章中谈到了这个问题。 现在更详细。 这是一个示例文本:

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 signal a : std_logic; signal b : std_logic; signal c : std_logic; signal d : std_logic; begin ---    --- clk1 <= not clk1 after 5 ns; pr_a: process begin a <= '0' after 1 ns; wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); a <= '1' after 1 ns; wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); wait until rising_edge( clk1 ); end process; ---   -    --- clk2 <= clk1; --    ,        ---  1 -     --- b <= a when rising_edge( clk1 ); c <= b when rising_edge( clk1 ); d <= b when rising_edge( clk2 ); ---  2 -     --- -- --clk2 <= clk1; --b <= a after 1 ns when rising_edge( clk1 ); --c <= b after 1 ns when rising_edge( clk1 ); --d <= b after 1 ns when rising_edge( clk2 ); ---  3 -          alias --- --b <= a when rising_edge( clk1 ); --c <= b when rising_edge( clk1 ); --d <= b when rising_edge( clk3 ); end delta_delay; 

为了简单起见,所有代码都放在一个组件中。

信号clk1a是测试曝光信号。 clk1是100 MHz的时钟频率,信号a保持两个时钟周期为0,四个时钟周期为1。信号a相对于clk1的上升沿具有1 nc的延迟。 这两个信号足以描述问题。

不同的合成代码选项可以不加注释和建模。
考虑第一个选项,这是没有延迟并且使用时钟频率的重新分配的合成代码。

这是选项1的仿真结果:



该图从视觉上显示了时钟信号clk1clk2一致,但实际上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。 因此,该过程将继续进行,直到指定的模拟时间结束为止。

现在让我们添加信号ab

在此过程中分配了信号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时刻。

现在让我们添加信号cd ,看看它们为什么不同。
最好考虑模型延迟25 ns的时刻,并考虑到增量延迟

三角洲clk1clk2重新(clk1)重新(clk2)bçd
01个0是真的错误的000
1个1个1个错误的是真的1个00
21个0错误的错误的1个01个

注意:重新-上升边缘

该表显示,在触发rising_edge( clk2 )函数的时刻, b的值已经为1。因此,它将被分配给信号d

基于常识,这不是我们期望的代码行为。 毕竟,我们只是将信号clk1重新分配给clk2,并期望信号cd相同。 但是按照模拟器的逻辑,事实并非如此。 这是主要功能。 当然,FPGA项目的开发人员必须知道此功能,因此这是采访的一个很好且必要的问题。

综合过程中会发生什么? 但是合成器将遵循常识,它将使信号clk2clk1成为一个信号,因此cd也将相同。 通过某些合成器设置,它们也将被组合为一个信号。

当在实际设备中进行建模和工作会导致不同的结果时,就是这种情况。 我想指出,产生不同结果的原因是模拟器和合成器的逻辑不同。 这是基本的区别。 这与时间限制无关。 如果您的模型和模型中的项目显示不同的结果,请检查一下,也许是这样的设计

 clk2 <= clk1 

现在第二个问题是延迟修复此代码。
这是选项2。可以不加注释和建模。
这是结果。



结果是正确的。 怎么了 让我们重新制作一张表格,间隔为25-36 ns
时间三角洲clk1clk2重新(clk1)重新(clk2)bçd
2501个0是真的错误的000
251个1个1个错误的是真的000
2601个1个错误的错误的1个00
3501个0是真的错误的1个00
351个1个1个错误的是真的1个00
3601个1个错误的错误的1个1个1个

可以看出,在前沿clk1clk2的时刻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 //   -    --- always @(clk1) clk2 <= clk1; //  1 -     always @(posedge clk2) d <= b; always @(posedge clk1) begin c <= b; b <= a; end //  2 -     //always @(posedge clk1) b = #1 a; // //always @(posedge clk1) c = #1 b; // //always @(posedge clk2) d = #1 b; //  3 -     //      assign //assign clk3 = clk1; // //always @(posedge clk3) d <= b; // //always @(posedge clk1) //begin // c <= b; // b <= a; //end endmodule 



  • 选项1-无延迟。 它不能正常工作。
  • 选项2-有延迟。 它可以正常工作。
  • 选项3-通过电线重新分配。 它可以正常工作。

Verilog具有reg和wire的概念。 在这种情况下,通过电线重新分配时钟信号看起来更加自然。 这类似于VHDL中的别名分配。 这在某种程度上减轻了问题的压力,但是您仍然需要知道这一点。
Verilog还具有阻塞和非阻塞分配的概念。 信号分配bc可以用另一种方式编写:

 always @(posedge clk1) begin c = b; b = a; end 

您可以这样做:

 always @(posedge clk1) begin b = a; c = b; end 

根据行的顺序,结果将有所不同。

回到访谈的话题,我想再次强调,这些问题是为了理解问题的实质。 从对问题的理解中,可以得出各种结论,例如,使用哪种代码样式。 就个人而言,我总是使用延迟分配。

样本文件在这里。

Source: https://habr.com/ru/post/zh-CN419875/


All Articles