我如何解决这个增量周期时钟延迟问题

How do I solve this delta cycle clock delay issue

我有以下简化的代码示例,其中可以模拟 DeltasTest 实体来显示问题。实际设计中的时钟是基于通用的倒置或不倒置的,并为这个下面的几个其他实体供电。

问题是简单的边缘检测器在行为模拟中不起作用(data_out 只是一个小故障),这是由于反转阶段在时钟上引入的增量周期延迟。有没有标准或优雅的方法来解决这个问题?

到目前为止,我最好的解决方案是将 data_in 信号分配给另一个信号,使其具有与 clk 相同的增量周期延迟。我想使用一个函数来根据需要根据泛型将时钟反转作为函数的第二个参数,但是在很多地方都使用了时钟,这看起来不是很优雅,我注意到它甚至可以解决问题。抓住救命稻草,我还尝试将时钟反转赋值设为 transport 赋值,但是,正如预期的那样,这没有任何区别。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Deltas is
    Generic (
        CLK_INVERT : boolean := false
    );
    Port (
        clk : in std_logic;
        data_in : in std_logic
    );
end Deltas;

architecture Behavioral of Deltas is

    -- Signals
    signal data_reg : std_logic := '0';
    signal clk_inverted : std_logic := '0';
    signal data_out : std_logic := '0';

begin

    ClkInvert : if (CLK_INVERT) generate
        clk_inverted <= not clk;
    else generate
        clk_inverted <= clk;
    end generate;

    process (clk_inverted)  
    begin
        if (rising_edge(clk_inverted)) then
            data_reg <= data_in;
        end if;
    end process;

    process (data_reg, data_in) 
    begin
        if (data_reg /= data_in) then
            data_out <= '1';
        else
            data_out <= '0';
        end if;
    end process;

    -- Other entities use `clk_inverted`. Commented out so that this example compiles
    --LowerEntity : entity work.Counter
    --port map (
    --  clk => clk_inverted
    --);

end Behavioral;

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity DeltasTest is
end DeltasTest;

architecture Behavioral of DeltasTest is

    signal clk : std_logic := '0';
    signal data_in : std_logic := '0';

begin

    clk <= not clk after 10 ns;

    process (clk)
        variable count : integer := 0;
    begin
        if (rising_edge(clk)) then
            count := count + 1;
            if (count = 4) then
                count := 0;
                data_in <= not data_in;
            end if;
        end if;
    end process;

    uut : entity work.Deltas
    Port map (
        clk => clk,
        data_in => data_in
    );

end Behavioral;

我认为 "solution" 在 data_in 上的额外增量循环可能是最干净简单的解决方案。

从语义上讲,对 clk_inverted 的赋值可以转化为硬件作为时钟信号中的反相器,因此模拟中的增量周期延迟表示真实硬件中引入的(可能(*)真实的)竞争条件.

因此,模拟行为不仅令人烦恼,而且很有用 属性,警告您必须注意可能偏离良好的同步设计实践。

(*) 仅 "possibly real" 因为根据技术的不同,合成可能 "push" 通过检测相反的边缘来反转到下游 FF - 根据 Morten 的评论自动转换您的设计。

当额外的增量循环被埋在别人的 IP 中时,问题就变得更严重了,也许是内存等外部组件的模型。 IP 不再受您控制,板级仿真可能会在错误的时钟周期读取或写入数据。

在这里,我的答案是添加近似 I/O 延迟,作为 FPGA I/O 分配中的惯性延迟(来自 FPGA 数据表或综合报告),或者作为 PCB 上的传输延迟.这确保我的模拟近似于电路板的行为 - 不完美,但足够接近以允许我按照正常的同步实践设计内部逻辑。


一个更简洁的解决方案是基于泛型生成两个不同的时钟进程。这完全消除了讨厌的增量循环。

终于! SM 的 2 进程形式的用途,其中保持时钟进程尽可能简单有好处......

没那么快...

您要重构的行为是时钟边沿检测,它已经从过时的复杂(级别和事件)表达式重构为一个简单的函数。

为什么不进一步重构时钟边沿检测,例如...

function clock_edge(signal clk : in std_logic) return boolean is
begin
   if CLK_INVERT then
      return falling_edge(clk);
   else 
      return rising_edge(clk);
   end if;
end clock_edge;
...

process (my_clock) is
begin
   if clock_edge(my_clk) then ...

我没有对此进行测试以查看综合工具是否可以按需要实现它。有些可能会,有些可能不会(如果他们只是对系统提供的功能进行特殊处理而不是正确地完成工作)

在分层设计中,此功能将位于一个包中,供任何需要它的实体使用。这带来了泛型可见性的问题:一个建议是在包中使用常量,另一个是将泛型作为第二个参数传递给函数。正确的方法可能取决于综合工具施加的限制。

我认为您遇到了亚稳态发生的情况。您输入的数据,如果它来自外部世界,则可能不会同步。

通过两个级联寄存器为 data_in 计时,并在两个寄存器之间进行比较,而不是将 data_reg 与输入信号进行比较。

我很确定我以前犯过这个错误

使用旧式时钟并将 CLK_INVERT 替换为 CLK_POLARITY 怎么样?我自己没做过,但我很确定我在哪里见过它。

entity Deltas is
    Generic (
        CLK_POLARITY : std_logic := '1'
    );
    Port (
        clk : in std_logic;
        data_in : in std_logic
    );
end Deltas;
architecture Behavioral of Deltas is
    signal data_reg : std_logic;
    signal data_out : std_logic;

begin

    process (clk)  
    begin
        if clk = CLK_POLARITY and clk'event then
            data_reg <= data_in;
        end if;
    end process;

这提出了一个更大的问题,一个编写可能针对不同设计的 IP 如何需要不同的时钟极性(如此处所做)、不同的复位极性和不同的复位方式(异步复位与同步复位)。我认为我们需要一个实现触发器的程序包。除了专门构建的具有异步重置功能的 DFF 之外,我认为我们还可以拥有诸如 DFFR(具有重置功能的 DFF)之类的东西,其实现由所使用的包体决定。

-- In package body asynchronous resets
procedure DFFR (
  signal Clk   : std_logic ; 
  signal Reset : std_logic ; 
  signal DataIn : std_logic ;
  signal DataOut : std_logic ;
  constant ResetValue : std_logic 
) is
begin
  if Reset = RESET_POLARITY then 
    DataOut <= ResetValue ; 
  elsif Clk = CLOCK_POLARITY and Clk'event then 
    DataOut <= DataIn ; 
  end if ;
end procedure DFFR ; 

-- In package body synchronous resets
procedure DFFR (
  signal Clk   : std_logic ; 
  signal Reset : std_logic ; 
  signal DataIn : std_logic ;
  signal DataOut : std_logic ;
  constant ResetValue : std_logic 
) is
begin
  if Clk = CLOCK_POLARITY and Clk'event then 
    if Reset = RESET_POLARITY then 
      DataOut <= ResetValue ; 
    else
      DataOut <= DataIn ; 
    end if ;
  end if ; 
end procedure DFFR ; 

-- In package body power on reset by initialization
procedure DFFR (
  signal Clk   : std_logic ; 
  signal Reset : std_logic ; 
  signal DataIn : std_logic ;
  signal DataOut : std_logic ;
  constant ResetValue : std_logic 
) is
begin
  if Clk = CLOCK_POLARITY and Clk'event then 
    DataOut <= DataIn ; 
  end if ;
end procedure DFFR ; 

针对不同时钟域做出不同决策的设计需要为每个时钟域使用一个单独的库。

即将到来的 IEEE 1076-2018 正在为 IEEE 开源做一个试点,将当前的 VHDL 包开源。也许我们可以在下一次修订中得到这个——但是,目前我的想法比我有的时间多,所以我们需要更多的参与者。