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' 场景应该无关紧要。