在过去,我向question询问了关于复位的问题,以及如何将高时钟频率分解为一系列较低时钟的方波频率,其中每个输出是彼此的谐波,例如第一个输出是10赫兹,第二个是20赫兹等。
我收到了几个非常有用的答案,建议使用时钟使能引脚创建较低频率的惯例。
我发生了另一种选择;使用经常递增的n位数,并将该数字的最后x位作为时钟输出,其中x是输出的数量。
它适用于我的综合 - 但我很想知道 - 因为我从未见过它在网上或在任何地方提到它,我错过了一些意味着它实际上是一个可怕的想法而我#&# 39;我只是为以后创造问题?
我知道对此的限制是我只能产生输入频率除以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;
答案 0 :(得分:3)
在功能上,两个输出OUT1
和OUT2
可以用作时钟,但这种制作时钟的方法不会扩展,并且很可能在实现中引起问题,所以这是一个坏习惯。但是,了解其原因当然很重要。
它无法扩展的原因是,FPGA中用作时钟的每个信号都要通过一个特殊的时钟网络进行分配,其中延迟和偏移是明确定义的,因此每个时钟上的所有触发器和存储器同步更新。这种时钟网络的数量非常有限,在FPGA器件中通常在10到40的范围内,并且对使用和位置的一些限制使得计划使用时钟网络通常更为关键。因此,通常只需要为真正的异步时钟保留时钟网络,除了使用时钟网之外别无选择。
它可能导致问题的原因是,基于计数器中的位创建的时钟没有保证的时序关系。因此,如果需要在这些时钟域之间移动数据,则需要额外的同步约束,以确保正确处理时钟域交叉(CDC)。这是通过合成和/或静态时序分析(STA)的约束来完成的,并且通常有点难以实现,因此使用简化STA的设计方法可以节省设计时间。
因此,在可以使用公共时钟然后生成同步时钟使能信号的设计中,这应该是首选方法。对于上面的特定设计,可以通过检测相关'0'
位的'1'
到counter
转换来生成时钟使能,然后在单周期中断言时钟使能检测到转换。然后可以使用单个时钟网络,以及CE1
和CE2
等2个时钟使能,并且不需要特殊的STA约束。
答案 1 :(得分:2)
Morten已经在his answer中指出了这个理论。 借助两个示例,我将演示使用生成的时钟而不是时钟使能时遇到的问题。
首先,必须注意时钟在所有目的地触发器上(几乎)同时到达。否则,即使是像这样的2级的简单移位寄存器也会失败:
process(clk_gen)
begin
if rising_edge(clk_gen) then
tmp <= d;
q <= tmp;
end if;
end if;
此示例的预期行为是q
在生成的时钟d
的两个上升沿之后获得clock_gen
的值。
如果生成的时钟没有被全局时钟缓冲器缓冲,则每个目标触发器的延迟将不同,因为它将通过通用路由进行路由。
因此,移位寄存器的行为可以用一些明确的延迟描述如下:
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',因此{1}在1个时钟边沿后应为'0',在两个时钟边沿后为'1'。
q
但是,仿真波形显示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;
在第一个时钟沿(3.1 ns)之后已经变为'1',这不是预期的行为。
那是因为当时钟到达时,FF 1已经从FF 0看到新值。
这个问题可以通过具有低偏斜的时钟树分配生成的时钟来解决。 要访问FPGA的其中一个时钟树,必须使用全局时钟缓冲器,例如Xilinx FPGA上的BUFG。
第二个问题是在两个时钟域之间切换多位信号。 假设我们有2个寄存器,每个寄存器2位。寄存器0由原始时钟提供时钟,寄存器1由生成的时钟提供时钟。 生成的时钟已经由时钟树分配。
寄存器1只对寄存器0的输出进行采样。 但是现在,两个寄存器位之间的不同导线延迟起着重要作用。这些已在以下设计中明确建模:
q
现在,只需使用此测试平台通过寄存器0输入新数据值“11”:
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;
从下面的模拟输出中可以看出,寄存器1的输出首先在3.1 ns时切换到中间值“01”,因为寄存器1(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;
)的输入在上升时仍在变化生成时钟的边缘发生。
中间值不是预期的,可能导致不希望的行为。直到生成的时钟的另一个上升沿才能看到正确的值。
要解决此问题,可以使用: