VHDL - 从电平采样转换为边沿触发 - 一个直观的解释?
VHDL - converting from level sampling to edge triggered - an intuitive explanation?
我有以下代码(一个原始的 "RS-232 signalling" 发射器)...
LIBRARY ieee;
USE ieee.std_logic_1164.all;
entity SerialTX is
port(
baud_clk : in std_logic;
data : in std_logic_vector(7 downto 0);
send : in std_logic;
serial_out : out std_logic := '0';
busy : out std_logic := '0'
);
end entity;
----------------------------------------
architecture behavioural of SerialTX is
constant IDLE_BITS : std_logic_vector(10 downto 0) := "00000000001";
signal shifter : std_logic_vector(10 downto 0) := IDLE_BITS;
signal shift : std_logic := '0';
signal internal_busy : std_logic := '0';
begin
-------- ALWAYS HAPPENING --------
serial_out <= shifter(0);
busy <= internal_busy;
internal_busy <= '1' when (shifter /= IDLE_BITS) else '0';
----------------------------------
shifting_handler:
process(baud_clk) is
begin
if rising_edge(baud_clk) then
if (send = '1') and (shifter = IDLE_BITS) then
shifter <= "11" & data & '0';
elsif (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
end if;
end process;
end architecture behavioural;
...它运行良好(在模拟中)但有局限性。 send
信号(导致传输开始)必须为“1”电平的时间必须长于 baud_clk
的至少一个完整周期,以便发射器可靠地看到它。
我一直在尝试找到一种方法来转换此代码,以便它响应 send
信号的上升沿,而不是在 baud_clk
的上升沿测试其电平。我希望能够响应持续时间小于 100ns 的 send
脉冲,即使 baud_clk
是 运行 的速度要慢得多(例如 115200 赫兹)。
我曾尝试(天真地)改变流程,因此...
shifting_handler:
process(baud_clk) is
begin
if rising_edge(baud_clk) then
if (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
elsif rising_edge(send) and (shifter = IDLE_BITS) then
shifter <= "11" & data & '0';
end if;
end process;
这里我希望更改逻辑以在 baud_clk
上没有上升沿时测试 send
上的上升沿。
我知道这不是解决问题的有效方法(合成器当然会抱怨)但我希望有人能用简单的术语解释为什么不能这样做。如果可以在一个过程中使用两个边缘检测器会发生什么?这里有一个我无法理解的概念,我似乎总是以同样的方式编写代码并产生这个问题。我正在与多年根深蒂固的软件编程习惯作斗争,这并没有多大帮助!
听起来 send
相对于 baud_clk
是异步的。因此,您需要执行某种形式的时钟域交叉 (CDC) 才能正确实现您的设计,否则您的设计将无法通过时序,并且有可能无法正常运行。 CDC 是一个标准术语,您应该能够在其他问题和其他地方找到更多相关信息。
如您所见,如果设计的过程对两个不同信号的边沿敏感,则无法在真实硬件中实现该设计。没有一种 'right' 方法可以满足您的需求,但这里有一个使用简单 'toggle' CDC 的示例。这非常简单,但请注意,如果一个 send
请求在前一个字节传输之前到达,则设计可能会错过发送一个字节。在 send
信号的断言和传输开始之间也会引入一些延迟。目前尚不清楚这些问题在您的系统中是否重要。
创建另一个对 send
敏感的进程:
-- The initial state doesn't matter, but we want the design to work in simulation
signal send_toggle : std_logic := '0';
process(send)
begin
if (rising_edge(send)) then
send_toggle <= not send_toggle;
end if;
end process;
现在另一个进程将此同步到 baud_clk
域。使用两个级联寄存器来生成一个设计,该设计在很大程度上不受任何亚稳态(这是您可以查找的另一个标准术语)的影响,这种亚稳态可能来自对从不同时钟域生成的信号进行采样:
signal send_toggle_r1 : std_logic;
signal send_toggle_r2 : std_logic;
process(baud_clk)
begin
if (rising_edge(baud_clk)) then
send_toggle_r1 <= send_toggle;
send_toggle_r2 <= send_toggle_r1;
end if;
end process;
以上是一个非常标准的电路块,你可以在很多单比特CDC场景中使用。
然后您的发送进程可以注册 send_toggle_r2
信号以查找转换,以确定它是否应该开始发送。该信号在正确的时钟域中:
signal send_toggle_r3 : std_logic;
process(baud_clk) is
begin
if rising_edge(baud_clk) then
send_toggle_r3 <= send_toggle_r2;
if ((send_toggle_r3 /= send_toggle_r2) and (shifter = IDLE_BITS)) then
shifter <= "11" & data & '0';
elsif (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
end if;
end process;
最后,您需要实施时序约束以告诉您的工具链不必担心 send_toggle_r1
寄存器的时序。
您可能会发现,如果您的目标是寄存器初始状态随机的硬件,您可能会在前几个 baud_clk
周期后收到错误的字节传输。为防止这种情况,您可以选择在启动后将 baud_clk
进程重置为一些时钟周期,但由于我不知道这是否与您相关,因此我不会详细说明这部分。
整个答案直接解决了您的问题,但我个人的方法是使用任何更高速率的时钟来生成您的 send
信号来驱动整个设计。串行传输实际上将使用更高速率的时钟,由 baud_clk
驱动的 CDC > 边缘检测器链启用移位。位时序不会绝对完美,但这对于标准 'UART' 场景应该无关紧要。
我有以下代码(一个原始的 "RS-232 signalling" 发射器)...
LIBRARY ieee;
USE ieee.std_logic_1164.all;
entity SerialTX is
port(
baud_clk : in std_logic;
data : in std_logic_vector(7 downto 0);
send : in std_logic;
serial_out : out std_logic := '0';
busy : out std_logic := '0'
);
end entity;
----------------------------------------
architecture behavioural of SerialTX is
constant IDLE_BITS : std_logic_vector(10 downto 0) := "00000000001";
signal shifter : std_logic_vector(10 downto 0) := IDLE_BITS;
signal shift : std_logic := '0';
signal internal_busy : std_logic := '0';
begin
-------- ALWAYS HAPPENING --------
serial_out <= shifter(0);
busy <= internal_busy;
internal_busy <= '1' when (shifter /= IDLE_BITS) else '0';
----------------------------------
shifting_handler:
process(baud_clk) is
begin
if rising_edge(baud_clk) then
if (send = '1') and (shifter = IDLE_BITS) then
shifter <= "11" & data & '0';
elsif (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
end if;
end process;
end architecture behavioural;
...它运行良好(在模拟中)但有局限性。 send
信号(导致传输开始)必须为“1”电平的时间必须长于 baud_clk
的至少一个完整周期,以便发射器可靠地看到它。
我一直在尝试找到一种方法来转换此代码,以便它响应 send
信号的上升沿,而不是在 baud_clk
的上升沿测试其电平。我希望能够响应持续时间小于 100ns 的 send
脉冲,即使 baud_clk
是 运行 的速度要慢得多(例如 115200 赫兹)。
我曾尝试(天真地)改变流程,因此...
shifting_handler:
process(baud_clk) is
begin
if rising_edge(baud_clk) then
if (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
elsif rising_edge(send) and (shifter = IDLE_BITS) then
shifter <= "11" & data & '0';
end if;
end process;
这里我希望更改逻辑以在 baud_clk
上没有上升沿时测试 send
上的上升沿。
我知道这不是解决问题的有效方法(合成器当然会抱怨)但我希望有人能用简单的术语解释为什么不能这样做。如果可以在一个过程中使用两个边缘检测器会发生什么?这里有一个我无法理解的概念,我似乎总是以同样的方式编写代码并产生这个问题。我正在与多年根深蒂固的软件编程习惯作斗争,这并没有多大帮助!
听起来 send
相对于 baud_clk
是异步的。因此,您需要执行某种形式的时钟域交叉 (CDC) 才能正确实现您的设计,否则您的设计将无法通过时序,并且有可能无法正常运行。 CDC 是一个标准术语,您应该能够在其他问题和其他地方找到更多相关信息。
如您所见,如果设计的过程对两个不同信号的边沿敏感,则无法在真实硬件中实现该设计。没有一种 'right' 方法可以满足您的需求,但这里有一个使用简单 'toggle' CDC 的示例。这非常简单,但请注意,如果一个 send
请求在前一个字节传输之前到达,则设计可能会错过发送一个字节。在 send
信号的断言和传输开始之间也会引入一些延迟。目前尚不清楚这些问题在您的系统中是否重要。
创建另一个对 send
敏感的进程:
-- The initial state doesn't matter, but we want the design to work in simulation
signal send_toggle : std_logic := '0';
process(send)
begin
if (rising_edge(send)) then
send_toggle <= not send_toggle;
end if;
end process;
现在另一个进程将此同步到 baud_clk
域。使用两个级联寄存器来生成一个设计,该设计在很大程度上不受任何亚稳态(这是您可以查找的另一个标准术语)的影响,这种亚稳态可能来自对从不同时钟域生成的信号进行采样:
signal send_toggle_r1 : std_logic;
signal send_toggle_r2 : std_logic;
process(baud_clk)
begin
if (rising_edge(baud_clk)) then
send_toggle_r1 <= send_toggle;
send_toggle_r2 <= send_toggle_r1;
end if;
end process;
以上是一个非常标准的电路块,你可以在很多单比特CDC场景中使用。
然后您的发送进程可以注册 send_toggle_r2
信号以查找转换,以确定它是否应该开始发送。该信号在正确的时钟域中:
signal send_toggle_r3 : std_logic;
process(baud_clk) is
begin
if rising_edge(baud_clk) then
send_toggle_r3 <= send_toggle_r2;
if ((send_toggle_r3 /= send_toggle_r2) and (shifter = IDLE_BITS)) then
shifter <= "11" & data & '0';
elsif (shifter /= IDLE_BITS) then
shifter <= '0' & shifter(10 downto 1); -- shifter >>= 1;
end if;
end if;
end process;
最后,您需要实施时序约束以告诉您的工具链不必担心 send_toggle_r1
寄存器的时序。
您可能会发现,如果您的目标是寄存器初始状态随机的硬件,您可能会在前几个 baud_clk
周期后收到错误的字节传输。为防止这种情况,您可以选择在启动后将 baud_clk
进程重置为一些时钟周期,但由于我不知道这是否与您相关,因此我不会详细说明这部分。
整个答案直接解决了您的问题,但我个人的方法是使用任何更高速率的时钟来生成您的 send
信号来驱动整个设计。串行传输实际上将使用更高速率的时钟,由 baud_clk
驱动的 CDC > 边缘检测器链启用移位。位时序不会绝对完美,但这对于标准 'UART' 场景应该无关紧要。