为什么移位寄存器的这种简单VHDL模式无法按预期工作


8

乍一看,您会期望下面的VHDL源代码充当移位寄存器。在那个q中,随着时间的流逝,

"UUUU0", "UUU00", "UU000", "U0000", "00000", ....

但是它总是U在五个(或更多)连续时钟周期之后。

为什么是这样?

该代码实际上是复杂得多的模拟的简化版本。但这证明了我看到的症状。

它在ModelSim和ActiveHDL下的仿真过程中都显示出这一有趣且出乎意料的结果,我没有尝试过其他仿真器,并且(第二个原因的解释)想知道其他程序是否以相同的方式运行。

要正确回答此问题,您必须了解:

  • 我知道这不是实现移位寄存器的最佳方法
  • 我知道对于RTL综合,应该重新设置。
  • 我知道std_logic的数组是std_logic_vector。
  • 我知道聚合运算符&

我还发现了:

  • 如果将分配temp(0)<='0';移到流程中,则它将起作用。
  • 如果循环已展开(请参见注释的代码),则它将起作用。

我要重申的是,这是一个更为复杂的设计(用于流水线CPU)的非常简化的版本,被配置为纯粹显示意外的仿真结果。实际信号类型只是一种简化。因此,您必须按照原样考虑代码中的答案。

我的猜测是,VHDL仿真引擎的优化器错误地(或按照规范)没有费心地在循环内运行表达式,因为没有外部变化的信号,尽管我可以通过将展开的循环置于循环中来证明这一点。

因此,我希望这个问题的答案更多地与模糊VHDL语法的VHDL仿真标准以及VHDL仿真引擎如何进行优化有关,而不是是否给出代码示例是完成某件事的最佳方法。

现在我要模拟的代码:

 library ieee;
 use ieee.std_logic_1164.all;   

 entity test_simple is
    port (
        clk : in  std_logic;
        q   : out std_logic
    );                   
 end entity;

 architecture example of test_simple is
    type   t_temp is array(4 downto 0) of std_logic;
    signal temp : t_temp;
 begin

    temp(0) <= '0';

    p : process (clk)
    begin               
        if rising_edge(clk) then
            for i in 1 to 4 loop
                    temp(i) <= temp(i - 1);
            end loop;

            --temp(1) <= temp(0);   
            --temp(2) <= temp(1);
            --temp(3) <= temp(2);
            --temp(4) <= temp(3);
        end if;
    end process p;
    q <= temp(4);
 end architecture;

和测试台:

library ieee;
use ieee.std_logic_1164.all;

entity Bench is
end entity;

architecture tb of bench is

component test_simple is
    port (
        clk : in  std_logic;
        q   : out std_logic
    );                   
end component;

signal clk:std_logic:='0';
signal q:std_logic;     
signal rst:std_logic;

constant freq:real:=100.0e3;

begin                       
    clk<=not clk after 0.5 sec / freq;

    TB:process
    begin
        rst<='1';
        wait for 10 us;
        rst<='0';
        wait for 100 us;
        wait;
    end process;

     --Note: rst is not connected
    UUT:test_simple  port map (clk=>clk,q=>q) ;
end architecture;

首先尝试在信号声明中初始化temp,我发现vhdl模拟器对您在哪里初始化东西很古怪
Matt

temp(0)由于没有与文字常量关联的“事件” ,因此模拟器似乎忽略了并发分配。将分配放入process会创建与时钟事件的关联,从而使它起作用。我不知道after在分配中添加一个子句是否可能是一种解决方法。
Dave Tweed 2012年

Answers:


7

它与在细化时可以很容易地进行评估的形式有关,即所谓的“局部静态表达式”。这是一个晦涩的规则,但值得一想-最终它确实是有道理的,并且您的模拟器通过生成非明显的结果来提醒您非常正确。

现在,temp(1)可以在编译时(甚至比精化时间更早)进行评估,并且可以在“ temp”的位1上生成驱动程序。

但是,temp(i)这些工具需要更多的工作。鉴于这里的循环界限的琐碎性质(1到4),对于我们人类来说很明显,无法驱动temp(0),并且您正在做的事情是安全的。但是,想象一下边界是lower(foo) to upper(bar)其他地方声明的包中的函数……现在,您可以肯定地说的temp是被驱动的-因此“局部静态”表达式为temp

这意味着流程受这些规则约束来驱动所有temp驱动程序,此时,您需要多个驱动程序temp(0)-流程驱动程序(无初始值,即'u')和外部驱动程序temp(0) <= '0';。因此,两个驱动程序自然会解析为“ U”。

另一种选择是“ hacky小规则”(观点),如果循环边界是常量,则可以做一件事,但是如果将它们声明为其他东西,则可以做其他事情,依此类推……等等。语言变得越复杂……我认为,这不是更好的解决方案。


好的答案(+1),但我不同意您对“骇人的小规矩”的描述。仿真的全部要点是代表真实硬件的行为。我了解各个模块的独立编译所产生的约束,但是我认为规则应该是在编译时可以评估的任何事物应该如此。这将是一条更为通用的规则,并且将有助于系统遵守“最少惊喜”的原则。让工具执行那些评估对我来说更“棘手”。
Dave Tweed 2012年

公平的评论-例如,Ada对这样的规则有(并正式表示)更多的复杂性,并且设法向我们的用户提供了更简单的视图(没有C的WTF因子)。VHDL最初是从Ada简化的(IMO有点过分)。但是,也许它可以采用Ada的“类型冻结”规则,从而在明显安全的情况下(如此处)允许进行此类优化,并以其他方式禁止这样做……
Brian Drummond 2012年

谢谢Brian,您所说的肯定很有意义。一个简单规则而不是许多晦涩规则的想法似乎也很有意义。您是说对所有模拟器都是正确的(并且确实已指定)这种行为,还是仅是我尝试过的两个?
杰森·摩根

2
如果我发现一个做了不同的事情,我将对此提出一个错误!VHDL最大的反对者会说一声对它有利的一件事是,在其他语言(不只是Verilog)不提供的情况下,它可以保证一致的仿真结果。(尽管是的,有时它的缺点也使我感到烦恼!)
Brian Drummond 2012年

1
快速修复实验:如果我的回答正确,则可以驱动“ temp(0)<='Z';” 在此过程中,因此可以“断开连接”幻像驱动程序,并且外部驱动程序将可以工作...
Brian Drummond 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.