Morten 已经在他的回答中指出了这个理论。借助两个示例,我将演示您在使用生成时钟而不是时钟使能时遇到的问题。
时钟分配
首先,必须注意时钟在所有目标触发器上(几乎)同时到达。否则,即使是像这样的具有 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;
以下测试台仅在 input 输入“1” d
,因此,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”,因为reg_1_d
当生成时钟的上升沿出现时,寄存器 1 ( ) 的输入仍在变化。中间值不是预期的,可能导致不良行为。直到生成时钟的另一个上升沿才能看到正确的值。

要解决此问题,可以使用:
- 特殊代码,一次只翻转一位,例如格雷码,或
- 跨时钟 FIFO,或
- 在单个控制位的帮助下进行握手。