【问题标题】:Alternative method for creating low clock frequencies in VHDL在 VHDL 中创建低时钟频率的替代方法
【发布时间】:2015-12-16 12:39:36
【问题描述】:

过去,我向question 询问了有关复位的问题,以及如何将高时钟频率划分为一系列较低的时钟方波频率,其中每个输出都是彼此的谐波,例如第一个输出是 10 Hz,第二个是 20 Hz,以此类推。

我收到了几个非常有用的答案,推荐了使用时钟启用引脚创建较低频率的惯例。

我想到了另一种选择;使用一个不断递增的 n 位数字,并将该数字的最后 x 位作为时钟输出,其中 x 是输出的数量。

它对我来说是综合的 - 但我很想知道 - 因为我从未在网上或 SO 上的任何地方看到过它,我是否遗漏了一些意味着它实际上是一个糟糕的想法的东西,我只是在制造问题以后用吗?

我知道对此的限制是我只能产生输入频率除以 2 的幂的频率,因此大多数情况下它只会接近所需的输出频率(但仍将是的正确顺序)。这个限制是不推荐的唯一原因吗?

非常感谢!

大卫

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
library UNISIM;
use UNISIM.VComponents.all;
use IEEE.math_real.all;

ENTITY CLK_DIVIDER IS
    GENERIC(INPUT_FREQ : INTEGER;       --Can only divide the input frequency by a power a of 2 
            OUT1_FREQ  : INTEGER
    );
    PORT(SYSCLK  : IN  STD_LOGIC;
         RESET_N : IN  STD_LOGIC;
         OUT1    : OUT STD_LOGIC;       --Actual divider is  2^(ceiling[log2(input/freq)])
         OUT2    : OUT STD_LOGIC);      --Actual output is input over value above
END CLK_DIVIDER;

architecture Behavioral of Clk_Divider is
    constant divider      : integer                             := INPUT_FREQ / OUT1_FREQ;
    constant counter_bits : integer                             := integer(ceil(log2(real(divider))));
    signal counter        : unsigned(counter_bits - 1 downto 0) := (others => '0');
begin
    proc : process(SYSCLK)
    begin
        if rising_edge(SYSCLK) then
            counter <= counter + 1;
            if RESET_N = '0' then
                counter <= (others => '0');
            end if;
        end if;
    end process;
    OUT1 <= counter(counter'length - 1);
    OUT2 <= not counter(counter'length - 2);
end Behavioral;

【问题讨论】:

    标签: vhdl clock conventions xilinx-ise spartan


    【解决方案1】:

    在功能上OUT1OUT2这两个输出可以作为时钟使用,但是这种制作时钟的方法没有伸缩性,很可能在实现中出现问题,所以这是一个坏习惯。但是,理解为什么会这样当然很重要。

    它无法扩展的原因是,在 FPGA 中用作时钟的每个信号都将通过一个特殊的时钟网络进行分配,其中延迟和偏移是明确定义的,因此每个时钟上的所有触发器和存储器是同步更新的。此类时钟网络的数量非常有限,在 FPGA 器件中通常在 10 到 40 个范围内,并且对使用和位置的一些限制使得规划时钟网络的使用通常更加关键。因此,通常需要为真正的异步时钟保留时钟网络,在这种情况下,除了使用时钟网络别无选择。

    它可能导致问题的原因是基于计数器中的位创建的时钟没有保证的时序关系。因此,如果需要在这些时钟域之间移动数据,则需要额外的同步约束,以确保正确处理时钟域交叉 (CDC)。这是通过对综合和/或静态时序分析 (STA) 的约束来完成的,并且通常有点难以做到正确,因此使用简化 STA 的设计方法是一种可以节省设计时间的习惯。

    因此,在可以使用公共时钟然后生成同步时钟使能信号的设计中,这应该是首选方法。对于上述特定设计,只需检测相关counter 位的'0''1' 转换,然后在检测到转换的单个周期中断言时钟使能,即可生成时钟使能。然后可以使用单个时钟网络,以及CE1CE2 等 2 个时钟使能,并且不需要特殊的 STA 约束。

    【讨论】:

    • 只是为了澄清 - 你说它不能扩展是什么意思?
    【解决方案2】:

    Morten 已经在his answer 中指出了这个理论。 借助两个示例,我将演示您在使用生成时钟而不是时钟使能时遇到的问题。

    时钟分布

    首先,必须注意时钟在所有目标触发器上(几乎)同时到达。否则,即使是像这样的具有 2 个阶段的简单移位寄存器也会失败:

    process(clk_gen)
    begin
      if rising_edge(clk_gen) then
        tmp <= d;
        q   <= tmp;
      end if;
    end if;
    

    此示例的预期行为是q 在生成的时钟clock_gen 的两个上升沿之后获取d 的值。 如果生成的时钟没有被全局时钟缓冲器缓冲,那么每个目标触发器的延迟将不同,因为它将通过通用路由进行路由。 因此,移位寄存器的行为可以用一些明确的延迟描述如下:

    library ieee;
    use ieee.std_logic_1164.all;
    entity shift_reg is
      port (
        clk_gen : in  std_logic;
        d       : in  std_logic;
        q       : out std_logic);
    end shift_reg;
    
    architecture rtl of shift_reg is
      signal ff_0_q : std_logic := '0';  -- output of flip-flop 0
      signal ff_1_q : std_logic := '0';  -- output of flip-flop 1
      signal ff_0_c : std_logic;    -- clock input of flip-flop 0
      signal ff_1_c : std_logic;    -- clock input of flip-flop 1
    begin  -- rtl
    
      -- different clock delay per flip-flop if general-purpose routing is used
      ff_0_c <= transport clk_gen after  500 ps;
      ff_1_c <= transport clk_gen after 1000 ps;
    
      -- two closely packed registers with clock-to-output delay of 100 ps
      ff_0_q <= d      after 100 ps when rising_edge(ff_0_c);
      ff_1_q <= ff_0_q after 100 ps when rising_edge(ff_1_c);
    
      q <= ff_1_q;
    end rtl;
    

    以下测试台仅在输入 d 处输入“1”,因此,q 在 1 个时钟沿后应为“0”,在两个时钟沿后应为“1”。

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity shift_reg_tb is
    end shift_reg_tb;
    
    architecture sim of shift_reg_tb is
      signal clk_gen : std_logic;
      signal d       : std_logic;
      signal q       : std_logic;
    begin  -- sim
      DUT: entity work.shift_reg port map (clk_gen => clk_gen, d => d, q => q);
    
      WaveGen_Proc: process
      begin
        -- Note: registers inside DUT are initialized to zero
        d       <= '1';                     -- shift in '1'
        clk_gen <= '0';
        wait for 2 ns;
        clk_gen <= '1';                     -- just one rising edge
        wait for 2 ns;
        assert q = '0' report "Wrong output" severity error;
        wait;
      end process WaveGen_Proc;
    end sim;
    

    但是,仿真波形显示q 在第一个时钟沿(3.1 ns)之后已经得到“1”,这不是预期的行为。 这是因为 FF 1 在时钟到达时已经从 FF 0 看到了新值。

    这个问题可以通过通过具有低偏差的时钟树分配生成的时钟来解决。 要访问 FPGA 的时钟树之一,必须使用全局时钟缓冲器,例如 Xilinx FPGA 上的 BUFG。

    数据交接

    第二个问题是多位信号在两个时钟域之间的切换。 假设我们有 2 个寄存器,每个寄存器 2 位。寄存器 0 由原始时钟计时,寄存器 1 由生成时钟计时。 生成的时钟已经按时钟树分布。

    寄存器 1 只是对寄存器 0 的输出进行采样。 但现在,两者之间的两个寄存器位的不同线路延迟起着重要作用。这些已在以下设计中明确建模:

    library ieee;
    use ieee.std_logic_1164.all;
    library unisim;
    use unisim.vcomponents.all;
    
    entity handover is
      port (
        clk_orig : in  std_logic;                      -- original clock
        d        : in  std_logic_vector(1 downto 0);   -- data input
        q        : out std_logic_vector(1 downto 0));  -- data output
    end handover;
    
    architecture rtl of handover is
      signal div_q   : std_logic := '0';    -- output of clock divider
      signal bufg_o  : std_logic := '0';    -- output of clock buffer
      signal clk_gen : std_logic;           -- generated clock
      signal reg_0_q : std_logic_vector(1 downto 0) := "00";  -- output of register 0
      signal reg_1_d : std_logic_vector(1 downto 0);     -- data input  of register 1
      signal reg_1_q : std_logic_vector(1 downto 0) := "00";  -- output of register 1
    begin  -- rtl
    
      -- Generate a clock by dividing the original clock by 2.
      -- The 100 ps delay is the clock-to-output time of the flip-flop.
      div_q <= not div_q after 100 ps when rising_edge(clk_orig);
    
      -- Add global clock-buffer as well as mimic some delay.
      -- Clock arrives at (almost) same time on all destination flip-flops.
      clk_gen_bufg : BUFG port map (I => div_q, O => bufg_o);
      clk_gen <= transport bufg_o after 1000 ps;
    
      -- Sample data input with original clock
      reg_0_q <= d after 100 ps when rising_edge(clk_orig);
    
      -- Different wire delays between register 0 and register 1 for each bit
      reg_1_d(0) <= transport reg_0_q(0) after  500 ps;
      reg_1_d(1) <= transport reg_0_q(1) after 1500 ps;
    
      -- All flip-flops of register 1 are clocked at the same time due to clock buffer.
      reg_1_q <= reg_1_d after 100 ps when rising_edge(clk_gen);
      q <= reg_1_q;
    end rtl;
    

    现在,只需使用此测试台通过寄存器 0 输入新数据值“11”:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity handover_tb is
    end handover_tb;
    
    architecture sim of handover_tb is
      signal clk_orig : std_logic := '0';
      signal d        : std_logic_vector(1 downto 0);
      signal q        : std_logic_vector(1 downto 0);
    begin  -- sim
      DUT: entity work.handover port map (clk_orig => clk_orig, d => d, q => q);
    
      WaveGen_Proc: process
      begin
        -- Note: registers inside DUT are initialized to zero
        d <= "11";
        clk_orig <= '0';
        for i in 0 to 7 loop                -- 4 clock periods
          wait for 2 ns;
          clk_orig <= not clk_orig;
        end loop;  -- i
        wait;
      end process WaveGen_Proc;
    end sim;
    

    从下面的仿真输出中可以看出,寄存器 1 的输出首先在 3.1 ns 时切换到中间值“01”,因为寄存器 1 的输入(reg_1_d)在产生的时钟发生。 中间值不是预期的,可能导致不良行为。直到生成时钟的另一个上升沿才能看到正确的值。

    要解决这个问题,可以使用:

    • 特殊代码,一次只翻转一位,例如格雷码,或
    • 跨时钟 FIFO,或
    • 借助单个控制位进行握手。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-12
      • 1970-01-01
      • 2020-09-04
      • 2020-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多